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:
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_ =>