blob: c75333fb87b61c603549319bbd6cf9770fa5a35a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/pdu.h"
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/sdp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::sdp {
namespace {
// The Default MTU in basic mode (Spec v5.1, Vol 3 Part A Section 5.1)
const uint16_t kDefaultMaxSize = 672;
// The smallest MTU allowed by the spec.
const uint16_t kMinMaxSize = 48;
// Helper function to match one of two options, and print useful information on
// failure.
template <class Container1, class Container2, class Container3>
bool MatchesOneOf(const Container1& one,
const Container2& two,
const Container3& actual) {
bool opt_one =
std::equal(one.begin(), one.end(), actual.begin(), actual.end());
bool opt_two =
std::equal(two.begin(), two.end(), actual.begin(), actual.end());
if (!(opt_one || opt_two)) {
std::cout << "Expected one of {";
PrintByteContainer(one.begin(), one.end());
std::cout << "}\n or {";
PrintByteContainer(two.begin(), two.end());
std::cout << "}\n Found: { ";
PrintByteContainer(actual.begin(), actual.end());
std::cout << "}" << std::endl;
}
return opt_one || opt_two;
}
TEST(PDUTest, ErrorResponse) {
ErrorResponse response;
EXPECT_FALSE(response.complete());
EXPECT_EQ(nullptr,
response.GetPDU(0xF00F /* ignored */,
0xDEAD,
kDefaultMaxSize /* ignored */,
BufferView()));
StaticByteBuffer kInvalidContState(
0x01, // opcode: kErrorResponse
0xDE,
0xAD, // transaction ID: 0xDEAD
0x00,
0x02, // parameter length: 2 bytes
0x00,
0x05, // ErrorCode: Invalid Continuation State
0xFF,
0x00 // extra bytes to cause an error
);
fit::result<Error<>> status =
response.Parse(kInvalidContState.view(sizeof(Header)));
EXPECT_EQ(ToResult(HostError::kPacketMalformed), status);
status = response.Parse(kInvalidContState.view(sizeof(Header), 2));
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(response.complete());
EXPECT_EQ(ErrorCode::kInvalidContinuationState, response.error_code());
response.set_error_code(ErrorCode::kInvalidContinuationState);
auto ptr = response.GetPDU(0xF00F /* ignored */,
0xDEAD,
kDefaultMaxSize /* ignored */,
BufferView());
ASSERT_TRUE(ptr);
EXPECT_TRUE(ContainersEqual(kInvalidContState.view(0, 7), *ptr));
}
TEST(PDUTest, ServiceSearchRequestParse) {
const StaticByteBuffer kL2capSearch(
// ServiceSearchPattern
0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0x00,
0x10, // MaximumServiceRecordCount: 16
0x00 // Continuation State: none
);
ServiceSearchRequest req(kL2capSearch);
EXPECT_TRUE(req.valid());
EXPECT_EQ(1u, req.service_search_pattern().size());
EXPECT_TRUE(req.service_search_pattern().count(protocol::kL2CAP));
EXPECT_EQ(16, req.max_service_record_count());
const StaticByteBuffer kL2capSearchOne(
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0x19,
0xED,
0xFE, // UUID: 0xEDFE (unknown, doesn't need to be found)
0x00,
0x01, // MaximumServiceRecordCount: 1
0x00 // Continuation State: none
);
ServiceSearchRequest req_one(kL2capSearchOne);
EXPECT_TRUE(req_one.valid());
EXPECT_EQ(2u, req_one.service_search_pattern().size());
EXPECT_EQ(1, req_one.max_service_record_count());
const StaticByteBuffer kInvalidNoItems(
// ServiceSearchPattern
0x35,
0x00, // Sequence uint8 0 bytes
0xFF,
0xFF, // MaximumServiceRecordCount: (none)
0x00 // Continuation State: none
);
ServiceSearchRequest req2(kInvalidNoItems);
EXPECT_FALSE(req2.valid());
const StaticByteBuffer kInvalidTooManyItems(
// ServiceSearchPattern
0x35,
0x27, // Sequence uint8 27 bytes
0x19,
0x30,
0x01, // 13 UUIDs in the search
0x19,
0x30,
0x02,
0x19,
0x30,
0x03,
0x19,
0x30,
0x04,
0x19,
0x30,
0x05,
0x19,
0x30,
0x06,
0x19,
0x30,
0x07,
0x19,
0x30,
0x08,
0x19,
0x30,
0x09,
0x19,
0x30,
0x10,
0x19,
0x30,
0x11,
0x19,
0x30,
0x12,
0x19,
0x30,
0x13,
0xFF,
0xFF, // MaximumServiceRecordCount: (none)
0x00 // Continuation State: none
);
ServiceSearchRequest req3(kInvalidTooManyItems);
EXPECT_FALSE(req3.valid());
const StaticByteBuffer kInvalidMaxSizeZero(
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0x19,
0xED,
0xFE, // UUID: 0xEDFE (unknown, doesn't need to be found)
0x00,
0x00, // MaximumServiceRecordCount: 0
0x00 // Continuation State: none
);
ServiceSearchRequest req4(kInvalidMaxSizeZero);
EXPECT_FALSE(req4.valid());
}
TEST(PDUTest, ServiceSearchRequestGetPDU) {
ServiceSearchRequest req;
req.set_search_pattern({protocol::kATT, protocol::kL2CAP});
req.set_max_service_record_count(64);
// Order is not specified, so there are two valid PDUs representing this.
const StaticByteBuffer kExpected(kServiceSearchRequest,
0x12,
0x34, // Transaction ID
0x00,
0x0B, // Parameter length (11 bytes)
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x00,
0x07, // UUID (ATT)
0x19,
0x01,
0x00, // UUID (L2CAP)
0x00,
0x40, // MaximumServiceRecordCount: 64
0x00 // No continuation state
);
const auto kExpected2 =
StaticByteBuffer(kServiceSearchRequest,
0x12,
0x34, // Transaction ID
0x00,
0x0B, // Parameter length (11 bytes)
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x01,
0x00, // UUID (L2CAP)
0x19,
0x00,
0x07, // UUID (ATT)
0x00,
0x40, // MaximumServiceRecordCount: 64
0x00 // No continuation state
);
auto pdu = req.GetPDU(0x1234);
EXPECT_TRUE(MatchesOneOf(kExpected, kExpected2, *pdu));
}
TEST(PDUTest, ServiceSearchResponseParse) {
const StaticByteBuffer kValidResponse(
0x00,
0x02, // Total service record count: 2
0x00,
0x02, // Current service record count: 2
0x00,
0x00,
0x00,
0x01, // Service Handle 1
0x00,
0x00,
0x00,
0x02, // Service Handle 2
0x00 // No continuation state
);
ServiceSearchResponse resp;
auto status = resp.Parse(kValidResponse);
EXPECT_EQ(fit::ok(), status);
// Can't parse into an already complete record.
status = resp.Parse(kValidResponse);
EXPECT_TRUE(status.is_error());
const auto kNotEnoughRecords =
StaticByteBuffer(0x00,
0x02, // Total service record count: 2
0x00,
0x02, // Current service record count: 2
0x00,
0x00,
0x00,
0x01, // Service Handle 1
0x00 // No continuation state
);
// Doesn't contain the right # of records.
ServiceSearchResponse resp2;
status = resp2.Parse(kNotEnoughRecords);
EXPECT_TRUE(status.is_error());
// A Truncated packet doesn't parse either.
const StaticByteBuffer kTruncated(0x00,
0x02, // Total service record count: 2
0x00,
0x02 // Current service record count: 2
);
ServiceSearchResponse resp3;
status = resp3.Parse(kTruncated);
EXPECT_TRUE(status.is_error());
// Too many bytes for the number of records is also not allowed (with or
// without a continuation state)
const StaticByteBuffer kTooLong(0x00,
0x01, // Total service record count: 1
0x00,
0x01, // Current service record count: 1
0x00,
0x00,
0x00,
0x01, // Service Handle 1
0x00,
0x00,
0x00,
0x02, // Service Handle 2
0x00 // No continuation state
);
ServiceSearchResponse resp4;
status = resp4.Parse(kTooLong);
EXPECT_TRUE(status.is_error());
const auto kTooLongWithContinuation =
StaticByteBuffer(0x00,
0x04, // Total service record count: 1
0x00,
0x01, // Current service record count: 1
0x00,
0x00,
0x00,
0x01, // Service Handle 1
0x00,
0x00,
0x00,
0x02, // Service Handle 2
0x04, // Continuation state (len: 4)
0xF0,
0x9F,
0x92,
0x96 // Continuation state
);
ServiceSearchResponse resp5;
status = resp5.Parse(kTooLongWithContinuation);
EXPECT_TRUE(status.is_error());
}
TEST(PDUTest, ServiceSearchResponsePDU) {
std::vector<ServiceHandle> results{1, 2};
ServiceSearchResponse resp;
// Empty results
const StaticByteBuffer kExpectedEmpty(
0x03, // ServiceSearch Response PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x05, // Parameter length: 5 bytes
0x00,
0x00, // Total service record count: 0
0x00,
0x00, // Current service record count: 0
0x00 // No continuation state
);
auto pdu = resp.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedEmpty, *pdu));
resp.set_service_record_handle_list(results);
const StaticByteBuffer kExpected(0x03, // ServiceSearch Response PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x0d, // Parameter length: 13 bytes
0x00,
0x02, // Total service record count: 2
0x00,
0x02, // Current service record count: 2
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00,
0x00,
0x00,
0x02, // Service record 2
0x00 // No continuation state
);
pdu = resp.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpected, *pdu));
const auto kExpectedLimited =
StaticByteBuffer(0x03, // ServiceSearchResponse PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x09, // Parameter length: 9
0x00,
0x01, // Total service record count: 1
0x00,
0x01, // Current service record count: 1
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00 // No continuation state
);
pdu = resp.GetPDU(1, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedLimited, *pdu));
resp.set_service_record_handle_list({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
const auto kExpectedLarge =
StaticByteBuffer(0x03, // ServiceSearchResponse PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x31, // Parameter length: 49
0x00,
0x0B, // Total service record count: 11
0x00,
0x0B, // Current service record count: 11
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00,
0x00,
0x00,
0x02, // Service record 2
0x00,
0x00,
0x00,
0x03, // Service record 3
0x00,
0x00,
0x00,
0x04, // Service record 4
0x00,
0x00,
0x00,
0x05, // Service record 5
0x00,
0x00,
0x00,
0x06, // Service record 6
0x00,
0x00,
0x00,
0x07, // Service record 7
0x00,
0x00,
0x00,
0x08, // Service record 8
0x00,
0x00,
0x00,
0x09, // Service record 9
0x00,
0x00,
0x00,
0x0A, // Service record 10
0x00,
0x00,
0x00,
0x0B, // Service record 11
0x00 // No continuation state.
);
pdu = resp.GetPDU(0x00FF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedLarge, *pdu));
}
TEST(PDUTest, ServiceSearchResponsePDU_MaxSize) {
std::vector<ServiceHandle> results{1, 2};
ServiceSearchResponse resp;
// Empty results
const StaticByteBuffer kExpectedEmpty(
0x03, // ServiceSearch Response PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x05, // Parameter length: 5 bytes
0x00,
0x00, // Total service record count: 0
0x00,
0x00, // Current service record count: 0
0x00 // No continuation state
);
auto pdu = resp.GetPDU(0xFFFF, 0x0110, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedEmpty, *pdu));
resp.set_service_record_handle_list(results);
const StaticByteBuffer kExpected(0x03, // ServiceSearch Response PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x0d, // Parameter length: 13 bytes
0x00,
0x02, // Total service record count: 2
0x00,
0x02, // Current service record count: 2
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00,
0x00,
0x00,
0x02, // Service record 2
0x00 // No continuation state
);
pdu = resp.GetPDU(0xFFFF, 0x0110, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpected, *pdu));
const auto kExpectedLimited =
StaticByteBuffer(0x03, // ServiceSearchResponse PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x09, // Parameter length: 9
0x00,
0x01, // Total service record count: 1
0x00,
0x01, // Current service record count: 1
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00 // No continuation state
);
pdu = resp.GetPDU(1, 0x0110, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedLimited, *pdu));
resp.set_service_record_handle_list({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
const auto kExpectedContinuation =
StaticByteBuffer(0x03, // ServiceSearchResponse PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x2B, // Parameter length: 42
0x00,
0x0B, // Total service record count: 11
0x00,
0x09, // Current service record count: 9
0x00,
0x00,
0x00,
0x01, // Service record 1
0x00,
0x00,
0x00,
0x02, // Service record 2
0x00,
0x00,
0x00,
0x03, // Service record 3
0x00,
0x00,
0x00,
0x04, // Service record 4
0x00,
0x00,
0x00,
0x05, // Service record 5
0x00,
0x00,
0x00,
0x06, // Service record 6
0x00,
0x00,
0x00,
0x07, // Service record 7
0x00,
0x00,
0x00,
0x08, // Service record 8
0x00,
0x00,
0x00,
0x09, // Service record 9
0x02,
0x00,
0x09 // Continuation state.
);
// The MTU size here should limit the number of service records returned to 9.
pdu = resp.GetPDU(0x00FF, 0x0110, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedContinuation, *pdu));
const StaticByteBuffer kExpectedRest(0x03, // ServiceSearchResponse PDU ID
0x01,
0x10, // Transaction ID (0x0110)
0x00,
0x0D, // Parameter length: 13
0x00,
0x0B, // Total service record count: 11
0x00,
0x02, // Current service record count: 2
0x00,
0x00,
0x00,
0x0A, // Service record 10
0x00,
0x00,
0x00,
0x0B, // Service record 11
0x00 // No continuation state.
);
pdu = resp.GetPDU(0x00FF, 0x0110, kMinMaxSize, StaticByteBuffer(0x00, 0x09));
EXPECT_TRUE(ContainersEqual(kExpectedRest, *pdu));
}
TEST(PDUTest, ServiceAttributeRequestValidity) {
ServiceAttributeRequest req;
// No attributes requested, so it begins invalid
EXPECT_FALSE(req.valid());
// Adding an attribute makes it valid, and adds a range.
req.AddAttribute(kServiceClassIdList);
EXPECT_TRUE(req.valid());
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(kServiceClassIdList, req.attribute_ranges().front().start);
// Adding an attribute adjacent just adds to the range (on either end)
req.AddAttribute(kServiceClassIdList - 1); // kServiceRecordHandle
req.AddAttribute(kServiceClassIdList + 1);
req.AddAttribute(kServiceClassIdList + 2);
EXPECT_TRUE(req.valid());
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(kServiceClassIdList - 1, req.attribute_ranges().front().start);
EXPECT_EQ(kServiceClassIdList + 2, req.attribute_ranges().front().end);
// Adding a disjoint range makes it two ranges, and they're in the right
// order.
req.AddAttribute(kServiceClassIdList + 4);
EXPECT_TRUE(req.valid());
EXPECT_EQ(2u, req.attribute_ranges().size());
EXPECT_EQ(kServiceClassIdList - 1, req.attribute_ranges().front().start);
EXPECT_EQ(kServiceClassIdList + 2, req.attribute_ranges().front().end);
EXPECT_EQ(kServiceClassIdList + 4, req.attribute_ranges().back().start);
EXPECT_EQ(kServiceClassIdList + 4, req.attribute_ranges().back().end);
// Adding one that makes it contiguous collapses them to a single range.
req.AddAttribute(kServiceClassIdList + 3);
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(kServiceClassIdList - 1, req.attribute_ranges().front().start);
EXPECT_EQ(kServiceClassIdList + 4, req.attribute_ranges().front().end);
EXPECT_TRUE(req.valid());
}
TEST(PDUTest, ServiceAttributeRequestAddRange) {
ServiceAttributeRequest req;
req.AddAttributeRange(0x0010, 0xFFF0);
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0010, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFF0, req.attribute_ranges().front().end);
req.AddAttributeRange(0x0100, 0xFF00);
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0010, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFF0, req.attribute_ranges().front().end);
req.AddAttributeRange(0x0000, 0x0002);
EXPECT_EQ(2u, req.attribute_ranges().size());
EXPECT_EQ(0x0000, req.attribute_ranges().front().start);
EXPECT_EQ(0x0002, req.attribute_ranges().front().end);
req.AddAttributeRange(0x0003, 0x000F);
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0000, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFF0, req.attribute_ranges().front().end);
req.AddAttributeRange(0xFFF2, 0xFFF3);
req.AddAttributeRange(0xFFF5, 0xFFF6);
req.AddAttributeRange(0xFFF8, 0xFFF9);
req.AddAttributeRange(0xFFFB, 0xFFFC);
EXPECT_EQ(5u, req.attribute_ranges().size());
// merges 0x0000-0xFFF0 with 0xFFF2-0xFFF3 and new range leaving
// 0x0000-0xFFF3, 0xFFF5-0xFFF6, 0xFFF8-0xFFF9 and 0xFFFB-0xFFFC
req.AddAttributeRange(0xFFF1, 0xFFF1);
EXPECT_EQ(4u, req.attribute_ranges().size());
// merges everything except 0xFFFB-0xFFFC
req.AddAttributeRange(0xFFF1, 0xFFF9);
EXPECT_EQ(2u, req.attribute_ranges().size());
req.AddAttributeRange(0xFFFA, 0xFFFF);
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0000, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFFF, req.attribute_ranges().front().end);
}
TEST(PDUTest, ServiceAttributeRequestParse) {
const StaticByteBuffer kSDPAllAttributes(
0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0xF0,
0x0F, // Maximum attribute byte count: (max = 61455)
// Attribute ID List
0x35,
0x05, // Sequence uint8 5 bytes
0x0A, // uint32_t
0x00,
0x00,
0xFF,
0xFF, // Attribute range: 0x000 - 0xFFFF (All of them)
0x00 // No continuation state
);
ServiceAttributeRequest req(kSDPAllAttributes);
EXPECT_TRUE(req.valid());
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0000, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFFF, req.attribute_ranges().front().end);
EXPECT_EQ(61455, req.max_attribute_byte_count());
const auto kContinued =
StaticByteBuffer(0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0x00,
0x0F, // Maximum attribute byte count: (max = 15)
// Attribute ID List
0x35,
0x06, // Sequence uint8 3 bytes
0x09, // uint16_t
0x00,
0x01, // Attribute ID: 1
0x09, // uint16_t
0x00,
0x02, // Attribute ID: 2
0x03,
0x12,
0x34,
0x56 // Continuation state
);
ServiceAttributeRequest req_cont_state(kContinued);
EXPECT_TRUE(req_cont_state.valid());
EXPECT_EQ(2u, req_cont_state.attribute_ranges().size());
EXPECT_EQ(0x0001, req_cont_state.attribute_ranges().front().start);
EXPECT_EQ(0x0001, req_cont_state.attribute_ranges().front().end);
EXPECT_EQ(0x0002, req_cont_state.attribute_ranges().back().start);
EXPECT_EQ(0x0002, req_cont_state.attribute_ranges().back().end);
EXPECT_EQ(15, req_cont_state.max_attribute_byte_count());
// Too short request.
ServiceAttributeRequest req_short((BufferView()));
EXPECT_FALSE(req_short.valid());
const auto kInvalidMaxBytes =
StaticByteBuffer(0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0x00,
0x04, // Maximum attribute byte count (4)
// Attribute ID List
0x35,
0x03, // Sequence uint8 3 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x00 // No continuation state
);
ServiceAttributeRequest req_minmax(kInvalidMaxBytes);
EXPECT_FALSE(req_minmax.valid());
// Invalid order of the attributes.
const auto kInvalidAttributeListOrder =
StaticByteBuffer(0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0xFF,
0xFF, // Maximum attribute byte count: (max = 65535)
// Attribute ID List
0x35,
0x06, // Sequence uint8 6 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x09,
0x00,
0x01, // uint16_t (1)
0x00 // No continuation state
);
ServiceAttributeRequest req_order(kInvalidAttributeListOrder);
EXPECT_FALSE(req_short.valid());
// AttributeID List has invalid items
const auto kInvalidAttributeList =
StaticByteBuffer(0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0xFF,
0xFF, // Maximum attribute byte count: (max = 65535)
// Attribute ID List
0x35,
0x06, // Sequence uint8 5 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x19,
0x00,
0x01, // UUID (0x0001)
0x00 // No continuation state
);
ServiceAttributeRequest req_baditems(kInvalidAttributeList);
EXPECT_FALSE(req_baditems.valid());
// AttributeID list is empty
const auto kInvalidNoItems =
StaticByteBuffer(0x00,
0x00,
0x00,
0x00, // ServiceRecordHandle: 0
0xFF,
0xFF, // Maximum attribute byte count: max
0x35,
0x00, // Sequence uint8 no bytes
0x00 // No continuation state
);
ServiceAttributeRequest req_noitems(kInvalidNoItems);
EXPECT_FALSE(req_noitems.valid());
}
TEST(PDUTest, ServiceAttributeRequestGetPDU) {
ServiceAttributeRequest req;
req.AddAttribute(0xF00F);
req.AddAttributeRange(0x0001, 0xBECA);
req.set_service_record_handle(0xEFFECACE);
req.set_max_attribute_byte_count(32);
const auto kExpected =
StaticByteBuffer(kServiceAttributeRequest,
0x12,
0x34, // transaction id
0x00,
0x11, // Parameter Length (17 bytes)
0xEF,
0xFE,
0xCA,
0xCE, // ServiceRecordHandle (0xEFFECACE)
0x00,
0x20, // MaxAttributeByteCount (32)
// Attribute ID list
0x35,
0x08, // Sequence uint8 8 bytes
0x0A,
0x00,
0x01,
0xBE,
0xCA, // uint32_t (0x0001BECA)
0x09,
0xF0,
0x0F, // uint16_t (0xF00F)
0x00 // No continuation state
);
auto pdu = req.GetPDU(0x1234);
EXPECT_TRUE(ContainersEqual(kExpected, *pdu));
}
TEST(PDUTest, ServiceAttributeRequestGetPDUFailsTooManyAttributes) {
ServiceAttributeRequest req;
// Skip attributes to prevent them from being combined into a range.
for (AttributeId attr = 0x0000; attr <= kMaxAttributeRangesInRequest * 2;
attr += 2) {
req.AddAttribute(attr);
}
req.set_service_record_handle(0xEFFECACE);
req.set_max_attribute_byte_count(32);
EXPECT_EQ(req.GetPDU(0x1234), nullptr);
}
TEST(PDUTest, ServiceAttributeResponseParse) {
const StaticByteBuffer kValidResponseEmpty(0x00,
0x02, // AttributeListByteCount (2
// bytes) Attribute List
0x35,
0x00, // Sequence uint8 0 bytes
0x00 // No continuation state
);
ServiceAttributeResponse resp;
auto status = resp.Parse(kValidResponseEmpty);
EXPECT_EQ(fit::ok(), status);
const StaticByteBuffer kValidResponseItems(
0x00,
0x12, // AttributeListByteCount (18 bytes)
// Attribute List
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x09,
0x00,
0x01, // Handle: uint16_t (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x00 // No continuation state
);
ServiceAttributeResponse resp2;
status = resp2.Parse(kValidResponseItems);
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(2u, resp2.attributes().size());
auto it = resp2.attributes().find(0x00);
EXPECT_NE(resp2.attributes().end(), it);
EXPECT_EQ(DataElement::Type::kUnsignedInt, it->second.type());
it = resp2.attributes().find(0x01);
EXPECT_NE(resp2.attributes().end(), it);
EXPECT_EQ(DataElement::Type::kSequence, it->second.type());
const StaticByteBuffer kInvalidItemsWrongOrder(
0x00,
0x12, // AttributeListByteCount (18 bytes)
// Attribute List
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x01, // Handle: uint16_t (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x00 // No continuation state
);
ServiceAttributeResponse resp3;
status = resp3.Parse(kInvalidItemsWrongOrder);
EXPECT_TRUE(status.is_error());
const StaticByteBuffer kInvalidHandleWrongType(
0x00,
0x11, // AttributeListByteCount (17 bytes)
// Attribute List
0x35,
0x0F, // Sequence uint8 15 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x08,
0x01, // Handle: uint8_t (!) (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x00 // No continuation state
);
ServiceAttributeResponse resp_wrongtype;
status = resp_wrongtype.Parse(kInvalidHandleWrongType);
EXPECT_TRUE(status.is_error());
const StaticByteBuffer kInvalidByteCount(
0x00,
0x12, // AttributeListByteCount (18 bytes)
// Attribute List (only 17 bytes long)
0x35,
0x0F, // Sequence uint8 15 bytes
0x09,
0x00,
0x01, // Handle: uint16_t (1)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x09,
0x00,
0x00, // Handle: uint16_t (0)
0x25,
0x02,
'h',
'i', // Element: String ('hi')
0x00 // No continuation state
);
ServiceAttributeResponse resp4;
status = resp4.Parse(kInvalidByteCount);
EXPECT_EQ(ToResult(HostError::kPacketMalformed), status);
// Sending too much data eventually fails.
auto kContinuationPacket = DynamicByteBuffer(4096);
// Put the correct byte count at the front: 4096 - 2 (byte count) - 5
// (continuation size)
const StaticByteBuffer kAttributeListByteCount(0x0F, 0xF9);
kContinuationPacket.Write(kAttributeListByteCount, 0);
// Put the correct continuation at the end.
const StaticByteBuffer kContinuation(0x04, 0xF0, 0x9F, 0x92, 0x96);
kContinuationPacket.Write(kContinuation, kContinuationPacket.size() - 5);
ServiceAttributeResponse resp5;
status = resp5.Parse(kContinuationPacket);
EXPECT_EQ(ToResult(HostError::kInProgress), status);
while (status == ToResult(HostError::kInProgress)) {
status = resp5.Parse(kContinuationPacket);
}
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
TEST(PDUTest, ServiceAttributeResponseGetPDU_MaxSize) {
ServiceAttributeResponse resp;
// Even if set in the wrong order, attributes should be sorted in the PDU.
resp.set_attribute(0x4000, DataElement(uint16_t{0xfeed}));
resp.set_attribute(0x4001, DataElement(protocol::kSDP));
resp.set_attribute(0x4002, DataElement(uint32_t{0xc0decade}));
DataElement str;
str.Set(std::string("💖"));
resp.set_attribute(0x4003, std::move(str));
resp.set_attribute(0x4005, DataElement(uint32_t{0xC0DEB4BE}));
resp.set_attribute(kServiceRecordHandle, DataElement(uint32_t{0}));
const uint16_t kTransactionID = 0xfeed;
const StaticByteBuffer kExpectedContinuation(
0x05, // ServiceAttributeResponse
UpperBits(kTransactionID),
LowerBits(kTransactionID), // Transaction ID
0x00,
0x2B, // Param Length (43 bytes)
0x00,
0x24, // AttributeListsByteCount (36 bytes)
// AttributeLists
0x35,
0x2d, // Sequence uint8 46 bytes
0x09,
0x00,
0x00, // uint16_t (handle) = kServiceRecordHandle
0x0A,
0x00,
0x00,
0x00,
0x00, // uint32_t, 0
0x09,
0x40,
0x00, // uint16_t (handle) = 0x4000
0x09,
0xfe,
0xed, // uint16_t (0xfeed)
0x09,
0x40,
0x01, // uint16_t (handle) = 0x4001
0x19,
0x00,
0x01, // UUID (kSDP)
0x09,
0x40,
0x02, // uint32_t (handle) = 0x4002
0x0A,
0xc0,
0xde,
0xca,
0xde, // uint32_t = 0xc0decade
0x09,
0x40,
0x03, // uint32_t (handle) = 0x4003
0x25,
0x04,
0xf0, // Partial String: '💖' (type, length, first char)
0x04,
0x00,
0x00,
0x00,
0x24 // Continuation state (4 bytes: 36 bytes offset)
);
auto pdu = resp.GetPDU(
0xFFFF /* no max */, kTransactionID, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedContinuation, *pdu));
const StaticByteBuffer kExpectedRemaining(
0x05, // ServiceSearchAttributeResponse
UpperBits(kTransactionID),
LowerBits(kTransactionID), // Transaction ID
0x00,
0x0e, // Param Length (14 bytes)
0x00,
0x0b, // AttributeListsByteCount (12 bytes)
// AttributeLists (continued)
0x9f,
0x92,
0x96, // Remaining 3 bytes of String: '💖'
0x09,
0x40,
0x05, // uint16_t (handle) = 0x4005
0x0A,
0xc0,
0xde,
0xb4,
0xbe, // value: uint32_t (0xC0DEB4BE)
0x00 // Continuation state (none)
);
pdu = resp.GetPDU(0xFFFF /* no max */,
kTransactionID,
kMinMaxSize,
StaticByteBuffer(0x00, 0x00, 0x00, 0x24));
EXPECT_TRUE(ContainersEqual(kExpectedRemaining, *pdu));
}
TEST(PDUTest, ServiceSearchAttributeRequestParse) {
const StaticByteBuffer kSDPL2CAPAllAttributes(
// Service search pattern
0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0xF0,
0x0F, // Maximum attribute byte count: (max = 61455)
// Attribute ID List
0x35,
0x05, // Sequence uint8 5 bytes
0x0A, // uint32_t
0x00,
0x00,
0xFF,
0xFF, // Attribute range: 0x000 - 0xFFFF (All of them)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req(kSDPL2CAPAllAttributes);
EXPECT_TRUE(req.valid());
EXPECT_EQ(1u, req.service_search_pattern().size());
EXPECT_EQ(1u, req.service_search_pattern().count(protocol::kL2CAP));
EXPECT_EQ(1u, req.attribute_ranges().size());
EXPECT_EQ(0x0000, req.attribute_ranges().front().start);
EXPECT_EQ(0xFFFF, req.attribute_ranges().front().end);
EXPECT_EQ(61455, req.max_attribute_byte_count());
const auto kContinued =
StaticByteBuffer(0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0x00,
0x0F, // Maximum attribute byte count: (max = 15)
// Attribute ID List
0x35,
0x06, // Sequence uint8 6 bytes
0x09, // uint16_t
0x00,
0x01, // Attribute ID: 1
0x09, // uint16_t
0x00,
0x02, // Attribute ID: 2
0x03,
0x12,
0x34,
0x56 // Continuation state
);
ServiceSearchAttributeRequest req_cont_state(kContinued);
EXPECT_TRUE(req_cont_state.valid());
EXPECT_EQ(1u, req.service_search_pattern().size());
EXPECT_EQ(1u, req.service_search_pattern().count(protocol::kL2CAP));
EXPECT_EQ(2u, req_cont_state.attribute_ranges().size());
EXPECT_EQ(0x0001, req_cont_state.attribute_ranges().front().start);
EXPECT_EQ(0x0001, req_cont_state.attribute_ranges().front().end);
EXPECT_EQ(0x0002, req_cont_state.attribute_ranges().back().start);
EXPECT_EQ(0x0002, req_cont_state.attribute_ranges().back().end);
EXPECT_EQ(15, req_cont_state.max_attribute_byte_count());
// Too short request.
ServiceSearchAttributeRequest req_short((BufferView()));
EXPECT_FALSE(req_short.valid());
const auto kInvalidMaxBytes =
StaticByteBuffer(0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0x00,
0x04, // Maximum attribute byte count (4)
// Attribute ID List
0x35,
0x03, // Sequence uint8 3 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_minmax(kInvalidMaxBytes);
EXPECT_FALSE(req_minmax.valid());
// Invalid order of the attributes.
const auto kInvalidAttributeListOrder =
StaticByteBuffer(0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0xFF,
0xFF, // Maximum attribute byte count: (max = 65535)
// Attribute ID List
0x35,
0x06, // Sequence uint8 6 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x09,
0x00,
0x01, // uint16_t (1)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_order(kInvalidAttributeListOrder);
EXPECT_FALSE(req_short.valid());
// AttributeID List has invalid items
const auto kInvalidAttributeList =
StaticByteBuffer(0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0xFF,
0xFF, // Maximum attribute byte count: (max = 65535)
// Attribute ID List
0x35,
0x06, // Sequence uint8 6 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x19,
0x00,
0x01, // UUID (0x0001)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_baditems(kInvalidAttributeList);
EXPECT_FALSE(req_baditems.valid());
const auto kInvalidNoItems =
StaticByteBuffer(0x35,
0x00, // Sequence uint8 0 bytes
0xF0,
0x0F, // Maximum attribute byte count (61455)
// Attribute ID List
0x35,
0x03, // Sequence uint8 3 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_noitems(kInvalidNoItems);
EXPECT_FALSE(req_noitems.valid());
const auto kInvalidNoAttributes =
StaticByteBuffer(0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x01,
0x00, // UUID: Protocol: L2CAP
0xF0,
0x0F, // Maximum attribute byte count (61455)
// Attribute ID List
0x35,
0x00, // Sequence uint8 0 bytes
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_noattributes(kInvalidNoAttributes);
EXPECT_FALSE(req_noattributes.valid());
const StaticByteBuffer kInvalidTooManyItems(
// ServiceSearchPattern
0x35,
0x27, // Sequence uint8 27 bytes
0x19,
0x30,
0x01, // 13 UUIDs in the search
0x19,
0x30,
0x02,
0x19,
0x30,
0x03,
0x19,
0x30,
0x04,
0x19,
0x30,
0x05,
0x19,
0x30,
0x06,
0x19,
0x30,
0x07,
0x19,
0x30,
0x08,
0x19,
0x30,
0x09,
0x19,
0x30,
0x10,
0x19,
0x30,
0x11,
0x19,
0x30,
0x12,
0x19,
0x30,
0x13,
0xF0,
0x0F, // Maximum attribute byte count (61455)
0x35,
0x03, // Sequence uint8 3 bytes
0x09,
0x00,
0x02, // uint16_t (2)
0x00 // No continuation state
);
ServiceSearchAttributeRequest req_toomany(kInvalidTooManyItems);
EXPECT_FALSE(req_toomany.valid());
}
TEST(PDUTest, ServiceSearchAttributeRequestGetPDU) {
ServiceSearchAttributeRequest req;
req.AddAttribute(0xF00F);
req.AddAttributeRange(0x0001, 0xBECA);
req.set_search_pattern({protocol::kATT, protocol::kL2CAP});
req.set_max_attribute_byte_count(32);
const auto kExpected = StaticByteBuffer(kServiceSearchAttributeRequest,
0x12,
0x34, // transaction id
0x00,
0x15, // Parameter Length (21 bytes)
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x00,
0x07, // UUID (ATT)
0x19,
0x01,
0x00, // UUID (L2CAP)
0x00,
0x20, // MaxAttributeByteCount (32)
// Attribute ID list
0x35,
0x08, // Sequence uint8 8 bytes
0x0A,
0x00,
0x01,
0xBE,
0xCA, // uint32_t (0x0001BECA)
0x09,
0xF0,
0x0F, // uint16_t (0xF00F)
0x00 // No continuation state
);
const auto kExpected2 = StaticByteBuffer(kServiceSearchAttributeRequest,
0x12,
0x34, // transaction id
0x00,
0x15, // Parameter Length (21 bytes)
// ServiceSearchPattern
0x35,
0x06, // Sequence uint8 6 bytes
0x19,
0x01,
0x00, // UUID (L2CAP)
0x19,
0x00,
0x07, // UUID (ATT)
0x00,
0x20, // MaxAttributeByteCount (32)
// Attribute ID list
0x35,
0x08, // Sequence uint8 8 bytes
0x0A,
0x00,
0x01,
0xBE,
0xCA, // uint32_t (0x0001BECA)
0x09,
0xF0,
0x0F, // uint16_t (0xF00F)
0x00 // No continuation state
);
auto pdu = req.GetPDU(0x1234);
EXPECT_TRUE(MatchesOneOf(kExpected, kExpected2, *pdu));
}
TEST(PDUTest, ServiceSearchAttributeRequestGetPDUTooManyAttributes) {
ServiceSearchAttributeRequest req;
// Skip attributes to prevent them from being combined into a range.
for (AttributeId attr = 0x0000; attr <= kMaxAttributeRangesInRequest * 2;
attr += 2) {
req.AddAttribute(attr);
}
req.set_search_pattern({protocol::kATT, protocol::kL2CAP});
req.set_max_attribute_byte_count(32);
EXPECT_EQ(req.GetPDU(0x1234), nullptr);
}
TEST(PDUTest, ServiceSearchAttributeResponseParse) {
const StaticByteBuffer kValidResponseEmpty(0x00,
0x02, // AttributeListsByteCount
// (2 bytes) Attribute List
0x35,
0x00, // Sequence uint8 0 bytes
0x00 // No continuation state
);
ServiceSearchAttributeResponse resp;
auto status = resp.Parse(kValidResponseEmpty);
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(resp.complete());
EXPECT_EQ(0u, resp.num_attribute_lists());
const StaticByteBuffer kValidResponseItems(
0x00,
0x14, // AttributeListsByteCount (20 bytes)
// Wrapping Attribute List
0x35,
0x12, // Sequence uint8 18 bytes
// Attribute List
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x09,
0x00,
0x01, // Handle: uint16_t (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x00 // No continuation state
);
status = resp.Parse(kValidResponseItems);
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(resp.complete());
EXPECT_EQ(1u, resp.num_attribute_lists());
EXPECT_EQ(2u, resp.attributes(0).size());
auto it = resp.attributes(0).find(0x0000);
EXPECT_NE(resp.attributes(0).end(), it);
EXPECT_EQ(DataElement::Type::kUnsignedInt, it->second.type());
it = resp.attributes(0).find(0x0001);
EXPECT_NE(resp.attributes(0).end(), it);
EXPECT_EQ(DataElement::Type::kSequence, it->second.type());
const StaticByteBuffer kValidResponseTwoLists(
0x00,
0x1E, // AttributeListsByteCount (30 bytes)
// Wrapping Attribute List
0x35,
0x1C, // Sequence uint8 28 bytes
// Attribute List 0 (first service)
0x35,
0x08, // Sequence uint8 8 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
// Attribute List 1 (second service)
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xDB,
0xAC,
0x01, // Element: uint32_t (0xFEDBAC01)
0x09,
0x00,
0x01, // Handle: uint16_t (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x00 // No continuation state
);
ServiceSearchAttributeResponse resp2;
status = resp2.Parse(kValidResponseTwoLists);
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(resp2.complete());
EXPECT_EQ(2u, resp2.num_attribute_lists());
EXPECT_EQ(1u, resp2.attributes(0).size());
it = resp2.attributes(0).find(0x0000);
EXPECT_NE(resp2.attributes(0).end(), it);
EXPECT_EQ(DataElement::Type::kUnsignedInt, it->second.type());
EXPECT_EQ(2u, resp2.attributes(1).size());
it = resp2.attributes(1).find(0x0000);
EXPECT_NE(resp2.attributes(1).end(), it);
EXPECT_EQ(DataElement::Type::kUnsignedInt, it->second.type());
// Note: see test below, some peers will send in the wrong order.
// We still will fail if the peer sends us two of the same type.
const StaticByteBuffer kInvalidItemsDuplicateAttribute(
0x00,
0x14, // AttributeListByteCount (20 bytes)
// Wrapping Attribute List
0x35,
0x12, // Sequence uint8 18 bytes
// Attribute List
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xB0,
0xBA,
0xCA,
0xFE, // Element: uint32_t (0xB0BACAFE)
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x00 // No continuation state
);
ServiceSearchAttributeResponse resp3;
status = resp3.Parse(kInvalidItemsDuplicateAttribute);
EXPECT_TRUE(status.is_error());
EXPECT_EQ(0u, resp3.num_attribute_lists());
const StaticByteBuffer kInvalidHandleWrongType(
0x00,
0x13, // AttributeListByteCount (19 bytes)
// Wrapping Attribute List
0x35,
0x11, // Sequence uint8 17 bytes
// Attribute List
0x35,
0x0F, // Sequence uint8 15 bytes
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x08,
0x01, // Handle: uint8_t (!) (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x00 // No continuation state
);
ServiceAttributeResponse resp_wrongtype;
status = resp_wrongtype.Parse(kInvalidHandleWrongType);
EXPECT_TRUE(status.is_error());
const StaticByteBuffer kInvalidByteCount(
0x00,
0x12, // AttributeListsByteCount (18 bytes)
0x35,
0x11, // Sequence uint8 17 bytes
// Attribute List
0x35,
0x0F, // Sequence uint8 15 bytes
0x09,
0x00,
0x01, // Handle: uint16_t (1)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x09,
0x00,
0x00, // Handle: uint16_t (0)
0x25,
0x02,
'h',
'i', // Element: String ('hi')
0x00 // No continuation state
);
status = resp3.Parse(kInvalidByteCount);
EXPECT_TRUE(status.is_error());
// Sending too much data eventually fails.
auto kContinuationPacket = DynamicByteBuffer(4096);
// Put the correct byte count at the front: 4096 - 2 (byte count) - 5
// (continuation size)
const StaticByteBuffer kAttributeListByteCount(0x0F, 0xF9);
kContinuationPacket.Write(kAttributeListByteCount, 0);
// Put the correct continuation at the end.
const StaticByteBuffer kContinuation(0x04, 0xF0, 0x9F, 0x92, 0x96);
kContinuationPacket.Write(kContinuation, kContinuationPacket.size() - 5);
ServiceSearchAttributeResponse resp4;
status = resp4.Parse(kContinuationPacket);
EXPECT_EQ(ToResult(HostError::kInProgress), status);
while (status == ToResult(HostError::kInProgress)) {
status = resp4.Parse(kContinuationPacket);
}
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
// Some peers will send the attributes in the wrong order.
// As long as they are not duplicate attributes, we are flexible to this.
TEST(PDUTest, ServiceSearchAttributeResponseParseContinuationWrongOrder) {
// Attributes can come in the wrong order.
const StaticByteBuffer kItemsWrongOrder(
0x00,
0x14, // AttributeListByteCount (20 bytes)
// Wrapping Attribute List
0x35,
0x12, // Sequence uint8 18 bytes
// Attribute List
0x35,
0x10, // Sequence uint8 16 bytes
0x09,
0x00,
0x01, // Handle: uint16_t (1 = kServiceClassIdList)
0x35,
0x03,
0x19,
0x11,
0x01, // Element: Sequence (3) { UUID(0x1101) }
0x09,
0x00,
0x00, // Handle: uint16_t (0 = kServiceRecordHandle)
0x0A,
0xFE,
0xED,
0xBE,
0xEF, // Element: uint32_t (0xFEEDBEEF)
0x00 // No continuation state
);
ServiceSearchAttributeResponse resp_simple;
auto status = resp_simple.Parse(kItemsWrongOrder);
EXPECT_EQ(fit::ok(), status);
// Packets seen in real-world testing, a more complicated example.
const StaticByteBuffer kFirstPacket(
0x00,
0x77, // AttributeListsByteCount (119 bytes (in this packet))
0x35,
0x7e, // Wrapping attribute list sequence: 126 bytes
0x35,
0x33, // Begin attribute list 1 (51 bytes)
0x09,
0x00,
0x01, // Attribute 0x01
0x35,
0x06,
0x19,
0x11,
0x0e,
0x19,
0x11,
0x0f, // Attribute 0x01 data: 2 UUIDs list
0x09,
0x00,
0x04, // Attribute 0x04
0x35,
0x10,
0x35,
0x06,
0x19,
0x01,
0x00,
0x09,
0x00,
0x17,
0x35,
0x06,
0x19,
0x00,
0x17,
0x09,
0x01,
0x04, // Attribute 0x04 data
0x09,
0x00,
0x09, // Attribute 0x09
0x35,
0x08,
0x35,
0x06,
0x19,
0x11,
0x0e,
0x09,
0x01,
0x05, // Attribute 0x09 data
0x09,
0x03,
0x11, // Attribute 0x311
0x09,
0x00,
0x01, // Attribute 0x311 data: uint16 (0x01)
0x35,
0x47, // Begin attribute list 2 (71 bytes)
0x09,
0x00,
0x01, // Attribute 0x01
0x35,
0x03,
0x19,
0x11,
0x0c, // Attribute 0x01 data, Sequence of 1 UUID
0x09,
0x00,
0x04, // Attribute 0x04
0x35,
0x10,
0x35,
0x06,
0x19,
0x01,
0x00,
0x09,
0x00,
0x17,
0x35,
0x06,
0x19,
0x00,
0x17,
0x09,
0x01,
0x04, // Attribute 0x04 data
0x09,
0x00,
0x0d, // Attribute 0x0d
0x35,
0x12,
0x35,
0x10,
0x35,
0x06,
0x19,
0x01,
0x00,
0x09,
0x00,
0x1b,
0x35,
0x06,
0x19,
0x00,
0x17,
0x09,
0x01,
0x04, // Attribute 0x0d data
0x09,
0x00,
0x09, // Attribute 0x09
0x35,
0x08,
0x35,
0x06,
0x19,
0x11,
0x0e,
0x01, // Start of Attribute 0x09 data
// (continued in next packet)
0x01 // Continuation state (yes, one byte, 0x01)
);
ServiceSearchAttributeResponse resp;
status = resp.Parse(kFirstPacket);
EXPECT_EQ(ToResult(HostError::kInProgress), status);
const StaticByteBuffer kSecondPacket(
0x00,
0x09, // AttributeListsByteCount (9 bytes)
// 9 bytes continuing the previous response
0x09,
0x01,
0x05, // Continuation of previous 0x09 attribute
0x09,
0x03,
0x11, // Attribute 0x311
0x09,
0x00,
0x02, // Attribute 0x0311 data: uint16(0x02)
0x00 // No continuation state.
);
status = resp.Parse(kSecondPacket);
EXPECT_EQ(fit::ok(), status);
}
TEST(PDUTest, ServiceSearchAttributeResponseGetPDU) {
ServiceSearchAttributeResponse resp;
// Even if set in the wrong order, attributes should be sorted in the PDU.
resp.SetAttribute(0, 0x4000, DataElement(uint16_t{0xfeed}));
resp.SetAttribute(0, 0x4001, DataElement(protocol::kSDP));
resp.SetAttribute(0, kServiceRecordHandle, DataElement(uint32_t{0}));
// Attributes do not need to be continuous
resp.SetAttribute(5, kServiceRecordHandle, DataElement(uint32_t{0x10002000}));
const uint16_t kTransactionID = 0xfeed;
const StaticByteBuffer kExpected(
0x07, // ServiceSearchAttributeResponse
UpperBits(kTransactionID),
LowerBits(kTransactionID), // Transaction ID
0x00,
0x25, // Param Length (37 bytes)
0x00,
0x22, // AttributeListsByteCount (34 bytes)
// AttributeLists
0x35,
0x20, // Sequence uint8 32 bytes
0x35,
0x14, // Sequence uint8 20 bytes
0x09,
0x00,
0x00, // uint16_t (handle) = kServiceRecordHandle
0x0A,
0x00,
0x00,
0x00,
0x00, // uint32_t, 0
0x09,
0x40,
0x00, // uint16_t (handle) = 0x4000
0x09,
0xfe,
0xed, // uint16_t (0xfeed)
0x09,
0x40,
0x01, // uint16_t (handle) = 0x4001
0x19,
0x00,
0x01, // UUID (kSDP)
0x35,
0x08, // Sequence uint8 8 bytes
0x09,
0x00,
0x00, // uint16_t (handle) = kServiceRecordHandle
0x0A,
0x10,
0x00,
0x20,
0x00, // value: uint32_t (0x10002000)
0x00 // Continuation state (none)
);
auto pdu = resp.GetPDU(
0xFFFF /* no max */, kTransactionID, kDefaultMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpected, *pdu));
}
TEST(PDUTest, ServiceSearchAttributeResponseGetPDU_MaxSize) {
ServiceSearchAttributeResponse resp;
// Even if set in the wrong order, attributes should be sorted in the PDU.
resp.SetAttribute(0, 0x4000, DataElement(uint16_t{0xfeed}));
resp.SetAttribute(0, 0x4001, DataElement(protocol::kSDP));
resp.SetAttribute(0, 0x4002, DataElement(uint32_t{0xc0decade}));
DataElement str;
str.Set(std::string("💖"));
resp.SetAttribute(0, 0x4003, std::move(str));
resp.SetAttribute(0, kServiceRecordHandle, DataElement(uint32_t{0}));
// Attributes do not need to be continuous
resp.SetAttribute(5, kServiceRecordHandle, DataElement(uint32_t{0x10002000}));
const uint16_t kTransactionID = 0xfeed;
const StaticByteBuffer kExpectedContinuation(
0x07, // ServiceSearchAttributeResponse
UpperBits(kTransactionID),
LowerBits(kTransactionID), // Transaction ID
0x00,
0x2B, // Param Length (43 bytes)
0x00,
0x24, // AttributeListsByteCount (36 bytes)
// AttributeLists
0x35,
0x31, // Sequence uint8 49 bytes = 37 + 2 + 8 + 2
0x35,
0x25, // Sequence uint8 37 bytes
0x09,
0x00,
0x00, // uint16_t (handle) = kServiceRecordHandle
0x0A,
0x00,
0x00,
0x00,
0x00, // uint32_t, 0
0x09,
0x40,
0x00, // uint16_t (handle) = 0x4000
0x09,
0xfe,
0xed, // uint16_t (0xfeed)
0x09,
0x40,
0x01, // uint16_t (handle) = 0x4001
0x19,
0x00,
0x01, // UUID (kSDP)
0x09,
0x40,
0x02, // uint32_t (handle) = 0x4002
0x0A,
0xc0,
0xde,
0xca,
0xde, // uint32_t = 0xc0decade
0x09,
0x40,
0x03, // uint32_t (handle) = 0x4003
0x25, // Partial String: '💖' (just the type)
0x04,
0x00,
0x00,
0x00,
0x24 // Continuation state (4 bytes: 36 bytes offset)
);
auto pdu = resp.GetPDU(
0xFFFF /* no max */, kTransactionID, kMinMaxSize, BufferView());
EXPECT_TRUE(ContainersEqual(kExpectedContinuation, *pdu));
const StaticByteBuffer kExpectedRemaining(
0x07, // ServiceSearchAttributeResponse
UpperBits(kTransactionID),
LowerBits(kTransactionID), // Transaction ID
0x00,
0x12, // Param Length (18 bytes)
0x00,
0x0F, // AttributeListsByteCount (14 bytes)
// AttributeLists (continued)
0x04,
0xf0,
0x9f,
0x92,
0x96, // Remaining 5 bytes of String: '💖'
0x35,
0x08, // Sequence uint8 8 bytes
0x09,
0x00,
0x00, // uint16_t (handle) = kServiceRecordHandle
0x0A,
0x10,
0x00,
0x20,
0x00, // value: uint32_t (0x10002000)
0x00 // Continuation state (none)
);
pdu = resp.GetPDU(0xFFFF /* no max */,
kTransactionID,
kMinMaxSize,
StaticByteBuffer(0x00, 0x00, 0x00, 0x24));
EXPECT_TRUE(ContainersEqual(kExpectedRemaining, *pdu));
}
TEST(PDUTest, ResponseOutOfRangeContinuation) {
ServiceSearchResponse rsp_search;
rsp_search.set_service_record_handle_list({1, 2, 3, 4});
auto buf = rsp_search.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(buf);
// Out of Range (continuation is zero-indexed)
uint16_t handle_count =
htobe16(rsp_search.service_record_handle_list().size());
auto service_search_cont = DynamicByteBuffer(sizeof(uint16_t));
service_search_cont.WriteObj(handle_count, 0);
buf = rsp_search.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, service_search_cont);
EXPECT_FALSE(buf);
// Wrong size continuation state
buf = rsp_search.GetPDU(
0xFFFF, 0x0110, kDefaultMaxSize, StaticByteBuffer(0x01, 0xFF, 0xFF));
EXPECT_FALSE(buf);
ServiceAttributeResponse rsp_attr;
rsp_attr.set_attribute(1, DataElement(uint32_t{45}));
buf = rsp_attr.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(buf);
uint32_t rsp_size = htobe32(buf->size() + 5);
auto too_large_cont = DynamicByteBuffer(sizeof(uint32_t));
too_large_cont.WriteObj(rsp_size, 0);
buf = rsp_attr.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, too_large_cont);
EXPECT_FALSE(buf);
ServiceSearchAttributeResponse rsp_search_attr;
rsp_search_attr.SetAttribute(0, 0x4000, DataElement(uint16_t{0xfeed}));
rsp_search_attr.SetAttribute(0, 0x4001, DataElement(protocol::kSDP));
rsp_search_attr.SetAttribute(
0, kServiceRecordHandle, DataElement(uint32_t{0}));
rsp_search_attr.SetAttribute(
5, kServiceRecordHandle, DataElement(uint32_t{0x10002000}));
buf = rsp_search_attr.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, BufferView());
EXPECT_TRUE(buf);
rsp_size = htobe32(buf->size() + 5);
too_large_cont.WriteObj(rsp_size, 0);
buf = rsp_attr.GetPDU(0xFFFF, 0x0110, kDefaultMaxSize, too_large_cont);
EXPECT_FALSE(buf);
}
} // namespace
} // namespace bt::sdp