2017-10-09 12:52:09 +02:00
|
|
|
#! /usr/bin/env python3
|
2019-07-24 01:54:21 +02:00
|
|
|
# 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,<msgname>,<value>[,<option>]
|
2023-05-22 02:51:44 +02:00
|
|
|
# msgdata,<msgname>,<fieldname>,<typename>,[<count>]
|
2019-07-24 01:54:21 +02:00
|
|
|
#
|
|
|
|
# TLV types:
|
2023-05-22 02:51:44 +02:00
|
|
|
# tlvtype,<tlvstreamname>,<tlvname>,<value>
|
|
|
|
# tlvdata,<tlvstreamname>,<tlvname>,<fieldname>,<typename>,[<count>]
|
2019-07-24 01:54:21 +02:00
|
|
|
#
|
|
|
|
# Subtypes:
|
|
|
|
# subtype,<subtypename>
|
2023-05-22 02:51:44 +02:00
|
|
|
# subtypedata,<subtypename>,<fieldname>,<typename>
|
2021-10-12 13:22:42 +02:00
|
|
|
#
|
|
|
|
# Note: <count> can be a fixed value, a named value read before,
|
|
|
|
# or '...' to read until the end of the current structure.
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
from argparse import ArgumentParser, REMAINDER
|
|
|
|
from collections import OrderedDict
|
2017-11-03 01:10:57 +01:00
|
|
|
import copy
|
2016-11-29 20:51:50 +01:00
|
|
|
import fileinput
|
2019-07-24 01:54:21 +02:00
|
|
|
from mako.template import Template
|
|
|
|
import os
|
2016-11-29 20:51:50 +01:00
|
|
|
import re
|
2019-07-24 01:54:21 +02:00
|
|
|
import sys
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2017-01-04 04:39:21 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
# Generator to give us one line at a time.
|
|
|
|
def next_line(args, lines):
|
|
|
|
if lines is None:
|
|
|
|
lines = fileinput.input(args)
|
2019-04-03 04:30:48 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
for i, line in enumerate(lines):
|
|
|
|
yield i + 1, line.strip()
|
2017-01-04 04:39:21 +01:00
|
|
|
|
2019-03-28 02:58:29 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
# Class definitions, to keep things classy
|
|
|
|
class Field(object):
|
2023-05-22 02:51:44 +02:00
|
|
|
def __init__(self, name, type_obj,
|
2019-07-24 01:54:21 +02:00
|
|
|
field_comments=[], optional=False):
|
|
|
|
self.name = name
|
|
|
|
self.type_obj = type_obj
|
|
|
|
self.count = 1
|
|
|
|
self.len_field_of = None
|
|
|
|
self.len_field = None
|
2019-07-28 02:52:43 +02:00
|
|
|
self.implicit_len = False
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
self.is_optional = optional
|
|
|
|
self.field_comments = field_comments
|
|
|
|
|
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
deepcopy_method = self.__deepcopy__
|
|
|
|
self.__deepcopy__ = None
|
|
|
|
field = copy.deepcopy(self, memo)
|
|
|
|
self.__deepcopy__ = deepcopy_method
|
|
|
|
|
|
|
|
field.type_obj = self.type_obj
|
|
|
|
return field
|
|
|
|
|
|
|
|
def add_count(self, count):
|
|
|
|
self.count = int(count)
|
|
|
|
|
|
|
|
def add_len_field(self, len_field):
|
|
|
|
self.count = False
|
|
|
|
# we cache our len-field's name
|
|
|
|
self.len_field = len_field.name
|
|
|
|
# the len-field caches our name
|
|
|
|
len_field.len_field_of = self.name
|
2017-03-16 05:05:23 +01:00
|
|
|
|
2019-07-28 02:52:43 +02:00
|
|
|
def add_implicit_len(self):
|
|
|
|
self.count = False
|
|
|
|
self.implicit_len = True
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_array(self):
|
|
|
|
return self.count > 1
|
2019-03-20 19:49:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_varlen(self):
|
|
|
|
return not self.count
|
2019-03-28 02:58:29 +01:00
|
|
|
|
2019-07-28 02:52:43 +02:00
|
|
|
def is_implicit_len(self):
|
|
|
|
return self.implicit_len
|
|
|
|
|
|
|
|
def size(self, implicit_expression=None):
|
2019-07-24 01:54:21 +02:00
|
|
|
if self.count:
|
|
|
|
return self.count
|
2019-07-28 02:52:43 +02:00
|
|
|
if self.len_field:
|
|
|
|
return self.len_field
|
|
|
|
assert self.is_implicit_len()
|
|
|
|
assert implicit_expression
|
|
|
|
return implicit_expression
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def needs_context(self):
|
|
|
|
""" A field needs a context if it's varsized """
|
|
|
|
return self.is_varlen() or self.type_obj.needs_context()
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def arg_desc_to(self):
|
|
|
|
if self.len_field_of:
|
|
|
|
return ''
|
|
|
|
type_name = self.type_obj.type_name()
|
|
|
|
if self.is_array():
|
|
|
|
return ', const {} {}[{}]'.format(type_name, self.name, self.count)
|
|
|
|
if self.type_obj.is_assignable() and not self.is_varlen():
|
|
|
|
name = self.name
|
|
|
|
if self.is_optional:
|
|
|
|
name = '*' + name
|
|
|
|
return ', {} {}'.format(type_name, name)
|
|
|
|
if self.is_varlen() and self.type_obj.is_varsize():
|
|
|
|
return ', const {} **{}'.format(type_name, self.name)
|
|
|
|
return ', const {} *{}'.format(type_name, self.name)
|
|
|
|
|
|
|
|
def arg_desc_from(self):
|
2019-09-25 22:38:45 +02:00
|
|
|
type_name = self.type_obj.type_name()
|
|
|
|
if self.type_obj.is_const_ptr_ptr_type():
|
|
|
|
return ', const {} **{}'.format(type_name, self.name)
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
if self.len_field_of:
|
|
|
|
return ''
|
|
|
|
if self.is_array():
|
|
|
|
return ', {} {}[{}]'.format(type_name, self.name, self.count)
|
|
|
|
ptrs = '*'
|
|
|
|
if self.is_varlen() or self.is_optional or self.type_obj.is_varsize():
|
|
|
|
ptrs += '*'
|
|
|
|
if self.is_varlen() and self.type_obj.is_varsize():
|
|
|
|
ptrs += '*'
|
|
|
|
return ', {} {}{}'.format(type_name, ptrs, self.name)
|
|
|
|
|
|
|
|
|
|
|
|
class FieldSet(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.fields = OrderedDict()
|
|
|
|
self.len_fields = {}
|
|
|
|
|
|
|
|
def add_data_field(self, field_name, type_obj, count=1,
|
2023-05-22 02:51:44 +02:00
|
|
|
comments=[], optional=False,
|
2019-07-28 02:52:43 +02:00
|
|
|
implicit_len_ok=False):
|
2023-05-22 02:51:44 +02:00
|
|
|
field = Field(field_name, type_obj,
|
2019-07-24 01:54:21 +02:00
|
|
|
field_comments=comments, optional=optional)
|
|
|
|
if bool(count):
|
|
|
|
try:
|
|
|
|
field.add_count(int(count))
|
|
|
|
except ValueError:
|
2019-07-28 02:52:43 +02:00
|
|
|
if count in self.fields:
|
|
|
|
len_field = self.find_data_field(count)
|
|
|
|
field.add_len_field(len_field)
|
|
|
|
self.len_fields[len_field.name] = len_field
|
|
|
|
else:
|
|
|
|
# '...' means "rest of TLV"
|
|
|
|
assert implicit_len_ok
|
|
|
|
assert count == '...'
|
|
|
|
field.add_implicit_len()
|
|
|
|
|
|
|
|
# You can't have any fields after an implicit-length field.
|
|
|
|
if len(self.fields) != 0:
|
|
|
|
assert not self.fields[next(reversed(self.fields))].is_implicit_len()
|
2019-07-24 01:54:21 +02:00
|
|
|
self.fields[field_name] = field
|
|
|
|
|
|
|
|
def find_data_field(self, field_name):
|
|
|
|
return self.fields[field_name]
|
|
|
|
|
|
|
|
def get_len_fields(self):
|
|
|
|
return list(self.len_fields.values())
|
|
|
|
|
|
|
|
def has_len_fields(self):
|
|
|
|
return bool(self.len_fields)
|
|
|
|
|
|
|
|
def needs_context(self):
|
|
|
|
return any([field.needs_context() or field.is_optional for field in self.fields.values()])
|
|
|
|
|
2020-05-06 12:41:54 +02:00
|
|
|
def singleton(self):
|
|
|
|
"""Return the single message, if there's only one, otherwise None"""
|
|
|
|
if len(self.fields) == 1:
|
|
|
|
return next(iter(self.fields.values()))
|
|
|
|
return None
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
class Type(FieldSet):
|
|
|
|
assignables = [
|
|
|
|
'u8',
|
|
|
|
'u16',
|
|
|
|
'u32',
|
|
|
|
'u64',
|
2023-05-05 21:10:53 +02:00
|
|
|
's8',
|
|
|
|
's16',
|
|
|
|
's32',
|
|
|
|
's64',
|
2019-07-24 01:54:21 +02:00
|
|
|
'tu16',
|
|
|
|
'tu32',
|
|
|
|
'tu64',
|
|
|
|
'bool',
|
|
|
|
'amount_sat',
|
|
|
|
'amount_msat',
|
2019-07-27 12:20:25 +02:00
|
|
|
'bigsize',
|
|
|
|
'varint'
|
2019-07-24 01:54:21 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
typedefs = [
|
|
|
|
'u8',
|
|
|
|
'u16',
|
|
|
|
'u32',
|
|
|
|
'u64',
|
2023-05-05 21:10:53 +02:00
|
|
|
's8',
|
|
|
|
's16',
|
|
|
|
's32',
|
|
|
|
's64',
|
2019-07-24 01:54:21 +02:00
|
|
|
'bool',
|
|
|
|
'secp256k1_ecdsa_signature',
|
|
|
|
'secp256k1_ecdsa_recoverable_signature',
|
2020-12-05 03:19:54 +01:00
|
|
|
'utf8',
|
2019-07-24 01:54:21 +02:00
|
|
|
'wirestring',
|
2019-07-27 12:20:25 +02:00
|
|
|
'bigsize',
|
|
|
|
'varint',
|
2019-07-24 01:54:21 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
truncated_typedefs = [
|
|
|
|
'tu16',
|
|
|
|
'tu32',
|
|
|
|
'tu64',
|
|
|
|
]
|
|
|
|
|
|
|
|
# Externally defined variable size types (require a context)
|
|
|
|
varsize_types = [
|
|
|
|
'peer_features',
|
2021-09-09 07:29:35 +02:00
|
|
|
'channel_type',
|
2019-07-24 01:54:21 +02:00
|
|
|
'gossip_getnodes_entry',
|
|
|
|
'gossip_getchannels_entry',
|
|
|
|
'failed_htlc',
|
2020-04-03 05:14:07 +02:00
|
|
|
'existing_htlc',
|
2021-12-15 02:37:35 +01:00
|
|
|
'simple_htlc',
|
2023-07-27 23:37:52 +02:00
|
|
|
'inflight',
|
2019-07-24 01:54:21 +02:00
|
|
|
'utxo',
|
|
|
|
'bitcoin_tx',
|
|
|
|
'wirestring',
|
|
|
|
'per_peer_state',
|
2019-08-15 19:41:23 +02:00
|
|
|
'bitcoin_tx_output',
|
2019-08-31 14:52:35 +02:00
|
|
|
'exclude_entry',
|
2019-12-12 18:18:25 +01:00
|
|
|
'fee_states',
|
2021-06-22 20:25:59 +02:00
|
|
|
'height_states',
|
2020-01-22 07:28:25 +01:00
|
|
|
'onionreply',
|
2020-03-31 00:39:00 +02:00
|
|
|
'feature_set',
|
2022-10-17 02:44:39 +02:00
|
|
|
'onionmsg_hop',
|
|
|
|
'blinded_path',
|
2020-04-11 05:22:43 +02:00
|
|
|
'route_hop',
|
2020-05-26 05:39:40 +02:00
|
|
|
'tx_parts',
|
2020-06-06 21:50:23 +02:00
|
|
|
'wally_psbt',
|
2020-09-11 20:55:22 +02:00
|
|
|
'wally_tx',
|
2022-06-24 02:34:32 +02:00
|
|
|
'scb_chan',
|
2023-05-12 00:07:32 +02:00
|
|
|
'inflight',
|
2019-07-24 01:54:21 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
# Some BOLT types are re-typed based on their field name
|
2019-08-01 07:58:09 +02:00
|
|
|
# ('fieldname partial', 'original type', 'outer type'): ('true type', 'collapse array?')
|
2019-07-24 01:54:21 +02:00
|
|
|
name_field_map = {
|
|
|
|
('txid', 'sha256'): ('bitcoin_txid', False),
|
|
|
|
('amt', 'u64'): ('amount_msat', False),
|
|
|
|
('msat', 'u64'): ('amount_msat', False),
|
|
|
|
('satoshis', 'u64'): ('amount_sat', False),
|
2019-08-01 07:58:09 +02:00
|
|
|
('node_id', 'pubkey', 'channel_announcement'): ('node_id', False),
|
|
|
|
('node_id', 'pubkey', 'node_announcement'): ('node_id', False),
|
2019-07-24 01:54:21 +02:00
|
|
|
('temporary_channel_id', 'u8'): ('channel_id', True),
|
|
|
|
('secret', 'u8'): ('secret', True),
|
|
|
|
('preimage', 'u8'): ('preimage', True),
|
|
|
|
}
|
|
|
|
|
|
|
|
# For BOLT specified types, a few type names need to be simply 'remapped'
|
|
|
|
# 'original type': 'true type'
|
|
|
|
name_remap = {
|
|
|
|
'byte': 'u8',
|
|
|
|
'signature': 'secp256k1_ecdsa_signature',
|
|
|
|
'chain_hash': 'bitcoin_blkid',
|
|
|
|
'point': 'pubkey',
|
|
|
|
# FIXME: omits 'pad'
|
|
|
|
}
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-09-25 22:38:45 +02:00
|
|
|
# Types that are const pointer-to-pointers, such as chainparams, i.e.,
|
|
|
|
# they set a reference to some const entry.
|
|
|
|
const_ptr_ptr_types = [
|
|
|
|
'chainparams'
|
|
|
|
]
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
@staticmethod
|
2019-08-01 07:58:09 +02:00
|
|
|
def true_type(type_name, field_name=None, outer_name=None):
|
2019-07-24 01:54:21 +02:00
|
|
|
""" Returns 'true' type of a given type and a flag if
|
|
|
|
we've remapped a variable size/array type to a single struct
|
|
|
|
(an example of this is 'temporary_channel_id' which is specified
|
|
|
|
as a 32*byte, but we re-map it to a channel_id
|
|
|
|
"""
|
|
|
|
if type_name in Type.name_remap:
|
|
|
|
type_name = Type.name_remap[type_name]
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
if field_name:
|
2019-08-01 07:58:09 +02:00
|
|
|
for t, true_type in Type.name_field_map.items():
|
|
|
|
if t[0] in field_name and t[1] == type_name:
|
|
|
|
if len(t) == 2 or outer_name == t[2]:
|
|
|
|
return true_type
|
2019-07-24 01:54:21 +02:00
|
|
|
return (type_name, False)
|
2018-07-09 08:50:31 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def __init__(self, name):
|
|
|
|
FieldSet.__init__(self)
|
|
|
|
self.name, self.is_enum = self.parse_name(name)
|
|
|
|
self.depends_on = {}
|
|
|
|
self.type_comments = []
|
|
|
|
self.tlv = False
|
|
|
|
|
|
|
|
def parse_name(self, name):
|
2019-07-24 06:20:04 +02:00
|
|
|
if name.startswith('enum '):
|
|
|
|
return name[5:], True
|
2019-07-24 01:54:21 +02:00
|
|
|
return name, False
|
|
|
|
|
|
|
|
def add_data_field(self, field_name, type_obj, count=1,
|
2023-05-22 02:51:44 +02:00
|
|
|
comments=[], optional=False):
|
2019-07-24 01:54:21 +02:00
|
|
|
FieldSet.add_data_field(self, field_name, type_obj, count,
|
|
|
|
comments=comments, optional=optional)
|
|
|
|
if type_obj.name not in self.depends_on:
|
|
|
|
self.depends_on[type_obj.name] = type_obj
|
|
|
|
|
|
|
|
def type_name(self):
|
|
|
|
if self.name in self.typedefs:
|
|
|
|
return self.name
|
|
|
|
if self.name in self.truncated_typedefs:
|
|
|
|
return self.name[1:]
|
|
|
|
if self.is_enum:
|
|
|
|
prefix = 'enum '
|
|
|
|
else:
|
|
|
|
prefix = 'struct '
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
return prefix + self.struct_name()
|
|
|
|
|
2020-12-05 03:19:54 +01:00
|
|
|
# We accelerate the u8 case: it's common and trivial.
|
|
|
|
# We handle the utf8 case so we can be sure it's actually a UTF-8 string.
|
2017-03-16 05:05:23 +01:00
|
|
|
def has_array_helper(self):
|
2020-12-05 03:19:54 +01:00
|
|
|
return self.name in ['u8', 'utf8']
|
2017-12-10 13:41:10 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def struct_name(self):
|
|
|
|
if self.is_tlv():
|
|
|
|
return self.tlv.struct_name()
|
|
|
|
return self.name
|
2018-07-16 05:12:44 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def subtype_deps(self):
|
|
|
|
return [dep for dep in self.depends_on.values() if dep.is_subtype()]
|
2018-07-16 05:12:44 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_subtype(self):
|
|
|
|
return bool(self.fields)
|
2018-07-16 05:12:44 +02:00
|
|
|
|
2019-09-25 22:38:45 +02:00
|
|
|
def is_const_ptr_ptr_type(self):
|
|
|
|
return self.name in self.const_ptr_ptr_types
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_truncated(self):
|
|
|
|
return self.name in self.truncated_typedefs
|
2018-07-16 05:12:44 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def needs_context(self):
|
|
|
|
return self.is_varsize()
|
2018-07-16 05:12:44 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_assignable(self):
|
|
|
|
""" Generally typedef's and enums """
|
|
|
|
return self.name in self.assignables or self.is_enum
|
2017-03-16 05:05:23 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_varsize(self):
|
|
|
|
""" A type is variably sized if it's marked as such (in varsize_types)
|
|
|
|
or it contains a field of variable length """
|
2022-03-23 00:31:14 +01:00
|
|
|
return self.name in self.varsize_types or self.has_len_fields() or self.is_tlv()
|
2016-11-29 20:51:50 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def add_comments(self, comments):
|
|
|
|
self.type_comments = comments
|
2017-12-10 13:41:10 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def mark_tlv(self, tlv):
|
|
|
|
self.tlv = tlv
|
2017-03-16 05:05:23 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def is_tlv(self):
|
|
|
|
return bool(self.tlv)
|
2019-03-28 22:25:19 +01:00
|
|
|
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
class Message(FieldSet):
|
|
|
|
def __init__(self, name, number, option=[], enum_prefix='wire',
|
|
|
|
struct_prefix=None, comments=[]):
|
|
|
|
FieldSet.__init__(self)
|
|
|
|
self.name = name
|
|
|
|
self.number = number
|
|
|
|
self.enum_prefix = enum_prefix
|
|
|
|
self.option = option[0] if len(option) else None
|
|
|
|
self.struct_prefix = struct_prefix
|
|
|
|
self.enumname = None
|
|
|
|
self.msg_comments = comments
|
2019-09-20 19:49:45 +02:00
|
|
|
self.if_token = None
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def has_option(self):
|
|
|
|
return self.option is not None
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def enum_name(self):
|
|
|
|
name = self.enumname if self.enumname else self.name
|
|
|
|
return "{}_{}".format(self.enum_prefix, name).upper()
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def struct_name(self):
|
|
|
|
if self.struct_prefix:
|
|
|
|
return self.struct_prefix + "_" + self.name
|
|
|
|
return self.name
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-09-20 19:49:45 +02:00
|
|
|
def add_if(self, if_token):
|
|
|
|
self.if_token = if_token
|
|
|
|
|
2019-04-05 05:06:49 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
class Tlv(object):
|
|
|
|
def __init__(self, name):
|
2022-03-23 00:31:14 +01:00
|
|
|
self.name = 'tlv_' + name
|
2019-07-24 01:54:21 +02:00
|
|
|
self.messages = {}
|
|
|
|
|
|
|
|
def add_message(self, tokens, comments=[]):
|
|
|
|
""" tokens -> (name, value[, option]) """
|
|
|
|
self.messages[tokens[0]] = Message(tokens[0], tokens[1], option=tokens[2:],
|
|
|
|
enum_prefix=self.name,
|
|
|
|
struct_prefix=self.struct_name(),
|
|
|
|
comments=comments)
|
|
|
|
|
|
|
|
def type_name(self):
|
|
|
|
return 'struct ' + self.struct_name()
|
|
|
|
|
|
|
|
def struct_name(self):
|
2022-03-23 00:31:14 +01:00
|
|
|
return self.name
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
def find_message(self, name):
|
|
|
|
return self.messages[name]
|
|
|
|
|
2019-08-21 04:46:32 +02:00
|
|
|
def ordered_msgs(self):
|
|
|
|
return sorted(self.messages.values(), key=lambda item: int(item.number))
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
class Master(object):
|
|
|
|
types = {}
|
|
|
|
tlvs = {}
|
|
|
|
messages = {}
|
|
|
|
inclusions = []
|
|
|
|
top_comments = []
|
|
|
|
|
|
|
|
def add_comments(self, comments):
|
|
|
|
self.top_comments += comments
|
|
|
|
|
|
|
|
def add_include(self, inclusion):
|
|
|
|
self.inclusions.append(inclusion)
|
|
|
|
|
|
|
|
def add_tlv(self, tlv_name):
|
|
|
|
if tlv_name not in self.tlvs:
|
|
|
|
self.tlvs[tlv_name] = Tlv(tlv_name)
|
|
|
|
|
|
|
|
if tlv_name not in self.types:
|
|
|
|
self.types[tlv_name] = Type(tlv_name)
|
|
|
|
|
|
|
|
return self.tlvs[tlv_name]
|
|
|
|
|
|
|
|
def add_message(self, tokens, comments=[]):
|
|
|
|
""" tokens -> (name, value[, option])"""
|
|
|
|
self.messages[tokens[0]] = Message(tokens[0], tokens[1], option=tokens[2:],
|
|
|
|
comments=comments)
|
|
|
|
|
2019-08-01 07:58:09 +02:00
|
|
|
def add_type(self, type_name, field_name=None, outer_name=None):
|
2019-07-24 01:54:21 +02:00
|
|
|
optional = False
|
|
|
|
if type_name.startswith('?'):
|
|
|
|
type_name = type_name[1:]
|
|
|
|
optional = True
|
|
|
|
# Check for special type name re-mapping
|
2019-08-01 07:58:09 +02:00
|
|
|
type_name, collapse_original = Type.true_type(type_name, field_name,
|
|
|
|
outer_name)
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
if type_name not in self.types:
|
|
|
|
self.types[type_name] = Type(type_name)
|
|
|
|
return self.types[type_name], collapse_original, optional
|
|
|
|
|
|
|
|
def find_type(self, type_name):
|
|
|
|
return self.types[type_name]
|
|
|
|
|
|
|
|
def find_message(self, msg_name):
|
|
|
|
if msg_name in self.messages:
|
|
|
|
return self.messages[msg_name]
|
|
|
|
return None
|
|
|
|
|
|
|
|
def find_tlv(self, tlv_name):
|
|
|
|
return self.tlvs[tlv_name]
|
|
|
|
|
|
|
|
def get_ordered_subtypes(self):
|
|
|
|
""" We want to order subtypes such that the 'no dependency'
|
|
|
|
types are printed first """
|
|
|
|
subtypes = [s for s in self.types.values() if s.is_subtype()]
|
|
|
|
|
|
|
|
# Start with subtypes without subtype dependencies
|
|
|
|
sorted_types = [s for s in subtypes if not len(s.subtype_deps())]
|
|
|
|
unsorted = [s for s in subtypes if len(s.subtype_deps())]
|
|
|
|
while len(unsorted):
|
|
|
|
names = [s.name for s in sorted_types]
|
|
|
|
for s in list(unsorted):
|
|
|
|
if all([dependency.name in names for dependency in s.subtype_deps()]):
|
|
|
|
sorted_types.append(s)
|
|
|
|
unsorted.remove(s)
|
|
|
|
return sorted_types
|
|
|
|
|
2020-05-06 12:41:54 +02:00
|
|
|
def tlv_structs(self):
|
|
|
|
ret = []
|
|
|
|
for tlv in self.tlvs.values():
|
|
|
|
for v in tlv.messages.values():
|
|
|
|
if not v.singleton():
|
|
|
|
ret.append(v)
|
|
|
|
|
|
|
|
return ret
|
2019-07-24 01:54:21 +02:00
|
|
|
|
|
|
|
def find_template(self, options):
|
|
|
|
dirpath = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
filename = dirpath + '/gen/{}{}_template'.format(
|
|
|
|
'print_' if options.print_wire else '', options.page)
|
|
|
|
|
|
|
|
return Template(filename=filename)
|
|
|
|
|
|
|
|
def post_process(self):
|
|
|
|
""" method to handle any 'post processing' that needs to be done.
|
|
|
|
for now, we just need match up types to TLVs """
|
|
|
|
for tlv_name, tlv in self.tlvs.items():
|
|
|
|
if tlv_name in self.types:
|
|
|
|
self.types[tlv_name].mark_tlv(tlv)
|
|
|
|
|
|
|
|
def write(self, options, output):
|
|
|
|
template = self.find_template(options)
|
|
|
|
enum_sets = []
|
2020-05-02 12:40:16 +02:00
|
|
|
if len(self.messages.values()) != 0:
|
|
|
|
enum_sets.append({
|
|
|
|
'name': options.enum_name,
|
|
|
|
'set': self.messages.values(),
|
|
|
|
})
|
2019-07-24 01:54:21 +02:00
|
|
|
stuff = {}
|
|
|
|
stuff['top_comments'] = self.top_comments
|
|
|
|
stuff['options'] = options
|
2021-01-13 04:00:24 +01:00
|
|
|
stuff['idem'] = re.sub(r'[^A-Z0-9]+', '_', options.header_filename.upper())
|
2019-07-24 01:54:21 +02:00
|
|
|
stuff['header_filename'] = options.header_filename
|
|
|
|
stuff['includes'] = self.inclusions
|
|
|
|
stuff['enum_sets'] = enum_sets
|
|
|
|
subtypes = self.get_ordered_subtypes()
|
2020-05-06 12:41:54 +02:00
|
|
|
stuff['structs'] = subtypes + self.tlv_structs()
|
2019-07-24 01:54:21 +02:00
|
|
|
stuff['tlvs'] = self.tlvs
|
|
|
|
|
2023-05-22 02:51:44 +02:00
|
|
|
stuff['messages'] = list(self.messages.values())
|
2019-07-24 01:54:21 +02:00
|
|
|
stuff['subtypes'] = subtypes
|
2019-04-05 05:06:49 +02:00
|
|
|
|
2020-09-09 07:48:21 +02:00
|
|
|
for line in template.render(**stuff).splitlines():
|
|
|
|
print(line.rstrip(), file=output)
|
2018-02-01 01:08:25 +01:00
|
|
|
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
def main(options, args=None, output=sys.stdout, lines=None):
|
|
|
|
genline = next_line(args, lines)
|
2018-02-01 01:08:25 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
comment_set = []
|
2019-09-20 19:49:45 +02:00
|
|
|
token_name = None
|
2018-07-09 08:50:31 +02:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
# Create a new 'master' that serves as the coordinator for the file generation
|
|
|
|
master = Master()
|
2020-05-02 12:41:16 +02:00
|
|
|
for i in options.include:
|
|
|
|
master.add_include('#include <{}>'.format(i))
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
ln, line = next(genline)
|
|
|
|
tokens = line.split(',')
|
|
|
|
token_type = tokens[0]
|
2019-03-27 23:39:58 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
if not bool(line):
|
|
|
|
master.add_comments(comment_set)
|
|
|
|
comment_set = []
|
2019-09-20 19:49:45 +02:00
|
|
|
token_name = None
|
2019-03-28 22:19:12 +01:00
|
|
|
continue
|
2019-07-24 01:54:21 +02:00
|
|
|
|
2019-09-20 19:49:45 +02:00
|
|
|
if len(tokens) > 2:
|
|
|
|
token_name = tokens[1]
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
if token_type == 'subtype':
|
|
|
|
subtype, _, _ = master.add_type(tokens[1])
|
|
|
|
|
|
|
|
subtype.add_comments(list(comment_set))
|
|
|
|
comment_set = []
|
|
|
|
elif token_type == 'subtypedata':
|
|
|
|
subtype = master.find_type(tokens[1])
|
|
|
|
if not subtype:
|
|
|
|
raise ValueError('Unknown subtype {} for data.\nat {}:{}'
|
|
|
|
.format(tokens[1], ln, line))
|
2019-08-01 07:58:09 +02:00
|
|
|
type_obj, collapse, optional = master.add_type(tokens[3], tokens[2], tokens[1])
|
2019-07-24 01:54:21 +02:00
|
|
|
if optional:
|
|
|
|
raise ValueError('Subtypes cannot have optional fields {}.{}\n at {}:{}'
|
|
|
|
.format(subtype.name, tokens[2], ln, line))
|
|
|
|
if collapse:
|
|
|
|
count = 1
|
2019-03-27 23:48:45 +01:00
|
|
|
else:
|
2019-07-24 01:54:21 +02:00
|
|
|
count = tokens[4]
|
|
|
|
|
|
|
|
subtype.add_data_field(tokens[2], type_obj, count, comments=list(comment_set),
|
|
|
|
optional=optional)
|
|
|
|
comment_set = []
|
|
|
|
elif token_type == 'tlvtype':
|
2023-05-22 02:51:44 +02:00
|
|
|
# Hack: modern spec assumes tlvs, so if there's a type to
|
|
|
|
# attach this to, do it now, by assuming it's the same name
|
|
|
|
# with _tlvs appended.
|
|
|
|
if tokens[1].endswith("_tlvs"):
|
|
|
|
container_name = tokens[1][:-5]
|
|
|
|
msg = master.find_message(container_name)
|
|
|
|
|
|
|
|
if msg is not None:
|
|
|
|
if tokens[1] not in master.types:
|
|
|
|
# Adding: msgdata,update_add_htlc,tlvs,update_add_htlc_tlvs,
|
|
|
|
type_obj, _, _ = master.add_type(tokens[1], "tlvs", container_name)
|
|
|
|
msg.add_data_field(container_name, type_obj)
|
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
tlv = master.add_tlv(tokens[1])
|
|
|
|
tlv.add_message(tokens[2:], comments=list(comment_set))
|
|
|
|
|
|
|
|
comment_set = []
|
|
|
|
elif token_type == 'tlvdata':
|
2019-08-01 07:58:09 +02:00
|
|
|
type_obj, collapse, optional = master.add_type(tokens[4], tokens[3], tokens[1])
|
2019-07-24 01:54:21 +02:00
|
|
|
if optional:
|
|
|
|
raise ValueError('TLV messages cannot have optional fields {}.{}\n at {}:{}'
|
|
|
|
.format(tokens[2], tokens[3], ln, line))
|
|
|
|
|
|
|
|
tlv = master.find_tlv(tokens[1])
|
|
|
|
if not tlv:
|
|
|
|
raise ValueError('tlvdata for unknown tlv {}.\nat {}:{}'
|
|
|
|
.format(tokens[1], ln, line))
|
|
|
|
msg = tlv.find_message(tokens[2])
|
|
|
|
if not msg:
|
|
|
|
raise ValueError('tlvdata for unknown tlv-message {}.\nat {}:{}'
|
|
|
|
.format(tokens[2], ln, line))
|
|
|
|
if collapse:
|
|
|
|
count = 1
|
|
|
|
else:
|
|
|
|
count = tokens[5]
|
|
|
|
|
|
|
|
msg.add_data_field(tokens[3], type_obj, count, comments=list(comment_set),
|
2019-07-28 02:52:43 +02:00
|
|
|
optional=optional, implicit_len_ok=True)
|
2019-07-24 01:54:21 +02:00
|
|
|
comment_set = []
|
|
|
|
elif token_type == 'msgtype':
|
|
|
|
master.add_message(tokens[1:], comments=list(comment_set))
|
|
|
|
comment_set = []
|
|
|
|
elif token_type == 'msgdata':
|
|
|
|
msg = master.find_message(tokens[1])
|
|
|
|
if not msg:
|
|
|
|
raise ValueError('Unknown message type {}. {}:{}'.format(tokens[1], ln, line))
|
2019-08-01 07:58:09 +02:00
|
|
|
type_obj, collapse, optional = master.add_type(tokens[3], tokens[2], tokens[1])
|
2019-07-24 01:54:21 +02:00
|
|
|
|
tools/generate-wire.py: accept multiple comma-separated options.
Somehow this change got lost, but it's needed for option_static_remotekey,
to quote gen_peer_wire_csv:
msgtype,channel_reestablish,136
msgdata,channel_reestablish,channel_id,channel_id,
msgdata,channel_reestablish,next_commitment_number,u64,
msgdata,channel_reestablish,next_revocation_number,u64,
msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32,option_data_loss_protect,option_static_remotekey
msgdata,channel_reestablish,my_current_per_commitment_point,point,,option_data_loss_protect
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2019-09-10 04:19:27 +02:00
|
|
|
if collapse:
|
|
|
|
count = 1
|
2019-09-20 19:51:19 +02:00
|
|
|
elif len(tokens) < 5:
|
|
|
|
raise ValueError('problem with parsing {}:{}'.format(ln, line))
|
tools/generate-wire.py: accept multiple comma-separated options.
Somehow this change got lost, but it's needed for option_static_remotekey,
to quote gen_peer_wire_csv:
msgtype,channel_reestablish,136
msgdata,channel_reestablish,channel_id,channel_id,
msgdata,channel_reestablish,next_commitment_number,u64,
msgdata,channel_reestablish,next_revocation_number,u64,
msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32,option_data_loss_protect,option_static_remotekey
msgdata,channel_reestablish,my_current_per_commitment_point,point,,option_data_loss_protect
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2019-09-10 04:19:27 +02:00
|
|
|
else:
|
|
|
|
count = tokens[4]
|
|
|
|
|
2023-05-22 02:51:44 +02:00
|
|
|
msg.add_data_field(tokens[2], type_obj, count, comments=list(comment_set),
|
|
|
|
optional=optional)
|
2019-07-24 01:54:21 +02:00
|
|
|
comment_set = []
|
|
|
|
elif token_type.startswith('#include'):
|
|
|
|
master.add_include(token_type)
|
2019-09-20 19:49:45 +02:00
|
|
|
elif token_type.startswith('#if'):
|
|
|
|
msg = master.find_message(token_name)
|
|
|
|
if (msg):
|
|
|
|
if_token = token_type[token_type.index(' ') + 1:]
|
|
|
|
msg.add_if(if_token)
|
2019-07-24 01:54:21 +02:00
|
|
|
elif token_type.startswith('#'):
|
|
|
|
comment_set.append(token_type[1:])
|
2019-03-28 22:45:07 +01:00
|
|
|
else:
|
2019-07-24 01:54:21 +02:00
|
|
|
raise ValueError("Unknown token type {} on line {}:{}".format(token_type, ln, line))
|
2017-11-03 01:10:57 +01:00
|
|
|
|
2019-07-24 01:54:21 +02:00
|
|
|
except StopIteration:
|
|
|
|
pass
|
|
|
|
|
|
|
|
master.post_process()
|
|
|
|
master.write(options, output)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = ArgumentParser()
|
|
|
|
parser.add_argument("-s", "--expose-subtypes", help="print subtypes in header",
|
|
|
|
action="store_true", default=False)
|
|
|
|
parser.add_argument("-P", "--print_wire", help="generate wire printing source files",
|
|
|
|
action="store_true", default=False)
|
|
|
|
parser.add_argument("--page", choices=['header', 'impl'], help="page to print")
|
2019-08-01 06:57:10 +02:00
|
|
|
parser.add_argument('--expose-tlv-type', action='append', default=[])
|
2020-05-02 12:41:16 +02:00
|
|
|
parser.add_argument('--include', action='append', default=[])
|
2019-07-24 01:54:21 +02:00
|
|
|
parser.add_argument('header_filename', help='The filename of the header')
|
|
|
|
parser.add_argument('enum_name', help='The name of the enum to produce')
|
|
|
|
parser.add_argument("files", help='Files to read in (or stdin)', nargs=REMAINDER)
|
|
|
|
parsed_args = parser.parse_args()
|
|
|
|
|
|
|
|
main(parsed_args, parsed_args.files)
|