1
0
Fork 0
mirror of https://github.com/lightning/bolts.git synced 2025-02-24 06:47:56 +01:00
lightning-bolts/tools/extract-formats.py
Rusty Russell a759dff029 BOLT 1, tools: add support for our first optional field.
We've always specified that fields can be appended, and we're about to
do our first.  Make it clear that you can't just send one if we add several
fields as part of the same option.

Change extract-formats to have an optional new field appended: the
input format is to simply add ' (optionname)' in the data
specification.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2017-11-13 11:10:32 -08:00

165 lines
5.2 KiB
Python
Executable file

#! /usr/bin/python3
# Simple script to parse specs and produce CSV files.
# Released by Rusty Russell under CC0:
# https://creativecommons.org/publicdomain/zero/1.0/
from optparse import OptionParser
import sys
import re
import fileinput
# Figure out if we can determine type from size.
def guess_alignment(message, name, sizestr):
# Exceptions:
# - Padding has no alignment requirements.
# - channel-id is size 8, but has alignment 4.
# - node_announcement.ipv6 has size 16, but alignment 4 (to align IPv4).
# - node_announcement.alias is a string, so alignment 1
# - signatures have no alignment requirement.
if name.startswith('pad'):
return 1
if name == 'channel-id':
return 4
if message == 'node_announcement' and name == 'ipv6':
return 4
if message == 'node_announcement' and name == 'alias':
return 1
if 'signature' in name:
return 1
# Size can be variable.
try:
size = int(sizestr)
except ValueError:
# If it contains a "*xxx" factor, that's our per-unit size.
s = re.search('\*([0-9]*)$', sizestr)
if s is None:
size = 1
else:
size = int(s.group(1))
if size % 8 == 0:
return 8
elif size % 4 == 0:
return 4
elif size % 2 == 0:
return 2
return 1
def main(options, args=None, output=sys.stdout, lines=None):
# Example inputs:
# 1. type: 17 (`error`)
# 2. data:
# * [`8`:`channel_id`]
# * [`4`:`len`]
# * [`len`:`data`] (optionXXX)
#
# 1. type: PERM|NODE|3 (`required_node_feature_missing`)
message = None
havedata = None
typeline = re.compile(
'1\. type: (?P<value>[-0-9A-Za-z_|]+) \(`(?P<name>[A-Za-z_]+)`\)')
dataline = re.compile(
'\s+\* \[`(?P<size>[_a-z0-9*+]+)`:`(?P<name>[_a-z0-9]+)`\]( \((?P<option>[^)]*)\))?')
if lines is None:
lines = fileinput.input(args)
for i, line in enumerate(lines):
line = line.rstrip()
linenum = i+1
match = typeline.fullmatch(line)
if match:
if message is not None:
raise ValueError('{}:Found a message while I was already in a '
'message'.format(linenum))
message = match.group('name')
if options.output_types:
print("{},{}".format(
match.group('name'),
match.group('value')), file=output)
havedata = None
alignoff = False
elif message is not None and havedata is None:
if line != '2. data:':
message = None
havedata = True
dataoff = 0
off_extraterms = ""
elif message is not None and havedata is not None:
match = dataline.fullmatch(line)
if match:
align = guess_alignment(message, match.group('name'),
match.group('size'))
# Do not check alignment if we previously had a variable
# length field in the message
if off_extraterms != "":
alignoff = True
if not alignoff and options.check_alignment and dataoff % align != 0:
raise ValueError('{}:message {} field {} Offset {} not '
'aligned on {} boundary:'.format(
linenum,
message,
match.group('name'),
dataoff,
align))
if options.output_fields:
print("{},{}{},{},{}".format(
message,
dataoff,
off_extraterms,
match.group('name'),
match.group('size')), file=output, end='')
if match.group('option'):
print(",{}".format(match.group('option')))
else:
print('')
# Size can be variable.
try:
dataoff += int(match.group('size'))
except ValueError:
# Offset has variable component.
off_extraterms = off_extraterms + "+" + match.group('size')
else:
message = None
if __name__ == "__main__":
parser = OptionParser()
parser.add_option(
"--message-types",
action="store_true",
dest="output_types",
default=False,
help="Output MESSAGENAME,VALUE for every message"
)
parser.add_option(
"--check-alignment",
action="store_true",
dest="check_alignment",
default=False,
help="Check alignment for every member of each message"
)
parser.add_option(
"--message-fields",
action="store_true",
dest="output_fields",
default=False,
help="Output MESSAGENAME,OFFSET,FIELDNAME,SIZE for every message"
)
(options, args) = parser.parse_args()
main(options, args)