2023-12-30 14:43:19 +01:00
#!/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 sys
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 ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [
{ " uuid " : 0x001D , " name " : " UDI C-Plane " } ,
]
} ,
{ # 0x1000
" yaml " : " service_class.yaml " ,
" description " : " Service Class " ,
" unCamelCase " : True ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" 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 ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [ ]
} ,
{ # 0x1800
" yaml " : " service_uuids.yaml " ,
" description " : " Service " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [ ]
} ,
{ # 0x2700
" yaml " : " units.yaml " ,
" description " : " Units " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [ ]
} ,
{ # 0x2800
" yaml " : " declarations.yaml " ,
" description " : " Declarations " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [ ]
} ,
{ # 0x2900
" yaml " : " descriptors.yaml " ,
" description " : " Descriptors " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" list " : [ ]
} ,
{ # 0x2a00
" yaml " : " characteristic_uuids.yaml " ,
" description " : " Characteristics " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ ] ,
2023-12-30 14:43:19 +01:00
" 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 " } ,
2024-07-17 22:20:25 +00:00
# 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 " } ,
2023-12-30 14:43:19 +01:00
]
} ,
{ # 0xfxxx
" yaml " : " member_uuids.yaml " ,
" description " : " Members " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" 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 " } ,
]
2023-12-30 14:43:19 +01:00
} ,
2024-07-17 22:20:25 +00:00
{ # 0xffef (and 0xfccc)
2023-12-30 14:43:19 +01:00
" yaml " : " sdo_uuids.yaml " ,
" description " : " SDO " ,
" unCamelCase " : False ,
2024-07-17 22:20:25 +00:00
" unlist " : [ 0xFCCC ,
] ,
2023-12-30 14:43:19 +01:00
" 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 )
2024-07-17 22:20:25 +00:00
for uuid in uuids_dir [ " uuids " ] :
if uuid [ " uuid " ] not in uuids [ " unlist " ] :
uuids [ " list " ] . append ( uuid )
2023-12-30 14:43:19 +01:00
'''
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
2024-02-25 22:46:47 +00:00
# not preceded by another capital letter or one of break_chars, insert a space
2023-12-30 14:43:19 +01:00
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 .
2024-07-08 20:56:12 +00:00
While at it , do a count of the number of UUIDs retrieved .
2023-12-30 14:43:19 +01:00
'''
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); " )