1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
|
#!/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-data.c.
'''
import sys
import urllib.request, urllib.error, urllib.parse
import yaml
base_url = "https://bitbucket.org/bluetooth-SIG/public/raw/HEAD/assigned_numbers/"
OUTPUT_FILE = "epan/dissectors/packet-bluetooth-data.c"
MIN_UUIDS = 1400 # 1424 as of 31-12-2023
MIN_COMPANY_IDS = 3400 # 3405 as of 31-12-2023
PROLOG = f'''/*
* This file was generated by running ./tools/make-bluetooth.py.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* The Bluetooth public SIG repository is available at:
* <{base_url}>
*
*/
#include "packet-bluetooth.h"
'''
##
## UUIDs
##
def retrieve_uuids():
'''
Retrieve the YAML files defining the UUIDs and add them to the lists
'''
'''
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,
"unlist": [],
"list": [
{ "uuid": 0x001D, "name": "UDI C-Plane" },
]
},
{ # 0x1000
"yaml": "service_class.yaml",
"description": "Service Class",
"unCamelCase": True,
"unlist": [],
"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,
"unlist": [],
"list": []
},
{ # 0x1800
"yaml": "service_uuids.yaml",
"description": "Service",
"unCamelCase": False,
"unlist": [],
"list": []
},
{ # 0x2700
"yaml": "units.yaml",
"description": "Units",
"unCamelCase": False,
"unlist": [],
"list": []
},
{ # 0x2800
"yaml": "declarations.yaml",
"description": "Declarations",
"unCamelCase": False,
"unlist": [],
"list": []
},
{ # 0x2900
"yaml": "descriptors.yaml",
"description": "Descriptors",
"unCamelCase": False,
"unlist": [],
"list": []
},
{ # 0x2a00
"yaml": "characteristic_uuids.yaml",
"description": "Characteristics",
"unCamelCase": False,
"unlist": [],
"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" },
# These have somehow disappeared. We keep them for if they were used.
{ "uuid": 0x2BA9, "name": "Media Player Icon Object Type" },
{ "uuid": 0x2BAA, "name": "Track Segments Object Type" },
{ "uuid": 0x2BAB, "name": "Track Object Type" },
{ "uuid": 0x2BAC, "name": "Group Object Type" },
]
},
{ # 0xfxxx
"yaml": "member_uuids.yaml",
"description": "Members",
"unCamelCase": False,
"unlist": [],
"list": [
# This they really screwed up. The UUID was moved to sdo_uuids,
# thereby breaking the range and ordering completely.
{ "uuid": 0xFCCC, "name": "Wi-Fi Easy Connect Specification" },
]
},
{ # 0xffef (and 0xfccc)
"yaml": "sdo_uuids.yaml",
"description": "SDO",
"unCamelCase": False,
"unlist": [ 0xFCCC,
],
"list": []
}]
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)
for uuid in uuids_dir["uuids"]:
if uuid["uuid"] not in uuids["unlist"]:
uuids["list"].append(uuid)
'''
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 preceded 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 retrieved.
'''
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)
return uuids_sources
##
## Company Identifiers
##
def retrieve_company_ids():
'''
Retrieve the YAML files defining the company IDs and add them to the lists
'''
'''
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" },
]
}]
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)
return company_ids_sources
def uuid_vals(uuids_sources):
'''
Output the source code for the bluetooth_uuid_vals value_string
'''
yield "const value_string bluetooth_uuid_vals[] = {\n"
for uuids in uuids_sources:
yield " /* {description} - {base_url}uuids/{yaml} */\n".format(description=uuids["description"], base_url=base_url, yaml=uuids["yaml"])
for uuid in uuids["list"]:
yield " {{ 0x{uuid:04X}, \"{name}\" }},\n".format(uuid=uuid["uuid"], name=uuid["name"])
yield " { 0, NULL }\n"
yield "};\n"
yield "value_string_ext bluetooth_uuid_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_uuid_vals);\n"
yield "\n"
def company_id_vals(company_ids_sources):
'''
Output the source code for the bluetooth_company_id_vals value_string
'''
yield "/* Taken from {base_url}company_identifiers/{yaml} */\n".format(base_url=base_url, yaml=company_ids_sources[0]["yaml"])
yield "static const value_string bluetooth_company_id_vals[] = {\n"
for company_ids in company_ids_sources:
for company_id in company_ids["list"]:
yield " {{ 0x{company_id:04X}, \"{name}\" }},\n".format(company_id=company_id["value"], name=company_id["name"])
yield " { 0, NULL }\n"
yield "};\n"
yield "value_string_ext bluetooth_company_id_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_company_id_vals);\n"
yield "\n"
def btatt_handle_vals(uuids_sources):
'''
Output the annotated source code for the BT ATT handles value string
'''
yield "const btatt_handle_strings_t btatt_handle_strings[] = {\n"
# Skip the UUIDs below 0x1800
for uuids in uuids_sources[3:]:
yield " /* {description} - {base_url}uuids/{yaml} */\n".format(description=uuids["description"], base_url=base_url, yaml=uuids["yaml"])
for uuid in uuids["list"]:
yield " {{ {uuid:#04x}, \"Bluetooth GATT Attribute {name} (UUID {uuid:#04x})\", \
\"BT GATT {name} (UUID {uuid:#04x})\", \
\"btgatt.uuid{uuid:#04x}\" }},\n".format(uuid=uuid["uuid"], name=uuid["name"])
yield " { 0, NULL, NULL, NULL }\n"
yield "};\n"
def main():
uuids_sources = retrieve_uuids()
company_ids_sources = retrieve_company_ids()
with open(OUTPUT_FILE, "w") as bt_uuids_f:
bt_uuids_f.write(PROLOG)
bt_uuids_f.writelines(uuid_vals(uuids_sources))
bt_uuids_f.writelines(company_id_vals(company_ids_sources))
bt_uuids_f.writelines(btatt_handle_vals(uuids_sources))
if __name__ == '__main__':
main()
|