mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-12-29 10:04:41 +01:00
18a23b31de
Since messages in TLV's are optional, the ideal way to deal with them is to have a 'master struct' object for every defined tlv, where the presence or lack of a field can be determined via the presence (or lack thereof) of a struct for each of the optional message types. In order to do this, appropriately, we need a struct for every TLV message. The next commit will make use of these. Note that right now TLV message structs aren't namespaced to the TLV they belong to, so there's the potential for collision. This should be fixed when/where it occurs (should fail to compile).
988 lines
34 KiB
Python
Executable File
988 lines
34 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
# Read from stdin, spit out C header or body.
|
|
|
|
import argparse
|
|
import copy
|
|
import fileinput
|
|
import re
|
|
|
|
from collections import namedtuple
|
|
|
|
Enumtype = namedtuple('Enumtype', ['name', 'value'])
|
|
|
|
type2size = {
|
|
'pad': 1,
|
|
'struct channel_id': 32,
|
|
'struct short_channel_id': 8,
|
|
'struct ipv6': 16,
|
|
'secp256k1_ecdsa_signature': 64,
|
|
'struct preimage': 32,
|
|
'struct pubkey': 33,
|
|
'struct sha256': 32,
|
|
'struct bitcoin_blkid': 32,
|
|
'struct bitcoin_txid': 32,
|
|
'struct secret': 32,
|
|
'struct amount_msat': 8,
|
|
'struct amount_sat': 8,
|
|
'u64': 8,
|
|
'u32': 4,
|
|
'u16': 2,
|
|
'u8': 1,
|
|
'bool': 1
|
|
}
|
|
|
|
# These struct array helpers require a context to allocate from.
|
|
varlen_structs = [
|
|
'peer_features',
|
|
'gossip_getnodes_entry',
|
|
'failed_htlc',
|
|
'utxo',
|
|
'bitcoin_tx',
|
|
'wirestring',
|
|
]
|
|
|
|
|
|
class FieldType(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def is_assignable(self):
|
|
return self.name in ['u8', 'u16', 'u32', 'u64', 'bool', 'struct amount_msat', 'struct amount_sat'] or self.name.startswith('enum ')
|
|
|
|
# We only accelerate the u8 case: it's common and trivial.
|
|
def has_array_helper(self):
|
|
return self.name in ['u8']
|
|
|
|
# Returns base size
|
|
@staticmethod
|
|
def _typesize(typename):
|
|
if typename in type2size:
|
|
return type2size[typename]
|
|
elif typename.startswith('struct ') or typename.startswith('enum '):
|
|
# We allow unknown structures/enums, for extensibility (can only happen
|
|
# if explicitly specified in csv)
|
|
return 0
|
|
else:
|
|
raise ValueError('Unknown typename {}'.format(typename))
|
|
|
|
|
|
# Full (message, fieldname)-mappings
|
|
typemap = {
|
|
('update_fail_htlc', 'reason'): FieldType('u8'),
|
|
('node_announcement', 'alias'): FieldType('u8'),
|
|
('update_add_htlc', 'onion_routing_packet'): FieldType('u8'),
|
|
('update_fulfill_htlc', 'payment_preimage'): FieldType('struct preimage'),
|
|
('error', 'data'): FieldType('u8'),
|
|
('shutdown', 'scriptpubkey'): FieldType('u8'),
|
|
('node_announcement', 'rgb_color'): FieldType('u8'),
|
|
('node_announcement', 'addresses'): FieldType('u8'),
|
|
('node_announcement', 'ipv6'): FieldType('struct ipv6'),
|
|
('announcement_signatures', 'short_channel_id'): FieldType('struct short_channel_id'),
|
|
('channel_announcement', 'short_channel_id'): FieldType('struct short_channel_id'),
|
|
('channel_update', 'short_channel_id'): FieldType('struct short_channel_id'),
|
|
('revoke_and_ack', 'per_commitment_secret'): FieldType('struct secret'),
|
|
('channel_reestablish_option_data_loss_protect', 'your_last_per_commitment_secret'): FieldType('struct secret'),
|
|
('channel_update', 'fee_base_msat'): FieldType('u32'),
|
|
('final_incorrect_htlc_amount', 'incoming_htlc_amt'): FieldType('struct amount_msat'),
|
|
}
|
|
|
|
# Partial names that map to a datatype
|
|
partialtypemap = {
|
|
'signature': FieldType('secp256k1_ecdsa_signature'),
|
|
'features': FieldType('u8'),
|
|
'channel_id': FieldType('struct channel_id'),
|
|
'chain_hash': FieldType('struct bitcoin_blkid'),
|
|
'funding_txid': FieldType('struct bitcoin_txid'),
|
|
'pad': FieldType('pad'),
|
|
'msat': FieldType('struct amount_msat'),
|
|
'satoshis': FieldType('struct amount_sat'),
|
|
}
|
|
|
|
# Size to typename match
|
|
sizetypemap = {
|
|
33: FieldType('struct pubkey'),
|
|
32: FieldType('struct sha256'),
|
|
8: FieldType('u64'),
|
|
4: FieldType('u32'),
|
|
2: FieldType('u16'),
|
|
1: FieldType('u8')
|
|
}
|
|
|
|
|
|
# It would be nicer if we had put '*u8' in spec and disallowed bare lenvar.
|
|
# In practice we only recognize lenvar when it's the previous field.
|
|
|
|
# size := baresize | arraysize
|
|
# baresize := simplesize | lenvar
|
|
# simplesize := number | type
|
|
# arraysize := length '*' type
|
|
# length := lenvar | number
|
|
class Field(object):
|
|
def __init__(self, message, name, size, comments, prevname):
|
|
self.message = message
|
|
self.comments = comments
|
|
self.name = name
|
|
self.is_len_var = False
|
|
self.lenvar = None
|
|
self.num_elems = 1
|
|
self.optional = False
|
|
self.is_tlv = False
|
|
|
|
# field name appended with '+' means this field contains a tlv
|
|
if name.endswith('+'):
|
|
self.is_tlv = True
|
|
self.name = name[:-1]
|
|
if self.name not in tlv_fields:
|
|
# FIXME: use the rest of this
|
|
tlv_includes, tlv_messages, tlv_comments = parse_tlv_file(self.name)
|
|
tlv_fields[self.name] = tlv_messages
|
|
|
|
# ? means optional field (not supported for arrays)
|
|
if size.startswith('?'):
|
|
self.optional = True
|
|
size = size[1:]
|
|
# If it's an arraysize, swallow prefix.
|
|
elif '*' in size:
|
|
number = size.split('*')[0]
|
|
if number == prevname:
|
|
self.lenvar = number
|
|
else:
|
|
self.num_elems = int(number)
|
|
size = size.split('*')[1]
|
|
elif options.bolt and size == prevname:
|
|
# Raw length field, implies u8.
|
|
self.lenvar = size
|
|
size = '1'
|
|
|
|
# Bolts use just a number: Guess type based on size.
|
|
if options.bolt:
|
|
base_size = int(size)
|
|
self.fieldtype = Field._guess_type(message, self.name, base_size)
|
|
# There are some arrays which we have to guess, based on sizes.
|
|
tsize = FieldType._typesize(self.fieldtype.name)
|
|
if base_size % tsize != 0:
|
|
raise ValueError('Invalid size {} for {}.{} not a multiple of {}'
|
|
.format(base_size,
|
|
self.message,
|
|
self.name,
|
|
tsize))
|
|
self.num_elems = int(base_size / tsize)
|
|
else:
|
|
# Real typename.
|
|
self.fieldtype = FieldType(size)
|
|
|
|
def basetype(self):
|
|
base = self.fieldtype.name
|
|
if base.startswith('struct '):
|
|
base = base[7:]
|
|
elif base.startswith('enum '):
|
|
base = base[5:]
|
|
return base
|
|
|
|
def is_padding(self):
|
|
return self.name.startswith('pad')
|
|
|
|
# Padding is always treated as an array.
|
|
def is_array(self):
|
|
return self.num_elems > 1 or self.is_padding()
|
|
|
|
def is_variable_size(self):
|
|
return self.lenvar is not None
|
|
|
|
def needs_ptr_to_ptr(self):
|
|
return self.is_variable_size() or self.optional
|
|
|
|
def is_assignable(self):
|
|
if self.is_array() or self.needs_ptr_to_ptr():
|
|
return False
|
|
return self.fieldtype.is_assignable()
|
|
|
|
def has_array_helper(self):
|
|
return self.fieldtype.has_array_helper()
|
|
|
|
# Returns FieldType
|
|
@staticmethod
|
|
def _guess_type(message, fieldname, base_size):
|
|
# Check for full (message, fieldname)-matches
|
|
if (message, fieldname) in typemap:
|
|
return typemap[(message, fieldname)]
|
|
|
|
# Check for partial field names
|
|
for k, v in partialtypemap.items():
|
|
if k in fieldname:
|
|
return v
|
|
|
|
# Check for size matches
|
|
if base_size in sizetypemap:
|
|
return sizetypemap[base_size]
|
|
|
|
raise ValueError('Unknown size {} for {}'.format(base_size, fieldname))
|
|
|
|
|
|
fromwire_impl_templ = """bool fromwire_{name}({ctx}const void *p{args})
|
|
{{
|
|
{fields}
|
|
\tconst u8 *cursor = p;
|
|
\tsize_t plen = tal_count(p);
|
|
|
|
\tif (fromwire_u16(&cursor, &plen) != {enum.name})
|
|
\t\treturn false;
|
|
{subcalls}
|
|
\treturn cursor != NULL;
|
|
}}
|
|
"""
|
|
|
|
fromwire_header_templ = """bool fromwire_{name}({ctx}const void *p{args});
|
|
"""
|
|
|
|
towire_header_templ = """u8 *towire_{name}(const tal_t *ctx{args});
|
|
"""
|
|
towire_impl_templ = """u8 *towire_{name}(const tal_t *ctx{args})
|
|
{{
|
|
{field_decls}
|
|
\tu8 *p = tal_arr(ctx, u8, 0);
|
|
\ttowire_u16(&p, {enumname});
|
|
{subcalls}
|
|
|
|
\treturn memcheck(p, tal_count(p));
|
|
}}
|
|
"""
|
|
|
|
towire_tlv_templ = """u8 *towire_{name}(const tal_t *ctx{args})
|
|
{{
|
|
{field_decls}
|
|
\tu8 *p = tal_arr(ctx, u8, 0);
|
|
\ttowire_u16(&p, {enumname});
|
|
\ttowire_u16(&p, {len});
|
|
{subcalls}
|
|
|
|
\treturn memcheck(p, tal_count(p));
|
|
}}
|
|
"""
|
|
|
|
fromwire_tlv_templ = """bool frowire_{name}({ctx}const void *p{args})
|
|
{{
|
|
{fields}
|
|
\tconst u8 *cursor = p;
|
|
\tsize_t plen = tal_count(p);
|
|
|
|
\tif (frmwire_u16(&cursor, &plen) != {enum.name})
|
|
\t\treturn false;
|
|
{subcalls}
|
|
\treturn cursor != NULL;
|
|
}}
|
|
"""
|
|
|
|
printwire_header_templ = """void printwire_{name}(const char *fieldname, const u8 *cursor);
|
|
"""
|
|
printwire_impl_templ = """void printwire_{name}(const char *fieldname, const u8 *cursor)
|
|
{{
|
|
\tsize_t plen = tal_count(cursor);
|
|
|
|
\tif (fromwire_u16(&cursor, &plen) != {enum.name}) {{
|
|
\t\tprintf("WRONG TYPE?!\\n");
|
|
\t\treturn;
|
|
\t}}
|
|
|
|
{subcalls}
|
|
|
|
\tif (plen != 0)
|
|
\t\tprintf("EXTRA: %s\\n", tal_hexstr(NULL, cursor, plen));
|
|
}}
|
|
"""
|
|
|
|
|
|
class CCode(object):
|
|
"""Simple class to create indented C code"""
|
|
def __init__(self):
|
|
self.indent = 1
|
|
self.single_indent = False
|
|
self.code = []
|
|
|
|
def append(self, lines):
|
|
for line in lines.split('\n'):
|
|
# Let us to the indenting please!
|
|
assert '\t' not in line
|
|
|
|
# Special case: } by itself is pre-unindented.
|
|
if line == '}':
|
|
self.indent -= 1
|
|
self.code.append("\t" * self.indent + line)
|
|
continue
|
|
|
|
self.code.append("\t" * self.indent + line)
|
|
if self.single_indent:
|
|
self.indent -= 1
|
|
self.single_indent = False
|
|
|
|
if line.endswith('{'):
|
|
self.indent += 1
|
|
elif line.endswith('}'):
|
|
self.indent -= 1
|
|
elif line.startswith('for') or line.startswith('if'):
|
|
self.indent += 1
|
|
self.single_indent = True
|
|
|
|
def __str__(self):
|
|
assert self.indent == 1
|
|
assert not self.single_indent
|
|
return '\n'.join(self.code)
|
|
|
|
|
|
class Message(object):
|
|
def __init__(self, name, enum, comments, is_tlv):
|
|
self.name = name
|
|
self.enum = enum
|
|
self.comments = comments
|
|
self.fields = []
|
|
self.has_variable_fields = False
|
|
self.is_tlv = is_tlv
|
|
|
|
def checkLenField(self, field):
|
|
# Optional fields don't have a len.
|
|
if field.optional:
|
|
return
|
|
for f in self.fields:
|
|
if f.name == field.lenvar:
|
|
if f.fieldtype.name != 'u16' and options.bolt:
|
|
raise ValueError('Field {} has non-u16 length variable {} (type {})'
|
|
.format(field.name, field.lenvar, f.fieldtype.name))
|
|
|
|
if f.is_array() or f.needs_ptr_to_ptr():
|
|
raise ValueError('Field {} has non-simple length variable {}'
|
|
.format(field.name, field.lenvar))
|
|
f.is_len_var = True
|
|
f.lenvar_for = field
|
|
return
|
|
raise ValueError('Field {} unknown length variable {}'
|
|
.format(field.name, field.lenvar))
|
|
|
|
def addField(self, field):
|
|
# We assume field lengths are 16 bit, to avoid overflow issues and
|
|
# massive allocations.
|
|
if field.is_variable_size():
|
|
self.checkLenField(field)
|
|
self.has_variable_fields = True
|
|
elif field.basetype() in varlen_structs or field.optional:
|
|
self.has_variable_fields = True
|
|
self.fields.append(field)
|
|
|
|
def print_fromwire_array(self, ctx, subcalls, basetype, f, name, num_elems):
|
|
if f.has_array_helper():
|
|
subcalls.append('fromwire_{}_array(&cursor, &plen, {}, {});'
|
|
.format(basetype, name, num_elems))
|
|
else:
|
|
subcalls.append('for (size_t i = 0; i < {}; i++)'
|
|
.format(num_elems))
|
|
if f.fieldtype.is_assignable():
|
|
subcalls.append('({})[i] = fromwire_{}(&cursor, &plen);'
|
|
.format(name, basetype))
|
|
elif basetype in varlen_structs:
|
|
subcalls.append('({})[i] = fromwire_{}({}, &cursor, &plen);'
|
|
.format(name, basetype, ctx))
|
|
else:
|
|
subcalls.append('fromwire_{}(&cursor, &plen, {} + i);'
|
|
.format(basetype, name))
|
|
|
|
def print_fromwire(self, is_header):
|
|
ctx_arg = 'const tal_t *ctx, ' if self.has_variable_fields else ''
|
|
|
|
args = []
|
|
|
|
for f in self.fields:
|
|
if f.is_len_var or f.is_padding():
|
|
continue
|
|
elif f.is_array():
|
|
args.append(', {} {}[{}]'.format(f.fieldtype.name, f.name, f.num_elems))
|
|
else:
|
|
ptrs = '*'
|
|
# If we're handing a variable array, we need a ptr-to-ptr.
|
|
if f.needs_ptr_to_ptr():
|
|
ptrs += '*'
|
|
# If each type is a variable length, we need a ptr to that.
|
|
if f.basetype() in varlen_structs:
|
|
ptrs += '*'
|
|
|
|
args.append(', {} {}{}'.format(f.fieldtype.name, ptrs, f.name))
|
|
|
|
template = fromwire_header_templ if is_header else fromwire_impl_templ
|
|
fields = ['\t{} {};\n'.format(f.fieldtype.name, f.name) for f in self.fields if f.is_len_var]
|
|
|
|
subcalls = CCode()
|
|
for f in self.fields:
|
|
basetype = f.basetype()
|
|
|
|
for c in f.comments:
|
|
subcalls.append('/*{} */'.format(c))
|
|
|
|
if f.is_padding():
|
|
subcalls.append('fromwire_pad(&cursor, &plen, {});'
|
|
.format(f.num_elems))
|
|
elif f.is_array():
|
|
self.print_fromwire_array('ctx', subcalls, basetype, f, f.name,
|
|
f.num_elems)
|
|
elif f.is_variable_size():
|
|
subcalls.append("//2nd case {name}".format(name=f.name))
|
|
typename = f.fieldtype.name
|
|
# If structs are varlen, need array of ptrs to them.
|
|
if basetype in varlen_structs:
|
|
typename += ' *'
|
|
subcalls.append('*{} = {} ? tal_arr(ctx, {}, {}) : NULL;'
|
|
.format(f.name, f.lenvar, typename, f.lenvar))
|
|
|
|
# Allocate these off the array itself, if they need alloc.
|
|
self.print_fromwire_array('*' + f.name, subcalls, basetype, f,
|
|
'*' + f.name, f.lenvar)
|
|
else:
|
|
if f.optional:
|
|
assignable = f.fieldtype.is_assignable()
|
|
deref = '*'
|
|
else:
|
|
deref = ''
|
|
assignable = f.is_assignable()
|
|
|
|
if assignable:
|
|
if f.is_len_var:
|
|
s = '{} = fromwire_{}(&cursor, &plen);'.format(f.name, basetype)
|
|
else:
|
|
s = '{}*{} = fromwire_{}(&cursor, &plen);'.format(deref, f.name, basetype)
|
|
elif basetype in varlen_structs:
|
|
s = '{}*{} = fromwire_{}(ctx, &cursor, &plen);'.format(deref, f.name, basetype)
|
|
else:
|
|
s = 'fromwire_{}(&cursor, &plen, {}{});'.format(basetype, deref, f.name)
|
|
|
|
if f.optional:
|
|
subcalls.append("if (!fromwire_bool(&cursor, &plen))\n"
|
|
"*{} = NULL;\n"
|
|
"else {{\n"
|
|
"*{} = tal(ctx, {});\n"
|
|
"{}\n"
|
|
"}}"
|
|
.format(f.name, f.name, f.fieldtype.name,
|
|
s))
|
|
else:
|
|
subcalls.append(s)
|
|
|
|
return template.format(
|
|
name=self.name,
|
|
ctx=ctx_arg,
|
|
args=''.join(args),
|
|
fields=''.join(fields),
|
|
enum=self.enum,
|
|
subcalls=str(subcalls)
|
|
)
|
|
|
|
def print_towire_array(self, subcalls, basetype, f, num_elems):
|
|
if f.has_array_helper():
|
|
subcalls.append('towire_{}_array(&p, {}, {});'
|
|
.format(basetype, f.name, num_elems))
|
|
else:
|
|
subcalls.append('for (size_t i = 0; i < {}; i++)'
|
|
.format(num_elems))
|
|
if f.fieldtype.is_assignable() or basetype in varlen_structs:
|
|
subcalls.append('towire_{}(&p, {}[i]);'
|
|
.format(basetype, f.name))
|
|
else:
|
|
subcalls.append('towire_{}(&p, {} + i);'
|
|
.format(basetype, f.name))
|
|
|
|
def print_towire(self, is_header):
|
|
template = towire_header_templ if is_header else towire_impl_templ
|
|
args = []
|
|
for f in self.fields:
|
|
if f.is_padding() or f.is_len_var:
|
|
continue
|
|
if f.is_array():
|
|
args.append(', const {} {}[{}]'.format(f.fieldtype.name, f.name, f.num_elems))
|
|
elif f.is_assignable():
|
|
args.append(', {} {}'.format(f.fieldtype.name, f.name))
|
|
elif f.is_variable_size() and f.basetype() in varlen_structs:
|
|
args.append(', const {} **{}'.format(f.fieldtype.name, f.name))
|
|
else:
|
|
args.append(', const {} *{}'.format(f.fieldtype.name, f.name))
|
|
|
|
field_decls = []
|
|
for f in self.fields:
|
|
if f.is_len_var:
|
|
field_decls.append('\t{0} {1} = tal_count({2});'.format(
|
|
f.fieldtype.name, f.name, f.lenvar_for.name
|
|
))
|
|
|
|
subcalls = CCode()
|
|
for f in self.fields:
|
|
basetype = f.fieldtype.name
|
|
if basetype.startswith('struct '):
|
|
basetype = basetype[7:]
|
|
elif basetype.startswith('enum '):
|
|
basetype = basetype[5:]
|
|
|
|
for c in f.comments:
|
|
subcalls.append('/*{} */'.format(c))
|
|
|
|
if f.is_padding():
|
|
subcalls.append('towire_pad(&p, {});'
|
|
.format(f.num_elems))
|
|
elif f.is_array():
|
|
self.print_towire_array(subcalls, basetype, f, f.num_elems)
|
|
elif f.is_variable_size():
|
|
self.print_towire_array(subcalls, basetype, f, f.lenvar)
|
|
else:
|
|
if f.optional:
|
|
if f.fieldtype.is_assignable():
|
|
deref = '*'
|
|
else:
|
|
deref = ''
|
|
subcalls.append("if (!{})\n"
|
|
"towire_bool(&p, false);\n"
|
|
"else {{\n"
|
|
"towire_bool(&p, true);\n"
|
|
"towire_{}(&p, {}{});\n"
|
|
"}}".format(f.name, basetype, deref, f.name))
|
|
else:
|
|
subcalls.append('towire_{}(&p, {});'
|
|
.format(basetype, f.name))
|
|
|
|
return template.format(
|
|
name=self.name,
|
|
args=''.join(args),
|
|
enumname=self.enum.name,
|
|
field_decls='\n'.join(field_decls),
|
|
subcalls=str(subcalls),
|
|
)
|
|
|
|
def add_truncate_check(self, subcalls):
|
|
# Report if truncated, otherwise print.
|
|
subcalls.append('if (!cursor) {\n'
|
|
'printf("**TRUNCATED**\\n");\n'
|
|
'return;\n'
|
|
'}')
|
|
|
|
def print_printwire_array(self, subcalls, basetype, f, num_elems):
|
|
if f.has_array_helper():
|
|
subcalls.append('printwire_{}_array(tal_fmt(NULL, "%s.{}", fieldname), &cursor, &plen, {});'
|
|
.format(basetype, f.name, num_elems))
|
|
else:
|
|
subcalls.append('printf("[");')
|
|
subcalls.append('for (size_t i = 0; i < {}; i++) {{'
|
|
.format(num_elems))
|
|
subcalls.append('{} v;'.format(f.fieldtype.name))
|
|
if f.fieldtype.is_assignable():
|
|
subcalls.append('v = fromwire_{}(&cursor, plen);'
|
|
.format(f.fieldtype.name, basetype))
|
|
else:
|
|
# We don't handle this yet!
|
|
assert(basetype not in varlen_structs)
|
|
|
|
subcalls.append('fromwire_{}(&cursor, &plen, &v);'
|
|
.format(basetype))
|
|
|
|
self.add_truncate_check(subcalls)
|
|
|
|
subcalls.append('printwire_{}(tal_fmt(NULL, "%s.{}", fieldname), &v);'
|
|
.format(basetype, f.name))
|
|
subcalls.append('}')
|
|
subcalls.append('printf("]");')
|
|
|
|
def print_printwire(self, is_header):
|
|
template = printwire_header_templ if is_header else printwire_impl_templ
|
|
fields = ['\t{} {};\n'.format(f.fieldtype.name, f.name) for f in self.fields if f.is_len_var]
|
|
|
|
subcalls = CCode()
|
|
for f in self.fields:
|
|
basetype = f.basetype()
|
|
|
|
for c in f.comments:
|
|
subcalls.append('/*{} */'.format(c))
|
|
|
|
if f.is_len_var:
|
|
subcalls.append('{} {} = fromwire_{}(&cursor, &plen);'
|
|
.format(f.fieldtype.name, f.name, basetype))
|
|
self.add_truncate_check(subcalls)
|
|
continue
|
|
|
|
subcalls.append('printf("{}=");'.format(f.name))
|
|
if f.is_padding():
|
|
subcalls.append('printwire_pad(tal_fmt(NULL, "%s.{}", fieldname), &cursor, &plen, {});'
|
|
.format(f.name, f.num_elems))
|
|
self.add_truncate_check(subcalls)
|
|
elif f.is_array():
|
|
self.print_printwire_array(subcalls, basetype, f, f.num_elems)
|
|
self.add_truncate_check(subcalls)
|
|
elif f.is_variable_size():
|
|
self.print_printwire_array(subcalls, basetype, f, f.lenvar)
|
|
self.add_truncate_check(subcalls)
|
|
else:
|
|
if f.optional:
|
|
subcalls.append("if (fromwire_bool(&cursor, &plen)) {")
|
|
|
|
if f.is_assignable():
|
|
subcalls.append('{} {} = fromwire_{}(&cursor, &plen);'
|
|
.format(f.fieldtype.name, f.name, basetype))
|
|
else:
|
|
# Don't handle these yet.
|
|
assert(basetype not in varlen_structs)
|
|
subcalls.append('{} {};'.
|
|
format(f.fieldtype.name, f.name))
|
|
subcalls.append('fromwire_{}(&cursor, &plen, &{});'
|
|
.format(basetype, f.name))
|
|
|
|
self.add_truncate_check(subcalls)
|
|
subcalls.append('printwire_{}(tal_fmt(NULL, "%s.{}", fieldname), &{});'
|
|
.format(basetype, f.name, f.name))
|
|
if f.optional:
|
|
subcalls.append("} else {")
|
|
self.add_truncate_check(subcalls)
|
|
subcalls.append("}")
|
|
|
|
return template.format(
|
|
name=self.name,
|
|
fields=''.join(fields),
|
|
enum=self.enum,
|
|
subcalls=str(subcalls)
|
|
)
|
|
|
|
def print_struct(self):
|
|
""" returns a string representation of this message as
|
|
a struct"""
|
|
if not self.is_tlv:
|
|
raise TypeError('{} is not a TLV-message').format(self.name)
|
|
|
|
fmt_fields = CCode()
|
|
for f in self.fields:
|
|
if f.is_len_var or f.is_padding():
|
|
# there is no ethical padding under TLVs
|
|
continue
|
|
elif f.is_variable_size():
|
|
fmt_fields.append('{} *{};'.format(f.fieldtype.name, f.name))
|
|
elif f.is_array():
|
|
fmt_fields.append('{} {}[{}];'.format(f.fieldtype.name, f.name, f.num_elems))
|
|
else:
|
|
fmt_fields.append('{} {};'.format(f.fieldtype.name, f.name))
|
|
|
|
return tlv_struct_template.format(
|
|
tlv_name=self.name,
|
|
fields=str(fmt_fields))
|
|
|
|
|
|
tlv_struct_template = """
|
|
struct tlv_{tlv_name} {{
|
|
{fields}
|
|
}};
|
|
"""
|
|
|
|
|
|
def find_message(messages, name):
|
|
for m in messages:
|
|
if m.name == name:
|
|
return m
|
|
|
|
return None
|
|
|
|
|
|
def find_message_with_option(messages, optional_messages, name, option):
|
|
fullname = name + "_" + option.replace('-', '_')
|
|
|
|
base = find_message(messages, name)
|
|
if not base:
|
|
raise ValueError('Unknown message {}'.format(name))
|
|
|
|
m = find_message(optional_messages, fullname)
|
|
if not m:
|
|
# Add a new option.
|
|
m = copy.deepcopy(base)
|
|
m.name = fullname
|
|
optional_messages.append(m)
|
|
return m
|
|
|
|
|
|
def get_directory_prefix():
|
|
# FIXME: use prefix of filename
|
|
return "wire/"
|
|
|
|
|
|
def get_tlv_filename(field_name):
|
|
return 'gen_{}_csv'.format(field_name)
|
|
|
|
|
|
def parse_tlv_file(tlv_field_name):
|
|
tlv_includes = []
|
|
tlv_messages = []
|
|
tlv_comments = []
|
|
tlv_prevfield = None
|
|
with open(get_directory_prefix() + get_tlv_filename(tlv_field_name)) as f:
|
|
for line in f:
|
|
# #include gets inserted into header
|
|
if line.startswith('#include '):
|
|
tlv_includes.append(line)
|
|
continue
|
|
|
|
by_comments = line.rstrip().split('#')
|
|
|
|
# Emit a comment if they included one
|
|
if by_comments[1:]:
|
|
tlv_comments.append(' '.join(by_comments[1:]))
|
|
|
|
parts = by_comments[0].split(',')
|
|
if parts == ['']:
|
|
continue
|
|
|
|
if len(parts) == 2:
|
|
# eg commit_sig,132
|
|
tlv_msg = Message(parts[0], Enumtype("TLV_" + parts[0].upper(), parts[1]), tlv_comments, True)
|
|
tlv_messages.append(tlv_msg)
|
|
messages.append(tlv_msg)
|
|
|
|
tlv_comments = []
|
|
tlv_prevfield = None
|
|
else:
|
|
if len(parts) == 4:
|
|
# eg commit_sig,0,channel-id,8 OR
|
|
# commit_sig,0,channel-id,u64
|
|
m = find_message(tlv_messages, parts[0])
|
|
if m is None:
|
|
raise ValueError('Unknown message {}'.format(parts[0]))
|
|
elif len(parts) == 5:
|
|
# eg.
|
|
# channel_reestablish,48,your_last_per_commitment_secret,32,option209
|
|
m = find_message_with_option(tlv_messages, messages_with_option, parts[0], parts[4])
|
|
else:
|
|
raise ValueError('Line {} malformed'.format(line.rstrip()))
|
|
|
|
f = Field(m.name, parts[2], parts[3], tlv_comments, tlv_prevfield)
|
|
m.addField(f)
|
|
# If it used prevfield as lenvar, keep that for next
|
|
# time (multiple fields can use the same lenvar).
|
|
if not f.lenvar:
|
|
tlv_prevfield = parts[2]
|
|
tlv_comments = []
|
|
return tlv_includes, tlv_messages, tlv_comments
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='Generate C from CSV')
|
|
parser.add_argument('--header', action='store_true', help="Create wire header")
|
|
parser.add_argument('--bolt', action='store_true', help="Generate wire-format for BOLT")
|
|
parser.add_argument('--printwire', action='store_true', help="Create print routines")
|
|
parser.add_argument('headerfilename', help='The filename of the header')
|
|
parser.add_argument('enumname', help='The name of the enum to produce')
|
|
parser.add_argument('files', nargs='*', help='Files to read in (or stdin)')
|
|
options = parser.parse_args()
|
|
|
|
# Maps message names to messages
|
|
messages = []
|
|
messages_with_option = []
|
|
comments = []
|
|
includes = []
|
|
tlv_fields = {}
|
|
prevfield = None
|
|
|
|
# Read csv lines. Single comma is the message values, more is offset/len.
|
|
for line in fileinput.input(options.files):
|
|
# #include gets inserted into header
|
|
if line.startswith('#include '):
|
|
includes.append(line)
|
|
continue
|
|
|
|
by_comments = line.rstrip().split('#')
|
|
|
|
# Emit a comment if they included one
|
|
if by_comments[1:]:
|
|
comments.append(' '.join(by_comments[1:]))
|
|
|
|
parts = by_comments[0].split(',')
|
|
if parts == ['']:
|
|
continue
|
|
|
|
if len(parts) == 2:
|
|
# eg commit_sig,132
|
|
messages.append(Message(parts[0], Enumtype("WIRE_" + parts[0].upper(), parts[1]), comments, False))
|
|
comments = []
|
|
prevfield = None
|
|
else:
|
|
if len(parts) == 4:
|
|
# eg commit_sig,0,channel-id,8 OR
|
|
# commit_sig,0,channel-id,u64
|
|
m = find_message(messages, parts[0])
|
|
if m is None:
|
|
raise ValueError('Unknown message {}'.format(parts[0]))
|
|
elif len(parts) == 5:
|
|
# eg.
|
|
# channel_reestablish,48,your_last_per_commitment_secret,32,option209
|
|
m = find_message_with_option(messages, messages_with_option, parts[0], parts[4])
|
|
else:
|
|
raise ValueError('Line {} malformed'.format(line.rstrip()))
|
|
|
|
f = Field(m.name, parts[2], parts[3], comments, prevfield)
|
|
m.addField(f)
|
|
# If it used prevfield as lenvar, keep that for next
|
|
# time (multiple fields can use the same lenvar).
|
|
if not f.lenvar:
|
|
prevfield = parts[2]
|
|
comments = []
|
|
|
|
|
|
def construct_hdr_enums(msgs):
|
|
enums = ""
|
|
for m in msgs:
|
|
for c in m.comments:
|
|
enums += '\t/*{} */\n'.format(c)
|
|
enums += '\t{} = {},\n'.format(m.enum.name, m.enum.value)
|
|
return enums
|
|
|
|
|
|
def construct_impl_enums(msgs):
|
|
return '\n\t'.join(['case {enum.name}: return "{enum.name}";'.format(enum=m.enum) for m in msgs])
|
|
|
|
|
|
def enum_header(enums, enumname):
|
|
return format_enums(enum_header_template, enums, enumname)
|
|
|
|
|
|
def enum_impl(enums, enumname):
|
|
return format_enums(enum_impl_template, enums, enumname)
|
|
|
|
|
|
def format_enums(template, enums, enumname):
|
|
return template.format(
|
|
enums=enums,
|
|
enumname=enumname)
|
|
|
|
|
|
def build_hdr_enums(toplevel_enumname, toplevel_messages, tlv_fields):
|
|
enum_set = ""
|
|
enum_set += enum_header(construct_hdr_enums(toplevel_messages), toplevel_enumname)
|
|
for field_name, tlv_messages in tlv_fields.items():
|
|
enum_set += "\n"
|
|
enum_set += enum_header(construct_hdr_enums(tlv_messages), field_name + '_type')
|
|
return enum_set
|
|
|
|
|
|
def build_impl_enums(toplevel_enumname, toplevel_messages, tlv_fields):
|
|
enum_set = ""
|
|
enum_set += enum_impl(construct_impl_enums(toplevel_messages), toplevel_enumname)
|
|
for field_name, tlv_messages in tlv_fields.items():
|
|
enum_set += "\n"
|
|
enum_set += enum_impl(construct_impl_enums(tlv_messages), field_name + '_type')
|
|
return enum_set
|
|
|
|
|
|
def build_tlv_structs(tlv_fields):
|
|
structs = ""
|
|
for field_name, tlv_messages in tlv_fields.items():
|
|
for m in tlv_messages:
|
|
structs += m.print_struct()
|
|
return structs
|
|
|
|
|
|
enum_header_template = """enum {enumname} {{
|
|
{enums}
|
|
}};
|
|
const char *{enumname}_name(int e);
|
|
"""
|
|
|
|
enum_impl_template = """
|
|
const char *{enumname}_name(int e)
|
|
{{
|
|
\tstatic char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)];
|
|
|
|
\tswitch ((enum {enumname})e) {{
|
|
\t{enums}
|
|
\t}}
|
|
|
|
\tsnprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e);
|
|
\treturn invalidbuf;
|
|
}}
|
|
"""
|
|
|
|
header_template = """/* This file was generated by generate-wire.py */
|
|
/* Do not modify this file! Modify the _csv file it was generated from. */
|
|
#ifndef LIGHTNING_{idem}
|
|
#define LIGHTNING_{idem}
|
|
#include <ccan/tal/tal.h>
|
|
#include <wire/wire.h>
|
|
{includes}
|
|
{formatted_hdr_enums}{tlv_structs}
|
|
{func_decls}
|
|
#endif /* LIGHTNING_{idem} */
|
|
"""
|
|
|
|
impl_template = """/* This file was generated by generate-wire.py */
|
|
/* Do not modify this file! Modify the _csv file it was generated from. */
|
|
#include <{headerfilename}>
|
|
#include <ccan/mem/mem.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <stdio.h>
|
|
{formatted_impl_enums}
|
|
{func_decls}
|
|
"""
|
|
|
|
print_header_template = """/* This file was generated by generate-wire.py */
|
|
/* Do not modify this file! Modify the _csv file it was generated from. */
|
|
#ifndef LIGHTNING_{idem}
|
|
#define LIGHTNING_{idem}
|
|
#include <ccan/tal/tal.h>
|
|
#include <devtools/print_wire.h>
|
|
{includes}
|
|
|
|
void print{enumname}_message(const u8 *msg);
|
|
|
|
{func_decls}
|
|
#endif /* LIGHTNING_{idem} */
|
|
"""
|
|
|
|
print_template = """/* This file was generated by generate-wire.py */
|
|
/* Do not modify this file! Modify the _csv file it was generated from. */
|
|
#include "{headerfilename}"
|
|
#include <ccan/mem/mem.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/utils.h>
|
|
#include <stdio.h>
|
|
|
|
void print{enumname}_message(const u8 *msg)
|
|
{{
|
|
\tswitch ((enum {enumname})fromwire_peektype(msg)) {{
|
|
\t{printcases}
|
|
\t}}
|
|
|
|
\tprintf("UNKNOWN: %s\\n", tal_hex(msg, msg));
|
|
}}
|
|
|
|
{func_decls}
|
|
"""
|
|
|
|
idem = re.sub(r'[^A-Z]+', '_', options.headerfilename.upper())
|
|
if options.printwire:
|
|
if options.header:
|
|
template = print_header_template
|
|
else:
|
|
template = print_template
|
|
elif options.header:
|
|
template = header_template
|
|
else:
|
|
template = impl_template
|
|
|
|
# Print out all the things
|
|
toplevel_messages = [m for m in messages if not m.is_tlv]
|
|
built_hdr_enums = build_hdr_enums(options.enumname, toplevel_messages, tlv_fields)
|
|
built_impl_enums = build_impl_enums(options.enumname, toplevel_messages, tlv_fields)
|
|
tlv_structs = build_tlv_structs(tlv_fields)
|
|
includes = '\n'.join(includes)
|
|
printcases = ['case {enum.name}: printf("{enum.name}:\\n"); printwire_{name}("{name}", msg); return;'.format(enum=m.enum, name=m.name) for m in toplevel_messages]
|
|
|
|
if options.printwire:
|
|
decls = [m.print_printwire(options.header) for m in messages + messages_with_option]
|
|
else:
|
|
fromwire_decls = [m.print_fromwire(options.header) for m in messages + messages_with_option]
|
|
towire_decls = towire_decls = [m.print_towire(options.header) for m in messages + messages_with_option]
|
|
decls = fromwire_decls + towire_decls
|
|
|
|
print(template.format(
|
|
headerfilename=options.headerfilename,
|
|
printcases='\n\t'.join(printcases),
|
|
idem=idem,
|
|
includes=includes,
|
|
enumname=options.enumname,
|
|
formatted_hdr_enums=built_hdr_enums,
|
|
formatted_impl_enums=built_impl_enums,
|
|
tlv_structs=tlv_structs,
|
|
func_decls='\n'.join(decls)))
|