linux-bluetooth.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor
@ 2018-10-05  9:00 Luiz Augusto von Dentz
  2018-10-05  9:00 ` [PATCH BlueZ 2/3] monitor: Add support for user input/output data Luiz Augusto von Dentz
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2018-10-05  9:00 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds option -m/--monitor that can be used toguether with
tester_monitor to send protocol data to be decoded by btmon.

In addition to that this also logs the tester output into btmon since
that has support to store its output on file this can be quite
convenient for reporting.
---
 src/shared/tester.c | 237 ++++++++++++++++++++++++++++++++++++++++----
 src/shared/tester.h |   4 +
 2 files changed, 220 insertions(+), 21 deletions(-)

diff --git a/src/shared/tester.c b/src/shared/tester.c
index 80d65110b..05c81a66d 100644
--- a/src/shared/tester.c
+++ b/src/shared/tester.c
@@ -27,14 +27,19 @@
 
 #include <stdio.h>
 #include <errno.h>
+#include <syslog.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
 #include <sys/signalfd.h>
+#include <sys/socket.h>
 
 #include <glib.h>
 
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+
 #ifdef HAVE_VALGRIND_MEMCHECK_H
 #include <valgrind/memcheck.h>
 #endif
@@ -54,15 +59,15 @@
 #define COLOR_HIGHLIGHT	"\x1B[1;39m"
 
 #define print_text(color, fmt, args...) \
-		printf(color fmt COLOR_OFF "\n", ## args)
+		tester_log(color fmt COLOR_OFF, ## args)
 
 #define print_summary(label, color, value, fmt, args...) \
-			printf("%-52s " color "%-10s" COLOR_OFF fmt "\n", \
+		tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \
 							label, value, ## args)
 
 #define print_progress(name, color, fmt, args...) \
-		printf(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \
-				color fmt COLOR_OFF "\n", name, ## args)
+		tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \
+				color fmt COLOR_OFF, name, ## args)
 
 enum test_result {
 	TEST_RESULT_NOT_RUN,
@@ -100,6 +105,7 @@ struct test_case {
 };
 
 static GMainLoop *main_loop;
+static char *tester_name;
 
 static GList *test_list;
 static GList *test_current;
@@ -108,9 +114,25 @@ static GTimer *test_timer;
 static gboolean option_version = FALSE;
 static gboolean option_quiet = FALSE;
 static gboolean option_debug = FALSE;
+static gboolean option_monitor = FALSE;
 static gboolean option_list = FALSE;
 static const char *option_prefix = NULL;
 
+struct monitor_hdr {
+	uint16_t opcode;
+	uint16_t index;
+	uint16_t len;
+	uint8_t  priority;
+	uint8_t  ident_len;
+} __attribute__((packed));
+
+struct monitor_l2cap_hdr {
+	uint16_t cid;
+	uint16_t psm;
+} __attribute__((packed));
+
+static int monitor_fd = -1;
+
 static void test_destroy(gpointer data)
 {
 	struct test_case *test = data;
@@ -128,43 +150,208 @@ static void test_destroy(gpointer data)
 	free(test);
 }
 
-void tester_print(const char *format, ...)
+static int monitor_open(void)
 {
-	va_list ap;
+	struct sockaddr_hci addr;
+	int fd;
+
+	if (!option_monitor)
+		return -1;
+
+	if (monitor_fd >= 0)
+		return monitor_fd;
+
+	fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+	if (fd < 0)
+		return fd;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.hci_family = AF_BLUETOOTH;
+	addr.hci_dev = HCI_DEV_NONE;
+	addr.hci_channel = HCI_CHANNEL_LOGGING;
+
+	if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		option_monitor = FALSE;
+		tester_debug("Failed to open monitor socket: %s",
+			     strerror(errno));
+		close(fd);
+		return -1;
+	}
+
+	monitor_fd = fd;
+
+	return fd;
+}
+
+static void monitor_sendmsg(const char *label, int level, struct iovec *io,
+							size_t io_len)
+{
+	struct monitor_hdr hdr;
+	struct msghdr msg;
+	struct iovec iov[5];
+	size_t i;
+
+	monitor_fd = monitor_open();
+	if (monitor_fd < 0 || io_len > 3)
+		return;
+
+	hdr.opcode = cpu_to_le16(0x0000);
+	hdr.index = cpu_to_le16(0xffff);
+	hdr.ident_len = strlen(label) + 1;
+	hdr.len = cpu_to_le16(2 + hdr.ident_len);
+	hdr.priority = level;
+
+	iov[0].iov_base = &hdr;
+	iov[0].iov_len = sizeof(hdr);
+
+	iov[1].iov_base = (void *) label;
+	iov[1].iov_len = hdr.ident_len;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 2;
+
+	for (i = 0; i < io_len; i++) {
+		iov[i + 2] = io[i];
+		hdr.len += io[i].iov_len;
+		msg.msg_iovlen++;
+	}
 
+	if (sendmsg(monitor_fd, &msg, 0) < 0) {
+		/* Disable monitor */
+		option_monitor = FALSE;
+		tester_debug("Failed to send to monitor: %s", strerror(errno));
+		close(monitor_fd);
+		monitor_fd = -1;
+	}
+}
+
+static void monitor_vprintf(const char *id, int level, const char *format,
+								va_list ap)
+{
+	struct iovec iov;
+	char *str;
+
+	if (!option_monitor)
+		return;
+
+	if (vasprintf(&str, format, ap) < 0)
+		return;
+
+	iov.iov_base = str;
+	iov.iov_len = strlen(str) + 1;
+
+	monitor_sendmsg(id, level, &iov, 1);
+
+	free(str);
+}
+
+static void tester_vprintf(const char *format, va_list ap)
+{
 	if (tester_use_quiet())
 		return;
 
 	printf("  %s", COLOR_WHITE);
+	vprintf(format, ap);
+	printf("%s\n", COLOR_OFF);
+}
+
+static void tester_log(const char *format, ...)
+{
+	va_list ap;
+
 	va_start(ap, format);
 	vprintf(format, ap);
+	printf("\n");
+	va_end(ap);
+
+	va_start(ap, format);
+	monitor_vprintf(tester_name, LOG_INFO, format, ap);
+	va_end(ap);
+}
+
+void tester_print(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	tester_vprintf(format, ap);
+	va_end(ap);
+
+	va_start(ap, format);
+	monitor_vprintf(tester_name, LOG_INFO, format, ap);
 	va_end(ap);
-	printf("%s\n", COLOR_OFF);
 }
 
 void tester_debug(const char *format, ...)
 {
 	va_list ap;
 
-	if (!tester_use_debug())
-		return;
+	va_start(ap, format);
+	tester_vprintf(format, ap);
+	va_end(ap);
 
-	printf("  %s", COLOR_WHITE);
 	va_start(ap, format);
-	vprintf(format, ap);
+	monitor_vprintf(tester_name, LOG_DEBUG, format, ap);
 	va_end(ap);
-	printf("%s\n", COLOR_OFF);
 }
 
 void tester_warn(const char *format, ...)
 {
 	va_list ap;
 
-	printf("  %s", COLOR_WHITE);
 	va_start(ap, format);
-	vprintf(format, ap);
+	tester_vprintf(format, ap);
+	va_end(ap);
+
+	va_start(ap, format);
+	monitor_vprintf(tester_name, LOG_WARNING, format, ap);
 	va_end(ap);
-	printf("%s\n", COLOR_OFF);
+}
+
+static void monitor_debug(const char *str, void *user_data)
+{
+	const char *label = user_data;
+
+	tester_debug("%s: %s", label, str);
+}
+
+static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data,
+								size_t len)
+{
+	struct iovec iov[3];
+	struct monitor_l2cap_hdr hdr;
+	uint8_t term = 0x00;
+	char label[16];
+
+	if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0)
+		return;
+
+	hdr.cid = cpu_to_le16(cid);
+	hdr.psm = cpu_to_le16(psm);
+
+	iov[0].iov_base = &hdr;
+	iov[0].iov_len = sizeof(hdr);
+
+	iov[1].iov_base = (void *) data;
+	iov[1].iov_len = len;
+
+	/* Kernel won't forward if data is no NULL terminated */
+	iov[2].iov_base = &term;
+	iov[2].iov_len = sizeof(term);
+
+	monitor_sendmsg(label, LOG_INFO, iov, 3);
+}
+
+void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data,
+								size_t len)
+{
+	monitor_log(dir, cid, psm, data, len);
+
+	if (!tester_use_debug())
+		return;
+
+	util_hexdump(dir, data, len, monitor_debug, (void *) tester_name);
 }
 
 static void default_pre_setup(const void *test_data)
@@ -208,7 +395,7 @@ void tester_add_full(const char *name, const void *test_data,
 	}
 
 	if (option_list) {
-		printf("%s\n", name);
+		tester_log("%s", name);
 		if (destroy)
 			destroy(user_data);
 		return;
@@ -278,7 +465,7 @@ static int tester_summarize(void)
 	gdouble execution_time;
 	GList *list;
 
-	printf("\n");
+	tester_log("");
 	print_text(COLOR_HIGHLIGHT, "");
 	print_text(COLOR_HIGHLIGHT, "Test Summary");
 	print_text(COLOR_HIGHLIGHT, "------------");
@@ -312,17 +499,17 @@ static int tester_summarize(void)
 		}
         }
 
-	printf("\nTotal: %d, "
+	tester_log("Total: %d, "
 		COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", "
 		COLOR_RED "Failed: %d" COLOR_OFF ", "
-		COLOR_YELLOW "Not Run: %d" COLOR_OFF "\n",
+		COLOR_YELLOW "Not Run: %d" COLOR_OFF,
 			not_run + passed + failed, passed,
 			(not_run + passed + failed) ?
 			(float) passed * 100 / (not_run + passed + failed) : 0,
 			failed, not_run);
 
 	execution_time = g_timer_elapsed(test_timer, NULL);
-	printf("Overall execution time: %.3g seconds\n", execution_time);
+	tester_log("Overall execution time: %.3g seconds", execution_time);
 
 	return failed;
 }
@@ -379,7 +566,7 @@ static void next_test_case(void)
 
 	test = test_current->data;
 
-	printf("\n");
+	tester_log("");
 	print_progress(test->name, COLOR_BLACK, "init");
 
 	test->start_time = g_timer_elapsed(test_timer, NULL);
@@ -774,6 +961,8 @@ static GOptionEntry options[] = {
 				"Run tests without logging" },
 	{ "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug,
 				"Run tests with debug output" },
+	{ "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor,
+				"Enable monitor output" },
 	{ "list", 'l', 0, G_OPTION_ARG_NONE, &option_list,
 				"Only list the tests to be run" },
 	{ "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix,
@@ -807,6 +996,12 @@ void tester_init(int *argc, char ***argv)
 
 	main_loop = g_main_loop_new(NULL, FALSE);
 
+	tester_name = strrchr(*argv[0], '/');
+	if (!tester_name)
+		tester_name = strdup(*argv[0]);
+	else
+		tester_name = strdup(++tester_name);
+
 	test_list = NULL;
 	test_current = NULL;
 }
diff --git a/src/shared/tester.h b/src/shared/tester.h
index 83ef5de7a..96e8dc901 100644
--- a/src/shared/tester.h
+++ b/src/shared/tester.h
@@ -22,6 +22,8 @@
  */
 
 #include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
 
 void tester_init(int *argc, char ***argv);
 int tester_run(void);
@@ -35,6 +37,8 @@ void tester_warn(const char *format, ...)
 				__attribute__((format(printf, 1, 2)));
 void tester_debug(const char *format, ...)
 				__attribute__((format(printf, 1, 2)));
+void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data,
+								size_t len);
 
 typedef void (*tester_destroy_func_t)(void *user_data);
 typedef void (*tester_data_func_t)(const void *test_data);
-- 
2.17.1


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

* [PATCH BlueZ 2/3] monitor: Add support for user input/output data
  2018-10-05  9:00 [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
@ 2018-10-05  9:00 ` Luiz Augusto von Dentz
  2018-10-05  9:00 ` [PATCH BlueZ 3/3] unit: Make use of tester_monitor to print input/output PDUs Luiz Augusto von Dentz
  2018-10-08  7:53 ` [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2018-10-05  9:00 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This detects if the user logging is an input/output and then proceed to
decode the header which inform for which CID and PSM the data is for.
---
 monitor/l2cap.c  | 35 ++++++++++++++++++++---------------
 monitor/l2cap.h  |  3 +++
 monitor/packet.c | 41 ++++++++++++++++++++++++++++++++++++++---
 monitor/packet.h |  3 ++-
 4 files changed, 63 insertions(+), 19 deletions(-)

diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index ef2a14410..05a33b903 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -1384,7 +1384,8 @@ static const struct sig_opcode_data le_sig_opcode_table[] = {
 
 static void l2cap_frame_init(struct l2cap_frame *frame, uint16_t index, bool in,
 				uint16_t handle, uint8_t ident,
-				uint16_t cid, const void *data, uint16_t size)
+				uint16_t cid, uint16_t psm,
+				const void *data, uint16_t size)
 {
 	frame->index   = index;
 	frame->in      = in;
@@ -1393,10 +1394,10 @@ static void l2cap_frame_init(struct l2cap_frame *frame, uint16_t index, bool in,
 	frame->cid     = cid;
 	frame->data    = data;
 	frame->size    = size;
-	frame->psm     = get_psm(frame);
+	frame->psm     = psm ? psm : get_psm(frame);
 	frame->mode    = get_mode(frame);
 	frame->chan    = get_chan(frame);
-	frame->seq_num = get_seq_num(frame);
+	frame->seq_num = psm ? 1 : get_seq_num(frame);
 }
 
 static void bredr_sig_packet(uint16_t index, bool in, uint16_t handle,
@@ -1479,7 +1480,7 @@ static void bredr_sig_packet(uint16_t index, bool in, uint16_t handle,
 			}
 		}
 
-		l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid,
+		l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid, 0,
 								data, len);
 		opcode_data->func(&frame);
 
@@ -1561,7 +1562,8 @@ static void le_sig_packet(uint16_t index, bool in, uint16_t handle,
 		}
 	}
 
