xref: /btstack/test/mesh/dump_mesh_pklg.py (revision 293fd5fb21e86fbb29e9789cc0a2c06dafbbf1b3)
1#!/usr/bin/env python3
2# BlueKitchen GmbH (c) 2019
3
4# primitive dump for PacketLogger format
5
6# APPLE PacketLogger
7# typedef struct {
8#   uint32_t    len;
9#   uint32_t    ts_sec;
10#   uint32_t    ts_usec;
11#   uint8_t     type;   // 0xfc for note
12# }
13
14#define BLUETOOTH_DATA_TYPE_PB_ADV                                             0x29 // PB-ADV
15#define BLUETOOTH_DATA_TYPE_MESH_MESSAGE                                       0x2A // Mesh Message
16#define BLUETOOTH_DATA_TYPE_MESH_BEACON                                        0x2B // Mesh Beacon
17
18import re
19import sys
20import time
21import datetime
22import struct
23from mesh_crypto import *
24
25# state
26netkeys = {}
27appkeys = {}
28devkey = b''
29ivi = b'\x00'
30segmented_messages = {}
31
32
33# helpers
34def read_net_32_from_file(f):
35    data = f.read(4)
36    if len(data) < 4:
37        return -1
38    return struct.unpack('>I', data)[0]
39
40def as_hex(data):
41    return ''.join(["{0:02x} ".format(byte) for byte in data])
42
43def as_big_endian32(value):
44    return struct.pack('>I', value)
45
46def read_net_16(data):
47    return struct.unpack('>H', data)[0]
48
49def read_net_24(data):
50    return data[0] << 16 | struct.unpack('>H', data[1:3])[0]
51
52# log engine - simple pretty printer
53max_indent = 0
54def log_pdu(pdu, indent = 0, hide_properties = []):
55    spaces = '    ' * indent
56    print(spaces + pdu.type)
57    if len(pdu.status) > 0:
58        print (spaces + '|           status: ' + pdu.status)
59    for property in pdu.properties:
60        if property.key in hide_properties:
61            continue
62        if isinstance( property.value, int):
63            print (spaces + "|%15s: 0x%x (%u)" % (property.key, property.value, property.value))
64        elif isinstance( property.value, bytes):
65            print (spaces + "|%15s: %s" % (property.key, as_hex(property.value)))
66        else:
67            print (spaces + "|%15s: %s" % (property.key, str(property.value)))
68        hide_properties.append(property.key)
69    print (spaces + '|           data: ' + as_hex(pdu.data))
70    if indent >= max_indent:
71        return
72    print (spaces + '----')
73    for origin in pdu.origins:
74        log_pdu(origin, indent + 1, hide_properties)
75
76# classes
77
78class network_key(object):
79    def __init__(self, index, netkey):
80        self.index = index
81        self.netkey = netkey
82        (self.nid, self.encryption_key, self.privacy_key) = k2(netkey, b'\x00')
83
84    def __repr__(self):
85        return ("NetKey-%04x %s: NID %02x Encryption %s Privacy %s" % (self.index, self.netkey.hex(), self.nid, self.encryption_key.hex(), self.privacy_key.hex()))
86
87class application_key(object):
88    def __init__(self, index, appkey):
89        self.index = index
90        self.appkey = appkey
91        self.aid = k4(self.appkey)
92
93    def __repr__(self):
94        return ("AppKey-%04x %s: AID %02x" % (self.index, self.appkey.hex(), self.aid))
95
96class property(object):
97    def __init__(self, key, value):
98        self.key = key
99        self.value = value
100
101class layer_pdu(object):
102    def __init__(self, pdu_type, pdu_data):
103        self.status = ''
104        self.src = None
105        self.dst = None
106        self.type = pdu_type
107        self.data = pdu_data
108        self.origins = []
109        self.properties = []
110
111    def add_property(self, key, value):
112        self.properties.append(property(key, value))
113
114class network_pdu(layer_pdu):
115    def __init__(self, pdu_data):
116        super().__init__("Network(unencrpyted)", pdu_data)
117
118        # parse pdu
119        self.ivi = (self.data[1] & 0x80) >> 7
120        self.nid = self.data[0] & 0x7f
121        self.ctl = (self.data[1] & 0x80) == 0x80
122        self.ttl = self.data[1] & 0x7f
123        self.seq = read_net_24(self.data[2:5])
124        self.src = read_net_16(self.data[5:7])
125        self.dst = read_net_16(self.data[7:9])
126        self.lower_transport = self.data[9:]
127
128        # set properties
129        self.add_property('ivi', self.ivi)
130        self.add_property('nid', self.nid)
131        self.add_property('ctl', self.ctl)
132        self.add_property('ttl', self.ttl)
133        self.add_property('seq', self.seq)
134        self.add_property('src', self.src)
135        self.add_property('dst', self.dst)
136        self.add_property('lower_transport', self.lower_transport)
137
138class lower_transport_pdu(layer_pdu):
139    def __init__(self, network_pdu):
140        super().__init__('Lower Transport', network_pdu.lower_transport)
141
142        # inherit properties
143        self.ctl = network_pdu.ctl
144        self.seq = network_pdu.seq
145        self.src = network_pdu.src
146        self.dst = network_pdu.dst
147        self.add_property('ctl', self.ctl)
148        self.add_property('seq', self.seq)
149        self.add_property('src', self.src)
150        self.add_property('dst', self.dst)
151
152        # parse pdu and set propoerties
153        self.seg = (self.data[0] & 0x80) == 0x80
154        self.add_property('seg', self.seg)
155        self.szmic = False
156        if self.ctl:
157            self.opcode = self.data[0] & 0x7f
158            self.add_property('opcode', self.opcode)
159        else:
160            self.aid = self.data[0] & 0x3f
161            self.add_property('aid', self.aid)
162            self.akf = self.data[0] & 0x40 == 0x040
163            self.add_property('akf', self.akf)
164        if self.seg:
165            if self.ctl:
166                self.szmic = self.data[1] & 0x80 == 0x80
167                self.add_property('szmic', self.szmic)
168            temp_12 = struct.unpack('>H', self.data[1:3])[0]
169            self.seq_zero = (temp_12 >> 2) & 0x1fff
170            self.add_property('seq_zero', self.seq_zero)
171            temp_23 = struct.unpack('>H', self.data[2:4])[0]
172            self.seg_o = (temp_23 >> 5) & 0x1f
173            self.add_property('seg_o', self.seg_o)
174            self.seg_n =  temp_23 & 0x1f
175            self.add_property('seg_n', self.seg_n)
176            self.segment = self.data[4:]
177            self.add_property('segment', self.segment)
178        else:
179            self.upper_transport = self.data[1:]
180            self.add_property('upper_transport', self.upper_transport)
181
182class uppert_transport_pdu(layer_pdu):
183    def __init__(self, segment):
184        if segment.ctl:
185            super().__init__('Segmented Control', b'')
186        else:
187            super().__init__('Segmented Transport', b'')
188        self.ctl      = segment.ctl
189        self.src      = segment.src
190        self.dst      = segment.dst
191        self.seq      = segment.seq
192        self.akf      = segment.akf
193        self.aid      = segment.aid
194        self.szmic    = segment.szmic
195        self.seg_n    = segment.seg_n
196        self.seq_zero = segment.seq_zero
197        # TODO handle seq_zero overrun
198        self.seq_auth = segment.seq & 0xffffe000 | segment.seq_zero
199        self.add_property('seq_auth', self.seq_auth)
200        self.missing  = (1 << (segment.seg_n+1)) - 1
201        self.data = b''
202        self.processed = False
203        self.origins = []
204        if self.ctl:
205            self.segment_len = 8
206        else:
207            self.segment_len = 12
208
209        self.add_property('src', self.src)
210        self.add_property('dst', self.dst)
211        self.add_property('segment_len', self.segment_len)
212
213    def add_segment(self, network_pdu):
214        self.origins.append(network_pdu)
215        self.missing &= ~ (1 << network_pdu.seg_o)
216        if network_pdu.seg_o == self.seg_n:
217            # last segment, set len
218            self.len = (self.seg_n * self.segment_len) + len(network_pdu.segment)
219        if len(self.data) == 0 and self.complete():
220            self.reassemble()
221
222    def complete(self):
223        return self.missing == 0
224
225    def reassemble(self):
226        self.data = bytearray(self.len)
227        missing  = (1 << (self.seg_n+1)) - 1
228        for pdu in self.origins:
229            # copy data
230            pos = pdu.seg_o * self.segment_len
231            self.data[pos:pos+len(pdu.segment)] = pdu.segment
232            # done?
233            missing &= ~ (1 << pdu.seg_o)
234            if missing == 0:
235                break
236
237class access_pdu(layer_pdu):
238    def __init__(self, lower_pdu, data):
239        super().__init__('Access', b'')
240        self.src      = lower_pdu.src
241        self.dst      = lower_pdu.dst
242        self.akf      = lower_pdu.akf
243        self.aid      = lower_pdu.aid
244        self.data     = data
245        self.add_property('src', self.src)
246        self.add_property('dst', self.dst)
247        self.add_property('akf', self.akf)
248        self.add_property('aid', self.aid)
249
250def segmented_message_for_pdu(pdu):
251    if pdu.src in segmented_messages:
252        seg_message = segmented_messages[pdu.src]
253        # check seq zero
254    else:
255        seg_message = uppert_transport_pdu(pdu)
256        segmented_messages[pdu.src] = seg_message
257    return seg_message
258
259def mesh_set_iv_index(iv_index):
260    global ivi
261    ivi = iv_index
262    print ("IV-Index: " + as_big_endian32(ivi).hex())
263
264# key management
265def mesh_add_netkey(index, netkey):
266    key = network_key(index, netkey)
267    print (key)
268    netkeys[index] = key
269
270def mesh_network_keys_for_nid(nid):
271    for (index, key) in netkeys.items():
272        if key.nid == nid:
273            yield key
274
275def mesh_set_device_key(key):
276    global devkey
277    print ("DevKey: " + key.hex())
278    devkey = key
279
280def mesh_add_application_key(index, appkey):
281    key = application_key(index, appkey)
282    print (key)
283    appkeys[index] = key
284
285def mesh_application_keys_for_aid(aid):
286    for (index, key) in appkeys.items():
287        if key.aid == aid:
288            yield key
289
290def mesh_transport_nonce(pdu, nonce_type):
291    if pdu.szmic:
292        aszmic = 0x80
293    else:
294        aszmic = 0x00
295    return bytes( [nonce_type, aszmic, pdu.seq_auth >> 16, (pdu.seq_auth >> 8) & 0xff, pdu.seq_auth & 0xff, pdu.src >> 8, pdu.src & 0xff, pdu.dst >> 8, pdu.dst & 0xff]) + as_big_endian32(ivi)
296
297def mesh_application_nonce(pdu):
298    return mesh_transport_nonce(pdu, 0x01)
299
300def mesh_device_nonce(pdu):
301    return mesh_transport_nonce(pdu, 0x02)
302
303def mesh_upper_transport_decrypt(message, data):
304    if message.szmic:
305        trans_mic_len = 8
306    else:
307        trans_mic_len = 4
308    ciphertext = data[:-trans_mic_len]
309    trans_mic  = data[-trans_mic_len:]
310    nonce      = mesh_device_nonce(message)
311    decrypted = None
312    if message.akf:
313        for key in mesh_application_keys_for_aid(message.aid):
314            decrypted = aes_ccm_decrypt(key.appkey, nonce, ciphertext, b'', trans_mic_len, trans_mic)
315            if decrypted != None:
316                break
317    else:
318        decrypted =  aes_ccm_decrypt(devkey, nonce, ciphertext, b'', trans_mic_len, trans_mic)
319    return decrypted
320
321def mesh_process_control(control_pdu):
322    # TODO decode control message
323    # TODO add Seg Ack to sender access message origins
324    log_pdu(control_pdu, 0, [])
325
326def mesh_proess_access(access_pdu):
327    log_pdu(access_pdu, 0, [])
328
329def mesh_process_network_pdu_tx(network_pdu_encrypted):
330
331    # network layer - decrypt pdu
332    nid = network_pdu_encrypted.data[0] & 0x7f
333    for key in mesh_network_keys_for_nid(nid):
334        network_pdu_decrypted_data = network_decrypt(network_pdu_encrypted.data, as_big_endian32(ivi), key.encryption_key, key.privacy_key)
335        if network_pdu_decrypted_data != None:
336            break
337    if network_pdu_decrypted_data == None:
338        network_pdu_encrypted.status = 'No encryption key found'
339        log_pdu(network_pdu_encrypted, 0, [])
340        return
341
342    # decrypted network pdu
343    network_pdu_decrypted = network_pdu(network_pdu_decrypted_data)
344    network_pdu_decrypted.origins.append(network_pdu_encrypted)
345
346    # lower transport - reassemble
347    lower_transport = lower_transport_pdu(network_pdu_decrypted)
348    lower_transport.origins.append(network_pdu_decrypted)
349
350    if lower_transport.seg:
351        message = segmented_message_for_pdu(lower_transport)
352        message.add_segment(lower_transport)
353        if not message.complete():
354            return
355        if message.processed:
356            return
357
358        message.processed = True
359        if message.ctl:
360            mesh_process_control(message)
361        else:
362            access_payload = mesh_upper_transport_decrypt(message, message.data)
363            if access_payload == None:
364                message.status = 'No encryption key found'
365                log_pdu(message, 0, [])
366            else:
367                access = access_pdu(message, access_payload)
368                access.origins.append(message)
369                mesh_proess_access(access)
370
371    else:
372        if lower_transport.ctl:
373            control = layer_pdu('Unsegmented Control', lower_transport.data)
374            control.origins.add(lower_transport)
375            mesh_process_control(control)
376        else:
377            access_payload = mesh_upper_transport_decrypt(lower_transport, lower_transport.upper_transport)
378            if access_payload == None:
379                lower_transport.status = 'No encryption key found'
380                log_pdu(lower_transport, 0, [])
381            else:
382                access = access_pdu(lower_transport, access_payload)
383                access.add_property('seq_auth', lower_transport.seq)
384                access.origins.append(lower_transport)
385                mesh_proess_access(access)
386
387
388def mesh_process_beacon_pdu(adv_pdu):
389    log_pdu(adv_pdu, 0, [])
390
391def mesh_process_adv(adv_pdu):
392    ad_type = adv_pdu.data[1]
393    if ad_type == 0x2A:
394        network_pdu_encrypted = layer_pdu("Network(encrypted)", adv_data[2:])
395        network_pdu_encrypted.add_property('ivi', adv_data[2] >> 7)
396        network_pdu_encrypted.add_property('nid', adv_data[2] & 0x7f)
397        network_pdu_encrypted.origins.append(adv_pdu)
398        mesh_process_network_pdu_tx(network_pdu_encrypted)
399    if ad_type == 0x2b:
400        beacon_pdu = layer_pdu("Beacon", adv_data[2:])
401        beacon_pdu.origins.append(adv_pdu)
402        mesh_process_beacon_pdu(beacon_pdu)
403
404
405if len(sys.argv) == 1:
406    print ('Dump Mesh PacketLogger file')
407    print ('Copyright 2019, BlueKitchen GmbH')
408    print ('')
409    print ('Usage: ' + sys.argv[0] + 'hci_dump.pklg')
410    exit(0)
411
412infile = sys.argv[1]
413
414with open (infile, 'rb') as fin:
415    pos = 0
416    while True:
417        payload_length  = read_net_32_from_file(fin)
418        if payload_length < 0:
419            break
420        ts_sec  = read_net_32_from_file(fin)
421        ts_usec = read_net_32_from_file(fin)
422        type    = ord(fin.read(1))
423        packet_len = payload_length - 9;
424        if (packet_len > 66000):
425            print ("Error parsing pklg at offset %u (%x)." % (pos, pos))
426            break
427
428        packet  = fin.read(packet_len)
429        pos     = pos + 4 + payload_length
430        # time    = "[%s.%03u] " % (datetime.datetime.fromtimestamp(ts_sec).strftime("%Y-%m-%d %H:%M:%S"), ts_usec / 1000)
431
432        if type == 0:
433            # CMD
434            if packet[0] != 0x08:
435                continue
436            if packet[1] != 0x20:
437                continue
438            adv_data = packet[4:]
439            adv_pdu = layer_pdu("ADV", adv_data)
440            mesh_process_adv(adv_pdu)
441
442        elif type == 1:
443            # EVT
444            event = packet[0]
445            if event != 0x3e:
446                continue
447            event_len = packet[1]
448            if event_len != 0x2b:
449                continue
450            adv_data = packet[13:-1]
451            adv_pdu = layer_pdu("ADV", adv_data)
452            mesh_process_adv(adv_pdu)
453
454        elif type == 0xfc:
455            # LOG
456            log = packet.decode("utf-8")
457            parts = re.match('mesh-iv-index: (.*)', log)
458            if parts and len(parts.groups()) == 1:
459                mesh_set_iv_index(int(parts.groups()[0], 16))
460                continue
461            parts = re.match('mesh-devkey: (.*)', log)
462            if parts and len(parts.groups()) == 1:
463                mesh_set_device_key(bytes.fromhex(parts.groups()[0]))
464                continue
465            parts = re.match('mesh-appkey-(.*): (.*)', log)
466            if parts and len(parts.groups()) == 2:
467                mesh_add_application_key(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1]))
468                continue
469            parts = re.match('mesh-netkey-(.*): (.*)', log)
470            if parts and len(parts.groups()) == 2:
471                mesh_add_netkey(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1]))
472                continue
473
474