hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 5f65a5c112dd15efc0f0223ee895c2582e8f4915
parent 02bfe24618b6f79bcd5236bcce95f0dd327ad30f
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu,  8 Feb 2024 11:53:21 +0100

encoding::asn1: clean up doc strings and style

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mencoding/asn1/decoder.ha | 73++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mencoding/asn1/encoder.ha | 48++++++++++++++++++++++++++++--------------------
Mencoding/asn1/errors.ha | 8++++----
3 files changed, 74 insertions(+), 55 deletions(-)

diff --git a/encoding/asn1/decoder.ha b/encoding/asn1/decoder.ha @@ -60,11 +60,13 @@ export type decoder = struct { implicit: bool, }; -// Creates a new DER decoder that reads from 'src'. The decoder will do a lot of -// short reads, hence a buffered stream is recommended. +// Creates a new DER decoder that reads from 'src'. A buffered stream (see +// [[bufio]]) is recommended for efficiency, as the decoder performs mostly +// short reads. // -// Each entry must be read to the end, before the next one is attended to. -// [[finish]] must be called at the end to make sure everything is read. +// Each entry must be read in its entirety before the next one is attended to. +// The user must call [[finish]] when finished with the decoder to ensure that +// the entire input was read correctly. export fn derdecoder(src: io::handle) decoder = { return decoder { src = src, @@ -77,6 +79,7 @@ export fn derdecoder(src: io::handle) decoder = { }; }; +// Verifies that the entire input to the decoder was read. export fn finish(d: *decoder) (void | error) = { if (d.cstackp != 0 || d.next is head) return invalid; match (d.cur) { @@ -106,7 +109,8 @@ export fn peek(d: *decoder) (head | error) = { }; }; -// Tries to peek the header of the next data and returns EOF, if none exists. +// Tries to peek the header of the next data field, or returns EOF if none +// exists. export fn trypeek(d: *decoder) (head | error | io::EOF) = { if (!(d.next is void)) { return d.next: head; @@ -137,7 +141,7 @@ fn is_endofcons(d: *decoder) bool = { }; }; -// Returns the next data element or [[badformat]] on EOF. +// Returns the next data element, or [[badformat]] on EOF. fn next(d: *decoder) (head | error) = { match (trynext(d)?) { case io::EOF => @@ -236,7 +240,7 @@ fn scan_byte(d: *decoder) (u8 | error) = { }; // Reads data of current entry and advances pointer. Data must have been opened -// using [[next]] or [[trynext]]. EOF is returned on end of data. +// using [[next]] or [[trynext]]. [[io::EOF]] is returned on end of data. fn dataread(d: *decoder, buf: []u8) (size | io::EOF | io::error) = { let cur = match (d.cur) { case void => @@ -361,8 +365,8 @@ fn parse_len(p: *decoder, max: size) (size | error) = { }; // Expects an IMPLICIT defined data field having class 'c' and tag 'tag'. -// If the requirements meet, a read function (read_{*} or {*}reader) must -// follow, that defines and reads the actual data as its stored. +// If the requirements are met, a read call (i.e. one of the "read_" or "reader" +// functions) must follow to read the actual data as stored. export fn expect_implicit(d: *decoder, c: class, tag: u32) (void | error) = { let h = peek(d)?; expect_tag(h, c, tag)?; @@ -379,7 +383,7 @@ export fn close_explicit(d: *decoder) (void | badformat) = close_cons(d); // Opens a constructed value of given 'class' and 'tagid'. Fails if not a -// constructed value or it has an unexpected tag. +// constructed value or the encoded value has an unexpected tag. fn open_cons(d: *decoder, class: class, tagid: u32) (void | error) = { let dh = next(d)?; if (!dh.cons) { @@ -417,20 +421,21 @@ fn close_cons(d: *decoder) (void | badformat) = { d.cstackp -= 1; }; -// Opens a sequence +// Opens a sequence. Call [[close_seq]] after reading. export fn open_seq(d: *decoder) (void | error) = open_cons(d, class::UNIVERSAL, utag::SEQUENCE: u32)?; -// Closes current sequence. [[badformat]] is returned, if not all data has -// been read. +// Closes the current sequence. If the caller has not read all of the data +// present in the encoded seqeunce, [[badformat]] is returned. export fn close_seq(d: *decoder) (void | badformat) = close_cons(d); -// Opens a set. Though a set must be sorted according to DER, the order will not -// be validated. +// Opens a set. Note that sets must be ordered according to DER, but this module +// does not validate this constraint. Call [[close_set]] after reading. export fn open_set(d: *decoder) (void | error) = open_cons(d, class::UNIVERSAL, utag::SET: u32)?; -// Closes current set. [[badformat]] is returend, if not all data has been read. +// Closes the current set. If the caller has not read all of the data present in +// the encoded seqeunce, [[badformat]] is returned. export fn close_set(d: *decoder) (void | badformat) = close_cons(d); fn expect_tag(h: head, class: class, tagid: u32) (void | invalid | badformat) = { @@ -471,7 +476,7 @@ fn read_nbytes(d: *decoder, buf: []u8) (size | error) = { return n; }; -// Read a boolean. +// Reads a boolean value. export fn read_bool(d: *decoder) (bool | error) = { let dh = next(d)?; expect_utag(dh, utag::BOOLEAN)?; @@ -504,9 +509,10 @@ fn validate_intprefix(i: []u8) (void | error) = { }; }; -// Read an integer into 'buf'. Fails if size exceeds the buffer size. The -// integer is stored in big endian format. Negative values are stored as a -// twos complement. The minimum integer size is one byte. +// Reads an arbitrary-length integer into 'buf' and returns its length in bytes. +// Fails if the encoded integer size exceeds the buffer size. The integer is +// stored in big endian, and negative values are stored with two's compliment. +// The minimum integer size is one byte. export fn read_int(d: *decoder, buf: []u8) (size | error) = { assert(len(buf) > 0); @@ -517,8 +523,8 @@ export fn read_int(d: *decoder, buf: []u8) (size | error) = { return n; }; -// Similar to [[read_int]], but fails if it's not an unsigned integer. Will -// left trim 0 bytes. +// Similar to [[read_int]], but returns [[badformat]] if the encoded value is +// signed. Discards the most significant zero bytes. export fn read_uint(d: *decoder, buf: []u8) (size | error) = { let s = read_int(d, buf)?; if (buf[0] & 0x80 == 0x80) { @@ -614,14 +620,15 @@ export fn bitstr_isset(bitstr: ([]u8, u8), pos: size) (bool | invalid) = { }; // Returns an [[io::reader]] for octet string data. -// TODO add limit? export fn octetstrreader(d: *decoder) (bytestream | error) = { + // TODO add limit? let dh = next(d)?; expect_utag(dh, utag::OCTET_STRING)?; return newbytereader(d); }; -// Read an octet string into 'buf'. Fails if 'buf' is to small. +// Read an octet string into 'buf', returning its length. Returns [[badformat]] +// if 'buf' is too small. export fn read_octetstr(d: *decoder, buf: []u8) (size | error) = { assert(len(buf) > 0); @@ -660,8 +667,9 @@ const bytestream_vtable: io::vtable = io::vtable { fn bytestream_reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = dataread((s: *bytestream).d, buf); -// Returns an [[io::reader]] that allows to read the raw data in its encoded -// form. Note that this reader won't do any kind of validation. +// Returns an [[io::reader]] that reads raw data (in its ASN.1 encoded form) +// from a [[decoder]]. Note that this reader will not perform any kind of +// validation. export fn bytereader(d: *decoder, c: class, tagid: u32) (bytestream | error) = { let dh = next(d)?; expect_tag(dh, c, tagid)?; @@ -669,9 +677,9 @@ export fn bytereader(d: *decoder, c: class, tagid: u32) (bytestream | error) = { }; // Reads an UTC time. Since the stored date only has a two digit year, 'maxyear' -// is required to define the epoch switch. For example 'maxyear' = 2046 causes -// all encoded years <= 46 to be after 2000 and all values > 46 will have 1900 -// as the century. +// is required to define the epoch. For example 'maxyear' = 2046 causes all +// encoded years <= 46 to be after 2000 and all values > 46 will have 1900 as +// the century. export fn read_utctime(d: *decoder, maxyear: u16) (date::date | error) = { assert(maxyear > 100); @@ -711,7 +719,7 @@ export fn read_utctime(d: *decoder, maxyear: u16) (date::date | error) = { return dt; }; -// Reads a generalized datetime. +// Reads a generalized datetime value. export fn read_gtime(d: *decoder) (date::date | error) = { let dh = next(d)?; expect_utag(dh, utag::GENERALIZED_TIME)?; @@ -755,7 +763,10 @@ export fn read_gtime(d: *decoder) (date::date | error) = { }; // Skips an element and returns the size of the data that has been skipped. -// Returns an error, if the skipped data is invalid. +// Returns an error if the skipped data is invalid. +// +// Presently only supports BOOLEAN, INTEGER, NULL, OCTET_STRING, and BITSTRING +// utags, and will abort when attempting to skip anything else. export fn skip(d: *decoder, tag: utag, max: size) (size | error) = { static let buf: [os::BUFSZ]u8 = [0...]; let s = 0z; diff --git a/encoding/asn1/encoder.ha b/encoding/asn1/encoder.ha @@ -38,27 +38,33 @@ export type encoder = struct { parent: nullable *bytewstream, }; -// Creates a DER encoder. create_* methods are used to create constructed -// values. Functions to write primitive values start with write_ or end with -// writer. After the entries have been written, the result is encoded using -// [[encode]] or [[encodeto]]. +// Creates a new DER encoder. The user must provide a [[memio::stream]] for +// buffering data before it's encoded. The user may provide a dynamic or fixed +// stream at their discretion; fixed may be preferred if the user knows the +// required buffer size in advance. // -// 'mem' is required to buffer the written data before encoding it.Each entry -// will have an maximum overhead of [[MAXUTAGHEADSZ]], if entries are written -// using only methods provided here; or [[MAXHEADSZ]], if custom tag ids are in -// use. The encoder doesn't close after use 'mem', hence it's the caller's -// responsibility manage its lifetime. +// To encode DER data, the user must call one of the "create_" functions (e.g. +// [[create_explicit]]), followed by the appropriate "write_" functions (e.g. +// [[write_int]]). These operations will be buffered into the provided memio +// buffer, and the encoded form may be finalized and retrieved via [[encode]] or +// [[encodeto]]. // -// 'mem' as memio::stream allows the caller to decide whether to use a static or -// a dynamic allocated buffer. +// To determine the required buffer size for a fixed buffer, consider the +// maximum length of the input data (e.g. integer, string, etc length) plus the +// necessary overhead, which is given by [[MAXUTAGHEADSZ]] if only using the +// provided encoder functions (e.g. "write_" functions), or [[MAXHEADSZ]] if +// using custom tag IDs. +// +// The encoder does not close the provided [[memio::stream]] after use; the +// caller should manage its lifetime accordingly. export fn derencoder(mem: *memio::stream) encoder = encoder { mem = mem, start = io::tell(mem)!, ... }; -// Creates a DER encoder that is nested within another DER entry and hence can -// use the buffer of the parent. +// Creates a DER encoder nested within another DER entry, using the buffer of +// the parent. export fn derencoder_nested(b: *bytewstream) encoder = encoder { mem = b.e.mem, start = io::tell(b.e.mem)!, @@ -208,8 +214,8 @@ fn encode_dsz(sz: size) []u8 = { return buf[..n]; }; -// Creates an explicit constructed entry. [[finish_explicit]] must be called -// to close the entry. +// Creates an explicit constructed entry. The user must call [[finish_explicit]] +// to close the associated DER entry. export fn create_explicit(e: *encoder, c: class, tag: u32) (void | overflow) = create_cons(e, c, tag); @@ -248,7 +254,8 @@ fn finish_cons(e: *encoder) void = { bt_add_dsz(e, sz); }; -// Creates a sequence. [[finish_seq]] must be called to close it. +// Creates a sequence. The user must call [[finish_seq]] to close the associated +// DER entry. export fn create_seq(e: *encoder) (void | overflow) = return create_cons(e, class::UNIVERSAL, utag::SEQUENCE); @@ -293,7 +300,7 @@ fn bytewriter_write(s: *io::stream, buf: const []u8) (size | io::error) = { return len(buf); }; -// Creates a io::writer that adds written bytes as OctetString. +// Creates an [[io::writer]] that encodes data written to it as an OctetString. export fn octetstrwriter(e: *encoder) (bytewstream | overflow) = { return bytewriter(e, class::UNIVERSAL, utag::OCTET_STRING); }; @@ -336,8 +343,8 @@ export fn write_utf8str(e: *encoder, s: str) (void | overflow) = write_fixedprim(e, class::UNIVERSAL, utag::UTF8_STRING, strings::toutf8(s))?; -// Encodes currently written data in given memio stream and returns the buffer -// containing the result borrowed from 'mem' provided for [[derencoder]]. +// Encodes all buffered data in the [[encoder]] and returns a slice representing +// the encoded entry, borrowed from the encoder's buffer. export fn encode(e: *encoder) ([]u8 | io::error) = { assert(e.btn == 0); assert(e.start >= 0); @@ -415,7 +422,8 @@ export fn encode(e: *encoder) ([]u8 | io::error) = { return buf[..t]; }; -// Encodes written data and writes it to 'dest'. +// Encodes all buffered data in the [[encoder]] and writes it to the provided +// [[io::handle]]. export fn encodeto(e: *encoder, dest: io::handle) (size | io::error) = { const buf = encode(e)?; return io::writeall(dest, buf)?; diff --git a/encoding/asn1/errors.ha b/encoding/asn1/errors.ha @@ -4,13 +4,13 @@ use errors; use io; -// Invalid DER encoding. +// Encountered invalid DER encoded data. export type invalid = !void; // Unexpected data format. export type badformat = !void; -// Premature EOF +// Premature EOF. export type truncated = !void; // Data does not fit into the encoder buffer. @@ -26,7 +26,7 @@ export type error = !(...io::error | ...asn1error); export fn strerror(e: error) str = { match (e) { case invalid => - return "Data encoding does not follow the DER format"; + return "Encountered invalid DER encoded data"; case badformat => return "Unexpected data format"; case truncated => @@ -56,7 +56,7 @@ fn wrap_strerror(err: *errors::opaque_data) const str = { return strerror(*e); }; -// Unwrap [[io::error]] returned by readers into [[error]]. +// Unwraps an [[io::error]] returned by ASN.1 readers as an [[error]]. export fn unwrap_err(e: io::error) error = { match (e) { case let e: errors::opaque_ =>