@@ -190,6 +190,301 @@ struct ccc_state {
DISC_BCAST_AUDIO_SCAN_CP, \
BASS_READ_CHAR_DESC
+/* ATT: Write Request (0x12) len 4
+ * Handle: 0x0004 Type: Client Characteristic Configuration (0x2902)
+ * Data: 0100
+ * Notification (0x01)
+ * ATT: Write Response (0x13) len 0
+ * ATT: Write Request (0x12) len 4
+ * Handle: 0x0007 Type: Client Characteristic Configuration (0x2902)
+ * Data: 0100
+ * Notification (0x01)
+ * ATT: Write Response (0x13) len 0
+ */
+#define BASS_WRITE_CHAR_DESC \
+ IOV_DATA(0x12, 0x04, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13), \
+ IOV_DATA(0x12, 0x07, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13)
+
+/* ATT: Read Request (0x0a) len 2
+ * Handle: 0x0003 Type: Broadcast Receive State (0x2bc8)
+ * ATT: Read Response (0x0b) len 0
+ * Handle: 0x0003 Broadcast Receive State (0x2bc8)
+ * ATT: Read Request (0x0a) len 2
+ * Handle: 0x0006 Type: Broadcast Receive State (0x2bc8)
+ * ATT: Read Response (0x0b) len 0
+ * Handle: 0x0006 Broadcast Receive State (0x2bc8)
+ */
+#define BASS_READ_BCAST_RECV_STATE_CHARS \
+ IOV_DATA(0x0a, 0x03, 0x00), \
+ IOV_DATA(0x0b), \
+ IOV_DATA(0x0a, 0x06, 0x00), \
+ IOV_DATA(0x0b)
+
+#define BASS_CP_WRITE_CMD(_op, _args...) \
+ IOV_DATA(0x52, 0x09, 0x00, _op, _args)
+
+#define BASS_CP_WRITE_REQ(_op, _args...) \
+ IOV_DATA(0x12, 0x09, 0x00, _op, _args)
+
+/* ATT: Write Command (0x52) len 19
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0401693C4572685526613465597073275455
+ * Opcode: Set Broadcast_Code
+ * Source_ID: 1
+ * Broadcast_Code: 0x55542773705965346126556872453c69
+ * ATT: Write Command (0x52) len 2
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0501
+ * Opcode: Remove Source
+ * Source_ID: 1
+ */
+#define IGNORE_INVALID_SRC_ID \
+ EXCHANGE_MTU, \
+ BASS_FIND_BY_TYPE_VALUE, \
+ DISC_BASS_CHAR, \
+ BASS_FIND_INFO, \
+ BASS_WRITE_CHAR_DESC, \
+ BASS_READ_BCAST_RECV_STATE_CHARS, \
+ BASS_CP_WRITE_CMD(0x04, 0x01, 0x69, 0x3C, 0x45, 0x72, 0x68, \
+ 0x55, 0x26, 0x61, 0x34, 0x65, 0x59, 0x70, \
+ 0x73, 0x27, 0x54, 0x55), \
+ IOV_NULL, \
+ BASS_CP_WRITE_CMD(0x05, 0x01)
+
+/* ATT: Write Command (0x52) len 26
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0200F2698BE807C0003412000610270200000000000000000000
+ * Opcode: Add Source
+ * Advertiser_Address_Type: Public Device or Public Identity Address
+ * Advertiser_Address: c0:07:e8:8b:69:f2
+ * Advertising_SID: 0x00
+ * Broadcast_ID: 0x001234
+ * PA_Sync: 0x06 (Reserved for Future Use)
+ * PA_Interval: 0x2710
+ * Num_Subgroups: 2
+ * Subgroup #0:
+ * BIS_Sync: 00000000000000000000000000000000
+ * Metadata_Length: 0
+ * Subgroup #1:
+ * BIS_Sync: 00000000000000000000000000000000
+ * Metadata_Length: 0
+ * ATT: Write Command (0x52) len 26
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0205F2698BE807C0003412000210270200000000000000000000
+ * Opcode: Add Source
+ * Advertiser_Address_Type: 0x05 (Reserved for Future Use)
+ * Advertiser_Address: c0:07:e8:8b:69:f2
+ * Advertising_SID: 0x00
+ * Broadcast_ID: 0x001234
+ * PA_Sync: Synchronize to PA (PAST not available)
+ * PA_Interval: 0x2710
+ * Num_Subgroups: 2
+ * Subgroup #0:
+ * BIS_Sync: 00000000000000000000000000000000
+ * Metadata_Length: 0
+ * Subgroup #1:
+ * BIS_Sync: 00000000000000000000000000000000
+ * Metadata_Length: 0
+ * ATT: Write Command (0x52) len 26
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0200F2698BE807C0003412000210270201000000000100000000
+ * Opcode: Add Source
+ * Advertiser_Address_Type: Public Device or Public Identity Address
+ * Advertiser_Address: c0:07:e8:8b:69:f2
+ * Advertising_SID: 0x00
+ * Broadcast_ID: 0x001234
+ * PA_Sync: Synchronize to PA (PAST not available)
+ * PA_Interval: 0x2710
+ * Num_Subgroups: 2
+ * Subgroup #0:
+ * BIS_Sync: 00000000000000000000000000000001
+ * Metadata_Length: 0
+ * Subgroup #1:
+ * BIS_Sync: 00000000000000000000000000000001
+ * Metadata_Length: 0
+ */
+#define ADD_SRC_INVALID_PARAMS \
+ EXCHANGE_MTU, \
+ BASS_FIND_BY_TYPE_VALUE, \
+ DISC_BASS_CHAR, \
+ BASS_FIND_INFO, \
+ BASS_WRITE_CHAR_DESC,\
+ BASS_READ_BCAST_RECV_STATE_CHARS, \
+ BASS_CP_WRITE_CMD(0x02, 0x00, 0xF2, 0x69, 0x8B, 0xE8, 0x07, 0xC0, \
+ 0x00, 0x34, 0x12, 0x00, 0x06, 0x10, 0x27, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00), \
+ IOV_NULL, \
+ BASS_CP_WRITE_CMD(0x02, 0x05, 0xF2, 0x69, 0x8B, 0xE8, 0x07, 0xC0, \
+ 0x00, 0x34, 0x12, 0x00, 0x02, 0x10, 0x27, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00), \
+ IOV_NULL, \
+ BASS_CP_WRITE_CMD(0x02, 0x05, 0xF2, 0x69, 0x8B, 0xE8, 0x07, 0xC0, \
+ 0x3F, 0x34, 0x12, 0x00, 0x02, 0x10, 0x27, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00), \
+ IOV_NULL, \
+ BASS_CP_WRITE_CMD(0x02, 0x00, 0xF2, 0x69, 0x8B, 0xE8, 0x07, 0xC0, \
+ 0x00, 0x34, 0x12, 0x00, 0x02, 0x10, 0x27, 0x02, \
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, \
+ 0x00, 0x00)
+
+/* ATT: Write Request (0x12) len 3
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: FF
+ * Opcode: 0xff (Reserved For Future Use)
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Opcode Not Supported (0x80)
+ */
+#define OPCODE_NOT_SUPPORTED \
+ EXCHANGE_MTU, \
+ BASS_FIND_BY_TYPE_VALUE, \
+ DISC_BASS_CHAR, \
+ BASS_FIND_INFO, \
+ BASS_WRITE_CHAR_DESC,\
+ BASS_READ_BCAST_RECV_STATE_CHARS, \
+ BASS_CP_WRITE_REQ(0xFF), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0x80)
+
+/* ATT: Write Request (0x12) len 5
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 006dfe
+ * Opcode: Remote Scan Stopped
+ * Extra Data: 0xfe6d
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ * ATT: Write Request (0x12) len 5
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 006dfe
+ * Opcode: Remote Scan Started
+ * Extra Data: 0xa2c2
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ * ATT: Write Request (0x12) len 25
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0200F2698BE807C0003412000210270100000000000000
+ * Opcode: Add Source
+ * Advertiser_Address_Type: Public Device or Public Identity Address
+ * Advertiser_Address: c0:07:e8:8b:69:f2
+ * Advertising_SID: 0x00
+ * Broadcast_ID: 0x001234
+ * PA_Sync: Synchronize to PA (PAST not available)
+ * PA_Interval: 0x2710
+ * Num_Subgroups: 1
+ * Subgroup #0:
+ * BIS_Sync: 00000000000000000000000000000001
+ * Metadata_Length: 0
+ * Extra Data: 0x0000
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ * ATT: Write Request (0x12) len 13
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 03000210270100000000001500
+ * Opcode: Modify Source
+ * Source_ID: 0x00
+ * PA_Sync: Synchronize to PA (PAST not available)
+ * PA_Interval: 0x2710
+ * Num_Subgroups: 1
+ * Subgroup #0:
+ * BIS_Sync: 00000000000000000000000000000001
+ * Metadata_Length: 0
+ * Extra Data: 0x0015
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ * ATT: Write Request (0x12) len 20
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0400B803EAC6AFBB65A25A41F153056802010000
+ * Opcode: Set Broadcast_Code
+ * Source_ID: 0x00
+ * Broadcast_Code: 0x0102680553f1415aa265bbafc6ea03b8
+ * Extra Data: 0x0000
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ * ATT: Write Request (0x12) len 4
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 05008F13
+ * Opcode: Remove Source
+ * Source_ID: 0x00
+ * Extra Data: 0x138f
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Write Request Rejected (0xFC)
+ */
+#define INVALID_LEN \
+ EXCHANGE_MTU, \
+ BASS_FIND_BY_TYPE_VALUE, \
+ DISC_BASS_CHAR, \
+ BASS_FIND_INFO, \
+ BASS_WRITE_CHAR_DESC,\
+ BASS_READ_BCAST_RECV_STATE_CHARS, \
+ BASS_CP_WRITE_REQ(0x00, 0x6D, 0xFE), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC), \
+ BASS_CP_WRITE_REQ(0x01, 0xC2, 0xA2), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC), \
+ BASS_CP_WRITE_REQ(0x02, 0x00, 0xF2, 0x69, 0x8B, 0xE8, 0x07, 0xC0, \
+ 0x00, 0x34, 0x12, 0x00, 0x02, 0x10, 0x27, 0x01, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC), \
+ BASS_CP_WRITE_REQ(0x03, 0x00, 0x02, 0x10, 0x27, 0x01, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x15, 0x00), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC), \
+ BASS_CP_WRITE_REQ(0x04, 0x00, 0xB8, 0x03, 0xEA, 0xC6, 0xAF, 0xBB, \
+ 0x65, 0xA2, 0x5A, 0x41, 0xF1, 0x53, 0x05, 0x68, \
+ 0x02, 0x01, 0x00, 0x00), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC), \
+ BASS_CP_WRITE_REQ(0x05, 0x00, 0x8F, 0x13), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0xFC)
+
+/* ATT: Write Request (0x12) len 20
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 0400B803EAC6AFBB65A25A41F153056802010000
+ * Opcode: Set Broadcast_Code
+ * Source_ID: 0x05
+ * Broadcast_Code: 0x0102680553f1415aa265bbafc6ea03b
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Invalid Source ID (0x81)
+ * ATT: Write Request (0x12) len 4
+ * Handle: 0x0009 Type: Broadcast Audio Scan Control Point (0x2bc7)
+ * Data: 005
+ * Opcode: Remove Source
+ * Source_ID: 0x05
+ * ATT: Error Response (0x01) len 4
+ * Write Request (0x12)
+ * Handle: 0x0009
+ * Error: Invalid Source ID (0x81)
+ */
+#define INVALID_SRC_ID \
+ EXCHANGE_MTU, \
+ BASS_FIND_BY_TYPE_VALUE, \
+ DISC_BASS_CHAR, \
+ BASS_FIND_INFO, \
+ BASS_WRITE_CHAR_DESC, \
+ BASS_READ_BCAST_RECV_STATE_CHARS, \
+ BASS_CP_WRITE_REQ(0x04, 0x05, 0xB8, 0x03, 0xEA, 0xC6, 0xAF, 0xBB, \
+ 0x65, 0xA2, 0x5A, 0x41, 0xF1, 0x53, 0x05, 0x68, \
+ 0x02, 0x01), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0x81), \
+ BASS_CP_WRITE_REQ(0x05, 0x05), \
+ IOV_DATA(0x01, 0x12, 0x09, 0x00, 0x81)
+
#define iov_data(args...) ((const struct iovec[]) { args })
#define define_test(name, function, _cfg, args...) \
@@ -287,6 +582,63 @@ done:
gatt_db_attribute_read_result(attrib, id, ecode, value, len);
}
+static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct ccc_state *ccc_state;
+ uint16_t val;
+ uint8_t ecode = 0;
+
+ if (!value || len > 2) {
+ ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+ goto done;
+ }
+
+ if (offset > 2) {
+ ecode = BT_ATT_ERROR_INVALID_OFFSET;
+ goto done;
+ }
+
+ if (len == 1)
+ val = *value;
+ else
+ val = get_le16(value);
+
+ ccc_state = get_ccc_state(data, gatt_db_attribute_get_handle(attrib));
+ if (!ccc_state)
+ return;
+
+ /* If value is identical, then just succeed */
+ if (val == ccc_state->value)
+ goto done;
+
+ ccc_state->value = val;
+
+done:
+ gatt_db_attribute_write_result(attrib, id, ecode);
+}
+
+static void gatt_notify_cb(struct gatt_db_attribute *attrib,
+ struct gatt_db_attribute *ccc,
+ const uint8_t *value, size_t len,
+ struct bt_att *att, void *user_data)
+{
+ struct test_data *data = user_data;
+ struct ccc_state *ccc_state;
+
+ ccc_state = find_ccc_state(data, gatt_db_attribute_get_handle(ccc));
+ if (!ccc_state || !(ccc_state->value & 0x0001))
+ return;
+
+ bt_gatt_server_send_notification(data->server,
+ gatt_db_attribute_get_handle(attrib),
+ value, len, false);
+}
+
static void test_server(const void *user_data)
{
struct test_data *data = (void *)user_data;
@@ -306,12 +658,17 @@ static void test_server(const void *user_data)
data->db = gatt_db_new();
g_assert(data->db);
- gatt_db_ccc_register(data->db, gatt_ccc_read_cb, NULL,
- NULL, data);
+ gatt_db_ccc_register(data->db, gatt_ccc_read_cb, gatt_ccc_write_cb,
+ gatt_notify_cb, data);
data->bass = bt_bass_new(data->db, NULL, BDADDR_ANY);
g_assert(data->bass);
+ bt_bass_set_att(data->bass, att);
+ bt_bass_attach(data->bass, NULL);
+
+ bt_bass_set_debug(data->bass, print_debug, "bt_bass:", NULL);
+
data->server = bt_gatt_server_new(data->db, att, 64, 0);
g_assert(data->server);
@@ -387,11 +744,80 @@ static void test_sggit(void)
DISC_BCAST_RECV_STATE);
}
+static void test_spe(void)
+{
+ /* BASS/SR/SPE/BI-01-C [Ignore Invalid Source ID]
+ *
+ * Test Purpose:
+ * Verify that the BASS Server IUT does not respond to a control point
+ * procedure call that uses an invalid Source_ID parameter.
+ *
+ * Pass verdict:
+ * The IUT does not send a notification of the Broadcast Receive State
+ * characteristic.
+ */
+ define_test("BASS/SR/SPE/BI-01-C", test_server, NULL,
+ IGNORE_INVALID_SRC_ID);
+
+ /* BASS/SR/SPE/BI-03-C [Add Source - Ignore Invalid Values]
+ *
+ * Test Purpose:
+ * Verify that the BASS Server IUT ignores Add Source control point
+ * procedure calls that include an RFU or Invalid parameter.
+ *
+ * Pass verdict:
+ * The IUT does not send a notification of the Broadcast Receive State
+ * characteristic.
+ */
+ define_test("BASS/SR/SPE/BI-03-C", test_server, NULL,
+ ADD_SRC_INVALID_PARAMS);
+
+ /* BASS/SR/SPE/BI-04-C [Opcode Not Supported]
+ *
+ * Test Purpose:
+ * Verify that the BASS Server IUT returns an Opcode Not Supported error
+ * response when the opcode written is not supported by the IUT or is
+ * within a range that is reserved for future use being written to the
+ * Broadcast Audio Scan Control Point.
+ *
+ * Pass verdict:
+ * The IUT sends an error response of OPCODE NOT SUPPORTED.
+ */
+ define_test("BASS/SR/SPE/BI-04-C", test_server, NULL,
+ OPCODE_NOT_SUPPORTED);
+
+ /* BASS/SR/SPE/BI-06-C [Invalid Length]
+ *
+ * Test Purpose:
+ * Verify that the BASS Server IUT rejects writing of an opcode with
+ * an invalid length.
+ *
+ * Pass verdict:
+ * The IUT rejects the opcode.
+ */
+ define_test("BASS/SR/SPE/BI-06-C", test_server, NULL,
+ INVALID_LEN);
+
+ /* BASS/SR/SPE/BI-07-C [Invalid Source ID]
+ *
+ * Test Purpose:
+ * Verify that the BASS Server IUT returns an error when a control
+ * point procedure passing an invalid Source_ID parameter is called.
+ *
+ * Pass verdict:
+ * The IUT sends an ATT Error Response with the Error Code set to
+ * Invalid Source_ID.
+ */
+ define_test("BASS/SR/SPE/BI-07-C", test_server, NULL,
+ INVALID_SRC_ID);
+}
+
int main(int argc, char *argv[])
{
tester_init(&argc, &argv);
test_sggit();
+ test_spe();
return tester_run();
}