Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ 1/2] shared: harden btsnoop trace parsing
@ 2026-06-20 19:22 Geraldo Netto
  2026-06-20 19:22 ` [PATCH BlueZ 2/2] unit: split btsnoop pklg tests Geraldo Netto
  2026-06-20 21:09 ` [BlueZ,1/2] shared: harden btsnoop trace parsing bluez.test.bot
  0 siblings, 2 replies; 3+ messages in thread
From: Geraldo Netto @ 2026-06-20 19:22 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Geraldo Netto

---
 Makefile.am          |   6 +
 monitor/analyze.c    |   4 +-
 monitor/control.c    |   2 +-
 src/shared/btsnoop.c | 303 +++++++++++------
 src/shared/btsnoop.h |   6 +-
 unit/test-btsnoop.c  | 794 +++++++++++++++++++++++++++++++++++++++++++
 unit/test-btsnoop.h  |   3 +
 7 files changed, 1009 insertions(+), 109 deletions(-)
 create mode 100644 unit/test-btsnoop.c
 create mode 100644 unit/test-btsnoop.h

diff --git a/Makefile.am b/Makefile.am
index 76c4ab5d4..4887934a9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -587,6 +587,12 @@ unit_tests += unit/test-textfile
 unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c
 unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS)
 
+unit_tests += unit/test-btsnoop
+
+unit_test_btsnoop_SOURCES = unit/test-btsnoop.c \
+				src/shared/btsnoop.h src/shared/btsnoop.c
+unit_test_btsnoop_LDADD = src/libshared-glib.la $(GLIB_LIBS)
+
 unit_tests += unit/test-crc
 
 unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c
diff --git a/monitor/analyze.c b/monitor/analyze.c
index de9c23603..c9400ceb3 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -1404,8 +1404,8 @@ void analyze_trace(const char *path)
 		struct timeval tv;
 		uint16_t index, opcode, pktlen;
 
-		if (!btsnoop_read_hci(btsnoop_file, &tv, &index, &opcode,
-								buf, &pktlen))
+		if (!btsnoop_read_hci(btsnoop_file, &tv, &index,
+					&opcode, buf, sizeof(buf), &pktlen))
 			break;
 
 		switch (opcode) {
diff --git a/monitor/control.c b/monitor/control.c
index 83347d5db..975e1d117 100644
--- a/monitor/control.c
+++ b/monitor/control.c
@@ -1564,7 +1564,7 @@ void control_reader(const char *path, bool pager)
 			uint16_t index, opcode;
 
 			if (!btsnoop_read_hci(btsnoop_file, &tv, &index,
-							&opcode, buf, &pktlen))
+					&opcode, buf, sizeof(buf), &pktlen))
 				break;
 
 			if (opcode == 0xffff)
diff --git a/src/shared/btsnoop.c b/src/shared/btsnoop.c
index 9ce2f2655..39960c4b8 100644
--- a/src/shared/btsnoop.c
+++ b/src/shared/btsnoop.c
@@ -13,6 +13,7 @@
 #endif
 
 #define _GNU_SOURCE
+#include <errno.h>
 #include <endian.h>
 #include <fcntl.h>
 #include <unistd.h>
@@ -47,12 +48,16 @@ static const uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e,
 
 static const uint32_t btsnoop_version = 1;
 
+#define BTSNOOP_EPOCH_OFFSET 0x00E03AB44A676000ull
+#define BTSNOOP_UNIX_TIME_OFFSET 946684800ll
+
 struct pklg_pkt {
 	uint32_t	len;
 	uint64_t	ts;
 	uint8_t		type;
 } __attribute__ ((packed));
 #define PKLG_PKT_SIZE (sizeof(struct pklg_pkt))
