mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-27 23:37:25 +00:00
tool/compile_gatt:calculated GATT Database Hash
This commit is contained in:
parent
285653b26b
commit
043f8832f5
@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# BLE GATT configuration generator for use with BTstack
|
||||
# Copyright 2018 BlueKitchen GmbH
|
||||
# Copyright 2019 BlueKitchen GmbH
|
||||
#
|
||||
# Format of input file:
|
||||
# PRIMARY_SERVICE, SERVICE_UUID
|
||||
# CHARACTERISTIC, ATTRIBUTE_TYPE_UUID, [READ | WRITE | DYNAMIC], VALUE
|
||||
|
||||
# dependencies:
|
||||
# - pip3 install pycryptodomex
|
||||
|
||||
import codecs
|
||||
import csv
|
||||
import io
|
||||
@ -17,6 +20,16 @@ import sys
|
||||
import argparse
|
||||
import tempfile
|
||||
|
||||
# try to import Cryptodome
|
||||
try:
|
||||
from Cryptodome2.Cipher import AES
|
||||
from Cryptodome.Hash import CMAC
|
||||
have_crypto = True
|
||||
except ImportError:
|
||||
have_crypto = False
|
||||
print("\n[!] PyCryptodome required to calculate GATT Database Hash but not installed (using dummy hash value 00..00)")
|
||||
print("[!] Please install PyCryptodome, e.g. 'pip install pycryptodomex'\n")
|
||||
|
||||
header = '''
|
||||
// {0} generated from {1} for BTstack
|
||||
// it needs to be regenerated when the .gatt file is updated.
|
||||
@ -48,6 +61,7 @@ assigned_uuids = {
|
||||
'GAP_RECONNECTION_ADDRESS' : 0x2A03,
|
||||
'GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS' : 0x2A04,
|
||||
'GATT_SERVICE_CHANGED' : 0x2a05,
|
||||
'GATT_DATABASE_HASH' : 0x2b2a
|
||||
}
|
||||
|
||||
security_permsission = ['ANYBODY','ENCRYPTED', 'AUTHENTICATED', 'AUTHORIZED', 'AUTHENTICATED_SC']
|
||||
@ -117,10 +131,20 @@ current_characteristic_uuid_string = ""
|
||||
defines_for_characteristics = []
|
||||
defines_for_services = []
|
||||
include_paths = []
|
||||
database_hash_message = bytearray()
|
||||
|
||||
handle = 1
|
||||
total_size = 0
|
||||
|
||||
def aes_cmac(key, n):
|
||||
if have_crypto:
|
||||
cobj = CMAC.new(key, ciphermod=AES)
|
||||
cobj.update(n)
|
||||
return cobj.digest()
|
||||
else:
|
||||
# return dummy value
|
||||
return b'\x00' * 16
|
||||
|
||||
def read_defines(infile):
|
||||
defines = dict()
|
||||
with open (infile, 'rt') as fin:
|
||||
@ -276,6 +300,9 @@ def write_sequence(fout, text):
|
||||
for part in parts:
|
||||
fout.write("0x%s, " % (part.strip()))
|
||||
|
||||
def write_database_hash(fout):
|
||||
fout.write("THE-DATABASE-HASH")
|
||||
|
||||
def write_indent(fout):
|
||||
fout.write(" ")
|
||||
|
||||
@ -346,6 +373,20 @@ def dump_flags(fout, flags):
|
||||
fout.write('ENCRYPTION_KEY_SIZE=%u' % encryption_key_size)
|
||||
fout.write('\n')
|
||||
|
||||
def database_hash_append_uint8(value):
|
||||
global database_hash_message
|
||||
database_hash_message.append(value)
|
||||
|
||||
def database_hash_append_uint16(value):
|
||||
global database_hash_message
|
||||
database_hash_append_uint8(value & 0xff)
|
||||
database_hash_append_uint8((value >> 8) & 0xff)
|
||||
|
||||
def database_hash_append_value(value):
|
||||
global database_hash_message
|
||||
for byte in value:
|
||||
database_hash_append_uint8(byte)
|
||||
|
||||
def parseService(fout, parts, service_type):
|
||||
global handle
|
||||
global total_size
|
||||
@ -375,6 +416,10 @@ def parseService(fout, parts, service_type):
|
||||
write_uuid(fout, uuid)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(service_type)
|
||||
database_hash_append_value(uuid)
|
||||
|
||||
current_service_uuid_string = c_string_for_uuid(parts[1])
|
||||
current_service_start_handle = handle
|
||||
handle = handle + 1
|
||||
@ -416,6 +461,13 @@ def parseIncludeService(fout, parts):
|
||||
write_uuid(fout, uuid)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2802)
|
||||
database_hash_append_uint16(services[keyUUID][0])
|
||||
database_hash_append_uint16(services[keyUUID][1])
|
||||
if uuid_size > 0:
|
||||
database_hash_append_value(uuid)
|
||||
|
||||
handle = handle + 1
|
||||
total_size = total_size + size
|
||||
|
||||
@ -466,11 +518,22 @@ def parseCharacteristic(fout, parts):
|
||||
handle = handle + 1
|
||||
total_size = total_size + size
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2803)
|
||||
database_hash_append_uint8(characteristic_properties)
|
||||
database_hash_append_uint16(handle+1)
|
||||
database_hash_append_value(uuid)
|
||||
|
||||
uuid_is_database_hash = len(uuid) == 2 and uuid[0] == 0x2a and uuid[1] == 0x2b
|
||||
|
||||
size = 2 + 2 + 2 + uuid_size
|
||||
if is_string(value):
|
||||
size = size + len(value)
|
||||
if uuid_is_database_hash:
|
||||
size += 16
|
||||
else:
|
||||
size = size + len(value.split())
|
||||
if is_string(value):
|
||||
size = size + len(value)
|
||||
else:
|
||||
size = size + len(value.split())
|
||||
|
||||
value_flags = att_flags(properties)
|
||||
|
||||
@ -488,10 +551,13 @@ def parseCharacteristic(fout, parts):
|
||||
write_16(fout, value_flags)
|
||||
write_16(fout, handle)
|
||||
write_uuid(fout, uuid)
|
||||
if is_string(value):
|
||||
write_string(fout, value)
|
||||
if uuid_is_database_hash:
|
||||
write_database_hash(fout)
|
||||
else:
|
||||
write_sequence(fout,value)
|
||||
if is_string(value):
|
||||
write_string(fout, value)
|
||||
else:
|
||||
write_sequence(fout,value)
|
||||
|
||||
fout.write("\n")
|
||||
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_VALUE_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
|
||||
@ -518,9 +584,14 @@ def parseCharacteristic(fout, parts):
|
||||
write_16(fout, 0x2902)
|
||||
write_16(fout, 0)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2902)
|
||||
|
||||
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_CLIENT_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
|
||||
handle = handle + 1
|
||||
|
||||
|
||||
if properties & property_flags['RELIABLE_WRITE']:
|
||||
size = 2 + 2 + 2 + 2 + 2
|
||||
write_indent(fout)
|
||||
@ -532,6 +603,11 @@ def parseCharacteristic(fout, parts):
|
||||
write_16(fout, 0x2900)
|
||||
write_16(fout, 1) # Reliable Write
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2900)
|
||||
database_hash_append_uint16(1)
|
||||
|
||||
handle = handle + 1
|
||||
|
||||
def parseCharacteristicUserDescription(fout, parts):
|
||||
@ -568,6 +644,10 @@ def parseCharacteristicUserDescription(fout, parts):
|
||||
else:
|
||||
write_sequence(fout,value)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2901)
|
||||
|
||||
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_USER_DESCRIPTION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
|
||||
handle = handle + 1
|
||||
|
||||
@ -596,6 +676,10 @@ def parseServerCharacteristicConfiguration(fout, parts):
|
||||
write_16(fout, handle)
|
||||
write_16(fout, 0x2903)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2903)
|
||||
|
||||
defines_for_characteristics.append('#define ATT_CHARACTERISTIC_%s_SERVER_CONFIGURATION_HANDLE 0x%04x' % (current_characteristic_uuid_string, handle))
|
||||
handle = handle + 1
|
||||
|
||||
@ -630,6 +714,10 @@ def parseCharacteristicFormat(fout, parts):
|
||||
write_sequence(fout, name_space)
|
||||
write_uuid(fout, description)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2904)
|
||||
|
||||
handle = handle + 1
|
||||
|
||||
|
||||
@ -654,6 +742,10 @@ def parseCharacteristicAggregateFormat(fout, parts):
|
||||
sys.exit(1)
|
||||
write_16(fout, format_handle)
|
||||
fout.write("\n")
|
||||
|
||||
database_hash_append_uint16(handle)
|
||||
database_hash_append_uint16(0x2905)
|
||||
|
||||
handle = handle + 1
|
||||
|
||||
def parseReportReference(fout, parts):
|
||||
@ -930,11 +1022,26 @@ try:
|
||||
parse(args.gattfile, fin, filename, sys.argv[0], ftemp)
|
||||
listHandles(ftemp)
|
||||
|
||||
# calc GATT Database Hash
|
||||
db_hash = aes_cmac(bytearray(16), database_hash_message)
|
||||
if isinstance(db_hash, str):
|
||||
# python2
|
||||
db_hash_sequence = [('0x%02x' % ord(i)) for i in db_hash]
|
||||
elif isinstance(db_hash, bytes):
|
||||
# python3
|
||||
db_hash_sequence = [('0x%02x' % i) for i in db_hash]
|
||||
else:
|
||||
print("AES CMAC returns unexpected type %s, abort" % type(db_hash))
|
||||
sys.exit(1)
|
||||
# reverse hash to get little endian
|
||||
db_hash_sequence.reverse()
|
||||
db_hash_string = ', '.join(db_hash_sequence) + ', '
|
||||
|
||||
# pass 2: insert GATT Database Hash
|
||||
fout = open (filename, 'w')
|
||||
ftemp.seek(0)
|
||||
for line in ftemp:
|
||||
fout.write(line)
|
||||
fout.write(line.replace('THE-DATABASE-HASH', db_hash_string))
|
||||
fout.close()
|
||||
ftemp.close()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user