wireshark/tools/make-enterprises.py
2023-07-27 15:40:25 +01:00

197 lines
6.3 KiB
Python
Executable file

#!/usr/bin/env python3
# create the enterprises.c file from
# https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
# or an offline copy
#
# Copyright 2022 by Moshe Kaplan
# Based on make-sminmpec.pl by Gerald Combs
#
# Wireshark - Network traffic analyzer
# By Gerald Combs <gerald@wireshark.org>
# Copyright 2004 Gerald Combs
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import argparse
import re
import urllib.request
ENTERPRISES_CFILE = os.path.join('epan', 'enterprises.c')
ENTERPRISE_NUMBERS_URL = "https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers"
DECIMAL_PATTERN = r"^(\d+)"
# up to three spaces because of formatting errors in the source
ORGANIZATION_PATTERN = r"^ ?(\S.*)"
FORMERLY_PATTERN = r" \(((formerly|previously) .*)\)"
LOOKUP_FUNCTION = r"""
const char* global_enterprises_lookup(uint32_t value)
{
if (value > table.max_idx) {
return NULL;
}
else return table.values[value];
}
"""
DUMP_FUNCTION = r"""
void global_enterprises_dump(FILE *fp)
{
for (size_t idx = 0; idx <= table.max_idx; idx++) {
if (table.values[idx] != NULL) {
fprintf(fp, "%zu\t%s\n", idx, table.values[idx]);
}
}
}
"""
# This intermediate format is no longer written to a file - returned as string
def generate_enterprise_entries(file_content):
# We only care about the "Decimal" and "Organization",
# not the contact or email
org_lines = []
last_updated = ""
end_seen = False
for line in file_content.splitlines():
decimal_match = re.match(DECIMAL_PATTERN, line)
if decimal_match:
decimal = decimal_match.group(0)
elif re.match(ORGANIZATION_PATTERN, line):
organization = line.strip()
if organization.lower() == "unassigned":
continue
organization = re.sub(FORMERLY_PATTERN, r"\t# \1", organization)
org_lines += [decimal + "\t" + organization]
elif "last updated" in line.lower():
last_updated = line
elif "end of document" in line.lower():
end_seen = True
if not end_seen:
raise Exception('"End of Document" not found. Truncated source file?')
last_updated_line = "/* " + last_updated + " */\n\n"
output = "\n".join(org_lines) + "\n"
return (output,last_updated_line)
class CFile:
def __init__(self, filename, last_updated_line):
self.filename = filename
self.f = open(filename, 'w')
self.mappings = {}
self.highest_num = 0
# Write file header
self.f.write('/* ' + os.path.basename(self.filename) + '\n')
self.f.write(' *\n')
self.f.write(' * Wireshark - Network traffic analyzer\n')
self.f.write(' * By Gerald Combs <gerald@wireshark.org>\n')
self.f.write(' * Copyright 1998 Gerald Combs\n')
self.f.write(' *\n')
self.f.write(' * Do not edit - this file is automatically generated\n')
self.f.write(' * SPDX-License-Identifier: GPL-2.0-or-later\n')
self.f.write(' */\n\n')
self.f.write(last_updated_line)
# Include header files
self.f.write('#include "config.h"\n\n')
self.f.write('#include <stddef.h>\n')
self.f.write('#include "enterprises.h"\n')
self.f.write('\n\n')
def __del__(self):
self.f.write('typedef struct\n')
self.f.write('{\n')
self.f.write(' uint32_t max_idx;\n')
self.f.write(' const char* values[' + str(self.highest_num+1) + '];\n')
self.f.write('} global_enterprises_table_t;\n\n')
# Write static table
self.f.write('static global_enterprises_table_t table =\n')
self.f.write('{\n')
# Largest index
self.f.write(' ' + str(self.highest_num) + ',\n')
self.f.write(' {\n')
# Entries (read from dict)
for n in range(0, self.highest_num+1):
if n not in self.mappings:
# There are some gaps, write a NULL entry so can lookup by index
line = ' NULL'
else:
line = ' "' + self.mappings[n] + '"'
# Add coma.
if n < self.highest_num:
line += ','
# Add number as aligned comment.
line += ' '*(90-len(line)) + '// ' + str(n)
self.f.write(line+'\n')
# End of array
self.f.write(' }\n')
# End of struct
self.f.write('};\n')
print('Re-generated', self.filename)
# Lookup function
self.f.write(LOOKUP_FUNCTION)
# Dump function
self.f.write(DUMP_FUNCTION)
# Add an individual mapping to the function
def addMapping(self, num, name):
# Handle some escapings
name = name.replace('\\', '\\\\')
name = name.replace('"', '""')
# Record.
self.mappings[num] = name
self.highest_num = num if num>self.highest_num else self.highest_num
def main():
parser = argparse.ArgumentParser(description="Create the {} file.".format(ENTERPRISES_CFILE))
parser.add_argument('--infile')
parser.add_argument('outfile', nargs='?', default=ENTERPRISES_CFILE)
parsed_args = parser.parse_args()
# Read data from file or webpage
if parsed_args.infile:
with open(parsed_args.infile, encoding='utf-8') as fh:
data = fh.read()
else:
with urllib.request.urlopen(ENTERPRISE_NUMBERS_URL) as f:
if f.status != 200:
raise Exception("request for " + ENTERPRISE_NUMBERS_URL + " failed with result code " + f.status)
data = f.read().decode('utf-8')
# Find bits we need and generate enterprise entries
enterprises_content,last_updated_line = generate_enterprise_entries(data)
# Now write to a C file the contents (which is faster than parsing the global file at runtime).
c_file = CFile(parsed_args.outfile, last_updated_line)
mapping_re = re.compile(r'^(\d+)\s+(.*)$')
for line in enterprises_content.splitlines():
match = mapping_re.match(line)
if match:
num, name = match.group(1), match.group(2)
# Strip any comments and/or trailing whitespace
idx = name.find('#')
if idx != -1:
name = name[0:idx]
name = name.rstrip()
# Add
c_file.addMapping(int(num), name)
if __name__ == "__main__":
main()