+#define PKLG_PAYLOAD_OFFSET (PKLG_PKT_SIZE - sizeof(uint32_t))
 
 struct btsnoop {
 	int ref_count;
@@ -271,13 +276,14 @@ bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv,
 		if (!btsnoop_rotate(btsnoop))
 			return false;
 
-	ts = (tv->tv_sec - 946684800ll) * 1000000ll + tv->tv_usec;
+	ts = (tv->tv_sec - BTSNOOP_UNIX_TIME_OFFSET) * 1000000ll +
+								tv->tv_usec;
 
 	pkt.size  = htobe32(size);
 	pkt.len   = htobe32(size);
 	pkt.flags = htobe32(flags);
 	pkt.drops = htobe32(drops);
-	pkt.ts    = htobe64(ts + 0x00E03AB44A676000ll);
+	pkt.ts    = htobe64(ts + BTSNOOP_EPOCH_OFFSET);
 
 	written = write(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
 	if (written < 0)
@@ -324,6 +330,121 @@ static uint32_t get_flags_from_opcode(uint16_t opcode)
 	return 0xff;
 }
 
+static ssize_t read_exact(int fd, void *data, size_t size)
+{
+	uint8_t *ptr = data;
+	size_t offset = 0;
+
+	while (offset < size) {
+		ssize_t len;
+
+		len = read(fd, ptr + offset, size - offset);
+		if (len < 0) {
+			if (errno == EINTR)
+				continue;
+			return -1;
+		}
+
+		if (len == 0)
+			break;
+
+		offset += len;
+	}
+
+	return offset;
+}
+
+static bool read_packet_data(struct btsnoop *btsnoop, void *data,
+				uint16_t data_size, uint32_t toread,
+				uint16_t *size)
+{
+	ssize_t len;
+
+	if (!size || (!data && toread)) {
+		btsnoop->aborted = true;
+		return false;
+	}
+
+	if (toread > data_size) {
+		btsnoop->aborted = true;
+		return false;
+	}
+
+	len = read_exact(btsnoop->fd, data, toread);
+	if (len != (ssize_t) toread) {
+		btsnoop->aborted = true;
+		return false;
+	}
+
+	*size = toread;
+
+	return true;
+}
+
+static bool decode_btsnoop_timestamp(uint64_t raw_ts, struct timeval *tv)
+{
+	uint64_t ts;
+
+	if (raw_ts < BTSNOOP_EPOCH_OFFSET)
+		return false;
+
+	ts = raw_ts - BTSNOOP_EPOCH_OFFSET;
+	tv->tv_sec = (ts / 1000000ll) + BTSNOOP_UNIX_TIME_OFFSET;
+	tv->tv_usec = ts % 1000000ll;
+
+	return true;
+}
+
+static void get_pklg_opcode(uint8_t type, uint16_t *index, uint16_t *opcode)
+{
+	switch (type) {
+	case 0x00:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_COMMAND_PKT;
+		break;
+	case 0x01:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_EVENT_PKT;
+		break;
+	case 0x02:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_ACL_TX_PKT;
+		break;
+	case 0x03:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_ACL_RX_PKT;
+		break;
+	case 0x08:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_SCO_TX_PKT;
+		break;
+	case 0x09:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_SCO_RX_PKT;
+		break;
+	case 0x12:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_ISO_TX_PKT;
+		break;
+	case 0x13:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_ISO_RX_PKT;
+		break;
+	case 0x0b:
+		*index = 0x0000;
+		*opcode = BTSNOOP_OPCODE_VENDOR_DIAG;
+		break;
+	case 0xfc:
+		*index = 0xffff;
+		*opcode = BTSNOOP_OPCODE_SYSTEM_NOTE;
+		break;
+	default:
+		*index = 0xffff;
+		*opcode = 0xffff;
+		break;
+	}
+}
+
 bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv,
 			uint16_t index, uint16_t opcode, uint32_t drops,
 			const void *data, uint16_t size)
@@ -377,99 +498,53 @@ bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv,
 	return btsnoop_write(btsnoop, tv, flags, 0, data, size);
 }
 
-static bool pklg_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
-					uint16_t *index, uint16_t *opcode,
-					void *data, uint16_t *size)
+static bool pklg_read_hci(struct btsnoop *btsnoop,
+			struct timeval *tv, uint16_t *index, uint16_t *opcode,
+			void *data, uint16_t data_size, uint16_t *size)
 {
 	struct pklg_pkt pkt;
+	uint32_t pkt_len;
 	uint32_t toread;
 	uint64_t ts;
 	ssize_t len;
 
-	len = read(btsnoop->fd, &pkt, PKLG_PKT_SIZE);
+	len = read_exact(btsnoop->fd, &pkt, PKLG_PKT_SIZE);
 	if (len == 0)
 		return false;
 
-	if (len < 0 || len != PKLG_PKT_SIZE) {
+	if (len != PKLG_PKT_SIZE) {
 		btsnoop->aborted = true;
 		return false;
 	}
 
 	if (btsnoop->pklg_v2) {
-		toread = le32toh(pkt.len) - (PKLG_PKT_SIZE - 4);
+		pkt_len = le32toh(pkt.len);
 
 		ts = le64toh(pkt.ts);
 		tv->tv_sec = ts & 0xffffffff;
 		tv->tv_usec = ts >> 32;
 	} else {
-		toread = be32toh(pkt.len) - (PKLG_PKT_SIZE - 4);
+		pkt_len = be32toh(pkt.len);
 
 		ts = be64toh(pkt.ts);
 		tv->tv_sec = ts >> 32;
 		tv->tv_usec = ts & 0xffffffff;
 	}
 
-	if (toread > BTSNOOP_MAX_PACKET_SIZE) {
-                btsnoop->aborted = true;
-                return false;
-        }
-
-	switch (pkt.type) {
-	case 0x00:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_COMMAND_PKT;
-		break;
-	case 0x01:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_EVENT_PKT;
-		break;
-	case 0x02:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_ACL_TX_PKT;
-		break;
-	case 0x03:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_ACL_RX_PKT;
-		break;
-	case 0x08:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_SCO_TX_PKT;
-		break;
-	case 0x09:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_SCO_RX_PKT;
-		break;
-	case 0x12:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_ISO_TX_PKT;
-		break;
-	case 0x13:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_ISO_RX_PKT;
-		break;
-	case 0x0b:
-		*index = 0x0000;
-		*opcode = BTSNOOP_OPCODE_VENDOR_DIAG;
-		break;
-	case 0xfc:
-		*index = 0xffff;
-		*opcode = BTSNOOP_OPCODE_SYSTEM_NOTE;
-		break;
-	default:
-		*index = 0xffff;
-		*opcode = 0xffff;
-		break;
+	if (pkt_len < PKLG_PAYLOAD_OFFSET) {
+		btsnoop->aborted = true;
+		return false;
 	}
 
-	len = read(btsnoop->fd, data, toread);
-	if (len < 0) {
+	toread = pkt_len - PKLG_PAYLOAD_OFFSET;
+	if (toread > BTSNOOP_MAX_PACKET_SIZE) {
 		btsnoop->aborted = true;
 		return false;
 	}
 
-	*size = toread;
+	get_pklg_opcode(pkt.type, index, opcode);
 
-	return true;
+	return read_packet_data(btsnoop, data, data_size, toread, size);
 }
 
 static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags)
@@ -512,84 +587,106 @@ static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags)
 	return 0xffff;
 }
 
-bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
-					uint16_t *index, uint16_t *opcode,
-					void *data, uint16_t *size)
+static bool read_uart_type(struct btsnoop *btsnoop, uint32_t *toread,
+								uint8_t *type)
 {
-	struct btsnoop_pkt pkt;
-	uint32_t toread, flags;
-	uint64_t ts;
-	uint8_t pkt_type;
 	ssize_t len;
 
-	if (!btsnoop || btsnoop->aborted)
-		return false;
-
-	if (btsnoop->pklg_format)
-		return pklg_read_hci(btsnoop, tv, index, opcode, data, size);
-
-	len = read(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
-	if (len == 0)
-		return false;
-
-	if (len < 0 || len != BTSNOOP_PKT_SIZE) {
+	if (!*toread) {
 		btsnoop->aborted = true;
 		return false;
 	}
 
-	toread = be32toh(pkt.len);
-	if (toread > BTSNOOP_MAX_PACKET_SIZE) {
+	len = read_exact(btsnoop->fd, type, 1);
+	if (len != 1) {
 		btsnoop->aborted = true;
 		return false;
 	}
 
-	flags = be32toh(pkt.flags);
+	(*toread)--;
 
-	ts = be64toh(pkt.ts) - 0x00E03AB44A676000ll;
-	tv->tv_sec = (ts / 1000000ll) + 946684800ll;
-	tv->tv_usec = ts % 1000000ll;
+	return true;
+}
+
+static bool decode_btsnoop_record(struct btsnoop *btsnoop, uint32_t flags,
+					uint32_t *toread, uint16_t *index,
+					uint16_t *opcode)
+{
+	uint8_t pkt_type;
 
 	switch (btsnoop->format) {
 	case BTSNOOP_FORMAT_HCI:
 		*index = 0;
 		*opcode = get_opcode_from_flags(0xff, flags);
-		break;
-
+		return true;
 	case BTSNOOP_FORMAT_UART:
-		len = read(btsnoop->fd, &pkt_type, 1);
-		if (len < 0) {
-			btsnoop->aborted = true;
+		if (!read_uart_type(btsnoop, toread, &pkt_type))
 			return false;
-		}
-		toread--;
 
 		*index = 0;
 		*opcode = get_opcode_from_flags(pkt_type, flags);
-		break;
-
+		return true;
 	case BTSNOOP_FORMAT_MONITOR:
 		*index = flags >> 16;
 		*opcode = flags & 0xffff;
-		break;
-
+		return true;
 	default:
 		btsnoop->aborted = true;
 		return false;
 	}
+}
 
-	len = read(btsnoop->fd, data, toread);
-	if (len < 0) {
+bool btsnoop_read_hci(struct btsnoop *btsnoop,
+			struct timeval *tv, uint16_t *index, uint16_t *opcode,
+			void *data, uint16_t data_size, uint16_t *size)
+{
+	struct btsnoop_pkt pkt;
+	uint32_t toread, flags;
+	ssize_t len;
+
+	if (!btsnoop || !tv || !index || !opcode || !size || btsnoop->aborted)
+		return false;
+
+	if (btsnoop->pklg_format)
+		return pklg_read_hci(btsnoop, tv, index, opcode,
+							data, data_size, size);
+
+	len = read_exact(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
+	if (len == 0)
+		return false;
+
+	if (len != BTSNOOP_PKT_SIZE) {
 		btsnoop->aborted = true;
 		return false;
 	}
 
-	*size = toread;
+	toread = be32toh(pkt.len);
+	if (toread > BTSNOOP_MAX_PACKET_SIZE) {
+		btsnoop->aborted = true;
+		return false;
+	}
 
-	return true;
+	flags = be32toh(pkt.flags);
+
+	if (!decode_btsnoop_timestamp(be64toh(pkt.ts), tv)) {
+		btsnoop->aborted = true;
+		return false;
+	}
+
+	if (!decode_btsnoop_record(btsnoop, flags, &toread, index, opcode))
+		return false;
+
+	return read_packet_data(btsnoop, data, data_size, toread, size);
 }
 
 bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv,
-			uint16_t *frequency, void *data, uint16_t *size)
+				uint16_t *frequency, void *data, uint16_t *size)
 {
+	(void) btsnoop;
+	(void) tv;
+	(void) frequency;
+	(void) data;
+	(void) size;
+
 	return false;
 }
diff --git a/src/shared/btsnoop.h b/src/shared/btsnoop.h
index c24755d56..796604c58 100644
--- a/src/shared/btsnoop.h
+++ b/src/shared/btsnoop.h
@@ -106,8 +106,8 @@ bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv,
 bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv,
 			uint16_t frequency, const void *data, uint16_t size);
 
-bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
-					uint16_t *index, uint16_t *opcode,
-					void *data, uint16_t *size);
+bool btsnoop_read_hci(struct btsnoop *btsnoop,
+			struct timeval *tv, uint16_t *index, uint16_t *opcode,
+			void *data, uint16_t data_size, uint16_t *size);
 bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv,
 			uint16_t *frequency, void *data, uint16_t *size);