-	l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid, data, len);
+	l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid, 0,
+							data, len);
 	opcode_data->func(&frame);
 }
 
@@ -1592,7 +1594,7 @@ static void connless_packet(uint16_t index, bool in, uint16_t handle,
 		break;
 	}
 
-	l2cap_frame_init(&frame, index, in, handle, 0, cid, data, size);
+	l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, data, size);
 }
 
 static void print_controller_list(const uint8_t *data, uint16_t size)
@@ -1961,7 +1963,7 @@ static void amp_packet(uint16_t index, bool in, uint16_t handle,
 		}
 	}
 
-	l2cap_frame_init(&frame, index, in, handle, 0, cid, data + 6, len);
+	l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, data + 6, len);
 	opcode_data->func(&frame);
 }
 
@@ -2544,7 +2546,8 @@ static void att_packet(uint16_t index, bool in, uint16_t handle,
 		}
 	}
 
-	l2cap_frame_init(&frame, index, in, handle, 0, cid, data + 1, size - 1);
+	l2cap_frame_init(&frame, index, in, handle, 0, cid, 0,
+						data + 1, size - 1);
 	opcode_data->func(&frame);
 }
 
@@ -3000,12 +3003,13 @@ static void smp_packet(uint16_t index, bool in, uint16_t handle,
 		}
 	}
 
