dcerpc-nt: add UNION_ALIGN_TO... helpers
[wireshark-sm.git] / tools / make-bluetooth.py
blob71942b91d8510a505694cd31c43ce5fd4fdefed9
1 #!/usr/bin/env python3
3 # Wireshark - Network traffic analyzer
4 # By Gerald Combs <gerald@wireshark.org>
5 # Copyright 1998 Gerald Combs
7 # SPDX-License-Identifier: GPL-2.0-or-later
9 '''
10 make-bluetooth - Generate value_strings containing bluetooth uuids and company identifiers.
11 It makes use of the databases from
12 The Bluetooth SIG Repository: https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/
13 and processes the YAML into human-readable strings to go into packet-bluetooth.c.
14 '''
16 import sys
17 import urllib.request, urllib.error, urllib.parse
18 import yaml
20 base_url = "https://bitbucket.org/bluetooth-SIG/public/raw/HEAD/assigned_numbers/"
22 MIN_UUIDS = 1400 # 1424 as of 31-12-2023
23 MIN_COMPANY_IDS = 3400 # 3405 as of 31-12-2023
26 ## UUIDs
29 '''
30 List of all YAML files to retrieve, the lists of UUIDs to put into the value_string
31 and other information.
32 Unfortunately the encoding of the names among the YAML files is inconsistent,
33 to say the least. This will need post-processing.
34 Also the previous value_string contained additional uuids, which are not currently
35 present in the databases. Prepare the lists with these uuids so they are not lost.
36 When they do appear in the databases they must be removed here.
37 '''
39 uuids_sources = [
40 { # 0x0001
41 "yaml": "protocol_identifiers.yaml",
42 "description": "Protocol Identifiers",
43 "unCamelCase": True,
44 "unlist": [],
45 "list": [
46 { "uuid": 0x001D, "name": "UDI C-Plane" },
49 { # 0x1000
50 "yaml": "service_class.yaml",
51 "description": "Service Class",
52 "unCamelCase": True,
53 "unlist": [],
54 "list": [
55 # Then we have this weird one stuck in between "Service Class"
56 # from browse_group_identifiers.yaml
57 { "uuid": 0x1002, "name": "Public Browse Group" },
58 # And some from other sources
59 { "uuid": 0x1129, "name": "Video Conferencing GW" },
60 { "uuid": 0x112A, "name": "UDI MT" },
61 { "uuid": 0x112B, "name": "UDI TA" },
62 { "uuid": 0x112C, "name": "Audio/Video" },
65 { # 0x1600
66 "yaml": "mesh_profile_uuids.yaml",
67 "description": "Mesh Profile",
68 "unCamelCase": False,
69 "unlist": [],
70 "list": []
72 { # 0x1800
73 "yaml": "service_uuids.yaml",
74 "description": "Service",
75 "unCamelCase": False,
76 "unlist": [],
77 "list": []
79 { # 0x2700
80 "yaml": "units.yaml",
81 "description": "Units",
82 "unCamelCase": False,
83 "unlist": [],
84 "list": []
86 { # 0x2800
87 "yaml": "declarations.yaml",
88 "description": "Declarations",
89 "unCamelCase": False,
90 "unlist": [],
91 "list": []
93 { # 0x2900
94 "yaml": "descriptors.yaml",
95 "description": "Descriptors",
96 "unCamelCase": False,
97 "unlist": [],
98 "list": []
100 { # 0x2a00
101 "yaml": "characteristic_uuids.yaml",
102 "description": "Characteristics",
103 "unCamelCase": False,
104 "unlist": [],
105 "list": [
106 # Then we have these weird ones stuck in between "Characteristics"
107 # from object_types.yaml
108 { "uuid": 0x2ACA, "name": "Unspecified" },
109 { "uuid": 0x2ACB, "name": "Directory Listing" },
110 # And some from other sources
111 { "uuid": 0x2A0B, "name": "Exact Time 100" },
112 { "uuid": 0x2A10, "name": "Secondary Time Zone" },
113 { "uuid": 0x2A15, "name": "Time Broadcast" },
114 { "uuid": 0x2A1A, "name": "Battery Power State" },
115 { "uuid": 0x2A1B, "name": "Battery Level State" },
116 { "uuid": 0x2A1F, "name": "Temperature Celsius" },
117 { "uuid": 0x2A20, "name": "Temperature Fahrenheit" },
118 { "uuid": 0x2A2F, "name": "Position 2D" },
119 { "uuid": 0x2A30, "name": "Position 3D" },
120 { "uuid": 0x2A3A, "name": "Removable" },
121 { "uuid": 0x2A3B, "name": "Service Required" },
122 { "uuid": 0x2A3C, "name": "Scientific Temperature Celsius" },
123 { "uuid": 0x2A3D, "name": "String" },
124 { "uuid": 0x2A3E, "name": "Network Availability" },
125 { "uuid": 0x2A56, "name": "Digital" },
126 { "uuid": 0x2A57, "name": "Digital Output" },
127 { "uuid": 0x2A58, "name": "Analog" },
128 { "uuid": 0x2A59, "name": "Analog Output" },
129 { "uuid": 0x2A62, "name": "Pulse Oximetry Control Point" },
130 # These have somehow disappeared. We keep them for if they were used.
131 { "uuid": 0x2BA9, "name": "Media Player Icon Object Type" },
132 { "uuid": 0x2BAA, "name": "Track Segments Object Type" },
133 { "uuid": 0x2BAB, "name": "Track Object Type" },
134 { "uuid": 0x2BAC, "name": "Group Object Type" },
137 { # 0xfxxx
138 "yaml": "member_uuids.yaml",
139 "description": "Members",
140 "unCamelCase": False,
141 "unlist": [],
142 "list": [
143 # This they really screwed up. The UUID was moved to sdo_uuids,
144 # thereby breaking the range and ordering completely.
145 { "uuid": 0xFCCC, "name": "Wi-Fi Easy Connect Specification" },
148 { # 0xffef (and 0xfccc)
149 "yaml": "sdo_uuids.yaml",
150 "description": "SDO",
151 "unCamelCase": False,
152 "unlist": [ 0xFCCC,
154 "list": []
158 Retrieve the YAML files defining the UUIDs and add them to the lists
160 for uuids in uuids_sources:
161 req_headers = { 'User-Agent': 'Wireshark make-bluetooth' }
162 try:
163 req = urllib.request.Request(base_url + 'uuids/' + uuids["yaml"], headers=req_headers)
164 response = urllib.request.urlopen(req)
165 lines = response.read().decode('UTF-8', 'replace')
166 except Exception as e:
167 print("Failed to get UUIDs at {url}, because of: {e}".format(url=base_url + 'uuids/' + uuids["yaml"], e=e), file=sys.stderr)
168 sys.exit(1)
170 uuids_dir = yaml.safe_load(lines)
171 for uuid in uuids_dir["uuids"]:
172 if uuid["uuid"] not in uuids["unlist"]:
173 uuids["list"].append(uuid)
176 Go through the lists and perform general and specific transforms.
177 Several exceptional cases are addressed directly by their UUID, because of the inconsistent nature
178 by which their name is constructed.
179 When they appear more sensibly in the databases they must be removed here.
180 When new inconsistent entries appear in the databases their transforms can be added here,
181 but also add their UUID below.
183 for uuids in uuids_sources:
184 for uuid in uuids["list"]:
185 # Handle a few exceptional cases
186 if uuid["uuid"] == 0x001E:
187 uuid["name"] = "MCAP Control Channel"
188 elif uuid["uuid"] == 0x001F:
189 uuid["name"] = "MCAP Data Channel"
190 elif uuid["uuid"] == 0x1102:
191 uuid["name"] = "LAN Access Using PPP"
192 elif uuid["uuid"] == 0x1104:
193 uuid["name"] = "IrMC Sync"
194 elif uuid["uuid"] == 0x1105:
195 uuid["name"] = "OBEX Object Push"
196 elif uuid["uuid"] == 0x1106:
197 uuid["name"] = "OBEX File Transfer"
198 elif uuid["uuid"] == 0x1107:
199 uuid["name"] = "IrMC Sync Command"
200 elif uuid["uuid"] == 0x1200:
201 uuid["name"] = "PnP Information"
202 elif uuid["uuid"] == 0x2B8C:
203 uuid["name"] = "CO\u2082 Concentration"
204 else:
205 # And these in general
206 uuid["name"] = uuid["name"].replace("_", " ")
207 uuid["name"] = uuid["name"].replace('"', '\\"')
210 Go through the lists and, for those lists flagged as such, perform the unCamelCase transform
211 on all the names in that list.
212 Several exceptional cases were addressed directly by their UUID and must be excluded from this
213 transform.
214 When additional characters indicating a break in words appear in database entries they can be
215 added to break_chars.
217 for uuids in uuids_sources:
218 if uuids["unCamelCase"]:
219 for uuid in uuids["list"]:
220 # if not a few exceptional cases (see above)
221 if uuid["uuid"] not in [0x001E, 0x001F, 0x1102, 0x1104, 0x1105, 0x1106, 0x1107, 0x1200, 0x2B8C]:
222 # Parse through the names and look for capital letters; when
223 # not preceded by another capital letter or one of break_chars, insert a space
224 break_chars = [" ", "-", "+", "/", "(", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
225 was_break = True # fake space at beginning of string
226 was_upper = False
227 name = ""
228 for character in uuid["name"]:
229 is_upper = True if character.isupper() else False
230 if is_upper and not was_break and not was_upper:
231 name += " "
232 name += character
233 was_break = True if character in break_chars else False
234 was_upper = is_upper
235 uuid["name"] = name
238 To be able to generate a value_string_ext array the entries need to be sorted.
240 for uuids in uuids_sources:
241 uuids_sorted = sorted(uuids["list"], key=lambda uuid: uuid["uuid"])
242 uuids["list"] = uuids_sorted
245 Do a check on duplicate entries.
246 While at it, do a count of the number of UUIDs retrieved.
248 prev_uuid = 0
249 uuid_count = 0
250 for uuids in uuids_sources:
251 for uuid in uuids["list"]:
252 if uuid["uuid"] > prev_uuid:
253 prev_uuid = uuid["uuid"]
254 else:
255 print("Duplicate UUID detected: 0x{uuid:04X}".format(uuid=uuid["uuid"]), file=sys.stderr)
256 sys.exit(1)
257 uuid_count += len(uuids["list"])
260 Sanity check to see if enough entries were retrieved
262 if (uuid_count < MIN_UUIDS):
263 print("There are fewer UUIDs than expected: got {count} but was expecting {minimum}".format(count=uuid_count, minimum=MIN_UUIDS), file=sys.stderr)
264 sys.exit(1)
267 Finally output the annotated source code for the value_string
269 print("const value_string bluetooth_uuid_vals[] = {")
271 for uuids in uuids_sources:
272 print(" /* {description} - {base_url}uuids/{yaml} */".format(description=uuids["description"], base_url=base_url, yaml=uuids["yaml"]))
273 for uuid in uuids["list"]:
274 print(" {{ 0x{uuid:04X}, \"{name}\" }},".format(uuid=uuid["uuid"], name=uuid["name"]))
276 print(" { 0, NULL }")
277 print("};")
278 print("value_string_ext bluetooth_uuid_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_uuid_vals);")
279 print("")
282 ## Company Identifiers
286 List of the YAML files to retrieve and the lists of values to put into the value_string.
287 Also the previous value_string contained additional company IDs, which are not currently
288 present in the databases. Prepare the lists with these company IDs so they are not lost.
289 When they do appear in the databases they must be removed here.
292 company_ids_sources = [
294 "yaml": "company_identifiers.yaml",
295 "list": [
296 # Some from other sources
297 { "value": 0x0418, "name": "Alpine Electronics Inc." },
298 { "value": 0x0943, "name": "Inovonics Corp." },
299 { "value": 0xFFFF, "name": "For use in internal and interoperability tests" },
304 Retrieve the YAML files defining the company IDs and add them to the lists
306 for company_ids in company_ids_sources:
307 req_headers = { 'User-Agent': 'Wireshark make-bluetooth' }
308 try:
309 req = urllib.request.Request(base_url + 'company_identifiers/' + company_ids["yaml"], headers=req_headers)
310 response = urllib.request.urlopen(req)
311 lines = response.read().decode('UTF-8', 'replace')
312 except Exception as e:
313 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)
314 sys.exit(-1)
316 company_ids_dir = yaml.safe_load(lines)
317 company_ids["list"].extend(company_ids_dir["company_identifiers"])
320 Go through the lists and perform general transforms.
322 for company_ids in company_ids_sources:
323 for company_id in company_ids["list"]:
324 company_id["name"] = company_id["name"].replace('"', '\\"')
327 To be able to generate a value_string_ext array the entries need to be sorted.
329 for company_ids in company_ids_sources:
330 company_ids_sorted = sorted(company_ids["list"], key=lambda company_id: company_id['value'])
331 company_ids["list"] = company_ids_sorted
334 Do a check on duplicate entries.
335 While at it, do a count of the number of company IDs retrieved.
337 prev_company_id = -1
338 company_id_count = 0
339 for company_ids in company_ids_sources:
340 for company_id in company_ids["list"]:
341 if company_id["value"] > prev_company_id:
342 prev_company_id = company_id["value"]
343 else:
344 print("Duplicate company ID detected: 0x{company_id:04X}".format(company_id=company_id["value"]), file=sys.stderr)
345 sys.exit(1)
346 company_id_count += len(company_ids["list"])
349 Sanity check to see if enough entries were retrieved
351 if company_id_count < MIN_COMPANY_IDS:
352 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)
353 sys.exit(1)
356 Finally output the source code for the value_string
358 print("/* Taken from {base_url}company_identifiers/{yaml} */".format(base_url=base_url, yaml=company_ids_sources[0]["yaml"]))
359 print("static const value_string bluetooth_company_id_vals[] = {")
361 for company_ids in company_ids_sources:
362 for company_id in company_ids["list"]:
363 print(" {{ 0x{company_id:04X}, \"{name}\" }},".format(company_id=company_id["value"], name=company_id["name"]))
365 print(" { 0, NULL }")
366 print("};")
367 print("value_string_ext bluetooth_company_id_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_company_id_vals);")