diff --git a/unit/test-btsnoop.c b/unit/test-btsnoop.c
new file mode 100644
index 000000000..710209097
--- /dev/null
+++ b/unit/test-btsnoop.c
@@ -0,0 +1,794 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <endian.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "src/shared/att-types.h"
+#include "src/shared/btsnoop.h"
+#include "unit/test-btsnoop.h"
+
+#define BTSNOOP_EPOCH_OFFSET 0x00E03AB44A676000ull
+#define PKLG_PAYLOAD_OFFSET 9
+
+struct test_btsnoop_hdr {
+	uint8_t id[8];
+	uint32_t version;
+	uint32_t type;
+} __packed;
+
+struct test_btsnoop_pkt {
+	uint32_t size;
+	uint32_t len;
+	uint32_t flags;
+	uint32_t drops;
+	uint64_t ts;
+} __packed;
+
+struct test_pklg_pkt {
+	uint32_t len;
+	uint64_t ts;
+	uint8_t type;
+} __packed;
+
+static const uint8_t btsnoop_id[] = {
+	0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00
+};
+
+static void append_bytes(GByteArray *array, const void *data, size_t size)
+{
+	if (!size)
+		return;
+
+	g_byte_array_append(array, data, size);
+}
+
+static void append_btsnoop_header(GByteArray *array, uint32_t format)
+{
+	struct test_btsnoop_hdr hdr;
+
+	memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id));
+	hdr.version = htobe32(1);
+	hdr.type = htobe32(format);
+
+	append_bytes(array, &hdr, sizeof(hdr));
+}
+
+static void append_btsnoop_packet(GByteArray *array, uint32_t len,
+					uint32_t flags, uint64_t ts,
+					const void *data, size_t data_len)
+{
+	struct test_btsnoop_pkt pkt;
+
+	pkt.size = htobe32(len);
+	pkt.len = htobe32(len);
+	pkt.flags = htobe32(flags);
+	pkt.drops = 0;
+	pkt.ts = htobe64(ts);
+
+	append_bytes(array, &pkt, sizeof(pkt));
+	append_bytes(array, data, data_len);
+}
+
+static void append_pklg_packet(GByteArray *array, bool little_endian,
+				uint32_t payload_len, uint64_t ts,
+				uint8_t type, const void *data, size_t data_len)
+{
+	struct test_pklg_pkt pkt;
+	uint32_t len = PKLG_PAYLOAD_OFFSET + payload_len;
+
+	pkt.len = little_endian ? htole32(len) : htobe32(len);
+	pkt.ts = little_endian ? htole64(ts) : htobe64(ts);
+	pkt.type = type;
+
+	append_bytes(array, &pkt, sizeof(pkt));
+	append_bytes(array, data, data_len);
+}
+
+static char *write_tmp_trace(const void *data, size_t size)
+{
+	char *path = NULL;
+	ssize_t written;
+	int fd;
+
+	fd = g_file_open_tmp("bluez-btsnoop-XXXXXX", &path, NULL);
+	g_assert(fd >= 0);
+	written = write(fd, data, size);
+	g_assert_cmpint(written, ==, (ssize_t) size);
+	g_assert_cmpint(close(fd), ==, 0);
+
+	return path;
+}
+
+static char *new_tmp_path(void)
+{
+	char *path = NULL;
+	int fd;
+
+	fd = g_file_open_tmp("bluez-btsnoop-XXXXXX", &path, NULL);
+	g_assert(fd >= 0);
+	g_assert_cmpint(close(fd), ==, 0);
+	unlink(path);
+
+	return path;
+}
+
+static void unlink_rotated(const char *path, unsigned int count)
+{
+	unsigned int i;
+
+	for (i = 0; i <= count; i++) {
+		char *name;
+
+		name = g_strdup_printf("%s.%u", path, i);
+		unlink(name);
+		g_free(name);
+	}
+}
+
+static bool read_tmp_trace(const void *trace, size_t trace_len,
+				unsigned long flags, uint16_t data_size,
+				uint8_t *data, uint16_t *size,
+				uint16_t *index, uint16_t *opcode,
+				struct timeval *tv)
+{
+	struct btsnoop *btsnoop;
+	char *path;
+	bool result;
+
+	path = write_tmp_trace(trace, trace_len);
+	btsnoop = btsnoop_open(path, flags);
+	unlink(path);
+	g_free(path);
+
+	if (!btsnoop)
+		return false;
+
+	result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
+							data_size, size);
+	btsnoop_unref(btsnoop);
+
+	return result;
+}
+
+static bool read_trace_file(const char *path, unsigned long flags,
+				uint8_t *data, uint16_t data_size,
+				uint16_t *size, uint16_t *index,
+				uint16_t *opcode, struct timeval *tv)
+{
+	struct btsnoop *btsnoop;
+	bool result;
+
+	btsnoop = btsnoop_open(path, flags);
+	g_assert_nonnull(btsnoop);
+
+	result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
+							data_size, size);
+	btsnoop_unref(btsnoop);
+
+	return result;
+}
+
+static void test_btsnoop_hci_valid(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	uint8_t data[sizeof(payload)];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0xffff;
+	uint16_t opcode = 0xffff;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	append_btsnoop_packet(trace, sizeof(payload), 0x02,
+			BTSNOOP_EPOCH_OFFSET + 1234567, payload,
+			sizeof(payload));
+
+	g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 0);
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+	g_assert_cmpint(size, ==, sizeof(payload));
+	g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+	g_assert_cmpint(tv.tv_sec, ==, 946684801);
+	g_assert_cmpint(tv.tv_usec, ==, 234567);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_create_invalid_args(void)
+{
+	char *path = new_tmp_path();
+
+	g_assert_null(btsnoop_create(path, 0, 1, BTSNOOP_FORMAT_HCI));
+	g_assert_null(btsnoop_create("/tmp/bluez/no/such/path", 0, 0,
+							BTSNOOP_FORMAT_HCI));
+	g_assert_null(btsnoop_ref(NULL));
+	btsnoop_unref(NULL);
+	g_assert_cmpint(btsnoop_get_format(NULL), ==, BTSNOOP_FORMAT_INVALID);
+
+	g_free(path);
+}
+
+static void test_btsnoop_open_invalid_headers(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	struct test_btsnoop_hdr hdr;
+	char *path;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	((struct test_btsnoop_hdr *) trace->data)->version = htobe32(2);
+	path = write_tmp_trace(trace->data, trace->len);
+	g_assert_null(btsnoop_open(path, 0));
+	unlink(path);
+	g_free(path);
+	g_byte_array_set_size(trace, 0);
+
+	memset(&hdr, 0x55, sizeof(hdr));
+	append_bytes(trace, &hdr, sizeof(hdr));
+	path = write_tmp_trace(trace->data, trace->len);
+	g_assert_null(btsnoop_open(path, 0));
+	g_assert_null(btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT));
+	unlink(path);
+	g_free(path);
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_write_hci_roundtrip(void)
+{
+	const uint8_t command[] = { 0x01, 0x02, 0x03 };
+	const uint8_t event[] = { 0x04, 0x05 };
+	uint8_t data[sizeof(command)];
+	struct btsnoop *btsnoop;
+	struct timeval tv = { .tv_sec = 946684802, .tv_usec = 345678 };
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+	char *path = new_tmp_path();
+
+	btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_HCI);
+	g_assert_nonnull(btsnoop);
+	g_assert_cmpint(btsnoop_get_format(btsnoop), ==, BTSNOOP_FORMAT_HCI);
+	g_assert_true(btsnoop_write_hci(btsnoop, &tv, 0,
+					BTSNOOP_OPCODE_COMMAND_PKT, 0,
+					command, sizeof(command)));
+	g_assert_true(btsnoop_write_hci(btsnoop, &tv, 0,
+					BTSNOOP_OPCODE_EVENT_PKT, 0,
+					event, sizeof(event)));
+	g_assert_false(btsnoop_write_hci(btsnoop, &tv, 1,
+					BTSNOOP_OPCODE_COMMAND_PKT, 0,
+					command, sizeof(command)));
+	g_assert_false(btsnoop_write_hci(btsnoop, &tv, 0,
+					BTSNOOP_OPCODE_NEW_INDEX, 0,
+					command, sizeof(command)));
+	btsnoop_unref(btsnoop);
+
+	g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
+						&index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 0);
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+	g_assert_cmpint(size, ==, sizeof(command));
+	g_assert_cmpint(memcmp(data, command, sizeof(command)), ==, 0);
+
+	btsnoop = btsnoop_open(path, 0);
+	g_assert_nonnull(btsnoop);
+	g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+							sizeof(data), &size));
+	g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+							sizeof(data), &size));
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+	g_assert_cmpint(size, ==, sizeof(event));
+	g_assert_cmpint(memcmp(data, event, sizeof(event)), ==, 0);
+	g_assert_false(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+							sizeof(data), &size));
+	btsnoop_unref(btsnoop);
+
+	unlink(path);
+	g_free(path);
+}
+
+static void test_btsnoop_write_monitor_roundtrip(void)
+{
+	const uint8_t payload[] = { 0xaa, 0xbb };
+	uint8_t data[sizeof(payload)];
+	struct btsnoop *btsnoop;
+	struct timeval tv = { .tv_sec = 946684800, .tv_usec = 0 };
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+	char *path = new_tmp_path();
+
+	btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_MONITOR);
+	g_assert_nonnull(btsnoop);
+	g_assert_true(btsnoop_write_hci(btsnoop, &tv, 7, 0x1234, 0,
+						payload, sizeof(payload)));
+	btsnoop_unref(btsnoop);
+
+	g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
+						&index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 7);
+	g_assert_cmpint(opcode, ==, 0x1234);
+	g_assert_cmpint(size, ==, sizeof(payload));
+	g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+
+	unlink(path);
+	g_free(path);
+}
+
+static void test_btsnoop_write_phy_and_rotate(void)
+{
+	const uint8_t payload[] = { 0x01 };
+	struct btsnoop *btsnoop;
+	struct timeval tv = { .tv_sec = 946684800, .tv_usec = 0 };
+	char *path = new_tmp_path();
+
+	g_assert_false(btsnoop_write(NULL, &tv, 0, 0, payload,
+							sizeof(payload)));
+
+	btsnoop = btsnoop_create(path, 24, 1, BTSNOOP_FORMAT_SIMULATOR);
+	g_assert_nonnull(btsnoop);
+	g_assert_true(btsnoop_write_phy(btsnoop, &tv, 2402, payload,
+							sizeof(payload)));
+	btsnoop_unref(btsnoop);
+
+	btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_HCI);
+	g_assert_nonnull(btsnoop);
+	g_assert_false(btsnoop_write_phy(btsnoop, &tv, 2402, payload,
+							sizeof(payload)));
+	btsnoop_unref(btsnoop);
+
+	unlink(path);
+	unlink_rotated(path, 1);
+	g_free(path);
+}
+
+static void test_btsnoop_monitor_valid(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0xaa, 0xbb };
+	uint8_t data[sizeof(payload)];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0xffff;
+	uint16_t opcode = 0xffff;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_MONITOR);
+	append_btsnoop_packet(trace, sizeof(payload), 0x00051234,
+			BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+	g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 5);
+	g_assert_cmpint(opcode, ==, 0x1234);
+	g_assert_cmpint(size, ==, sizeof(payload));
+	g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_uart_valid(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x04, 0x0e, 0x01, 0x00 };
+	uint8_t data[sizeof(payload) - 1];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0xffff;
+	uint16_t opcode = 0xffff;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+	append_btsnoop_packet(trace, sizeof(payload), 0,
+			BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+	g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 0);
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+	g_assert_cmpint(size, ==, sizeof(payload) - 1);
+	g_assert_cmpint(memcmp(data, payload + 1, sizeof(data)), ==, 0);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_uart_opcode_map(void)
+{
+	static const struct {
+		uint8_t type;
+		uint32_t flags;
+		uint16_t opcode;
+	} cases[] = {
+		{ 0x01, 0x00, BTSNOOP_OPCODE_COMMAND_PKT },
+		{ 0x02, 0x00, BTSNOOP_OPCODE_ACL_TX_PKT },
+		{ 0x02, 0x01, BTSNOOP_OPCODE_ACL_RX_PKT },
+		{ 0x03, 0x00, BTSNOOP_OPCODE_SCO_TX_PKT },
+		{ 0x03, 0x01, BTSNOOP_OPCODE_SCO_RX_PKT },
+		{ 0x05, 0x00, BTSNOOP_OPCODE_ISO_TX_PKT },
+		{ 0x05, 0x01, BTSNOOP_OPCODE_ISO_RX_PKT },
+		{ 0x99, 0x00, 0xffff },
+	};
+	unsigned int i;
+
+	for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+		GByteArray *trace = g_byte_array_new();
+		const uint8_t payload[] = { cases[i].type, 0x00 };
+		uint8_t data[1];
+		struct timeval tv;
+		uint16_t size = 0;
+		uint16_t index = 0;
+		uint16_t opcode = 0;
+
+		append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+		append_btsnoop_packet(trace, sizeof(payload), cases[i].flags,
+				BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+		g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
+						sizeof(data), data, &size,
+						&index, &opcode, &tv));
+		g_assert_cmpint(opcode, ==, cases[i].opcode);
+		g_byte_array_unref(trace);
+	}
+}
+
+static void test_btsnoop_rejects_small_capacity(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	append_btsnoop_packet(trace, sizeof(payload), 0x02,
+			BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 2, data,
+						&size, &index, &opcode, &tv));
+	g_assert_cmpint(data[0], ==, 0xa5);
+	g_assert_cmpint(data[1], ==, 0xa5);
+	g_assert_cmpint(data[2], ==, 0xa5);
+	g_assert_cmpint(data[3], ==, 0xa5);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_timestamp_underflow(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	append_btsnoop_packet(trace, 0, 0x02, BTSNOOP_EPOCH_OFFSET - 1,
+								NULL, 0);
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+						&size, &index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_uart_zero_length(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+	append_btsnoop_packet(trace, 0, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+						&size, &index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_uart_short_type(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+	append_btsnoop_packet(trace, 1, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+						&size, &index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_short_payload(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02 };
+	uint8_t data[3];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	append_btsnoop_packet(trace, 3, 0x02, BTSNOOP_EPOCH_OFFSET,
+					payload, sizeof(payload));
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len, 0,
+					sizeof(data), data, &size,
+					&index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_big_endian_valid(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x0e, 0x01, 0x00 };
+	uint8_t data[sizeof(payload)];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0xffff;
+	uint16_t opcode = 0xffff;
+
+	append_pklg_packet(trace, false, sizeof(payload),
+			((uint64_t) 123 << 32) | 456, 0x01, payload,
+			sizeof(payload));
+
+	g_assert_true(read_tmp_trace(trace->data, trace->len,
+					BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 0);
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+	g_assert_cmpint(size, ==, sizeof(payload));
+	g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+	g_assert_cmpint(tv.tv_sec, ==, 123);
+	g_assert_cmpint(tv.tv_usec, ==, 456);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_little_endian_valid(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	uint8_t data[sizeof(payload)];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0xffff;
+	uint16_t opcode = 0xffff;
+
+	append_pklg_packet(trace, true, sizeof(payload),
+			((uint64_t) 456 << 32) | 123, 0x00, payload,
+			sizeof(payload));
+
+	g_assert_true(read_tmp_trace(trace->data, trace->len,
+					BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+	g_assert_cmpint(index, ==, 0);
+	g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+	g_assert_cmpint(size, ==, sizeof(payload));
+	g_assert_cmpint(data[0], ==, payload[0]);
+	g_assert_cmpint(tv.tv_sec, ==, 123);
+	g_assert_cmpint(tv.tv_usec, ==, 456);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_length(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	struct test_pklg_pkt pkt;
+	const uint8_t padding[] = { 0x00, 0x00, 0x00 };
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	pkt.len = htobe32(PKLG_PAYLOAD_OFFSET - 1);
+	pkt.ts = 0;
+	pkt.type = 0x01;
+
+	append_bytes(trace, &pkt, sizeof(pkt));
+	append_bytes(trace, padding, sizeof(padding));
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len,
+					BTSNOOP_FLAG_PKLG_SUPPORT, 0, NULL,
+					&size, &index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_small_capacity(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+							sizeof(payload));
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len,
+					BTSNOOP_FLAG_PKLG_SUPPORT, 2, data,
+					&size, &index, &opcode, &tv));
+	g_assert_cmpint(data[0], ==, 0xa5);
+	g_assert_cmpint(data[1], ==, 0xa5);
+	g_assert_cmpint(data[2], ==, 0xa5);
+	g_assert_cmpint(data[3], ==, 0xa5);
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_payload(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	uint8_t data[4];
+	struct timeval tv;
+	uint16_t size = 0;
+	uint16_t index = 0;
+	uint16_t opcode = 0;
+
+	append_pklg_packet(trace, false, 4, 0, 0x01, payload,
+							sizeof(payload));
+
+	g_assert_false(read_tmp_trace(trace->data, trace->len,
+					BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+					data, &size, &index, &opcode, &tv));
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_type_map(void)
+{
+	static const struct {
+		uint8_t type;
+		uint16_t index;
+		uint16_t opcode;
+	} cases[] = {
+		{ 0x02, 0x0000, BTSNOOP_OPCODE_ACL_TX_PKT },
+		{ 0x03, 0x0000, BTSNOOP_OPCODE_ACL_RX_PKT },
+		{ 0x08, 0x0000, BTSNOOP_OPCODE_SCO_TX_PKT },
+		{ 0x09, 0x0000, BTSNOOP_OPCODE_SCO_RX_PKT },
+		{ 0x12, 0x0000, BTSNOOP_OPCODE_ISO_TX_PKT },
+		{ 0x13, 0x0000, BTSNOOP_OPCODE_ISO_RX_PKT },
+		{ 0x0b, 0x0000, BTSNOOP_OPCODE_VENDOR_DIAG },
+		{ 0xfc, 0xffff, BTSNOOP_OPCODE_SYSTEM_NOTE },
+		{ 0xaa, 0xffff, 0xffff },
+	};
+	const uint8_t payload[] = { 0x00, 0x01, 0x02 };
+	unsigned int i;
+
+	for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+		GByteArray *trace = g_byte_array_new();
+		uint8_t data[sizeof(payload)];
+		struct timeval tv;
+		uint16_t size = 0;
+		uint16_t index = 0;
+		uint16_t opcode = 0;
+
+		append_pklg_packet(trace, false, sizeof(payload), 0,
+						cases[i].type, payload,
+						sizeof(payload));
+
+		g_assert_true(read_tmp_trace(trace->data, trace->len,
+						BTSNOOP_FLAG_PKLG_SUPPORT,
+						sizeof(data), data, &size,
+						&index, &opcode, &tv));
+		g_assert_cmpint(index, ==, cases[i].index);
+		g_assert_cmpint(opcode, ==, cases[i].opcode);
+		g_byte_array_unref(trace);
+	}
+}
+
+static void test_btsnoop_truncation_fuzz(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	size_t len;
+
+	append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+	append_btsnoop_packet(trace, sizeof(payload), 0x02,
+			BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+	for (len = 0; len < trace->len; len++) {
+		uint8_t data[sizeof(payload)];
+		struct timeval tv;
+		uint16_t size = 0;
+		uint16_t index = 0;
+		uint16_t opcode = 0;
+
+		g_assert_false(read_tmp_trace(trace->data, len, 0,
+						sizeof(data), data, &size,
+						&index, &opcode, &tv));
+	}
+
+	g_byte_array_unref(trace);
+}
+
+static void test_pklg_truncation_fuzz(void)
+{
+	GByteArray *trace = g_byte_array_new();
+	const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+	size_t len;
+
+	append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+							sizeof(payload));
+
+	for (len = 0; len < trace->len; len++) {
+		uint8_t data[sizeof(payload)];
+		struct timeval tv;
+		uint16_t size = 0;
+		uint16_t index = 0;
+		uint16_t opcode = 0;
+
+		g_assert_false(read_tmp_trace(trace->data, len,
+						BTSNOOP_FLAG_PKLG_SUPPORT,
+						sizeof(data), data, &size,
+						&index, &opcode, &tv));
+	}
+
+	g_byte_array_unref(trace);
+}
+
+int main(int argc, char *argv[])
+{
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/btsnoop/hci/valid", test_btsnoop_hci_valid);
+	g_test_add_func("/btsnoop/create/invalid",
+			test_btsnoop_create_invalid_args);
+	g_test_add_func("/btsnoop/open/invalid",
+			test_btsnoop_open_invalid_headers);
+	g_test_add_func("/btsnoop/write/hci-roundtrip",
+			test_btsnoop_write_hci_roundtrip);
+	g_test_add_func("/btsnoop/write/monitor-roundtrip",
+			test_btsnoop_write_monitor_roundtrip);
+	g_test_add_func("/btsnoop/write/phy-and-rotate",
+			test_btsnoop_write_phy_and_rotate);
+	g_test_add_func("/btsnoop/monitor/valid", test_btsnoop_monitor_valid);
+	g_test_add_func("/btsnoop/uart/valid", test_btsnoop_uart_valid);
+	g_test_add_func("/btsnoop/uart/opcode-map",
+			test_btsnoop_uart_opcode_map);
+	g_test_add_func("/btsnoop/capacity/reject",
+			test_btsnoop_rejects_small_capacity);
+	g_test_add_func("/btsnoop/timestamp/underflow",
+			test_btsnoop_rejects_timestamp_underflow);
+	g_test_add_func("/btsnoop/uart/zero-length",
+			test_btsnoop_rejects_uart_zero_length);
+	g_test_add_func("/btsnoop/uart/short-type",
+			test_btsnoop_rejects_uart_short_type);
+	g_test_add_func("/btsnoop/payload/short",
+			test_btsnoop_rejects_short_payload);
+	g_test_add_func("/pklg/big-endian/valid", test_pklg_big_endian_valid);
+	g_test_add_func("/pklg/little-endian/valid",
+			test_pklg_little_endian_valid);
+	g_test_add_func("/pklg/length/short", test_pklg_rejects_short_length);
+	g_test_add_func("/pklg/capacity/reject",
+			test_pklg_rejects_small_capacity);
+	g_test_add_func("/pklg/payload/short", test_pklg_rejects_short_payload);
+	g_test_add_func("/pklg/type-map", test_pklg_type_map);
+	g_test_add_func("/btsnoop/fuzz/truncation",
+			test_btsnoop_truncation_fuzz);
+	g_test_add_func("/pklg/fuzz/truncation", test_pklg_truncation_fuzz);
+
+	return g_test_run();
+}
diff --git a/unit/test-btsnoop.h b/unit/test-btsnoop.h
new file mode 100644
index 000000000..9a12f3e71
--- /dev/null
+++ b/unit/test-btsnoop.h
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+void add_pklg_tests(void);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-06-20 21:09 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-20 19:22 [PATCH BlueZ 1/2] shared: harden btsnoop trace parsing Geraldo Netto
2026-06-20 19:22 ` [PATCH BlueZ 2/2] unit: split btsnoop pklg tests Geraldo Netto
2026-06-20 21:09 ` [BlueZ,1/2] shared: harden btsnoop trace parsing bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox