wireshark/tools/make-bluetooth.py

349 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Wireshark - Network traffic analyzer
# By Gerald Combs <gerald@wireshark.org>
# Copyright 1998 Gerald Combs
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
'''
make-bluetooth - Generate value_strings containing bluetooth uuids and company identifiers.
It makes use of the databases from
The Bluetooth SIG Repository: https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/
and processes the YAML into human-readable strings to go into packet-bluetooth.c.
'''
import re
import sys
import string
import urllib.request, urllib.error, urllib.parse
import yaml
base_url = "https://bitbucket.org/bluetooth-SIG/public/raw/HEAD/assigned_numbers/"
MIN_UUIDS = 1400 # 1424 as of 31-12-2023
MIN_COMPANY_IDS = 3400 # 3405 as of 31-12-2023
##
## UUIDs
##
'''
List of all YAML files to retrieve, the lists of UUIDs to put into the value_string
and other information.
Unfortunately the encoding of the names among the YAML files is inconsistent,
to say the least. This will need post-processing.
Also the previous value_string contained additional uuids, which are not currently
present in the databases. Prepare the lists with these uuids so they are not lost.
When they do appear in the databases they must be removed here.
'''
uuids_sources = [
{ # 0x0001
"yaml": "protocol_identifiers.yaml",
"description": "Protocol Identifiers",
"unCamelCase": True,
"list": [
{ "uuid": 0x001D, "name": "UDI C-Plane" },
]
},
{ # 0x1000
"yaml": "service_class.yaml",
"description": "Service Class",
"unCamelCase": True,
"list": [
# Then we have this weird one stuck in between "Service Class"
# from browse_group_identifiers.yaml
{ "uuid": 0x1002, "name": "Public Browse Group" },
# And some from other sources
{ "uuid": 0x1129, "name": "Video Conferencing GW" },
{ "uuid": 0x112A, "name": "UDI MT" },
{ "uuid": 0x112B, "name": "UDI TA" },
{ "uuid": 0x112C, "name": "Audio/Video" },
]
},
{ # 0x1600
"yaml": "mesh_profile_uuids.yaml",
"description": "Mesh Profile",
"unCamelCase": False,
"list": []
},
{ # 0x1800
"yaml": "service_uuids.yaml",
"description": "Service",
"unCamelCase": False,
"list": []
},
{ # 0x2700
"yaml": "units.yaml",
"description": "Units",
"unCamelCase": False,
"list": []
},
{ # 0x2800
"yaml": "declarations.yaml",
"description": "Declarations",
"unCamelCase": False,
"list": []
},
{ # 0x2900
"yaml": "descriptors.yaml",
"description": "Descriptors",
"unCamelCase": False,
"list": []
},
{ # 0x2a00
"yaml": "characteristic_uuids.yaml",
"description": "Characteristics",
"unCamelCase": False,
"list": [
# Then we have these weird ones stuck in between "Characteristics"
# from object_types.yaml
{ "uuid": 0x2ACA, "name": "Unspecified" },
{ "uuid": 0x2ACB, "name": "Directory Listing" },
# And some from other sources
{ "uuid": 0x2A0B, "name": "Exact Time 100" },
{ "uuid": 0x2A10, "name": "Secondary Time Zone" },
{ "uuid": 0x2A15, "name": "Time Broadcast" },
{ "uuid": 0x2A1A, "name": "Battery Power State" },
{ "uuid": 0x2A1B, "name": "Battery Level State" },
{ "uuid": 0x2A1F, "name": "Temperature Celsius" },
{ "uuid": 0x2A20, "name": "Temperature Fahrenheit" },
{ "uuid": 0x2A2F, "name": "Position 2D" },
{ "uuid": 0x2A30, "name": "Position 3D" },
{ "uuid": 0x2A3A, "name": "Removable" },
{ "uuid": 0x2A3B, "name": "Service Required" },
{ "uuid": 0x2A3C, "name": "Scientific Temperature Celsius" },
{ "uuid": 0x2A3D, "name": "String" },
{ "uuid": 0x2A3E, "name": "Network Availability" },
{ "uuid": 0x2A56, "name": "Digital" },
{ "uuid": 0x2A57, "name": "Digital Output" },
{ "uuid": 0x2A58, "name": "Analog" },
{ "uuid": 0x2A59, "name": "Analog Output" },
{ "uuid": 0x2A62, "name": "Pulse Oximetry Control Point" },
]
},
{ # 0xfxxx
"yaml": "member_uuids.yaml",
"description": "Members",
"unCamelCase": False,
"list": []
},
{ # 0xfff2
"yaml": "sdo_uuids.yaml",
"description": "SDO",
"unCamelCase": False,
"list": []
}]
'''
Retrieve the YAML files defining the UUIDs and add them to the lists
'''
for uuids in uuids_sources:
req_headers = { 'User-Agent': 'Wireshark make-bluetooth' }
try:
req = urllib.request.Request(base_url + 'uuids/' + uuids["yaml"], headers=req_headers)
response = urllib.request.urlopen(req)
lines = response.read().decode('UTF-8', 'replace')
except Exception as e:
print("Failed to get UUIDs at {url}, because of: {e}".format(url=base_url + 'uuids/' + uuids["yaml"], e=e), file=sys.stderr)
sys.exit(1)
uuids_dir = yaml.safe_load(lines)
uuids["list"].extend(uuids_dir["uuids"])
'''
Go through the lists and perform general and specific transforms.
Several exceptional cases are addressed directly by their UUID, because of the inconsistent nature
by which their name is constructed.
When they appear more sensibly in the databases they must be removed here.
When new inconsistent entries appear in the databases their transforms can be added here,
but also add their UUID below.
'''
for uuids in uuids_sources:
for uuid in uuids["list"]:
# Handle a few exceptional cases
if uuid["uuid"] == 0x001E:
uuid["name"] = "MCAP Control Channel"
elif uuid["uuid"] == 0x001F:
uuid["name"] = "MCAP Data Channel"
elif uuid["uuid"] == 0x1102:
uuid["name"] = "LAN Access Using PPP"
elif uuid["uuid"] == 0x1104:
uuid["name"] = "IrMC Sync"
elif uuid["uuid"] == 0x1105:
uuid["name"] = "OBEX Object Push"
elif uuid["uuid"] == 0x1106:
uuid["name"] = "OBEX File Transfer"
elif uuid["uuid"] == 0x1107:
uuid["name"] = "IrMC Sync Command"
elif uuid["uuid"] == 0x1200:
uuid["name"] = "PnP Information"
elif uuid["uuid"] == 0x2B8C:
uuid["name"] = "CO\u2082 Concentration"
else:
# And these in general
uuid["name"] = uuid["name"].replace("_", " ")
uuid["name"] = uuid["name"].replace('"', '\\"')
'''
Go through the lists and, for those lists flagged as such, perform the unCamelCase transform
on all the names in that list.
Several exceptional cases were addressed directly by their UUID and must be excluded from this
transform.
When additional characters indicating a break in words appear in database entries they can be
added to break_chars.
'''
for uuids in uuids_sources:
if uuids["unCamelCase"]:
for uuid in uuids["list"]:
# if not a few exceptional cases (see above)
if uuid["uuid"] not in [0x001E, 0x001F, 0x1102, 0x1104, 0x1105, 0x1106, 0x1107, 0x1200, 0x2B8C]:
# Parse through the names and look for capital letters; when
# not preceeded by another capital letter or one of break_chars, insert a space
break_chars = [" ", "-", "+", "/", "(", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
was_break = True # fake space at beginning of string
was_upper = False
name = ""
for character in uuid["name"]:
is_upper = True if character.isupper() else False
if is_upper and not was_break and not was_upper:
name += " "
name += character
was_break = True if character in break_chars else False
was_upper = is_upper
uuid["name"] = name
'''
To be able to generate a value_string_ext array the entries need to be sorted.
'''
for uuids in uuids_sources:
uuids_sorted = sorted(uuids["list"], key=lambda uuid: uuid["uuid"])
uuids["list"] = uuids_sorted
'''
Do a check on duplicate entries.
While at it, do a count of the number of UUIDs retreived.
'''
prev_uuid = 0
uuid_count = 0
for uuids in uuids_sources:
for uuid in uuids["list"]:
if uuid["uuid"] > prev_uuid:
prev_uuid = uuid["uuid"]
else:
print("Duplicate UUID detected: 0x{uuid:04X}".format(uuid=uuid["uuid"]), file=sys.stderr)
sys.exit(1)
uuid_count += len(uuids["list"])
'''
Sanity check to see if enough entries were retrieved
'''
if (uuid_count < MIN_UUIDS):
print("There are fewer UUIDs than expected: got {count} but was expecting {minimum}".format(count=uuid_count, minimum=MIN_UUIDS), file=sys.stderr)
sys.exit(1)
'''
Finally output the annotated source code for the value_string
'''
print("const value_string bluetooth_uuid_vals[] = {")
for uuids in uuids_sources:
print(" /* {description} - {base_url}uuids/{yaml} */".format(description=uuids["description"], base_url=base_url, yaml=uuids["yaml"]))
for uuid in uuids["list"]:
print(" {{ 0x{uuid:04X}, \"{name}\" }},".format(uuid=uuid["uuid"], name=uuid["name"]))
print(" { 0, NULL }")
print("};")
print("value_string_ext bluetooth_uuid_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_uuid_vals);")
print("")
##
## Company Identifiers
##
'''
List of the YAML files to retrieve and the lists of values to put into the value_string.
Also the previous value_string contained additional company IDs, which are not currently
present in the databases. Prepare the lists with these company IDs so they are not lost.
When they do appear in the databases they must be removed here.
'''
company_ids_sources = [
{
"yaml": "company_identifiers.yaml",
"list": [
# Some from other sources
{ "value": 0x0418, "name": "Alpine Electronics Inc." },
{ "value": 0x0943, "name": "Inovonics Corp." },
{ "value": 0xFFFF, "name": "For use in internal and interoperability tests" },
]
}]
'''
Retrieve the YAML files defining the company IDs and add them to the lists
'''
for company_ids in company_ids_sources:
req_headers = { 'User-Agent': 'Wireshark make-bluetooth' }
try:
req = urllib.request.Request(base_url + 'company_identifiers/' + company_ids["yaml"], headers=req_headers)
response = urllib.request.urlopen(req)
lines = response.read().decode('UTF-8', 'replace')
except Exception as e:
print("Failed to get company IDs at {url}, because of: {e}".format(url=base_url + 'company_identifiers/' + company_ids["yaml"], e=e), file=sys.stderr)
sys.exit(-1)
company_ids_dir = yaml.safe_load(lines)
company_ids["list"].extend(company_ids_dir["company_identifiers"])
'''
Go through the lists and perform general transforms.
'''
for company_ids in company_ids_sources:
for company_id in company_ids["list"]:
company_id["name"] = company_id["name"].replace('"', '\\"')
'''
To be able to generate a value_string_ext array the entries need to be sorted.
'''
for company_ids in company_ids_sources:
company_ids_sorted = sorted(company_ids["list"], key=lambda company_id: company_id['value'])
company_ids["list"] = company_ids_sorted
'''
Do a check on duplicate entries.
While at it, do a count of the number of company IDs retrieved.
'''
prev_company_id = -1
company_id_count = 0
for company_ids in company_ids_sources:
for company_id in company_ids["list"]:
if company_id["value"] > prev_company_id:
prev_company_id = company_id["value"]
else:
print("Duplicate company ID detected: 0x{company_id:04X}".format(company_id=company_id["value"]), file=sys.stderr)
sys.exit(1)
company_id_count += len(company_ids["list"])
'''
Sanity check to see if enough entries were retrieved
'''
if company_id_count < MIN_COMPANY_IDS:
print("There are fewer company IDs than expected: got {count} but was expecting {minimum}".format(count=company_id_count, minimum=MIN_COMPANY_IDS), file=sys.stderr)
sys.exit(1)
'''
Finally output the source code for the value_string
'''
print("/* Taken from {base_url}company_identifiers/{yaml} */".format(base_url=base_url, yaml=company_ids_sources[0]["yaml"]))
print("static const value_string bluetooth_company_id_vals[] = {")
for company_ids in company_ids_sources:
for company_id in company_ids["list"]:
print(" {{ 0x{company_id:04X}, \"{name}\" }},".format(company_id=company_id["value"], name=company_id["name"]))
print(" { 0, NULL }")
print("};")
print("value_string_ext bluetooth_company_id_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_company_id_vals);")