-	l2cap_frame_init(&frame, index, in, handle, 0, cid, data + 1, size - 1);
+	l2cap_frame_init(&frame, index, in, handle, 0, cid, 0,
+						data + 1, size - 1);
 	opcode_data->func(&frame);
 }
 
-static void l2cap_frame(uint16_t index, bool in, uint16_t handle,
-			uint16_t cid, const void *data, uint16_t size)
+void l2cap_frame(uint16_t index, bool in, uint16_t handle, uint16_t cid,
+			uint16_t psm, const void *data, uint16_t size)
 {
 	struct l2cap_frame frame;
 	uint32_t ctrl32 = 0;
@@ -3033,7 +3037,8 @@ static void l2cap_frame(uint16_t index, bool in, uint16_t handle,
 		smp_packet(index, in, handle, cid, data, size);
 		break;
 	default:
-		l2cap_frame_init(&frame, index, in, handle, 0, cid, data, size);
+		l2cap_frame_init(&frame, index, in, handle, 0, cid, psm,
+							data, size);
 
 		if (frame.mode > 0) {
 			ext_ctrl = get_ext_ctrl(&frame);
@@ -3136,7 +3141,7 @@ void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags,
 
 		if (len == size) {
 			/* complete frame */
-			l2cap_frame(index, in, handle, cid, data, len);
+			l2cap_frame(index, in, handle, cid, 0, data, len);
 			return;
 		}
 
@@ -3181,7 +3186,7 @@ void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags,
 		if (!index_list[index][in].frag_len) {
 			/* complete frame */
 			l2cap_frame(index, in, handle,
-					index_list[index][in].frag_cid,
+					index_list[index][in].frag_cid, 0,
 					index_list[index][in].frag_buf,
 					index_list[index][in].frag_pos);
 			clear_fragment_buffer(index, in);
@@ -3216,7 +3221,7 @@ void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags,
 		}
 
 		/* complete frame */
-		l2cap_frame(index, in, handle, cid, data, len);
+		l2cap_frame(index, in, handle, cid, 0, data, len);
 		break;
 
 	default:
diff --git a/monitor/l2cap.h b/monitor/l2cap.h
index 813c7932a..07864caee 100644
--- a/monitor/l2cap.h
+++ b/monitor/l2cap.h
@@ -170,6 +170,9 @@ static inline bool l2cap_frame_get_be128(struct l2cap_frame *frame,
 	return true;
 }
 
+void l2cap_frame(uint16_t index, bool in, uint16_t handle, uint16_t cid,
+		uint16_t psm, const void *data, uint16_t size);
+
 void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags,
 					const void *data, uint16_t size);
 
diff --git a/monitor/packet.c b/monitor/packet.c
index aca0761fd..6932f592d 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -3947,7 +3947,8 @@ void packet_monitor(struct timeval *tv, struct ucred *cred,
 		ident = ul->ident_len ? data + sizeof(*ul) : NULL;
 
 		packet_user_logging(tv, cred, index, ul->priority, ident,
-					data + sizeof(*ul) + ul->ident_len);
+					data + sizeof(*ul) + ul->ident_len,
+					size - (sizeof(*ul) + ul->ident_len));
 		break;
 	case BTSNOOP_OPCODE_CTRL_OPEN:
 		control_disable_decoding();
@@ -9873,9 +9874,36 @@ void packet_system_note(struct timeval *tv, struct ucred *cred,
 					"Note", message, NULL);
 }
 
+struct monitor_l2cap_hdr {
+	uint16_t cid;
+	uint16_t psm;
+};
+
+static void packet_decode(struct timeval *tv, struct ucred *cred, char dir,
+				uint16_t index, const char *color,
+				const char *label, const void *data,
+				uint16_t size)
+{
+	const struct monitor_l2cap_hdr *hdr = data;
+
+	if (size < sizeof(*hdr)) {
+		print_packet(tv, cred, '*', index, NULL, COLOR_ERROR,
+			"Malformed User Data packet", NULL, NULL);
+	}
+
+	print_packet(tv, cred, dir, index, NULL, COLOR_HCI_ACLDATA, label,
+				dir == '>' ? "User Data RX" : "User Data TX",
+				NULL);
+
+	/* Discard last byte since it just a filler */
+	l2cap_frame(index, dir == '>', 0, hdr->cid, hdr->psm,
+			data + sizeof(*hdr), size - (sizeof(*hdr) + 1));
+}
+
 void packet_user_logging(struct timeval *tv, struct ucred *cred,
 					uint16_t index, uint8_t priority,
-					const char *ident, const char *message)
+					const char *ident, const void *data,
+					uint16_t size)
 {
 	char pid_str[140];
 	const char *label;
@@ -9930,7 +9958,14 @@ void packet_user_logging(struct timeval *tv, struct ucred *cred,
 			label = "Message";
 	}
 
-	print_packet(tv, cred, '=', index, NULL, color, label, message, NULL);
+	if (ident[0] == '<' || ident[0] == '>') {
+		packet_decode(tv, cred, ident[0], index, color,
+				label == ident ? &ident[2] : label,
+				data, size);
+		return;
+	}
+
+	print_packet(tv, cred, '=', index, NULL, color, label, data, NULL);
 }
 
 void packet_hci_command(struct timeval *tv, struct ucred *cred, uint16_t index,
diff --git a/monitor/packet.h b/monitor/packet.h
index 03279e114..199e15e58 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -83,7 +83,8 @@ void packet_system_note(struct timeval *tv, struct ucred *cred,
 					uint16_t index, const void *message);
 void packet_user_logging(struct timeval *tv, struct ucred *cred,
 					uint16_t index, uint8_t priority,
-					const char *ident, const char *message);
+					const char *ident, const void *data,
+					uint16_t size);
 
 void packet_hci_command(struct timeval *tv, struct ucred *cred, uint16_t index,
 					const void *data, uint16_t size);
-- 
2.17.1


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

* [PATCH BlueZ 3/3] unit: Make use of tester_monitor to print input/output PDUs
  2018-10-05  9:00 [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
  2018-10-05  9:00 ` [PATCH BlueZ 2/3] monitor: Add support for user input/output data Luiz Augusto von Dentz
@ 2018-10-05  9:00 ` Luiz Augusto von Dentz
  2018-10-08  7:53 ` [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2018-10-05  9:00 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

tester_monitor will forward the data to btmon when -m/--monitor is
enabled which will attempt to decode the PDUs:

= test-gatt: /robustness/unkown-command - init                                                                                                                        11:44:53.464325
= test-gatt: /robustness/unkown-command - setup
= test-gatt: /robustness/unkown-command - setup complete
= test-gatt: /robustness/unkown-command - run
< test-gatt: User Data TX
      ATT: Exchange MTU Request (0x02) len 2
        Client RX MTU: 23
> test-gatt: User Data RX
      ATT: Exchange MTU Response (0x03) len 2
        Server RX MTU: 512
< test-gatt: User Data TX
      ATT: Unknown (0xff) len 1
        00                                               .
= test-gatt: /robustness/unkown-command - test passed
= test-gatt: /robustness/unkown-command - teardown
= test-gatt: /robustness/unkown-command - teardown complete
= test-gatt: /robustness/unkown-command - done
---
 unit/test-avctp.c | 11 ++---------
 unit/test-avdtp.c | 11 ++---------
 unit/test-avrcp.c | 21 +++++++--------------
 unit/test-gatt.c  |  6 +++---
 unit/test-sdp.c   | 11 ++---------
 5 files changed, 16 insertions(+), 44 deletions(-)

diff --git a/unit/test-avctp.c b/unit/test-avctp.c
index 3bc35696d..60fd6ad71 100644
--- a/unit/test-avctp.c
+++ b/unit/test-avctp.c
@@ -81,13 +81,6 @@ struct context {
 		tester_add(name, &data, NULL, function, NULL);		\
 	} while (0)
 
-static void test_debug(const char *str, void *user_data)
-{
-	const char *prefix = user_data;
-
-	tester_debug("%s%s", prefix, str);
-}
-
 static void test_free(gconstpointer user_data)
 {
 	const struct test_data *data = user_data;
@@ -131,7 +124,7 @@ static gboolean send_pdu(gpointer user_data)
 
 	len = write(context->fd, pdu->data, pdu->size);
 
-	util_hexdump('<', pdu->data, len, test_debug, "AVCTP: ");
+	tester_monitor('<', 0x0000, 0x0017, pdu->data, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
@@ -172,7 +165,7 @@ static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
 
 	g_assert(len > 0);
 
-	util_hexdump('>', buf, len, test_debug, "AVCTP: ");
+	tester_monitor('>', 0x0000, 0x0017, buf, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
diff --git a/unit/test-avdtp.c b/unit/test-avdtp.c
index dd8aed73c..176852ae7 100644
--- a/unit/test-avdtp.c
+++ b/unit/test-avdtp.c
@@ -99,13 +99,6 @@ struct context {
 	const struct test_data *data;
 };
 
-static void test_debug(const char *str, void *user_data)
-{
-	const char *prefix = user_data;
-
-	tester_debug("%s%s", prefix, str);
-}
-
 static void test_free(gconstpointer user_data)
 {
 	const struct test_data *data = user_data;
@@ -158,7 +151,7 @@ static gboolean send_pdu(gpointer user_data)
 
 	len = write(context->fd, pdu->data, pdu->size);
 
-	util_hexdump('<', pdu->data, len, test_debug, "AVDTP: ");
+	tester_monitor('<', 0x0000, 0x0019, pdu->data, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
@@ -215,7 +208,7 @@ static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
 
 	g_assert(len > 0);
 
-	util_hexdump('>', buf, len, test_debug, "AVDTP: ");
+	tester_monitor('>', 0x0000, 0x0019, buf, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
diff --git a/unit/test-avrcp.c b/unit/test-avrcp.c
index 01307e679..9ffd44cfd 100644
--- a/unit/test-avrcp.c
+++ b/unit/test-avrcp.c
@@ -113,13 +113,6 @@ struct context {
 		tester_add(name, &data, NULL, function, NULL);		\
 	} while (0)
 
-static void test_debug(const char *str, void *user_data)
-{
-	const char *prefix = user_data;
-
-	tester_debug("%s%s", prefix, str);
-}
-
 static void test_free(gconstpointer user_data)
 {
 	const struct test_data *data = user_data;
@@ -164,12 +157,13 @@ static gboolean send_pdu(gpointer user_data)
 
 	pdu = &context->data->pdu_list[context->pdu_offset++];
 
-	if (pdu->browse)
+	if (pdu->browse) {
 		len = write(context->browse_fd, pdu->data, pdu->size);
-	else
+		tester_monitor('<', 0x0000, 0x001b, pdu->data, len);
+	} else {
 		len = write(context->fd, pdu->data, pdu->size);
-
-	util_hexdump('<', pdu->data, len, test_debug, "AVRCP: ");
+		tester_monitor('<', 0x0000, 0x0017, pdu->data, len);
+	}
 
 	g_assert_cmpint(len, ==, pdu->size);
 
@@ -215,8 +209,7 @@ static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
 
 	g_assert(len > 0);
 
-	if (g_test_verbose())
-		util_hexdump('>', buf, len, test_debug, "AVRCP: ");
+	tester_monitor('>', 0x0000, 0x0017, buf, len);
 
 	if (!pdu->continuing)
 		g_assert_cmpint(len, ==, pdu->size);
@@ -254,7 +247,7 @@ static gboolean browse_test_handler(GIOChannel *channel, GIOCondition cond,
 
 	g_assert(len > 0);
 
-	util_hexdump('>', buf, len, test_debug, "AVRCP: ");
+	tester_monitor('>', 0x0000, 0x001b, buf, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index c7e28f865..d8d007386 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -382,7 +382,7 @@ static gboolean send_pdu(gpointer user_data)
 
 	len = write(context->fd, pdu->data, pdu->size);
 
-	util_hexdump('<', pdu->data, len, test_debug, "GATT: ");
+	tester_monitor('<', 0x0004, 0x0000, pdu->data, len);
 
 	g_assert_cmpint(len, ==, pdu->size);
 
@@ -440,7 +440,7 @@ static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
 
 	g_assert(len > 0);
 
-	util_hexdump('>', buf, len, test_debug, "GATT: ");
+	tester_monitor('>', 0x0004, 0x0000, buf, len);
 
 	util_hexdump('=', pdu->data, pdu->size, test_debug, "PDU: ");
 
@@ -1910,7 +1910,7 @@ static void test_server(gconstpointer data)
 
 	g_assert_cmpint(len, ==, pdu.size);
 
-	util_hexdump('<', pdu.data, len, test_debug, "GATT: ");
+	tester_monitor('<', 0x0004, 0x0000, pdu.data, len);
 }
 
 static void test_search_primary(gconstpointer data)
diff --git a/unit/test-sdp.c b/unit/test-sdp.c
index ac921a9a0..b67a55189 100644
--- a/unit/test-sdp.c
+++ b/unit/test-sdp.c
@@ -120,13 +120,6 @@ struct context {
 	const struct test_data *data;
 };
 
-static void sdp_debug(const char *str, void *user_data)
-{
-	const char *prefix = user_data;
-
-	tester_debug("%s%s\n", prefix, str);
-}
-
 static void destroy_context(struct context *context)
 {
 	sdp_svcdb_collect_all(context->fd);
@@ -186,7 +179,7 @@ static gboolean server_handler(GIOChannel *channel, GIOCondition cond,
 		return FALSE;
 	}
 
-	util_hexdump('<', buf, len, sdp_debug, "SDP: ");
+	tester_monitor('<', 0x0000, 0x0001, buf, len);
 
 	handle_internal_request(fd, context->data->mtu, buf, len);
 
@@ -254,7 +247,7 @@ static gboolean client_handler(GIOChannel *channel, GIOCondition cond,
 	if (len < 0)
 		return FALSE;
 
-	util_hexdump('>', buf, len, sdp_debug, "SDP: ");
+	tester_monitor('>', 0x0000, 0x0001, buf, len);
 
 	g_assert(len > 0);
 	g_assert((size_t) len == rsp_pdu->raw_size + rsp_pdu->cont_len);
-- 
2.17.1


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

* Re: [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor
  2018-10-05  9:00 [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
  2018-10-05  9:00 ` [PATCH BlueZ 2/3] monitor: Add support for user input/output data Luiz Augusto von Dentz
  2018-10-05  9:00 ` [PATCH BlueZ 3/3] unit: Make use of tester_monitor to print input/output PDUs Luiz Augusto von Dentz
@ 2018-10-08  7:53 ` Luiz Augusto von Dentz
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2018-10-08  7:53 UTC (permalink / raw)
  To: linux-bluetooth

Hi,
On Fri, Oct 5, 2018 at 12:00 PM Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
>
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> This adds option -m/--monitor that can be used toguether with
> tester_monitor to send protocol data to be decoded by btmon.
>
> In addition to that this also logs the tester output into btmon since
> that has support to store its output on file this can be quite
> convenient for reporting.
> ---
>  src/shared/tester.c | 237 ++++++++++++++++++++++++++++++++++++++++----
>  src/shared/tester.h |   4 +
>  2 files changed, 220 insertions(+), 21 deletions(-)
>
> diff --git a/src/shared/tester.c b/src/shared/tester.c
> index 80d65110b..05c81a66d 100644
> --- a/src/shared/tester.c
> +++ b/src/shared/tester.c
> @@ -27,14 +27,19 @@
>
>  #include <stdio.h>
>  #include <errno.h>
> +#include <syslog.h>
>  #include <unistd.h>
>  #include <stdlib.h>
>  #include <string.h>
>  #include <signal.h>
>  #include <sys/signalfd.h>
> +#include <sys/socket.h>
>
>  #include <glib.h>
>
> +#include "lib/bluetooth.h"
> +#include "lib/hci.h"
> +
>  #ifdef HAVE_VALGRIND_MEMCHECK_H
>  #include <valgrind/memcheck.h>
>  #endif
> @@ -54,15 +59,15 @@
>  #define COLOR_HIGHLIGHT        "\x1B[1;39m"
>
>  #define print_text(color, fmt, args...) \
> -               printf(color fmt COLOR_OFF "\n", ## args)
> +               tester_log(color fmt COLOR_OFF, ## args)
>
>  #define print_summary(label, color, value, fmt, args...) \
> -                       printf("%-52s " color "%-10s" COLOR_OFF fmt "\n", \
> +               tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \
>                                                         label, value, ## args)
>
>  #define print_progress(name, color, fmt, args...) \
> -               printf(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \
> -                               color fmt COLOR_OFF "\n", name, ## args)
> +               tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \
> +                               color fmt COLOR_OFF, name, ## args)
>
>  enum test_result {
>         TEST_RESULT_NOT_RUN,
> @@ -100,6 +105,7 @@ struct test_case {
>  };
>
>  static GMainLoop *main_loop;
> +static char *tester_name;
>
>  static GList *test_list;
>  static GList *test_current;
> @@ -108,9 +114,25 @@ static GTimer *test_timer;
>  static gboolean option_version = FALSE;
>  static gboolean option_quiet = FALSE;
>  static gboolean option_debug = FALSE;
> +static gboolean option_monitor = FALSE;
>  static gboolean option_list = FALSE;
>  static const char *option_prefix = NULL;
>
> +struct monitor_hdr {
> +       uint16_t opcode;
> +       uint16_t index;
> +       uint16_t len;
> +       uint8_t  priority;
> +       uint8_t  ident_len;
> +} __attribute__((packed));
> +
> +struct monitor_l2cap_hdr {
> +       uint16_t cid;
> +       uint16_t psm;
> +} __attribute__((packed));
> +
> +static int monitor_fd = -1;
> +
>  static void test_destroy(gpointer data)
>  {
>         struct test_case *test = data;
> @@ -128,43 +150,208 @@ static void test_destroy(gpointer data)
>         free(test);
>  }
>
> -void tester_print(const char *format, ...)
> +static int monitor_open(void)
>  {
> -       va_list ap;
> +       struct sockaddr_hci addr;
> +       int fd;
> +
> +       if (!option_monitor)
> +               return -1;
> +
> +       if (monitor_fd >= 0)
> +               return monitor_fd;
> +
> +       fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> +       if (fd < 0)
> +               return fd;
> +
> +       memset(&addr, 0, sizeof(addr));
> +       addr.hci_family = AF_BLUETOOTH;
> +       addr.hci_dev = HCI_DEV_NONE;
> +       addr.hci_channel = HCI_CHANNEL_LOGGING;
> +
> +       if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
> +               option_monitor = FALSE;
> +               tester_debug("Failed to open monitor socket: %s",
> +                            strerror(errno));
> +               close(fd);
> +               return -1;
> +       }
> +
> +       monitor_fd = fd;
> +
> +       return fd;
> +}
> +
> +static void monitor_sendmsg(const char *label, int level, struct iovec *io,
> +                                                       size_t io_len)
> +{
> +       struct monitor_hdr hdr;
> +       struct msghdr msg;
> +       struct iovec iov[5];
> +       size_t i;
> +
> +       monitor_fd = monitor_open();
> +       if (monitor_fd < 0 || io_len > 3)
> +               return;
> +
> +       hdr.opcode = cpu_to_le16(0x0000);
> +       hdr.index = cpu_to_le16(0xffff);
> +       hdr.ident_len = strlen(label) + 1;
> +       hdr.len = cpu_to_le16(2 + hdr.ident_len);
> +       hdr.priority = level;
> +
> +       iov[0].iov_base = &hdr;
> +       iov[0].iov_len = sizeof(hdr);
> +
> +       iov[1].iov_base = (void *) label;
> +       iov[1].iov_len = hdr.ident_len;
> +
> +       memset(&msg, 0, sizeof(msg));
> +       msg.msg_iov = iov;
> +       msg.msg_iovlen = 2;
> +
> +       for (i = 0; i < io_len; i++) {
> +               iov[i + 2] = io[i];
> +               hdr.len += io[i].iov_len;
> +               msg.msg_iovlen++;
> +       }
>
> +       if (sendmsg(monitor_fd, &msg, 0) < 0) {
> +               /* Disable monitor */
> +               option_monitor = FALSE;
> +               tester_debug("Failed to send to monitor: %s", strerror(errno));
> +               close(monitor_fd);
> +               monitor_fd = -1;
> +       }
> +}
> +
> +static void monitor_vprintf(const char *id, int level, const char *format,
> +                                                               va_list ap)
> +{
> +       struct iovec iov;
> +       char *str;
> +
> +       if (!option_monitor)
> +               return;
> +
> +       if (vasprintf(&str, format, ap) < 0)
> +               return;
> +
> +       iov.iov_base = str;
> +       iov.iov_len = strlen(str) + 1;
> +
> +       monitor_sendmsg(id, level, &iov, 1);
> +
> +       free(str);
> +}
> +
> +static void tester_vprintf(const char *format, va_list ap)
> +{
>         if (tester_use_quiet())
>                 return;
>
>         printf("  %s", COLOR_WHITE);
> +       vprintf(format, ap);
> +       printf("%s\n", COLOR_OFF);
> +}
> +
> +static void tester_log(const char *format, ...)
> +{
> +       va_list ap;
> +
>         va_start(ap, format);
>         vprintf(format, ap);
> +       printf("\n");
> +       va_end(ap);
> +
> +       va_start(ap, format);
> +       monitor_vprintf(tester_name, LOG_INFO, format, ap);
> +       va_end(ap);
> +}
> +
> +void tester_print(const char *format, ...)
> +{
> +       va_list ap;
> +
> +       va_start(ap, format);
> +       tester_vprintf(format, ap);
> +       va_end(ap);
> +
> +       va_start(ap, format);
> +       monitor_vprintf(tester_name, LOG_INFO, format, ap);
>         va_end(ap);
> -       printf("%s\n", COLOR_OFF);
>  }
>
>  void tester_debug(const char *format, ...)
>  {
>         va_list ap;
>
> -       if (!tester_use_debug())
> -               return;
> +       va_start(ap, format);
> +       tester_vprintf(format, ap);
> +       va_end(ap);
>
> -       printf("  %s", COLOR_WHITE);
>         va_start(ap, format);
> -       vprintf(format, ap);
> +       monitor_vprintf(tester_name, LOG_DEBUG, format, ap);
>         va_end(ap);
> -       printf("%s\n", COLOR_OFF);
>  }
>
>  void tester_warn(const char *format, ...)
>  {
>         va_list ap;
>
> -       printf("  %s", COLOR_WHITE);
>         va_start(ap, format);
> -       vprintf(format, ap);
> +       tester_vprintf(format, ap);
> +       va_end(ap);
> +
> +       va_start(ap, format);
> +       monitor_vprintf(tester_name, LOG_WARNING, format, ap);
>         va_end(ap);
> -       printf("%s\n", COLOR_OFF);
> +}
> +
> +static void monitor_debug(const char *str, void *user_data)
> +{
> +       const char *label = user_data;
> +
> +       tester_debug("%s: %s", label, str);
> +}
> +
> +static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data,
> +                                                               size_t len)
> +{
> +       struct iovec iov[3];
> +       struct monitor_l2cap_hdr hdr;
> +       uint8_t term = 0x00;
> +       char label[16];
> +
> +       if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0)
> +               return;
> +
> +       hdr.cid = cpu_to_le16(cid);
> +       hdr.psm = cpu_to_le16(psm);
> +
> +       iov[0].iov_base = &hdr;
> +       iov[0].iov_len = sizeof(hdr);
> +
> +       iov[1].iov_base = (void *) data;
> +       iov[1].iov_len = len;
> +
> +       /* Kernel won't forward if data is no NULL terminated */
> +       iov[2].iov_base = &term;
> +       iov[2].iov_len = sizeof(term);
> +
> +       monitor_sendmsg(label, LOG_INFO, iov, 3);
> +}
> +
> +void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data,
> +                                                               size_t len)
> +{
> +       monitor_log(dir, cid, psm, data, len);
> +
> +       if (!tester_use_debug())
> +               return;
> +
> +       util_hexdump(dir, data, len, monitor_debug, (void *) tester_name);
>  }
>
>  static void default_pre_setup(const void *test_data)
> @@ -208,7 +395,7 @@ void tester_add_full(const char *name, const void *test_data,
>         }
>
>         if (option_list) {
> -               printf("%s\n", name);
> +               tester_log("%s", name);
>                 if (destroy)
>                         destroy(user_data);
>                 return;
> @@ -278,7 +465,7 @@ static int tester_summarize(void)
>         gdouble execution_time;
>         GList *list;
>
> -       printf("\n");
> +       tester_log("");
>         print_text(COLOR_HIGHLIGHT, "");
>         print_text(COLOR_HIGHLIGHT, "Test Summary");
>         print_text(COLOR_HIGHLIGHT, "------------");
> @@ -312,17 +499,17 @@ static int tester_summarize(void)
>                 }
>          }
>
> -       printf("\nTotal: %d, "
> +       tester_log("Total: %d, "
>                 COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", "
>                 COLOR_RED "Failed: %d" COLOR_OFF ", "
> -               COLOR_YELLOW "Not Run: %d" COLOR_OFF "\n",
> +               COLOR_YELLOW "Not Run: %d" COLOR_OFF,
>                         not_run + passed + failed, passed,
>                         (not_run + passed + failed) ?
>                         (float) passed * 100 / (not_run + passed + failed) : 0,
>                         failed, not_run);
>
>         execution_time = g_timer_elapsed(test_timer, NULL);
> -       printf("Overall execution time: %.3g seconds\n", execution_time);
> +       tester_log("Overall execution time: %.3g seconds", execution_time);
>
>         return failed;
>  }
> @@ -379,7 +566,7 @@ static void next_test_case(void)
>
>         test = test_current->data;
>
> -       printf("\n");
> +       tester_log("");
>         print_progress(test->name, COLOR_BLACK, "init");
>
>         test->start_time = g_timer_elapsed(test_timer, NULL);
> @@ -774,6 +961,8 @@ static GOptionEntry options[] = {
>                                 "Run tests without logging" },
>         { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug,
>                                 "Run tests with debug output" },
> +       { "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor,
> +                               "Enable monitor output" },
>         { "list", 'l', 0, G_OPTION_ARG_NONE, &option_list,
>                                 "Only list the tests to be run" },
>         { "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix,
> @@ -807,6 +996,12 @@ void tester_init(int *argc, char ***argv)
>
>         main_loop = g_main_loop_new(NULL, FALSE);
>
> +       tester_name = strrchr(*argv[0], '/');
> +       if (!tester_name)
> +               tester_name = strdup(*argv[0]);
> +       else
> +               tester_name = strdup(++tester_name);
> +
>         test_list = NULL;
>         test_current = NULL;
>  }
> diff --git a/src/shared/tester.h b/src/shared/tester.h
> index 83ef5de7a..96e8dc901 100644
> --- a/src/shared/tester.h
> +++ b/src/shared/tester.h
> @@ -22,6 +22,8 @@
>   */
>
>  #include <stdbool.h>
> +#include <stddef.h>
> +#include <stdint.h>
>
>  void tester_init(int *argc, char ***argv);
>  int tester_run(void);
> @@ -35,6 +37,8 @@ void tester_warn(const char *format, ...)
>                                 __attribute__((format(printf, 1, 2)));
>  void tester_debug(const char *format, ...)
>                                 __attribute__((format(printf, 1, 2)));
> +void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data,
> +                                                               size_t len);
>
>  typedef void (*tester_destroy_func_t)(void *user_data);
>  typedef void (*tester_data_func_t)(const void *test_data);
> --
> 2.17.1

Applied.

-- 
Luiz Augusto von Dentz

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

end of thread, other threads:[~2018-10-08  7:53 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-05  9:00 [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz
2018-10-05  9:00 ` [PATCH BlueZ 2/3] monitor: Add support for user input/output data Luiz Augusto von Dentz
2018-10-05  9:00 ` [PATCH BlueZ 3/3] unit: Make use of tester_monitor to print input/output PDUs Luiz Augusto von Dentz
2018-10-08  7:53 ` [PATCH BlueZ 1/3] shared/tester: Add option to print to monitor Luiz Augusto von Dentz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).