// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google, Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "src/shared/util.h" #include "lib/bluetooth.h" #include "lib/uuid.h" #include "attrib/att.h" #include "attrib/gattrib.h" #include "src/log.h" #define DEFAULT_MTU 23 #define data(args...) ((const unsigned char[]) { args }) struct test_pdu { bool valid; bool sent; bool received; const uint8_t *data; size_t size; }; #define pdu(args...) \ { \ .valid = true, \ .sent = false, \ .received = false, \ .data = data(args), \ .size = sizeof(data(args)), \ } struct context { GMainLoop *main_loop; GIOChannel *att_io; GIOChannel *server_io; GAttrib *att; }; static void setup_context(struct context *cxt, gconstpointer data) { int err, sv[2]; cxt->main_loop = g_main_loop_new(NULL, FALSE); g_assert(cxt->main_loop != NULL); err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sv); g_assert(err == 0); cxt->att_io = g_io_channel_unix_new(sv[0]); g_assert(cxt->att_io != NULL); g_io_channel_set_close_on_unref(cxt->att_io, TRUE); cxt->server_io = g_io_channel_unix_new(sv[1]); g_assert(cxt->server_io != NULL); g_io_channel_set_close_on_unref(cxt->server_io, TRUE); g_io_channel_set_encoding(cxt->server_io, NULL, NULL); g_io_channel_set_buffered(cxt->server_io, FALSE); cxt->att = g_attrib_new(cxt->att_io, DEFAULT_MTU, false); g_assert(cxt->att != NULL); } static void teardown_context(struct context *cxt, gconstpointer data) { if (cxt->att) g_attrib_unref(cxt->att); g_io_channel_unref(cxt->server_io); g_io_channel_unref(cxt->att_io); g_main_loop_unref(cxt->main_loop); } static void test_debug(const char *str, void *user_data) { const char *prefix = user_data; g_print("%s%s\n", prefix, str); } static void destroy_canary_increment(gpointer data) { int *canary = data; (*canary)++; } static void test_refcount(struct context *cxt, gconstpointer unused) { GAttrib *extra_ref; int destroy_canary = 0; g_attrib_set_destroy_function(cxt->att, destroy_canary_increment, &destroy_canary); extra_ref = g_attrib_ref(cxt->att); g_assert(extra_ref == cxt->att); g_assert(destroy_canary == 0); g_attrib_unref(extra_ref); g_assert(destroy_canary == 0); g_attrib_unref(cxt->att); g_assert(destroy_canary == 1); /* Avoid a double-free from the teardown function */ cxt->att = NULL; } static void test_get_channel(struct context *cxt, gconstpointer unused) { GIOChannel *chan; chan = g_attrib_get_channel(cxt->att); g_assert(chan == cxt->att_io); } struct expect_response { struct test_pdu expect; struct test_pdu respond; GSourceFunc receive_cb; gpointer user_data; }; static gboolean test_client(GIOChannel *channel, GIOCondition cond, gpointer data) { struct expect_response *cr = data; int fd; uint8_t buf[256]; ssize_t len; int cmp; if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) return FALSE; fd = g_io_channel_unix_get_fd(channel); len = read(fd, buf, sizeof(buf)); g_assert(len > 0); g_assert_cmpint(len, ==, cr->expect.size); if (g_test_verbose()) util_hexdump('?', cr->expect.data, cr->expect.size, test_debug, "test_client: "); cmp = memcmp(cr->expect.data, buf, len); g_assert(cmp == 0); cr->expect.received = true; if (cr->receive_cb != NULL) cr->receive_cb(cr->user_data); if (cr->respond.valid) { if (g_test_verbose()) util_hexdump('<', cr->respond.data, cr->respond.size, test_debug, "test_client: "); len = write(fd, cr->respond.data, cr->respond.size); g_assert_cmpint(len, ==, cr->respond.size); cr->respond.sent = true; } return TRUE; } struct result_data { guint8 status; guint8 *pdu; guint16 len; GSourceFunc complete_cb; gpointer user_data; }; static void result_canary(guint8 status, const guint8 *pdu, guint16 len, gpointer data) { struct result_data *result = data; result->status = status; result->pdu = g_malloc0(len); memcpy(result->pdu, pdu, len); result->len = len; if (g_test_verbose()) util_hexdump('<', pdu, len, test_debug, "result_canary: "); if (result->complete_cb != NULL) result->complete_cb(result->user_data); } static gboolean context_stop_main_loop(gpointer user_data) { struct context *cxt = user_data; g_main_loop_quit(cxt->main_loop); return FALSE; } static void test_send(struct context *cxt, gconstpointer unused) { int cmp; struct result_data results; struct expect_response data = { .expect = pdu(0x02, 0x00, 0x02), .respond = pdu(0x03, 0x02, 0x03, 0x04), .receive_cb = NULL, .user_data = NULL, }; g_io_add_watch(cxt->server_io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, test_client, &data); results.complete_cb = context_stop_main_loop; results.user_data = cxt; g_attrib_send(cxt->att, 0, data.expect.data, data.expect.size, result_canary, (gpointer) &results, NULL); g_main_loop_run(cxt->main_loop); g_assert(results.pdu != NULL); g_assert_cmpint(results.len, ==, data.respond.size); cmp = memcmp(results.pdu, data.respond.data, results.len); g_assert(cmp == 0); g_free(results.pdu); } struct event_info { struct context *context; int event_id; }; static gboolean cancel_existing_attrib_event(gpointer user_data) { struct event_info *info = user_data; gboolean canceled; canceled = g_attrib_cancel(info->context->att, info->event_id); g_assert(canceled); g_idle_add(context_stop_main_loop, info->context); return FALSE; } static void test_cancel(struct context *cxt, gconstpointer unused) { gboolean canceled; struct result_data results; struct event_info info; struct expect_response data = { .expect = pdu(0x02, 0x00, 0x02), .respond = pdu(0x03, 0x02, 0x03, 0x04), }; g_io_add_watch(cxt->server_io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, test_client, &data); results.pdu = NULL; info.context = cxt; info.event_id = g_attrib_send(cxt->att, 0, data.expect.data, data.expect.size, result_canary, &results, NULL); data.receive_cb = cancel_existing_attrib_event; data.user_data = &info; g_main_loop_run(cxt->main_loop); g_assert(results.pdu == NULL); results.pdu = NULL; data.expect.received = false; data.respond.sent = false; info.event_id = g_attrib_send(cxt->att, 0, data.expect.data, data.expect.size, result_canary, &results, NULL); canceled = g_attrib_cancel(cxt->att, info.event_id); g_assert(canceled); g_idle_add(context_stop_main_loop, info.context); g_main_loop_run(cxt->main_loop); g_assert(!data.expect.received); g_assert(!data.respond.sent); g_assert(results.pdu == NULL); /* Invalid ID */ canceled = g_attrib_cancel(cxt->att, 42); g_assert(!canceled); } static void send_test_pdus(gpointer context, struct test_pdu *pdus) { struct context *cxt = context; size_t len; int fd; struct test_pdu *cur_pdu; fd = g_io_channel_unix_get_fd(cxt->server_io); for (cur_pdu = pdus; cur_pdu->valid; cur_pdu++) cur_pdu->sent = false; for (cur_pdu = pdus; cur_pdu->valid; cur_pdu++) { if (g_test_verbose()) util_hexdump('>', cur_pdu->data, cur_pdu->size, test_debug, "send_test_pdus: "); len = write(fd, cur_pdu->data, cur_pdu->size); g_assert_cmpint(len, ==, cur_pdu->size); cur_pdu->sent = true; } g_idle_add(context_stop_main_loop, cxt); g_main_loop_run(cxt->main_loop); } #define PDU_MTU_RESP pdu(ATT_OP_MTU_RESP, 0x17) #define PDU_FIND_INFO_REQ pdu(ATT_OP_FIND_INFO_REQ, 0x01, 0x00, 0xFF, 0xFF) #define PDU_NO_ATT_ERR pdu(ATT_OP_ERROR, ATT_OP_FIND_INFO_REQ, 0x00, 0x00, 0x0A) #define PDU_IND_NODATA pdu(ATT_OP_HANDLE_IND, 0x01, 0x00) #define PDU_INVALID_IND pdu(ATT_OP_HANDLE_IND, 0x14) #define PDU_IND_DATA pdu(ATT_OP_HANDLE_IND, 0x14, 0x00, 0x01) struct expect_test_data { struct test_pdu *expected; GAttrib *att; }; static void notify_canary_expect(const guint8 *pdu, guint16 len, gpointer data) { struct expect_test_data *expect = data; struct test_pdu *expected = expect->expected; int cmp; if (g_test_verbose()) util_hexdump('<', pdu, len, test_debug, "notify_canary_expect: "); while (expected->valid && expected->received) expected++; g_assert(expected->valid); if (g_test_verbose()) util_hexdump('?', expected->data, expected->size, test_debug, "notify_canary_expect: "); g_assert_cmpint(expected->size, ==, len); cmp = memcmp(pdu, expected->data, expected->size); g_assert(cmp == 0); expected->received = true; if (pdu[0] == ATT_OP_FIND_INFO_REQ) { struct test_pdu no_attributes = PDU_NO_ATT_ERR; int reqid; reqid = g_attrib_send(expect->att, 0, no_attributes.data, no_attributes.size, NULL, NULL, NULL); g_assert(reqid != 0); } } static void test_register(struct context *cxt, gconstpointer user_data) { guint reg_id; gboolean canceled; struct test_pdu pdus[] = { /* * Unmatched PDU opcode * Unmatched handle (GATTRIB_ALL_REQS) */ PDU_FIND_INFO_REQ, /* * Matched PDU opcode * Unmatched handle (GATTRIB_ALL_HANDLES) */ PDU_IND_NODATA, /* * Matched PDU opcode * Invalid length? */ PDU_INVALID_IND, /* * Matched PDU opcode * Matched handle */ PDU_IND_DATA, { }, }; struct test_pdu req_pdus[] = { PDU_FIND_INFO_REQ, { } }; struct test_pdu all_ind_pdus[] = { PDU_IND_NODATA, PDU_INVALID_IND, PDU_IND_DATA, { }, }; struct test_pdu followed_ind_pdus[] = { PDU_IND_DATA, { } }; struct test_pdu *current_pdu; struct expect_test_data expect; expect.att = cxt->att; /* * Without registering anything, should be able to ignore everything but * an unexpected response. */ send_test_pdus(cxt, pdus); if (g_test_verbose()) g_print("ALL_REQS, ALL_HANDLES\r\n"); expect.expected = req_pdus; reg_id = g_attrib_register(cxt->att, GATTRIB_ALL_REQS, GATTRIB_ALL_HANDLES, notify_canary_expect, &expect, NULL); send_test_pdus(cxt, pdus); canceled = g_attrib_unregister(cxt->att, reg_id); g_assert(canceled); for (current_pdu = req_pdus; current_pdu->valid; current_pdu++) g_assert(current_pdu->received); if (g_test_verbose()) g_print("IND, ALL_HANDLES\r\n"); expect.expected = all_ind_pdus; reg_id = g_attrib_register(cxt->att, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, notify_canary_expect, &expect, NULL); send_test_pdus(cxt, pdus); canceled = g_attrib_unregister(cxt->att, reg_id); g_assert(canceled); for (current_pdu = all_ind_pdus; current_pdu->valid; current_pdu++) g_assert(current_pdu->received); if (g_test_verbose()) g_print("IND, 0x0014\r\n"); expect.expected = followed_ind_pdus; reg_id = g_attrib_register(cxt->att, ATT_OP_HANDLE_IND, 0x0014, notify_canary_expect, &expect, NULL); send_test_pdus(cxt, pdus); canceled = g_attrib_unregister(cxt->att, reg_id); g_assert(canceled); for (current_pdu = followed_ind_pdus; current_pdu->valid; current_pdu++) g_assert(current_pdu->received); canceled = g_attrib_unregister(cxt->att, reg_id); g_assert(!canceled); } static void test_buffers(struct context *cxt, gconstpointer unused) { size_t buflen; uint8_t *buf; gboolean success; buf = g_attrib_get_buffer(cxt->att, &buflen); g_assert(buf != 0); g_assert_cmpint(buflen, ==, DEFAULT_MTU); success = g_attrib_set_mtu(cxt->att, 5); g_assert(!success); success = g_attrib_set_mtu(cxt->att, 255); g_assert(success); buf = g_attrib_get_buffer(cxt->att, &buflen); g_assert(buf != 0); g_assert_cmpint(buflen, ==, 255); } int main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); if (g_test_verbose()) __btd_log_init("*", 0); /* * Test the GAttrib API behavior */ g_test_add("/gattrib/refcount", struct context, NULL, setup_context, test_refcount, teardown_context); g_test_add("/gattrib/get_channel", struct context, NULL, setup_context, test_get_channel, teardown_context); g_test_add("/gattrib/send", struct context, NULL, setup_context, test_send, teardown_context); g_test_add("/gattrib/cancel", struct context, NULL, setup_context, test_cancel, teardown_context); g_test_add("/gattrib/register", struct context, NULL, setup_context, test_register, teardown_context); g_test_add("/gattrib/buffers", struct context, NULL, setup_context, test_buffers, teardown_context); return g_test_run(); }