349 lines
12 KiB
Python
Executable File
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);")
|
|
|