xref: /libbtbb/lib/src/bluetooth_le_packet.c (revision 0a637dcafecee239e6cae92c1a1ba8acd9fd19aa)
1e25b118aSDominic Spill /* -*- c -*- */
2e25b118aSDominic Spill /*
3e25b118aSDominic Spill  * Copyright 2007 - 2012 Mike Ryan, Dominic Spill, Michael Ossmann
4e25b118aSDominic Spill  * Copyright 2005, 2006 Free Software Foundation, Inc.
5e25b118aSDominic Spill  *
6e25b118aSDominic Spill  * This file is part of libbtbb
7e25b118aSDominic Spill  *
8e25b118aSDominic Spill  * This program is free software; you can redistribute it and/or modify
9e25b118aSDominic Spill  * it under the terms of the GNU General Public License as published by
10e25b118aSDominic Spill  * the Free Software Foundation; either version 2, or (at your option)
11e25b118aSDominic Spill  * any later version.
12e25b118aSDominic Spill  *
13e25b118aSDominic Spill  * This program is distributed in the hope that it will be useful,
14e25b118aSDominic Spill  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15e25b118aSDominic Spill  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16e25b118aSDominic Spill  * GNU General Public License for more details.
17e25b118aSDominic Spill  *
18e25b118aSDominic Spill  * You should have received a copy of the GNU General Public License
19e25b118aSDominic Spill  * along with libbtbb; see the file COPYING.  If not, write to
20e25b118aSDominic Spill  * the Free Software Foundation, Inc., 51 Franklin Street,
21e25b118aSDominic Spill  * Boston, MA 02110-1301, USA.
22e25b118aSDominic Spill  */
23e25b118aSDominic Spill 
24e25b118aSDominic Spill #ifdef HAVE_CONFIG_H
25e25b118aSDominic Spill #include "config.h"
26e25b118aSDominic Spill #endif
27e25b118aSDominic Spill 
288f3e7eeaSChristopher Kilgour #include "btbb.h"
29e25b118aSDominic Spill #include "bluetooth_le_packet.h"
30e25b118aSDominic Spill #include <ctype.h>
318f3e7eeaSChristopher Kilgour #include <string.h>
32e25b118aSDominic Spill 
33e25b118aSDominic Spill /* string representations of advertising packet type */
34e25b118aSDominic Spill static const char *ADV_TYPE_NAMES[] = {
35e25b118aSDominic Spill 	"ADV_IND", "ADV_DIRECT_IND", "ADV_NONCONN_IND", "SCAN_REQ",
36e25b118aSDominic Spill 	"SCAN_RSP", "CONNECT_REQ", "ADV_SCAN_IND",
37e25b118aSDominic Spill };
38e25b118aSDominic Spill 
39e25b118aSDominic Spill /* source clock accuracy in a connect packet */
40e25b118aSDominic Spill static const char *CONNECT_SCA[] = {
41e25b118aSDominic Spill 	"251 ppm to 500 ppm", "151 ppm to 250 ppm", "101 ppm to 150 ppm",
42e25b118aSDominic Spill 	"76 ppm to 100 ppm", "51 ppm to 75 ppm", "31 ppm to 50 ppm",
43e25b118aSDominic Spill 	"21 ppm to 30 ppm", "0 ppm to 20 ppm",
44e25b118aSDominic Spill };
45e25b118aSDominic Spill 
46e25b118aSDominic Spill // count of objects in an array, shamelessly stolen from Chrome
47e25b118aSDominic Spill #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
48e25b118aSDominic Spill 
49*0a637dcaSDominic Spill static uint8_t count_bits(uint32_t n)
50*0a637dcaSDominic Spill {
51*0a637dcaSDominic Spill 	uint8_t i = 0;
52*0a637dcaSDominic Spill 	for (i = 0; n != 0; i++)
53*0a637dcaSDominic Spill 		n &= n - 1;
54*0a637dcaSDominic Spill 	return i;
55*0a637dcaSDominic Spill }
56*0a637dcaSDominic Spill 
578f3e7eeaSChristopher Kilgour static int aa_access_channel_off_by_one(const uint32_t aa) {
588f3e7eeaSChristopher Kilgour 	int retval = 0;
59*0a637dcaSDominic Spill 	if(count_bits(aa ^ LE_ADV_AA) == 1) {
608f3e7eeaSChristopher Kilgour 		retval = 1;
618f3e7eeaSChristopher Kilgour 	}
628f3e7eeaSChristopher Kilgour 	return retval;
638f3e7eeaSChristopher Kilgour }
64e25b118aSDominic Spill 
658f3e7eeaSChristopher Kilgour /*
668f3e7eeaSChristopher Kilgour  * A helper function for filtering bogus packets on data channels.
678f3e7eeaSChristopher Kilgour  *
688f3e7eeaSChristopher Kilgour  * If a candidate capture packet is random noise we would expect its
698f3e7eeaSChristopher Kilgour  * Access Address to be a randomly distributed 32-bit number.  An
708f3e7eeaSChristopher Kilgour  * exhaustive software analysis reveals that of 4294967296 possible
718f3e7eeaSChristopher Kilgour  * 32-bit Access Address values, 2900629660 (67.5%) are acceptable and
728f3e7eeaSChristopher Kilgour  * 1394337636 (32.5%) are invalid.  This function will identify which
738f3e7eeaSChristopher Kilgour  * category a candidate Access Address falls into by returning the
748f3e7eeaSChristopher Kilgour  * number of offenses contained.
758f3e7eeaSChristopher Kilgour  *
768f3e7eeaSChristopher Kilgour  * Refer to BT 4.x, Vol 6, Par B, Section 2.1.2.
778f3e7eeaSChristopher Kilgour  *
788f3e7eeaSChristopher Kilgour  * The Access Address in data channel packets meet the
798f3e7eeaSChristopher Kilgour  * following requirements:
808f3e7eeaSChristopher Kilgour  *  - It shall have no more than six consecutive zeros or ones.
818f3e7eeaSChristopher Kilgour  *  - It shall not be the advertising channel packets’ Access Address.
828f3e7eeaSChristopher Kilgour  *  - It shall not be a sequence that differs from the advertising channel packets’
838f3e7eeaSChristopher Kilgour  *    Access Address by only one bit.
848f3e7eeaSChristopher Kilgour  *  - It shall not have all four octets equal.
858f3e7eeaSChristopher Kilgour  *  - It shall have no more than 24 transitions.
868f3e7eeaSChristopher Kilgour  *  - It shall have a minimum of two transitions in the most significant six bits.
878f3e7eeaSChristopher Kilgour  */
888f3e7eeaSChristopher Kilgour static int aa_data_channel_offenses(const uint32_t aa) {
898f3e7eeaSChristopher Kilgour 	int retval = 0, transitions = 0;
908f3e7eeaSChristopher Kilgour 	unsigned shift, odd = (unsigned) (aa & 1);
918f3e7eeaSChristopher Kilgour 	uint8_t aab3, aab2, aab1, aab0 = (uint8_t) (aa & 0xff);
92e25b118aSDominic Spill 
938f3e7eeaSChristopher Kilgour 	const uint8_t EIGHT_BIT_TRANSITIONS_EVEN[256] = {
948f3e7eeaSChristopher Kilgour 		0, 2, 2, 2, 2, 4, 2, 2, 2, 4, 4, 4, 2, 4, 2, 2,
958f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 2, 4, 4, 4, 2, 4, 2, 2,
968f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 4, 6, 6, 6, 4, 6, 4, 4,
978f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 2, 4, 4, 4, 2, 4, 2, 2,
988f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 4, 6, 6, 6, 4, 6, 4, 4,
998f3e7eeaSChristopher Kilgour 		4, 6, 6, 6, 6, 8, 6, 6, 4, 6, 6, 6, 4, 6, 4, 4,
1008f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 4, 6, 6, 6, 4, 6, 4, 4,
1018f3e7eeaSChristopher Kilgour 		2, 4, 4, 4, 4, 6, 4, 4, 2, 4, 4, 4, 2, 4, 2, 2,
1028f3e7eeaSChristopher Kilgour 		1, 3, 3, 3, 3, 5, 3, 3, 3, 5, 5, 5, 3, 5, 3, 3,
1038f3e7eeaSChristopher Kilgour 		3, 5, 5, 5, 5, 7, 5, 5, 3, 5, 5, 5, 3, 5, 3, 3,
1048f3e7eeaSChristopher Kilgour 		3, 5, 5, 5, 5, 7, 5, 5, 5, 7, 7, 7, 5, 7, 5, 5,
1058f3e7eeaSChristopher Kilgour 		3, 5, 5, 5, 5, 7, 5, 5, 3, 5, 5, 5, 3, 5, 3, 3,
1068f3e7eeaSChristopher Kilgour 		1, 3, 3, 3, 3, 5, 3, 3, 3, 5, 5, 5, 3, 5, 3, 3,
1078f3e7eeaSChristopher Kilgour 		3, 5, 5, 5, 5, 7, 5, 5, 3, 5, 5, 5, 3, 5, 3, 3,
1088f3e7eeaSChristopher Kilgour 		1, 3, 3, 3, 3, 5, 3, 3, 3, 5, 5, 5, 3, 5, 3, 3,
1098f3e7eeaSChristopher Kilgour 		1, 3, 3, 3, 3, 5, 3, 3, 1, 3, 3, 3, 1, 3, 1, 1
1108f3e7eeaSChristopher Kilgour 	};
111e25b118aSDominic Spill 
1128f3e7eeaSChristopher Kilgour 	const uint8_t EIGHT_BIT_TRANSITIONS_ODD[256] = {
1138f3e7eeaSChristopher Kilgour 		1, 1, 3, 1, 3, 3, 3, 1, 3, 3, 5, 3, 3, 3, 3, 1,
1148f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 3, 3, 5, 3, 3, 3, 3, 1,
1158f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 5, 5, 7, 5, 5, 5, 5, 3,
1168f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 3, 3, 5, 3, 3, 3, 3, 1,
1178f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 5, 5, 7, 5, 5, 5, 5, 3,
1188f3e7eeaSChristopher Kilgour 		5, 5, 7, 5, 7, 7, 7, 5, 5, 5, 7, 5, 5, 5, 5, 3,
1198f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 5, 5, 7, 5, 5, 5, 5, 3,
1208f3e7eeaSChristopher Kilgour 		3, 3, 5, 3, 5, 5, 5, 3, 3, 3, 5, 3, 3, 3, 3, 1,
1218f3e7eeaSChristopher Kilgour 		2, 2, 4, 2, 4, 4, 4, 2, 4, 4, 6, 4, 4, 4, 4, 2,
1228f3e7eeaSChristopher Kilgour 		4, 4, 6, 4, 6, 6, 6, 4, 4, 4, 6, 4, 4, 4, 4, 2,
1238f3e7eeaSChristopher Kilgour 		4, 4, 6, 4, 6, 6, 6, 4, 6, 6, 8, 6, 6, 6, 6, 4,
1248f3e7eeaSChristopher Kilgour 		4, 4, 6, 4, 6, 6, 6, 4, 4, 4, 6, 4, 4, 4, 4, 2,
1258f3e7eeaSChristopher Kilgour 		2, 2, 4, 2, 4, 4, 4, 2, 4, 4, 6, 4, 4, 4, 4, 2,
1268f3e7eeaSChristopher Kilgour 		4, 4, 6, 4, 6, 6, 6, 4, 4, 4, 6, 4, 4, 4, 4, 2,
1278f3e7eeaSChristopher Kilgour 		2, 2, 4, 2, 4, 4, 4, 2, 4, 4, 6, 4, 4, 4, 4, 2,
1288f3e7eeaSChristopher Kilgour 		2, 2, 4, 2, 4, 4, 4, 2, 2, 2, 4, 2, 2, 2, 2, 0
1298f3e7eeaSChristopher Kilgour 	};
1308f3e7eeaSChristopher Kilgour 
1318f3e7eeaSChristopher Kilgour 	transitions += (odd ? EIGHT_BIT_TRANSITIONS_ODD[aab0] : EIGHT_BIT_TRANSITIONS_EVEN[aab0] );
1328f3e7eeaSChristopher Kilgour 	odd = (unsigned) (aab0 & 0x80);
1338f3e7eeaSChristopher Kilgour 	aab1 = (uint8_t) (aa >> 8);
1348f3e7eeaSChristopher Kilgour 	transitions += (odd ? EIGHT_BIT_TRANSITIONS_ODD[aab1] : EIGHT_BIT_TRANSITIONS_EVEN[aab1] );
1358f3e7eeaSChristopher Kilgour 	odd = (unsigned) (aab1 & 0x80);
1368f3e7eeaSChristopher Kilgour 	aab2 = (uint8_t) (aa >> 16);
1378f3e7eeaSChristopher Kilgour 	transitions += (odd ? EIGHT_BIT_TRANSITIONS_ODD[aab2] : EIGHT_BIT_TRANSITIONS_EVEN[aab2] );
1388f3e7eeaSChristopher Kilgour 	odd = (unsigned) (aab2 & 0x80);
1398f3e7eeaSChristopher Kilgour 	aab3 = (uint8_t) (aa >> 24);
1408f3e7eeaSChristopher Kilgour 	transitions += (odd ? EIGHT_BIT_TRANSITIONS_ODD[aab3] : EIGHT_BIT_TRANSITIONS_EVEN[aab3] );
1418f3e7eeaSChristopher Kilgour 
1428f3e7eeaSChristopher Kilgour 	/* consider excessive transitions as offenses */
1438f3e7eeaSChristopher Kilgour 	if (transitions > 24) {
1448f3e7eeaSChristopher Kilgour 		retval += (transitions - 24);
1458f3e7eeaSChristopher Kilgour 	}
1468f3e7eeaSChristopher Kilgour 
1478f3e7eeaSChristopher Kilgour 	const uint8_t AA_MSB6_ALLOWED[64] = {
1488f3e7eeaSChristopher Kilgour 		0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
1498f3e7eeaSChristopher Kilgour 		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1508f3e7eeaSChristopher Kilgour 		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1518f3e7eeaSChristopher Kilgour 		0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0
1528f3e7eeaSChristopher Kilgour 	};
1538f3e7eeaSChristopher Kilgour 
1548f3e7eeaSChristopher Kilgour 	/* consider excessive transitions in the 6 MSBs as an offense */
1558f3e7eeaSChristopher Kilgour 	retval += (1 - AA_MSB6_ALLOWED[aab3>>2]);
1568f3e7eeaSChristopher Kilgour 
1578f3e7eeaSChristopher Kilgour 	/* consider all bytes as being equal an offense */
1588f3e7eeaSChristopher Kilgour 	retval += (((aab0 == aab1) && (aab0 == aab2) && (aab0 == aab3)) ? 1 : 0);
1598f3e7eeaSChristopher Kilgour 
1608f3e7eeaSChristopher Kilgour 	/* access-channel address and off-by-ones are illegal */
161*0a637dcaSDominic Spill 	retval += ((aa == LE_ADV_AA) ? 1 : 0);
1628f3e7eeaSChristopher Kilgour 	retval += aa_access_channel_off_by_one(aa);
1638f3e7eeaSChristopher Kilgour 
1648f3e7eeaSChristopher Kilgour 	/* inspect nibble triples for insufficient bit transitions */
1658f3e7eeaSChristopher Kilgour 	for(shift=0; shift<=20; shift+=4) {
1668f3e7eeaSChristopher Kilgour 		uint16_t twelvebits = (uint16_t) ((aa >> shift) & 0xfff);
1678f3e7eeaSChristopher Kilgour 		switch( twelvebits ) {
1688f3e7eeaSChristopher Kilgour 			/* seven consecutive zeroes */
1698f3e7eeaSChristopher Kilgour 		case 0x080: case 0x180: case 0x280: case 0x380: case 0x480:
1708f3e7eeaSChristopher Kilgour 		case 0x580: case 0x680: case 0x780: case 0x880: case 0x980:
1718f3e7eeaSChristopher Kilgour 		case 0xa80: case 0xb80: case 0xc80: case 0xd80: case 0xe80:
1728f3e7eeaSChristopher Kilgour 		case 0xf80: case 0x101: case 0x301: case 0x501: case 0x701:
1738f3e7eeaSChristopher Kilgour 		case 0x901: case 0xb01: case 0xd01: case 0xf01: case 0x202:
1748f3e7eeaSChristopher Kilgour 		case 0x602: case 0xa02: case 0xe02: case 0x203: case 0x603:
1758f3e7eeaSChristopher Kilgour 		case 0xa03: case 0xe03: case 0x404: case 0xc04: case 0x405:
1768f3e7eeaSChristopher Kilgour 		case 0xc05: case 0x406: case 0xc06: case 0x407: case 0xc07:
1778f3e7eeaSChristopher Kilgour 		case 0x808: case 0x809: case 0x80a: case 0x80b: case 0x80c:
1788f3e7eeaSChristopher Kilgour 		case 0x80d: case 0x80e: case 0x80f: case 0x010: case 0x011:
1798f3e7eeaSChristopher Kilgour 		case 0x012: case 0x013: case 0x014: case 0x015: case 0x016:
1808f3e7eeaSChristopher Kilgour 		case 0x017: case 0x018: case 0x019: case 0x01a: case 0x01b:
1818f3e7eeaSChristopher Kilgour 		case 0x01c: case 0x01d: case 0x01e: case 0x01f:
1828f3e7eeaSChristopher Kilgour 			/* eight consecutive zeroes */
1838f3e7eeaSChristopher Kilgour 		case 0x100: case 0x300: case 0x500: case 0x700: case 0x900:
1848f3e7eeaSChristopher Kilgour 		case 0xb00: case 0xd00: case 0xf00: case 0x201: case 0x601:
1858f3e7eeaSChristopher Kilgour 		case 0xa01: case 0xe01: case 0x402: case 0xc02: case 0x403:
1868f3e7eeaSChristopher Kilgour 		case 0xc03: case 0x804: case 0x805: case 0x806: case 0x807:
1878f3e7eeaSChristopher Kilgour 		case 0x008: case 0x009: case 0x00a: case 0x00b: case 0x00c:
1888f3e7eeaSChristopher Kilgour 		case 0x00d: case 0x00e: case 0x00f:
1898f3e7eeaSChristopher Kilgour 			/* nine consecutive zeroes */
1908f3e7eeaSChristopher Kilgour 		case 0xe00: case 0xc01: case 0x802: case 0x803: case 0x004:
1918f3e7eeaSChristopher Kilgour 		case 0x005: case 0x006: case 0x007:
1928f3e7eeaSChristopher Kilgour 			/* ten consecutive zeroes */
1938f3e7eeaSChristopher Kilgour 		case 0x400: case 0xc00: case 0x801: case 0x002: case 0x003:
1948f3e7eeaSChristopher Kilgour 			/* eleven consecutive zeroes */
1958f3e7eeaSChristopher Kilgour 		case 0x800: case 0x001:
1968f3e7eeaSChristopher Kilgour 			/* twelve consecutive zeroes */
1978f3e7eeaSChristopher Kilgour 		case 0x000:
1988f3e7eeaSChristopher Kilgour 			/* seven consecutive ones */
1998f3e7eeaSChristopher Kilgour 		case 0x07f: case 0x0fe: case 0x2fe: case 0x4fe: case 0x6fe:
2008f3e7eeaSChristopher Kilgour 		case 0x8fe: case 0xafe: case 0xcfe: case 0xefe: case 0x1fc:
2018f3e7eeaSChristopher Kilgour 		case 0x5fc: case 0x9fc: case 0xdfc: case 0x1fd: case 0x5fd:
2028f3e7eeaSChristopher Kilgour 		case 0x9fd: case 0xdfd: case 0x3f8: case 0xbf8: case 0x3f9:
2038f3e7eeaSChristopher Kilgour 		case 0xbf9: case 0x3fa: case 0xbfa: case 0x3fb: case 0xbfb:
2048f3e7eeaSChristopher Kilgour 		case 0x7f4: case 0x7f5: case 0x7f6: case 0x7f7: case 0xfe0:
2058f3e7eeaSChristopher Kilgour 			/* eight consecutive ones */
2068f3e7eeaSChristopher Kilgour 		case 0x0ff: case 0x2ff: case 0x4ff: case 0x6ff: case 0x8ff:
2078f3e7eeaSChristopher Kilgour 		case 0xaff: case 0xcff: case 0xeff: case 0x1fe: case 0x5fe:
2088f3e7eeaSChristopher Kilgour 		case 0x9fe: case 0xdfe: case 0x3fc: case 0xbfc: case 0x3fd:
2098f3e7eeaSChristopher Kilgour 		case 0xbfd: case 0x7f8: case 0x7f9: case 0x7fa: case 0x7fb:
2108f3e7eeaSChristopher Kilgour 		case 0xff0: case 0xff1: case 0xff2: case 0xff3: case 0xff4:
2118f3e7eeaSChristopher Kilgour 		case 0xff5: case 0xff6: case 0xff7:
2128f3e7eeaSChristopher Kilgour 			/* nine consecutive ones */
2138f3e7eeaSChristopher Kilgour 		case 0x1ff: case 0x5ff: case 0x9ff: case 0xdff: case 0x3fe:
2148f3e7eeaSChristopher Kilgour 		case 0xbfe: case 0x7fc: case 0x7fd: case 0xff8: case 0xff9:
2158f3e7eeaSChristopher Kilgour 		case 0xffa: case 0xffb:
2168f3e7eeaSChristopher Kilgour 			/* ten consecutive ones */
2178f3e7eeaSChristopher Kilgour 		case 0x3ff: case 0xbff: case 0x7fe: case 0xffc: case 0xffd:
2188f3e7eeaSChristopher Kilgour 			/* eleven consecutive ones */
2198f3e7eeaSChristopher Kilgour 		case 0x7ff: case 0xffe:
2208f3e7eeaSChristopher Kilgour 			/* all ones */
2218f3e7eeaSChristopher Kilgour 		case 0xfff:
2228f3e7eeaSChristopher Kilgour 			retval++;
2238f3e7eeaSChristopher Kilgour 			break;
2248f3e7eeaSChristopher Kilgour 		default:
2258f3e7eeaSChristopher Kilgour 			break;
226e25b118aSDominic Spill 		}
227e25b118aSDominic Spill 	}
228e25b118aSDominic Spill 
2298f3e7eeaSChristopher Kilgour 	return retval;
230e25b118aSDominic Spill }
231e25b118aSDominic Spill 
2328f3e7eeaSChristopher Kilgour lell_packet *
2338f3e7eeaSChristopher Kilgour lell_packet_new(void)
2348f3e7eeaSChristopher Kilgour {
2358f3e7eeaSChristopher Kilgour 	lell_packet *pkt = (lell_packet *)calloc(1, sizeof(lell_packet));
2368f3e7eeaSChristopher Kilgour 	pkt->refcount = 1;
2378f3e7eeaSChristopher Kilgour 	return pkt;
2388f3e7eeaSChristopher Kilgour }
2398f3e7eeaSChristopher Kilgour 
2408f3e7eeaSChristopher Kilgour void
2418f3e7eeaSChristopher Kilgour lell_packet_ref(lell_packet *pkt)
2428f3e7eeaSChristopher Kilgour {
2438f3e7eeaSChristopher Kilgour 	pkt->refcount++;
2448f3e7eeaSChristopher Kilgour }
2458f3e7eeaSChristopher Kilgour 
2468f3e7eeaSChristopher Kilgour void
2478f3e7eeaSChristopher Kilgour lell_packet_unref(lell_packet *pkt)
2488f3e7eeaSChristopher Kilgour {
2498f3e7eeaSChristopher Kilgour 	pkt->refcount--;
2508f3e7eeaSChristopher Kilgour 	if (pkt->refcount == 0)
2518f3e7eeaSChristopher Kilgour 		free(pkt);
2528f3e7eeaSChristopher Kilgour }
2538f3e7eeaSChristopher Kilgour 
2548f3e7eeaSChristopher Kilgour static uint8_t le_channel_index(uint16_t phys_channel) {
255e25b118aSDominic Spill 	uint8_t ret;
256e25b118aSDominic Spill 	if (phys_channel == 2402) {
257e25b118aSDominic Spill 		ret = 37;
258e25b118aSDominic Spill 	} else if (phys_channel < 2426) { // 0 - 10
259e25b118aSDominic Spill 		ret = (phys_channel - 2404) / 2;
260e25b118aSDominic Spill 	} else if (phys_channel == 2426) {
261e25b118aSDominic Spill 		ret = 38;
262e25b118aSDominic Spill 	} else if (phys_channel < 2480) { // 11 - 36
263e25b118aSDominic Spill 		ret = 11 + (phys_channel - 2428) / 2;
264e25b118aSDominic Spill 	} else {
265e25b118aSDominic Spill 		ret = 39;
266e25b118aSDominic Spill 	}
267e25b118aSDominic Spill 	return ret;
268e25b118aSDominic Spill }
269e25b118aSDominic Spill 
2708f3e7eeaSChristopher Kilgour void lell_allocate_and_decode(const uint8_t *stream, uint16_t phys_channel, uint32_t clk100ns, lell_packet **pkt)
2718f3e7eeaSChristopher Kilgour {
2728f3e7eeaSChristopher Kilgour 	*pkt = lell_packet_new( );
2738f3e7eeaSChristopher Kilgour 	memcpy((*pkt)->symbols, stream, MAX_LE_SYMBOLS);
2748f3e7eeaSChristopher Kilgour 
2758f3e7eeaSChristopher Kilgour 	(*pkt)->channel_idx = le_channel_index(phys_channel);
2768f3e7eeaSChristopher Kilgour 	(*pkt)->channel_k = (phys_channel-2402)/2;
2778f3e7eeaSChristopher Kilgour 	(*pkt)->clk100ns = clk100ns;
2788f3e7eeaSChristopher Kilgour 
2798f3e7eeaSChristopher Kilgour 	(*pkt)->access_address = 0;
2808f3e7eeaSChristopher Kilgour 	(*pkt)->access_address |= (*pkt)->symbols[0];
2818f3e7eeaSChristopher Kilgour 	(*pkt)->access_address |= (*pkt)->symbols[1] << 8;
2828f3e7eeaSChristopher Kilgour 	(*pkt)->access_address |= (*pkt)->symbols[2] << 16;
2838f3e7eeaSChristopher Kilgour 	(*pkt)->access_address |= (*pkt)->symbols[3] << 24;
2848f3e7eeaSChristopher Kilgour 
2858f3e7eeaSChristopher Kilgour 	if (lell_packet_is_data(*pkt)) {
2868f3e7eeaSChristopher Kilgour 		// data PDU
2878f3e7eeaSChristopher Kilgour 		(*pkt)->length = (*pkt)->symbols[5] & 0x1f;
2888f3e7eeaSChristopher Kilgour 		(*pkt)->access_address_offenses = aa_data_channel_offenses((*pkt)->access_address);
2898f3e7eeaSChristopher Kilgour 		(*pkt)->flags.as_bits.access_address_ok = (*pkt)->access_address_offenses ? 0 : 1;
2908f3e7eeaSChristopher Kilgour 	} else {
2918f3e7eeaSChristopher Kilgour 		// advertising PDU
2928f3e7eeaSChristopher Kilgour 		(*pkt)->length = (*pkt)->symbols[5] & 0x3f;
2938f3e7eeaSChristopher Kilgour 		(*pkt)->adv_type = (*pkt)->symbols[4] & 0xf;
2948f3e7eeaSChristopher Kilgour 		(*pkt)->adv_tx_add = (*pkt)->symbols[4] & 0x40 ? 1 : 0;
2958f3e7eeaSChristopher Kilgour 		(*pkt)->adv_rx_add = (*pkt)->symbols[4] & 0x80 ? 1 : 0;
2968f3e7eeaSChristopher Kilgour 		(*pkt)->flags.as_bits.access_address_ok = ((*pkt)->access_address == 0x8e89bed6);
2978f3e7eeaSChristopher Kilgour 		(*pkt)->access_address_offenses = (*pkt)->flags.as_bits.access_address_ok ? 0 :
2988f3e7eeaSChristopher Kilgour 			(aa_access_channel_off_by_one((*pkt)->access_address) ? 1 : 32);
2998f3e7eeaSChristopher Kilgour 	}
3008f3e7eeaSChristopher Kilgour }
3018f3e7eeaSChristopher Kilgour 
3028f3e7eeaSChristopher Kilgour unsigned lell_packet_is_data(const lell_packet *pkt)
3038f3e7eeaSChristopher Kilgour {
3048f3e7eeaSChristopher Kilgour 	return (unsigned) (pkt->channel_idx < 37);
3058f3e7eeaSChristopher Kilgour }
3068f3e7eeaSChristopher Kilgour 
3078f3e7eeaSChristopher Kilgour uint32_t lell_get_access_address(const lell_packet *pkt)
3088f3e7eeaSChristopher Kilgour {
3098f3e7eeaSChristopher Kilgour 	return pkt->access_address;
3108f3e7eeaSChristopher Kilgour }
3118f3e7eeaSChristopher Kilgour 
3128f3e7eeaSChristopher Kilgour unsigned lell_get_access_address_offenses(const lell_packet *pkt)
3138f3e7eeaSChristopher Kilgour {
3148f3e7eeaSChristopher Kilgour 	return pkt->access_address_offenses;
3158f3e7eeaSChristopher Kilgour }
3168f3e7eeaSChristopher Kilgour 
3178f3e7eeaSChristopher Kilgour unsigned lell_get_channel_index(const lell_packet *pkt)
3188f3e7eeaSChristopher Kilgour {
3198f3e7eeaSChristopher Kilgour 	return pkt->channel_idx;
3208f3e7eeaSChristopher Kilgour }
3218f3e7eeaSChristopher Kilgour 
3228f3e7eeaSChristopher Kilgour unsigned lell_get_channel_k(const lell_packet *pkt)
3238f3e7eeaSChristopher Kilgour {
3248f3e7eeaSChristopher Kilgour 	return pkt->channel_k;
3258f3e7eeaSChristopher Kilgour }
3268f3e7eeaSChristopher Kilgour 
3278f3e7eeaSChristopher Kilgour const char * lell_get_adv_type_str(const lell_packet *pkt)
3288f3e7eeaSChristopher Kilgour {
3298f3e7eeaSChristopher Kilgour 	if (lell_packet_is_data(pkt))
330e25b118aSDominic Spill 		return NULL;
3318f3e7eeaSChristopher Kilgour 	if (pkt->adv_type < COUNT_OF(ADV_TYPE_NAMES))
3328f3e7eeaSChristopher Kilgour 		return ADV_TYPE_NAMES[pkt->adv_type];
333e25b118aSDominic Spill 	return "UNKNOWN";
334e25b118aSDominic Spill }
335e25b118aSDominic Spill 
3368f3e7eeaSChristopher Kilgour static void _dump_addr(const char *name, const uint8_t *buf, int offset, int random) {
337e25b118aSDominic Spill 	int i;
338e25b118aSDominic Spill 	printf("    %s%02x", name, buf[offset+5]);
339e25b118aSDominic Spill 	for (i = 4; i >= 0; --i)
340e25b118aSDominic Spill 		printf(":%02x", buf[offset+i]);
341e25b118aSDominic Spill 	printf(" (%s)\n", random ? "random" : "public");
342e25b118aSDominic Spill }
343e25b118aSDominic Spill 
3448f3e7eeaSChristopher Kilgour static void _dump_8(const char *name, const uint8_t *buf, int offset) {
345e25b118aSDominic Spill 	printf("    %s%02x (%d)\n", name, buf[offset], buf[offset]);
346e25b118aSDominic Spill }
347e25b118aSDominic Spill 
3488f3e7eeaSChristopher Kilgour static void _dump_16(const char *name, const uint8_t *buf, int offset) {
349e25b118aSDominic Spill 	uint16_t val = buf[offset+1] << 8 | buf[offset];
350e25b118aSDominic Spill 	printf("    %s%04x (%d)\n", name, val, val);
351e25b118aSDominic Spill }
352e25b118aSDominic Spill 
3538f3e7eeaSChristopher Kilgour static void _dump_24(const char *name, const uint8_t *buf, int offset) {
354e25b118aSDominic Spill 	uint16_t val = buf[offset+2] << 16 | buf[offset+1] << 8 | buf[offset];
355e25b118aSDominic Spill 	printf("    %s%06x\n", name, val);
356e25b118aSDominic Spill }
357e25b118aSDominic Spill 
3588f3e7eeaSChristopher Kilgour static void _dump_32(const char *name, const uint8_t *buf, int offset) {
359e25b118aSDominic Spill 	uint32_t val = buf[offset+3] << 24 |
360e25b118aSDominic Spill 				   buf[offset+2] << 16 |
361e25b118aSDominic Spill 				   buf[offset+1] << 8 |
362e25b118aSDominic Spill 				   buf[offset+0];
363e25b118aSDominic Spill 	printf("    %s%08x\n", name, val);
364e25b118aSDominic Spill }
365e25b118aSDominic Spill 
3668f3e7eeaSChristopher Kilgour static void _dump_uuid(const uint8_t *uuid) {
367e25b118aSDominic Spill 	int i;
368e25b118aSDominic Spill 	for (i = 0; i < 4; ++i)
369e25b118aSDominic Spill 		printf("%02x", uuid[i]);
370e25b118aSDominic Spill 	printf("-");
371e25b118aSDominic Spill 	for (i = 4; i < 6; ++i)
372e25b118aSDominic Spill 		printf("%02x", uuid[i]);
373e25b118aSDominic Spill 	printf("-");
374e25b118aSDominic Spill 	for (i = 6; i < 8; ++i)
375e25b118aSDominic Spill 		printf("%02x", uuid[i]);
376e25b118aSDominic Spill 	printf("-");
377e25b118aSDominic Spill 	for (i = 8; i < 10; ++i)
378e25b118aSDominic Spill 		printf("%02x", uuid[i]);
379e25b118aSDominic Spill 	printf("-");
380e25b118aSDominic Spill 	for (i = 10; i < 16; ++i)
381e25b118aSDominic Spill 		printf("%02x", uuid[i]);
382e25b118aSDominic Spill }
383e25b118aSDominic Spill 
384e25b118aSDominic Spill // Refer to pg 1735 of Bluetooth Core Spec 4.0
3858f3e7eeaSChristopher Kilgour static void _dump_scan_rsp_data(const uint8_t *buf, int len) {
386e25b118aSDominic Spill 	int pos = 0;
387e25b118aSDominic Spill 	int sublen, i;
388e25b118aSDominic Spill 	uint8_t type;
389e25b118aSDominic Spill 	uint16_t val;
390e25b118aSDominic Spill 	char *cval;
391e25b118aSDominic Spill 
392e25b118aSDominic Spill 	while (pos < len) {
393e25b118aSDominic Spill 		sublen = buf[pos];
394e25b118aSDominic Spill 		++pos;
395e25b118aSDominic Spill 		if (pos + sublen > len) {
396e25b118aSDominic Spill 			printf("Error: attempt to read past end of buffer (%d + %d > %d)\n", pos, sublen, len);
397e25b118aSDominic Spill 			return;
398e25b118aSDominic Spill 		}
399e25b118aSDominic Spill 		if (sublen == 0) {
400e25b118aSDominic Spill 			printf("Early return due to 0 length\n");
401e25b118aSDominic Spill 			return;
402e25b118aSDominic Spill 		}
403e25b118aSDominic Spill 		type = buf[pos];
404e25b118aSDominic Spill 		printf("        Type %02x", type);
405e25b118aSDominic Spill 		switch (type) {
406e25b118aSDominic Spill 			case 0x01:
407e25b118aSDominic Spill 				printf(" (Flags)\n");
408e25b118aSDominic Spill 				printf("           ");
409e25b118aSDominic Spill 				for (i = 0; i < 8; ++i)
410e25b118aSDominic Spill 					printf("%d", buf[pos+1] & (1 << (7-i)) ? 1 : 0);
411e25b118aSDominic Spill 				printf("\n");
412e25b118aSDominic Spill 				break;
41345000095SMike Ryan 			case 0x06:
41445000095SMike Ryan 				printf(" (128-bit Service UUIDs, more available)\n");
41545000095SMike Ryan 				goto print128;
416e25b118aSDominic Spill 			case 0x07:
417e25b118aSDominic Spill 				printf(" (128-bit Service UUIDs)\n");
41845000095SMike Ryan print128:
419e25b118aSDominic Spill 				if ((sublen - 1) % 16 == 0) {
420e25b118aSDominic Spill 					uint8_t uuid[16];
421e25b118aSDominic Spill 					for (i = 0; i < sublen - 1; ++i) {
422e25b118aSDominic Spill 						uuid[15 - (i % 16)] = buf[pos+1+i];
423e25b118aSDominic Spill 						if ((i & 15) == 15) {
424e25b118aSDominic Spill 							printf("           ");
425e25b118aSDominic Spill 							_dump_uuid(uuid);
426e25b118aSDominic Spill 							printf("\n");
427e25b118aSDominic Spill 						}
428e25b118aSDominic Spill 					}
429e25b118aSDominic Spill 				}
430e25b118aSDominic Spill 				else {
431e25b118aSDominic Spill 					printf("Wrong length (%d, must be divisible by 16)\n", sublen-1);
432e25b118aSDominic Spill 				}
433e25b118aSDominic Spill 				break;
434e25b118aSDominic Spill 			case 0x09:
435e25b118aSDominic Spill 				printf(" (Complete Local Name)\n");
436e25b118aSDominic Spill 				printf("           ");
437e25b118aSDominic Spill 				for (i = 1; i < sublen; ++i)
438e25b118aSDominic Spill 					printf("%c", isprint(buf[pos+i]) ? buf[pos+i] : '.');
439e25b118aSDominic Spill 				printf("\n");
440e25b118aSDominic Spill 				break;
441e25b118aSDominic Spill 			case 0x0a:
442e25b118aSDominic Spill 				printf(" (Tx Power Level)\n");
443e25b118aSDominic Spill 				printf("           ");
444e25b118aSDominic Spill 				if (sublen-1 == 1) {
445e25b118aSDominic Spill 					cval = (char *)&buf[pos+1];
446e25b118aSDominic Spill 					printf("%d dBm\n", *cval);
447e25b118aSDominic Spill 				} else {
448e25b118aSDominic Spill 					printf("Wrong length (%d, should be 1)\n", sublen-1);
449e25b118aSDominic Spill 				}
450e25b118aSDominic Spill 				break;
451e25b118aSDominic Spill 			case 0x12:
452e25b118aSDominic Spill 				printf(" (Slave Connection Interval Range)\n");
453e25b118aSDominic Spill 				printf("           ");
454e25b118aSDominic Spill 				if (sublen-1 == 4) {
455e25b118aSDominic Spill 					val = (buf[pos+2] << 8) | buf[pos+1];
456e25b118aSDominic Spill 					printf("(%0.2f, ", val * 1.25);
457e25b118aSDominic Spill 					val = (buf[pos+4] << 8) | buf[pos+3];
458e25b118aSDominic Spill 					printf("%0.2f) ms\n", val * 1.25);
459e25b118aSDominic Spill 				}
460e25b118aSDominic Spill 				else {
461e25b118aSDominic Spill 					printf("Wrong length (%d, should be 4)\n", sublen-1);
462e25b118aSDominic Spill 				}
463e25b118aSDominic Spill 				break;
464e25b118aSDominic Spill 			case 0x16:
465e25b118aSDominic Spill 				printf(" (Service Data)\n");
466e25b118aSDominic Spill 				printf("           ");
467e25b118aSDominic Spill 				if (sublen-1 >= 2) {
468e25b118aSDominic Spill 					val = (buf[pos+2] << 8) | buf[pos+1];
469e25b118aSDominic Spill 					printf("UUID: %02x", val);
470e25b118aSDominic Spill 					if (sublen-1 > 2) {
471e25b118aSDominic Spill 						printf(", Additional:");
472e25b118aSDominic Spill 						for (i = 3; i < sublen; ++i)
473e25b118aSDominic Spill 							printf(" %02x", buf[pos+i]);
474e25b118aSDominic Spill 					}
475e25b118aSDominic Spill 					printf("\n");
476e25b118aSDominic Spill 				}
477e25b118aSDominic Spill 				else {
478e25b118aSDominic Spill 					printf("Wrong length (%d, should be >= 2)\n", sublen-1);
479e25b118aSDominic Spill 				}
480e25b118aSDominic Spill 				break;
481e25b118aSDominic Spill 			default:
482e25b118aSDominic Spill 				printf("\n");
483e25b118aSDominic Spill 				printf("           ");
484e25b118aSDominic Spill 				for (i = 1; i < sublen; ++i)
485e25b118aSDominic Spill 					printf(" %02x", buf[pos+i]);
486e25b118aSDominic Spill 				printf("\n");
487e25b118aSDominic Spill 		}
488e25b118aSDominic Spill 		pos += sublen;
489e25b118aSDominic Spill 	}
490e25b118aSDominic Spill }
491e25b118aSDominic Spill 
4928f3e7eeaSChristopher Kilgour void lell_print(const lell_packet *pkt)
4938f3e7eeaSChristopher Kilgour {
494387b6603SIvan Krasin 	int i, opcode;
4958f3e7eeaSChristopher Kilgour 	if (lell_packet_is_data(pkt)) {
4968f3e7eeaSChristopher Kilgour 		int llid = pkt->symbols[4] & 0x3;
497e25b118aSDominic Spill 		static const char *llid_str[] = {
498e25b118aSDominic Spill 			"Reserved",
499e25b118aSDominic Spill 			"LL Data PDU / empty or L2CAP continuation",
500e25b118aSDominic Spill 			"LL Data PDU / L2CAP start",
501e25b118aSDominic Spill 			"LL Control PDU",
502e25b118aSDominic Spill 		};
503e25b118aSDominic Spill 
5048f3e7eeaSChristopher Kilgour 		printf("Data / AA %08x (%s) / %2d bytes\n", pkt->access_address,
5058f3e7eeaSChristopher Kilgour 		       pkt->flags.as_bits.access_address_ok ? "valid" : "invalid",
5068f3e7eeaSChristopher Kilgour 		       pkt->length);
5078f3e7eeaSChristopher Kilgour 		printf("    Channel Index: %d\n", pkt->channel_idx);
508e25b118aSDominic Spill 		printf("    LLID: %d / %s\n", llid, llid_str[llid]);
5098f3e7eeaSChristopher Kilgour 		printf("    NESN: %d  SN: %d  MD: %d\n", (pkt->symbols[4] >> 2) & 1,
5108f3e7eeaSChristopher Kilgour 												 (pkt->symbols[4] >> 3) & 1,
5118f3e7eeaSChristopher Kilgour 												 (pkt->symbols[4] >> 4) & 1);
512387b6603SIvan Krasin 		switch (llid) {
513387b6603SIvan Krasin 		case 3: // LL Control PDU
5148f3e7eeaSChristopher Kilgour 			opcode = pkt->symbols[6];
515387b6603SIvan Krasin 			static const char *opcode_str[] = {
516387b6603SIvan Krasin 				"LL_CONNECTION_UPDATE_REQ",
517387b6603SIvan Krasin 				"LL_CHANNEL_MAP_REQ",
518387b6603SIvan Krasin 				"LL_TERMINATE_IND",
519387b6603SIvan Krasin 				"LL_ENC_REQ",
520387b6603SIvan Krasin 				"LL_ENC_RSP",
521387b6603SIvan Krasin 				"LL_START_ENC_REQ",
522387b6603SIvan Krasin 				"LL_START_ENC_RSP",
523387b6603SIvan Krasin 				"LL_UNKNOWN_RSP",
524387b6603SIvan Krasin 				"LL_FEATURE_REQ",
525387b6603SIvan Krasin 				"LL_FEATURE_RSP",
526387b6603SIvan Krasin 				"LL_PAUSE_ENC_REQ",
527387b6603SIvan Krasin 				"LL_PAUSE_ENC_RSP",
528387b6603SIvan Krasin 				"LL_VERSION_IND",
529387b6603SIvan Krasin 				"LL_REJECT_IND",
530b3394904SMike Ryan 				"LL_SLAVE_FEATURE_REQ",
531b3394904SMike Ryan 				"LL_CONNECTION_PARAM_REQ",
532b3394904SMike Ryan 				"LL_CONNECTION_PARAM_RSP",
533b3394904SMike Ryan 				"LL_REJECT_IND_EXT",
534b3394904SMike Ryan 				"LL_PING_REQ",
535b3394904SMike Ryan 				"LL_PING_RSP",
536387b6603SIvan Krasin 				"Reserved for Future Use",
537387b6603SIvan Krasin 			};
538ba100f3dSMike Ryan 			printf("    Opcode: %d / %s\n", opcode, opcode_str[(opcode<0x14)?opcode:0x14]);
539387b6603SIvan Krasin 			break;
540387b6603SIvan Krasin 		default:
541387b6603SIvan Krasin 			break;
542387b6603SIvan Krasin 		}
543e25b118aSDominic Spill 	} else {
5448f3e7eeaSChristopher Kilgour 		printf("Advertising / AA %08x (%s)/ %2d bytes\n", pkt->access_address,
5458f3e7eeaSChristopher Kilgour 		       pkt->flags.as_bits.access_address_ok ? "valid" : "invalid",
5468f3e7eeaSChristopher Kilgour 		       pkt->length);
5478f3e7eeaSChristopher Kilgour 		printf("    Channel Index: %d\n", pkt->channel_idx);
5488f3e7eeaSChristopher Kilgour 		printf("    Type:  %s\n", lell_get_adv_type_str(pkt));
549e25b118aSDominic Spill 
5508f3e7eeaSChristopher Kilgour 		switch(pkt->adv_type) {
551e25b118aSDominic Spill 			case ADV_IND:
5528f3e7eeaSChristopher Kilgour 				_dump_addr("AdvA:  ", pkt->symbols, 6, pkt->adv_tx_add);
5538f3e7eeaSChristopher Kilgour 				if (pkt->length-6 > 0) {
554e25b118aSDominic Spill 					printf("    AdvData:");
5558f3e7eeaSChristopher Kilgour 					for (i = 0; i < pkt->length - 6; ++i)
5568f3e7eeaSChristopher Kilgour 						printf(" %02x", pkt->symbols[12+i]);
557e25b118aSDominic Spill 					printf("\n");
5588f3e7eeaSChristopher Kilgour 					_dump_scan_rsp_data(&pkt->symbols[12], pkt->length-6);
559e25b118aSDominic Spill 				}
560e25b118aSDominic Spill 				break;
561e25b118aSDominic Spill 			case SCAN_REQ:
5628f3e7eeaSChristopher Kilgour 				_dump_addr("ScanA: ", pkt->symbols, 6, pkt->adv_tx_add);
5638f3e7eeaSChristopher Kilgour 				_dump_addr("AdvA:  ", pkt->symbols, 12, pkt->adv_rx_add);
564e25b118aSDominic Spill 				break;
565e25b118aSDominic Spill 			case SCAN_RSP:
5668f3e7eeaSChristopher Kilgour 				_dump_addr("AdvA:  ", pkt->symbols, 6, pkt->adv_tx_add);
567e25b118aSDominic Spill 				printf("    ScanRspData:");
5688f3e7eeaSChristopher Kilgour 				for (i = 0; i < pkt->length - 6; ++i)
5698f3e7eeaSChristopher Kilgour 					printf(" %02x", pkt->symbols[12+i]);
570e25b118aSDominic Spill 				printf("\n");
5718f3e7eeaSChristopher Kilgour 				_dump_scan_rsp_data(&pkt->symbols[12], pkt->length-6);
572e25b118aSDominic Spill 				break;
573e25b118aSDominic Spill 			case CONNECT_REQ:
5748f3e7eeaSChristopher Kilgour 				_dump_addr("InitA: ", pkt->symbols, 6, pkt->adv_tx_add);
5758f3e7eeaSChristopher Kilgour 				_dump_addr("AdvA:  ", pkt->symbols, 12, pkt->adv_rx_add);
5768f3e7eeaSChristopher Kilgour 				_dump_32("AA:    ", pkt->symbols, 18);
5778f3e7eeaSChristopher Kilgour 				_dump_24("CRCInit: ", pkt->symbols, 22);
5788f3e7eeaSChristopher Kilgour 				_dump_8("WinSize: ", pkt->symbols, 25);
5798f3e7eeaSChristopher Kilgour 				_dump_16("WinOffset: ", pkt->symbols, 26);
5808f3e7eeaSChristopher Kilgour 				_dump_16("Interval: ", pkt->symbols, 28);
5818f3e7eeaSChristopher Kilgour 				_dump_16("Latency: ", pkt->symbols, 30);
5828f3e7eeaSChristopher Kilgour 				_dump_16("Timeout: ", pkt->symbols, 32);
583e25b118aSDominic Spill 
584e25b118aSDominic Spill 				printf("    ChM:");
585e25b118aSDominic Spill 				for (i = 0; i < 5; ++i)
5868f3e7eeaSChristopher Kilgour 					printf(" %02x", pkt->symbols[34+i]);
587e25b118aSDominic Spill 				printf("\n");
588e25b118aSDominic Spill 
5898f3e7eeaSChristopher Kilgour 				printf("    Hop: %d\n", pkt->symbols[39] & 0x1f);
590e25b118aSDominic Spill 				printf("    SCA: %d, %s\n",
5918f3e7eeaSChristopher Kilgour 						pkt->symbols[39] >> 5,
5928f3e7eeaSChristopher Kilgour 						CONNECT_SCA[pkt->symbols[39] >> 5]);
593e25b118aSDominic Spill 				break;
594e25b118aSDominic Spill 		}
595e25b118aSDominic Spill 	}
596e25b118aSDominic Spill 
597e25b118aSDominic Spill 	printf("\n");
598e25b118aSDominic Spill 	printf("    Data: ");
5998f3e7eeaSChristopher Kilgour 	for (i = 6; i < 6 + pkt->length; ++i)
6008f3e7eeaSChristopher Kilgour 		printf(" %02x", pkt->symbols[i]);
601e25b118aSDominic Spill 	printf("\n");
602e25b118aSDominic Spill 
603e25b118aSDominic Spill 	printf("    CRC:  ");
604e25b118aSDominic Spill 	for (i = 0; i < 3; ++i)
6058f3e7eeaSChristopher Kilgour 		printf(" %02x", pkt->symbols[6 + pkt->length + i]);
606e25b118aSDominic Spill 	printf("\n");
607e25b118aSDominic Spill }
608