aboutsummaryrefslogtreecommitdiffstats
path: root/tools/make-bluetooth.py
blob: 5ae099e42f02f050419d6c381462c86c5f05691a (plain)
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
#!/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 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 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);")