From 6c240ab589388a6319ff5736bee35944120c681e Mon Sep 17 00:00:00 2001 From: lisa neigut Date: Sun, 14 Jul 2019 19:10:01 -0500 Subject: [PATCH] bolts: new parsing script and templates for new bolt format the RFC's extract-format.py is switching to a new format. this script can correctly parse them. mostly moves logic over from generate-wire.py, uses a Python formatting libarary called mako, which needs to be installed prior to running this script. you can add it to your system with sudo apt-get install python3-mako --- tools/gen/header_template | 71 +++++ tools/gen/impl_template | 212 +++++++++++++++ tools/gen/print_header_template | 20 ++ tools/gen/print_impl_template | 109 ++++++++ tools/generate-bolts.py | 455 ++++++++++++++++++++++++++++++++ 5 files changed, 867 insertions(+) create mode 100644 tools/gen/header_template create mode 100644 tools/gen/impl_template create mode 100644 tools/gen/print_header_template create mode 100644 tools/gen/print_impl_template create mode 100755 tools/generate-bolts.py diff --git a/tools/gen/header_template b/tools/gen/header_template new file mode 100644 index 000000000..d37c4a77f --- /dev/null +++ b/tools/gen/header_template @@ -0,0 +1,71 @@ +/* This file was generated by generate-bolts.py */ +/* Do not modify this file! Modify the _csv file it was generated from. */ +/* Original template can be found at tools/gen/header_template */ + +#ifndef LIGHTNING_${idem} +#define LIGHTNING_${idem} +#include +#include +% for i in includes: +${i} +% endfor + +## Enum sets for wire messages & tlvs +% for enum_set in enum_sets: +enum ${enum_set['name']} { + % for msg in enum_set['set']: + ## TODO: add back comments for a message + ##% for comment in msg.comments: + ##/* ${comment} */ + ##% endfor + ${msg.enum_name()} = ${msg.number}, + % endfor +}; + +%endfor +## The 'name' functions for the enums +% for enum_set in enum_sets: +const char *${enum_set['name']}_name(int e); +% endfor + +## Structs for subtypes + tlv messages +% for struct in structs: +struct ${struct.struct_name()} { + % for f in struct.fields.values(): + % if f.is_len_field: +<% continue %> + % endif + % if f.has_len_field(): + ${f.type_obj.type_name()} *${f.name}; + % elif f.is_array(): + ${f.type_obj.type_name()} ${f.name}[${f.count}]; + % else: + ${f.type_obj.type_name()} ${f.name}; + % endif + % endfor +}; +% endfor +## Structs for TLV types! +% for tlv in tlvs: +struct ${tlv.name} { + % for msg_name in tlv.messages.keys(): + struct ${tlv.name}_${msg_name} *${msg_name}; + % endfor +}; +% endfor + +% if options.expose_subtypes and bool(subtypes): +% for subtype in subtypes: +/* SUBTYPE: ${subtype.name.upper()} */ +void towire_${subtype.name}(u8 **p, const struct ${subtype.name} *${subtype.name}); +bool fromwire_${subtype.name}(${'const tal_t *ctx, ' if subtype.has_len_fields() else '' }const u8 **cursor, size_t *plen, struct ${subtype.name} *${subtype.name}); + +% endfor +% endif +% for msg in messages: +/* WIRE: ${msg.name.upper()} */ +u8 *towire_${msg.name}(const tal_t *ctx${''.join([f.arg_desc_to() for f in msg.fields.values() if not f.is_optional])}); +bool fromwire_${msg.name}(${'const tal_t *ctx, ' if msg.has_len_fields() else ''}const void *p${''.join([f.arg_desc_from() for f in msg.fields.values() if not f.is_optional])}); + +% endfor +#endif /* LIGHTNING_${idem} */ diff --git a/tools/gen/impl_template b/tools/gen/impl_template new file mode 100644 index 000000000..52f61c8ba --- /dev/null +++ b/tools/gen/impl_template @@ -0,0 +1,212 @@ +/* This file was generated by generate-bolts.py */ +/* Do not modify this file! Modify the _csv file it was generated from. */ +/* Original template can be found at tools/gen/impl_template */ + +#include <${header_filename}> +#include +#include +#include + +% for enum_set in enum_sets: +const char *${enum_set['name']}_name(int e) +{ + static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)]; + + switch ((enum ${enum_set['name']})e) { + % for msg in enum_set['set']: + case ${msg.enum_name()}: return "${msg.enum_name()}"; + % endfor + } + + snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); + return invalidbuf; +} + +% endfor + +## FIXME: extract out partials for the method declarations +## (shared between here and header_template) +% for subtype in subtypes: +/* SUBTYPE: ${subtype.name.upper()} */ +<% static = '' if options.expose_subtypes else 'static ' %> +${static}void towire_${subtype.name}(u8 **p, const struct ${subtype.name} *${subtype.name}) +{ + % for f in subtype.get_len_fields(): + ${f.type_obj.type_name()} ${f.name} = tal_count(${subtype.name}->${f.len_field_of}); + % endfor + +## FIXME: abstract this out? (semi-shared with towire_msg, minus the optional bits) + % for f in subtype.fields.values(): +## FIXME: add field level comments +<% + fieldname = '{}->{}'.format(subtype.name,f.name) +%> \ + % if f.is_array() or f.has_len_field(): ## multiples? + % if f.type_obj.has_array_helper(): + towire_${f.type_obj.name}_array(&p, ${fieldname}, ${f.size()}); + % else: + for (size_t i = 0; i < ${f.size()}; i++) + % if f.type_obj.is_assignable() or f.type_obj.has_len_fields(): + towire_${f.type_obj.name}(p, ${fieldname}[i]); + % else: + towire_${f.type_obj.name}(p, ${fieldname} + i); + % endif + % endif + % elif f.len_field_of: + towire_${f.type_obj.name}(p, ${f.name}); + % else: + towire_${f.type_obj.name}(&p, ${'' if f.type_obj.is_assignable() else '*'}${fieldname}); + % endif + % endfor +} +${static}bool fromwire_${subtype.name}(${'const tal_t *ctx, ' if subtype.has_len_fields() else '' }const u8 **cursor, size_t *plen, struct ${subtype.name} *${subtype.name}) +{ + ## Length field declarations + % for f in subtype.get_len_fields(): + ${f.type_obj.type_name()} ${f.name}; + % endfor + +## FIXME: field level comments + % for f in subtype.fields.values(): +<% + fieldname = '{}->{}'.format(subtype.name,f.name) + typename = f.type_obj.type_name() + type_ = f.type_obj.name +%> \ + % if f.has_len_field(): + ${fieldname} = ${f.len_field} ? tal_arr(ctx, ${typename}${' *' if f.type_obj.has_len_fields() else ''}, ${f.len_field}) : NULL; + % endif +<% + if f.is_array(): + fieldname = '*' + fieldname + ctx = 'ctx' + else: + ctx = fieldname +%> \ + % if f.is_array() or f.has_len_field(): + % if f.type_obj.has_array_helper(): + fromwire_${type_}_array(cursor, plen, ${fieldname}, ${f.size()}); + % else: + for (size_t i = 0; i < ${f.size()}; i++) + % if f.type_obj.is_assignable(): + (${fieldname})[i] = fromwire_${type_}(cursor, plen); + % elif f.has_len_field(): + (${fieldname})[i] = fromwire_${type_}(${ctx}, cursor, plen); + % else: + fromwire_${type_}(${ctx}, cursor, plen, ${fieldname} + i); + % endif + % endif + % else: + % if f.type_obj.is_assignable(): + ${ f.name if f.len_field_of else fieldname} = fromwire_${type_}(cursor, plen); + % else: + fromwire_${type_}(cursor, plen, &${fieldname}); + % endif + %endif + % endfor + + return cursor != NULL; +} + +% endfor +% for msg in messages: +/* WIRE: ${msg.name.upper()} */ +u8 *towire_${msg.name}(const tal_t *ctx${''.join([f.arg_desc_to() for f in msg.fields.values() if not f.is_optional])}) +{ +## FIXME: we're ignoring TLV's rn + % for f in msg.get_len_fields(): + ${f.type_obj.type_name()} ${f.name} = tal_count(${f.len_field_of}); + % endfor + u8 *p = tal_arr(ctx, u8, 0); + + towire_u16(&p, ${msg.enum_name()}); + % for f in msg.fields.values(): +## FIXME: add field level comments + % if f.is_array() or f.has_len_field(): ## multiples? + % if f.type_obj.has_array_helper(): + towire_${f.type_obj.name}_array(&p, ${f.name}, ${f.size()}); + % else: + for (size_t i = 0; i < ${f.size()}; i++) + % if f.type_obj.is_assignable() or f.type_obj.has_len_fields(): + towire_${f.type_obj.name}(&p, ${f.name}[i]); + % else: + towire_${f.type_obj.name}(&p, ${f.name} + i); + % endif + % endif + % elif f.is_optional: ## is optional? + if (!${f.name}) + towire_bool(&p, false); + else { + towire_bool(&p, true); + towire_${f.type_obj.name}(&p, ${'*' if f.type_obj.is_assignable() else ''}${f.name}); + } + % else: ## all other cases + towire_${f.type_obj.name}(&p, ${f.name}); + % endif + % endfor + + return memcheck(p, tal_count(p)); +} +bool fromwire_${msg.name}(${'const tal_t *ctx, ' if msg.has_len_fields() else ''}const void *p${''.join([f.arg_desc_from() for f in msg.fields.values() if not f.is_optional])}) +{ + % if msg.get_len_fields(): + % for f in msg.get_len_fields(): + ${f.type_obj.type_name()} ${f.name}; + % endfor + + % endif + const u8 *cursor = p; + size_t plen = tal_count(p); + + if (fromwire_u16(&cursor, &plen) != ${msg.enum_name()}) + return false; +## FIXME: TLV has been omitted + % for f in msg.fields.values(): +<% + typename = f.type_obj.type_name() + if f.type_obj.has_len_fields(): + typename = typename + ' *' + type_ = f.type_obj.name + %> \ + % if f.has_len_field(): + // 2nd case ${f.name} + *${f.name} = ${f.len_field} ? tal_arr(ctx, ${typename}, ${f.len_field}) : NULL; + % endif + % if f.len_field_of: + ${f.name} = fromwire_${type_}(&cursor, &plen); + % elif f.is_array() or f.has_len_field(): +<% + if f.has_len_field(): + fieldname = '*' + f.name + ctx = fieldname + else: + fieldname = f.name + ctx = 'ctx' + %> \ + % if f.type_obj.has_array_helper(): + fromwire_${type_}_array(&cursor, &plen, ${fieldname}, ${f.size()}); + % else: + for (size_t i = 0; i < ${f.size()}; i++) + % if f.type_obj.is_assignable(): + (${fieldname})[i] = fromwire_${type_}(&cursor, &plen); + ## FIXME: case for 'varlen' structs + ## (${fieldname})[i] = fromwire_${type_}(${ctx}, &cursor, &plen); + % else: + fromwire_${type_}(${ctx + ', ' if f.type_obj.is_subtype() else ''}&cursor, &plen, ${fieldname} + i); + % endif + % endif + % else: +## FIXME: leaves out optional fields + 'varlen' structs + %if f.type_obj.is_assignable(): + *${f.name} = fromwire_${type_}(&cursor, &plen); + % else: + fromwire_${type_}(&cursor, &plen, ${f.name}); + ## assignment + % endif + % endif + % endfor + return cursor != NULL; +} + +% endfor +##${func_decls} diff --git a/tools/gen/print_header_template b/tools/gen/print_header_template new file mode 100644 index 000000000..6b8fa63e7 --- /dev/null +++ b/tools/gen/print_header_template @@ -0,0 +1,20 @@ +/* This file was generated by generate-bolts.py */ +/* Do not modify this file! Modify the _csv file it was generated from. */ +/* Template located at tools/gen/print_header_template */ +#ifndef LIGHTNING_${idem} +#define LIGHTNING_${idem} +#include +#include +% for i in includes: +${i} +% endfor + +void print${options.enum_name}_message(const u8 *msg); + +void print${options.enum_name}_tlv_message(const char *tlv_name, const u8 *msg); + +% for msg in messages: +void printwire_${msg.name}(const char *fieldname, const u8 *cursor); + +% endfor +#endif /* LIGHTNING_${idem} */ diff --git a/tools/gen/print_impl_template b/tools/gen/print_impl_template new file mode 100644 index 000000000..7d45b65c4 --- /dev/null +++ b/tools/gen/print_impl_template @@ -0,0 +1,109 @@ +/* This file was generated by generate-bolts.py */ +/* Do not modify this file! Modify the _csv file it was generated from. */ + +#include "${options.header_filename}" +#include +#include +#include +#include +#include + +void print${options.enum_name}_message(const u8 *msg) +{ + switch ((enum ${options.enum_name})fromwire_peektype(msg)) { +% for msg in enum_sets[0]['set']: + case ${msg.enum_name()}: + printf("${msg.enum_name()}:\n"); + printwire_${msg.name}("${msg.name}", msg); + return; +% endfor + } + + printf("UNKNOWN: %s\\n", tal_hex(msg, msg)); +} + +void print${options.enum_name}_tlv_message(const char *tlv_name, const u8 *msg) +{ +% if not bool(tlvs): + printf("~~ No TLV definition found for %s ~~\\n", tlv_name); +% else: + % for tlv in tlvs: + if (strcmp(tlv_name, "${tlv.name}") == 0) { + printwire_${tlv.name}("${tlv.name}", msg); + return; + } + % endfor + printf("ERR: Unknown TLV message type: %s\n", tlv_name); +% endif +} + +## 'component' for 'truncate check +<%def name="truncate_check(nested=False)"> + if (!${ '*' if nested else '' }cursor) { + printf("**TRUNCATED**\n"); + return; + } + \ +## definition for printing field sets +<%def name="print_fieldset(fields, nested, cursor, plen)"> +## FIXME: optional field handling omitted since we only generate these for bolts rn +% for f in fields: + % if f.len_field_of: + ${f.type_obj.type_name()} ${f.name} = fromwire_${f.type_obj.name}(${cursor}, ${plen});${truncate_check(nested)} <% continue %> \ + % endif + printf("${f.name}="); + % if f.is_array() or f.has_len_field(): + % if f.type_obj.has_array_helper(): + printwire_${f.type_obj.name}_array(tal_fmt(NULL, "%s.${f.name}", fieldname), ${cursor}, ${plen}, ${f.size()}); + % else: + printf("["); + for (size_t i = 0; i < ${f.size()}; i++) { + ${f.type_obj.type_name()} v; + % if f.type_obj.is_assignable(): + v = fromwire_${f.type_obj.name}(${cursor}, ${plen}); + % else: + fromwire_${f.type_obj.name}(${cursor}, ${plen}, &v); + % endif +${truncate_check(nested)} \ + printwire_${f.type_obj.name}(tal_fmt(NULL, "%s.${f.name}", fieldname), &v); + } + printf("]"); + % endif +${truncate_check(nested)} \ + % else: + % if f.type_obj.is_assignable(): + ${f.type_obj.type_name()} ${f.name} = fromwire_${f.type_obj.name}(${cursor}, ${plen}); + % else: + ${f.type_obj.type_name()} ${f.name}; + fromwire_${f.type_obj.name}(${cursor}, ${plen}, &${f.name}); + % endif + printwire_${f.type_obj.name}(tal_fmt(NULL, "%s.${f.name}", fieldname), &${f.name}); ${truncate_check(nested)} \ + % endif +% endfor + \ + +## Definitions for 'subtypes' +% for subtype in subtypes: +static void printwire_${subtype.name}(const char *fieldname, const u9 **cursor, size_t *plen) +{ + ${print_fieldset(subtype.fields.values(), True, 'cursor', 'plen')} +} +% endfor + +## FIXME: handling for tlv's :/ +% for msg in messages: +void printwire_${msg.name}(const char *fieldname, const u8 *cursor) +{ + + size_t plen = tal_count(cursor); + if (fromwire_u16(&cursor, &plen) != ${msg.enum_name()}) { + printf("WRONG TYPE?!\n"); + return; + } +${print_fieldset(msg.fields.values(), False, '&cursor', '&plen')} + +## Length check + if (plen != 0) + printf("EXTRA: %s\n", tal_hexstr(NULL, cursor, plen)); +} +% endfor diff --git a/tools/generate-bolts.py b/tools/generate-bolts.py new file mode 100755 index 000000000..2bf476adf --- /dev/null +++ b/tools/generate-bolts.py @@ -0,0 +1,455 @@ +#! /usr/bin/env python3 +# Script to parse spec output CSVs and produce C files. +# Released by lisa neigut under CC0: +# https://creativecommons.org/publicdomain/zero/1.0/ +# +# Reads from stdin, outputs C header or body file. +# +# Standard message types: +# msgtype,,[,