diff --git a/wire/Makefile b/wire/Makefile index bf73e9113..6aed7836a 100644 --- a/wire/Makefile +++ b/wire/Makefile @@ -6,6 +6,7 @@ wire-wrongdir: WIRE_HEADERS_NOGEN := wire/onion_defs.h \ wire/peer_wire.h \ + wire/tlvstream.h \ wire/wire.h \ wire/wire_sync.h \ wire/wire_io.h @@ -16,6 +17,7 @@ WIRE_SRC := wire/wire_sync.c \ wire/wire_io.c \ wire/fromwire.c \ wire/peer_wire.c \ + wire/tlvstream.c \ wire/towire.c WIRE_HEADERS := $(WIRE_HEADERS_NOGEN) $(WIRE_GEN_HEADERS) diff --git a/wire/tlvstream.c b/wire/tlvstream.c new file mode 100644 index 000000000..2ee863c48 --- /dev/null +++ b/wire/tlvstream.c @@ -0,0 +1,139 @@ +#include "wire/tlvstream.h" +#include +#include + +static const struct tlv_record_type * +find_record_type(u64 type, + const struct tlv_record_type types[], + size_t num_types) +{ + for (size_t i = 0; i < num_types; i++) + if (types[i].type == type) + return types + i; + return NULL; +} + +/* Pull all tlvs from a stream. Return false and calls fromwire_fail() on + * error. */ +bool fromwire_tlvs(const u8 **cursor, size_t *max, + const struct tlv_record_type types[], + size_t num_types, + void *record) +{ + u64 prev_type; + bool first = true; + + /* BOLT-EXPERIMENTAL #1: + * + * The receiving node: + * - if zero bytes remain before parsing a `type`: + * - MUST stop parsing the `tlv_stream`. + */ + while (*max > 0) { + u64 type, length; + const struct tlv_record_type *rtype; + + /* BOLT-EXPERIMENTAL #1: + * + * A `varint` is a variable-length, unsigned integer encoding + * using the [BigSize](#appendix-a-bigsize-test-vectors) + * format + */ + type = fromwire_bigsize(cursor, max); + length = fromwire_bigsize(cursor, max); + + /* BOLT-EXPERIMENTAL #1: + * - if a `type` or `length` is not minimally encoded: + * - MUST fail to parse the `tlv_stream`. + */ + if (!*cursor) + goto fail; + + /* BOLT-EXPERIMENTAL #1: + * - if `length` exceeds the number of bytes remaining in the + * message: + * - MUST fail to parse the `tlv_stream`. + */ + if (length > *max) + goto fail; + + /* BOLT-EXPERIMENTAL #1: + * - if decoded `type`s are not monotonically-increasing: + * - MUST fail to parse the `tlv_stream`. + */ + if (!first && type <= prev_type) + goto fail; + + /* BOLT-EXPERIMENTAL #1: + * - if `type` is known: + * - MUST decode the next `length` bytes using the known + * encoding for `type`. + */ + rtype = find_record_type(type, types, num_types); + if (rtype) { + /* Length of message can't exceed 16 bits anyway. */ + size_t tlvlen = length; + rtype->fromwire(cursor, &tlvlen, record); + + if (!*cursor) + goto fail; + + /* BOLT-EXPERIMENTAL #1: + * - if `length` is not exactly equal to that required + * for the known encoding for `type`: + * - MUST fail to parse the `tlv_stream`. + */ + if (tlvlen != 0) + goto fail; + + /* We've read bytes in ->fromwire, so update max */ + *max -= length; + } else { + /* BOLT-EXPERIMENTAL #1: + * - otherwise, if `type` is unknown: + * - if `type` is even: + * - MUST fail to parse the `tlv_stream`. + * - otherwise, if `type` is odd: + * - MUST discard the next `length` bytes. + */ + if (type & 1) + fromwire(cursor, max, NULL, length); + else + goto fail; + } + first = false; + prev_type = type; + } + return true; + +fail: + fromwire_fail(cursor, max); + return false; +} + +/* Append a stream of tlvs. */ +void towire_tlvs(u8 **pptr, + const struct tlv_record_type types[], + size_t num_types, + const void *record) +{ + for (size_t i = 0; i < num_types; i++) { + u8 *val; + if (i != 0) + assert(types[i].type > types[i-1].type); + val = types[i].towire(NULL, record); + if (!val) + continue; + + /* BOLT-EXPERIMENTAL #1: + * + * The sending node: + ... + * - MUST minimally encode `type` and `length`. + */ + towire_bigsize(pptr, types[i].type); + towire_bigsize(pptr, tal_bytelen(val)); + towire(pptr, val, tal_bytelen(val)); + tal_free(val); + } +} diff --git a/wire/tlvstream.h b/wire/tlvstream.h new file mode 100644 index 000000000..bbce0839d --- /dev/null +++ b/wire/tlvstream.h @@ -0,0 +1,31 @@ +#ifndef LIGHTNING_WIRE_TLVSTREAM_H +#define LIGHTNING_WIRE_TLVSTREAM_H +#include "config.h" +#include +#include +#include +#include + +struct tlv_record_type { + u64 type; + /* If this type is present return marshalled value. Otherwise + * returns NULL. */ + u8 *(*towire)(const tal_t *ctx, const void *record); + /* Must call fromwire_fail() it can't parse. */ + void (*fromwire)(const u8 **cursor, size_t *max, void *record); +}; + +/* Pull all tlvs from a stream. Return false and calls fromwire_fail() on + * error. */ +bool fromwire_tlvs(const u8 **cursor, size_t *max, + const struct tlv_record_type types[], + size_t num_types, + void *record); + +/* Append a stream of tlvs: types[] must be in increasing type order! */ +void towire_tlvs(u8 **pptr, + const struct tlv_record_type types[], + size_t num_types, + const void *record); + +#endif /* LIGHTNING_WIRE_TLVSTREAM_H */