xref: /libbtbb/lib/src/bluetooth_le_packet.c (revision b3394904d7a1c3b073d25876bb23be838d1323d5)
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 
28e25b118aSDominic Spill #include <string.h>
29e25b118aSDominic Spill #include "bluetooth_le_packet.h"
30e25b118aSDominic Spill #include <ctype.h>
31e25b118aSDominic Spill 
32e25b118aSDominic Spill /* string representations of advertising packet type */
33e25b118aSDominic Spill static const char *ADV_TYPE_NAMES[] = {
34e25b118aSDominic Spill 	"ADV_IND", "ADV_DIRECT_IND", "ADV_NONCONN_IND", "SCAN_REQ",
35e25b118aSDominic Spill 	"SCAN_RSP", "CONNECT_REQ", "ADV_SCAN_IND",
36e25b118aSDominic Spill };
37e25b118aSDominic Spill 
38e25b118aSDominic Spill /* source clock accuracy in a connect packet */
39e25b118aSDominic Spill static const char *CONNECT_SCA[] = {
40e25b118aSDominic Spill 	"251 ppm to 500 ppm", "151 ppm to 250 ppm", "101 ppm to 150 ppm",
41e25b118aSDominic Spill 	"76 ppm to 100 ppm", "51 ppm to 75 ppm", "31 ppm to 50 ppm",
42e25b118aSDominic Spill 	"21 ppm to 30 ppm", "0 ppm to 20 ppm",
43e25b118aSDominic Spill };
44e25b118aSDominic Spill 
45e25b118aSDominic Spill // count of objects in an array, shamelessly stolen from Chrome
46e25b118aSDominic Spill #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
47e25b118aSDominic Spill 
48e25b118aSDominic Spill void decode_le(uint8_t *stream, uint16_t phys_channel, uint32_t clk100ns, le_packet_t *p) {
49e25b118aSDominic Spill 	memcpy(p->symbols, stream, MAX_LE_SYMBOLS);
50e25b118aSDominic Spill 
51e25b118aSDominic Spill 	p->channel_idx = le_channel_index(phys_channel);
52e25b118aSDominic Spill 	p->clk100ns = clk100ns;
53e25b118aSDominic Spill 
54e25b118aSDominic Spill 	p->access_address = 0;
55e25b118aSDominic Spill 	p->access_address |= p->symbols[0];
56e25b118aSDominic Spill 	p->access_address |= p->symbols[1] << 8;
57e25b118aSDominic Spill 	p->access_address |= p->symbols[2] << 16;
58e25b118aSDominic Spill 	p->access_address |= p->symbols[3] << 24;
59e25b118aSDominic Spill 
60e25b118aSDominic Spill 	if (le_packet_is_data(p)) {
61e25b118aSDominic Spill 		// data PDU
62e25b118aSDominic Spill 		p->length = p->symbols[5] & 0x1f;
63e25b118aSDominic Spill 	} else {
64e25b118aSDominic Spill 		// advertising PDU
65e25b118aSDominic Spill 		p->length = p->symbols[5] & 0x3f;
66e25b118aSDominic Spill 		p->adv_type = p->symbols[4] & 0xf;
67e25b118aSDominic Spill 		p->adv_tx_add = p->symbols[4] & 0x40 ? 1 : 0;
68e25b118aSDominic Spill 		p->adv_rx_add = p->symbols[4] & 0x80 ? 1 : 0;
69e25b118aSDominic Spill 	}
70e25b118aSDominic Spill }
71e25b118aSDominic Spill 
72e25b118aSDominic Spill int le_packet_is_data(le_packet_t *p) {
73e25b118aSDominic Spill 	return p->channel_idx < 37;
74e25b118aSDominic Spill }
75e25b118aSDominic Spill 
76e25b118aSDominic Spill uint8_t le_channel_index(uint16_t phys_channel) {
77e25b118aSDominic Spill 	uint8_t ret;
78e25b118aSDominic Spill 	if (phys_channel == 2402) {
79e25b118aSDominic Spill 		ret = 37;
80e25b118aSDominic Spill 	} else if (phys_channel < 2426) { // 0 - 10
81e25b118aSDominic Spill 		ret = (phys_channel - 2404) / 2;
82e25b118aSDominic Spill 	} else if (phys_channel == 2426) {
83e25b118aSDominic Spill 		ret = 38;
84e25b118aSDominic Spill 	} else if (phys_channel < 2480) { // 11 - 36
85e25b118aSDominic Spill 		ret = 11 + (phys_channel - 2428) / 2;
86e25b118aSDominic Spill 	} else {
87e25b118aSDominic Spill 		ret = 39;
88e25b118aSDominic Spill 	}
89e25b118aSDominic Spill 	return ret;
90e25b118aSDominic Spill }
91e25b118aSDominic Spill 
92e25b118aSDominic Spill const char *le_adv_type(le_packet_t *p) {
93e25b118aSDominic Spill 	if (le_packet_is_data(p))
94e25b118aSDominic Spill 		return NULL;
95e25b118aSDominic Spill 	if (p->adv_type < COUNT_OF(ADV_TYPE_NAMES))
96e25b118aSDominic Spill 		return ADV_TYPE_NAMES[p->adv_type];
97e25b118aSDominic Spill 	return "UNKNOWN";
98e25b118aSDominic Spill }
99e25b118aSDominic Spill 
100e25b118aSDominic Spill static void _dump_addr(char *name, uint8_t *buf, int offset, int random) {
101e25b118aSDominic Spill 	int i;
102e25b118aSDominic Spill 	printf("    %s%02x", name, buf[offset+5]);
103e25b118aSDominic Spill 	for (i = 4; i >= 0; --i)
104e25b118aSDominic Spill 		printf(":%02x", buf[offset+i]);
105e25b118aSDominic Spill 	printf(" (%s)\n", random ? "random" : "public");
106e25b118aSDominic Spill }
107e25b118aSDominic Spill 
108e25b118aSDominic Spill static void _dump_8(char *name, uint8_t *buf, int offset) {
109e25b118aSDominic Spill 	printf("    %s%02x (%d)\n", name, buf[offset], buf[offset]);
110e25b118aSDominic Spill }
111e25b118aSDominic Spill 
112e25b118aSDominic Spill static void _dump_16(char *name, uint8_t *buf, int offset) {
113e25b118aSDominic Spill 	uint16_t val = buf[offset+1] << 8 | buf[offset];
114e25b118aSDominic Spill 	printf("    %s%04x (%d)\n", name, val, val);
115e25b118aSDominic Spill }
116e25b118aSDominic Spill 
117e25b118aSDominic Spill static void _dump_24(char *name, uint8_t *buf, int offset) {
118e25b118aSDominic Spill 	uint16_t val = buf[offset+2] << 16 | buf[offset+1] << 8 | buf[offset];
119e25b118aSDominic Spill 	printf("    %s%06x\n", name, val);
120e25b118aSDominic Spill }
121e25b118aSDominic Spill 
122e25b118aSDominic Spill static void _dump_32(char *name, uint8_t *buf, int offset) {
123e25b118aSDominic Spill 	uint32_t val = buf[offset+3] << 24 |
124e25b118aSDominic Spill 				   buf[offset+2] << 16 |
125e25b118aSDominic Spill 				   buf[offset+1] << 8 |
126e25b118aSDominic Spill 				   buf[offset+0];
127e25b118aSDominic Spill 	printf("    %s%08x\n", name, val);
128e25b118aSDominic Spill }
129e25b118aSDominic Spill 
130e25b118aSDominic Spill static void _dump_uuid(uint8_t *uuid) {
131e25b118aSDominic Spill 	int i;
132e25b118aSDominic Spill 	for (i = 0; i < 4; ++i)
133e25b118aSDominic Spill 		printf("%02x", uuid[i]);
134e25b118aSDominic Spill 	printf("-");
135e25b118aSDominic Spill 	for (i = 4; i < 6; ++i)
136e25b118aSDominic Spill 		printf("%02x", uuid[i]);
137e25b118aSDominic Spill 	printf("-");
138e25b118aSDominic Spill 	for (i = 6; i < 8; ++i)
139e25b118aSDominic Spill 		printf("%02x", uuid[i]);
140e25b118aSDominic Spill 	printf("-");
141e25b118aSDominic Spill 	for (i = 8; i < 10; ++i)
142e25b118aSDominic Spill 		printf("%02x", uuid[i]);
143e25b118aSDominic Spill 	printf("-");
144e25b118aSDominic Spill 	for (i = 10; i < 16; ++i)
145e25b118aSDominic Spill 		printf("%02x", uuid[i]);
146e25b118aSDominic Spill }
147e25b118aSDominic Spill 
148e25b118aSDominic Spill // Refer to pg 1735 of Bluetooth Core Spec 4.0
149e25b118aSDominic Spill static void _dump_scan_rsp_data(uint8_t *buf, int len) {
150e25b118aSDominic Spill 	int pos = 0;
151e25b118aSDominic Spill 	int sublen, i;
152e25b118aSDominic Spill 	uint8_t type;
153e25b118aSDominic Spill 	uint16_t val;
154e25b118aSDominic Spill 	char *cval;
155e25b118aSDominic Spill 
156e25b118aSDominic Spill 	while (pos < len) {
157e25b118aSDominic Spill 		sublen = buf[pos];
158e25b118aSDominic Spill 		++pos;
159e25b118aSDominic Spill 		if (pos + sublen > len) {
160e25b118aSDominic Spill 			printf("Error: attempt to read past end of buffer (%d + %d > %d)\n", pos, sublen, len);
161e25b118aSDominic Spill 			return;
162e25b118aSDominic Spill 		}
163e25b118aSDominic Spill 		if (sublen == 0) {
164e25b118aSDominic Spill 			printf("Early return due to 0 length\n");
165e25b118aSDominic Spill 			return;
166e25b118aSDominic Spill 		}
167e25b118aSDominic Spill 		type = buf[pos];
168e25b118aSDominic Spill 		printf("        Type %02x", type);
169e25b118aSDominic Spill 		switch (type) {
170e25b118aSDominic Spill 			case 0x01:
171e25b118aSDominic Spill 				printf(" (Flags)\n");
172e25b118aSDominic Spill 				printf("           ");
173e25b118aSDominic Spill 				for (i = 0; i < 8; ++i)
174e25b118aSDominic Spill 					printf("%d", buf[pos+1] & (1 << (7-i)) ? 1 : 0);
175e25b118aSDominic Spill 				printf("\n");
176e25b118aSDominic Spill 				break;
17745000095SMike Ryan 			case 0x06:
17845000095SMike Ryan 				printf(" (128-bit Service UUIDs, more available)\n");
17945000095SMike Ryan 				goto print128;
180e25b118aSDominic Spill 			case 0x07:
181e25b118aSDominic Spill 				printf(" (128-bit Service UUIDs)\n");
18245000095SMike Ryan print128:
183e25b118aSDominic Spill 				if ((sublen - 1) % 16 == 0) {
184e25b118aSDominic Spill 					uint8_t uuid[16];
185e25b118aSDominic Spill 					for (i = 0; i < sublen - 1; ++i) {
186e25b118aSDominic Spill 						uuid[15 - (i % 16)] = buf[pos+1+i];
187e25b118aSDominic Spill 						if ((i & 15) == 15) {
188e25b118aSDominic Spill 							printf("           ");
189e25b118aSDominic Spill 							_dump_uuid(uuid);
190e25b118aSDominic Spill 							printf("\n");
191e25b118aSDominic Spill 						}
192e25b118aSDominic Spill 					}
193e25b118aSDominic Spill 				}
194e25b118aSDominic Spill 				else {
195e25b118aSDominic Spill 					printf("Wrong length (%d, must be divisible by 16)\n", sublen-1);
196e25b118aSDominic Spill 				}
197e25b118aSDominic Spill 				break;
198e25b118aSDominic Spill 			case 0x09:
199e25b118aSDominic Spill 				printf(" (Complete Local Name)\n");
200e25b118aSDominic Spill 				printf("           ");
201e25b118aSDominic Spill 				for (i = 1; i < sublen; ++i)
202e25b118aSDominic Spill 					printf("%c", isprint(buf[pos+i]) ? buf[pos+i] : '.');
203e25b118aSDominic Spill 				printf("\n");
204e25b118aSDominic Spill 				break;
205e25b118aSDominic Spill 			case 0x0a:
206e25b118aSDominic Spill 				printf(" (Tx Power Level)\n");
207e25b118aSDominic Spill 				printf("           ");
208e25b118aSDominic Spill 				if (sublen-1 == 1) {
209e25b118aSDominic Spill 					cval = (char *)&buf[pos+1];
210e25b118aSDominic Spill 					printf("%d dBm\n", *cval);
211e25b118aSDominic Spill 				} else {
212e25b118aSDominic Spill 					printf("Wrong length (%d, should be 1)\n", sublen-1);
213e25b118aSDominic Spill 				}
214e25b118aSDominic Spill 				break;
215e25b118aSDominic Spill 			case 0x12:
216e25b118aSDominic Spill 				printf(" (Slave Connection Interval Range)\n");
217e25b118aSDominic Spill 				printf("           ");
218e25b118aSDominic Spill 				if (sublen-1 == 4) {
219e25b118aSDominic Spill 					val = (buf[pos+2] << 8) | buf[pos+1];
220e25b118aSDominic Spill 					printf("(%0.2f, ", val * 1.25);
221e25b118aSDominic Spill 					val = (buf[pos+4] << 8) | buf[pos+3];
222e25b118aSDominic Spill 					printf("%0.2f) ms\n", val * 1.25);
223e25b118aSDominic Spill 				}
224e25b118aSDominic Spill 				else {
225e25b118aSDominic Spill 					printf("Wrong length (%d, should be 4)\n", sublen-1);
226e25b118aSDominic Spill 				}
227e25b118aSDominic Spill 				break;
228e25b118aSDominic Spill 			case 0x16:
229e25b118aSDominic Spill 				printf(" (Service Data)\n");
230e25b118aSDominic Spill 				printf("           ");
231e25b118aSDominic Spill 				if (sublen-1 >= 2) {
232e25b118aSDominic Spill 					val = (buf[pos+2] << 8) | buf[pos+1];
233e25b118aSDominic Spill 					printf("UUID: %02x", val);
234e25b118aSDominic Spill 					if (sublen-1 > 2) {
235e25b118aSDominic Spill 						printf(", Additional:");
236e25b118aSDominic Spill 						for (i = 3; i < sublen; ++i)
237e25b118aSDominic Spill 							printf(" %02x", buf[pos+i]);
238e25b118aSDominic Spill 					}
239e25b118aSDominic Spill 					printf("\n");
240e25b118aSDominic Spill 				}
241e25b118aSDominic Spill 				else {
242e25b118aSDominic Spill 					printf("Wrong length (%d, should be >= 2)\n", sublen-1);
243e25b118aSDominic Spill 				}
244e25b118aSDominic Spill 				break;
245e25b118aSDominic Spill 			default:
246e25b118aSDominic Spill 				printf("\n");
247e25b118aSDominic Spill 				printf("           ");
248e25b118aSDominic Spill 				for (i = 1; i < sublen; ++i)
249e25b118aSDominic Spill 					printf(" %02x", buf[pos+i]);
250e25b118aSDominic Spill 				printf("\n");
251e25b118aSDominic Spill 		}
252e25b118aSDominic Spill 		pos += sublen;
253e25b118aSDominic Spill 	}
254e25b118aSDominic Spill }
255e25b118aSDominic Spill 
256e25b118aSDominic Spill void le_print(le_packet_t *p) {
257387b6603SIvan Krasin 	int i, opcode;
258e25b118aSDominic Spill 	if (le_packet_is_data(p)) {
259e25b118aSDominic Spill 		int llid = p->symbols[4] & 0x3;
260e25b118aSDominic Spill 		static const char *llid_str[] = {
261e25b118aSDominic Spill 			"Reserved",
262e25b118aSDominic Spill 			"LL Data PDU / empty or L2CAP continuation",
263e25b118aSDominic Spill 			"LL Data PDU / L2CAP start",
264e25b118aSDominic Spill 			"LL Control PDU",
265e25b118aSDominic Spill 		};
266e25b118aSDominic Spill 
267e25b118aSDominic Spill 		printf("Data / AA %08x / %2d bytes\n", p->access_address, p->length);
268e25b118aSDominic Spill 		printf("    Channel Index: %d\n", p->channel_idx);
269e25b118aSDominic Spill 		printf("    LLID: %d / %s\n", llid, llid_str[llid]);
270e25b118aSDominic Spill 		printf("    NESN: %d  SN: %d  MD: %d\n", (p->symbols[4] >> 2) & 1,
271e25b118aSDominic Spill 												 (p->symbols[4] >> 3) & 1,
272e25b118aSDominic Spill 												 (p->symbols[4] >> 4) & 1);
273387b6603SIvan Krasin 		switch (llid) {
274387b6603SIvan Krasin 		case 3: // LL Control PDU
275387b6603SIvan Krasin 			opcode = p->symbols[6];
276387b6603SIvan Krasin 			static const char *opcode_str[] = {
277387b6603SIvan Krasin 				"LL_CONNECTION_UPDATE_REQ",
278387b6603SIvan Krasin 				"LL_CHANNEL_MAP_REQ",
279387b6603SIvan Krasin 				"LL_TERMINATE_IND",
280387b6603SIvan Krasin 				"LL_ENC_REQ",
281387b6603SIvan Krasin 				"LL_ENC_RSP",
282387b6603SIvan Krasin 				"LL_START_ENC_REQ",
283387b6603SIvan Krasin 				"LL_START_ENC_RSP",
284387b6603SIvan Krasin 				"LL_UNKNOWN_RSP",
285387b6603SIvan Krasin 				"LL_FEATURE_REQ",
286387b6603SIvan Krasin 				"LL_FEATURE_RSP",
287387b6603SIvan Krasin 				"LL_PAUSE_ENC_REQ",
288387b6603SIvan Krasin 				"LL_PAUSE_ENC_RSP",
289387b6603SIvan Krasin 				"LL_VERSION_IND",
290387b6603SIvan Krasin 				"LL_REJECT_IND",
291*b3394904SMike Ryan 				"LL_SLAVE_FEATURE_REQ",
292*b3394904SMike Ryan 				"LL_CONNECTION_PARAM_REQ",
293*b3394904SMike Ryan 				"LL_CONNECTION_PARAM_RSP",
294*b3394904SMike Ryan 				"LL_REJECT_IND_EXT",
295*b3394904SMike Ryan 				"LL_PING_REQ",
296*b3394904SMike Ryan 				"LL_PING_RSP",
297387b6603SIvan Krasin 				"Reserved for Future Use",
298387b6603SIvan Krasin 			};
299387b6603SIvan Krasin 			printf("    Opcode: %d / %s\n", opcode, opcode_str[(opcode<0x0E)?opcode:0x0E]);
300387b6603SIvan Krasin 			break;
301387b6603SIvan Krasin 		default:
302387b6603SIvan Krasin 			break;
303387b6603SIvan Krasin 		}
304e25b118aSDominic Spill 	} else {
305e25b118aSDominic Spill 		printf("Advertising / AA %08x / %2d bytes\n", p->access_address, p->length);
306e25b118aSDominic Spill 		printf("    Channel Index: %d\n", p->channel_idx);
307e25b118aSDominic Spill 		printf("    Type:  %s\n", le_adv_type(p));
308e25b118aSDominic Spill 
309e25b118aSDominic Spill 		switch(p->adv_type) {
310e25b118aSDominic Spill 			case ADV_IND:
311e25b118aSDominic Spill 				_dump_addr("AdvA:  ", p->symbols, 6, p->adv_tx_add);
312e25b118aSDominic Spill 				if (p->length-6 > 0) {
313e25b118aSDominic Spill 					printf("    AdvData:");
314e25b118aSDominic Spill 					for (i = 0; i < p->length - 6; ++i)
315e25b118aSDominic Spill 						printf(" %02x", p->symbols[12+i]);
316e25b118aSDominic Spill 					printf("\n");
317e25b118aSDominic Spill 					_dump_scan_rsp_data(&p->symbols[12], p->length-6);
318e25b118aSDominic Spill 				}
319e25b118aSDominic Spill 				break;
320e25b118aSDominic Spill 			case SCAN_REQ:
321e25b118aSDominic Spill 				_dump_addr("ScanA: ", p->symbols, 6, p->adv_tx_add);
322e25b118aSDominic Spill 				_dump_addr("AdvA:  ", p->symbols, 12, p->adv_rx_add);
323e25b118aSDominic Spill 				break;
324e25b118aSDominic Spill 			case SCAN_RSP:
325e25b118aSDominic Spill 				_dump_addr("AdvA:  ", p->symbols, 6, p->adv_tx_add);
326e25b118aSDominic Spill 				printf("    ScanRspData:");
327e25b118aSDominic Spill 				for (i = 0; i < p->length - 6; ++i)
328e25b118aSDominic Spill 					printf(" %02x", p->symbols[12+i]);
329e25b118aSDominic Spill 				printf("\n");
330e25b118aSDominic Spill 				_dump_scan_rsp_data(&p->symbols[12], p->length-6);
331e25b118aSDominic Spill 				break;
332e25b118aSDominic Spill 			case CONNECT_REQ:
333e25b118aSDominic Spill 				_dump_addr("InitA: ", p->symbols, 6, p->adv_tx_add);
334e25b118aSDominic Spill 				_dump_addr("AdvA:  ", p->symbols, 12, p->adv_rx_add);
335e25b118aSDominic Spill 				_dump_32("AA:    ", p->symbols, 18);
336e25b118aSDominic Spill 				_dump_24("CRCInit: ", p->symbols, 22);
337e25b118aSDominic Spill 				_dump_8("WinSize: ", p->symbols, 25);
338e25b118aSDominic Spill 				_dump_16("WinOffset: ", p->symbols, 26);
339e25b118aSDominic Spill 				_dump_16("Interval: ", p->symbols, 28);
340e25b118aSDominic Spill 				_dump_16("Latency: ", p->symbols, 30);
341e25b118aSDominic Spill 				_dump_16("Timeout: ", p->symbols, 32);
342e25b118aSDominic Spill 
343e25b118aSDominic Spill 				printf("    ChM:");
344e25b118aSDominic Spill 				for (i = 0; i < 5; ++i)
345e25b118aSDominic Spill 					printf(" %02x", p->symbols[34+i]);
346e25b118aSDominic Spill 				printf("\n");
347e25b118aSDominic Spill 
3485d00a97fSIvan Krasin 				printf("    Hop: %d\n", p->symbols[39] & 0x1f);
349e25b118aSDominic Spill 				printf("    SCA: %d, %s\n",
3505d00a97fSIvan Krasin 						p->symbols[39] >> 5,
3515d00a97fSIvan Krasin 						CONNECT_SCA[p->symbols[39] >> 5]);
352e25b118aSDominic Spill 				break;
353e25b118aSDominic Spill 		}
354e25b118aSDominic Spill 	}
355e25b118aSDominic Spill 
356e25b118aSDominic Spill 	printf("\n");
357e25b118aSDominic Spill 	printf("    Data: ");
358e25b118aSDominic Spill 	for (i = 6; i < 6 + p->length; ++i)
359e25b118aSDominic Spill 		printf(" %02x", p->symbols[i]);
360e25b118aSDominic Spill 	printf("\n");
361e25b118aSDominic Spill 
362e25b118aSDominic Spill 	printf("    CRC:  ");
363e25b118aSDominic Spill 	for (i = 0; i < 3; ++i)
364e25b118aSDominic Spill 		printf(" %02x", p->symbols[6 + p->length + i]);
365e25b118aSDominic Spill 	printf("\n");
366e25b118aSDominic Spill }
367