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
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.
17 import urllib
.request
, urllib
.error
, urllib
.parse
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
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.
41 "yaml": "protocol_identifiers.yaml",
42 "description": "Protocol Identifiers",
46 { "uuid": 0x001D, "name": "UDI C-Plane" },
50 "yaml": "service_class.yaml",
51 "description": "Service Class",
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" },
66 "yaml": "mesh_profile_uuids.yaml",
67 "description": "Mesh Profile",
73 "yaml": "service_uuids.yaml",
74 "description": "Service",
81 "description": "Units",
87 "yaml": "declarations.yaml",
88 "description": "Declarations",
94 "yaml": "descriptors.yaml",
95 "description": "Descriptors",
101 "yaml": "characteristic_uuids.yaml",
102 "description": "Characteristics",
103 "unCamelCase": False,
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" },
138 "yaml": "member_uuids.yaml",
139 "description": "Members",
140 "unCamelCase": False,
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,
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' }
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
)
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"
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
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
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
:
233 was_break
= True if character
in break_chars
else False
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.
250 for uuids
in uuids_sources
:
251 for uuid
in uuids
["list"]:
252 if uuid
["uuid"] > prev_uuid
:
253 prev_uuid
= uuid
["uuid"]
255 print("Duplicate UUID detected: 0x{uuid:04X}".format(uuid
=uuid
["uuid"]), file=sys
.stderr
)
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
)
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 }")
278 print("value_string_ext bluetooth_uuid_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_uuid_vals);")
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",
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' }
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
)
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.
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"]
344 print("Duplicate company ID detected: 0x{company_id:04X}".format(company_id
=company_id
["value"]), file=sys
.stderr
)
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
)
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 }")
367 print("value_string_ext bluetooth_company_id_vals_ext = VALUE_STRING_EXT_INIT(bluetooth_company_id_vals);")