blob: 0ee120fb0ed6e6c837131a366d2b1b17bc6a159a [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/sdp/server.h"
#include <lib/inspect/testing/cpp/inspect.h>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel_test.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_l2cap.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/sdp/pdu.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller.h"
namespace bt::sdp {
using RegistrationHandle = Server::RegistrationHandle;
namespace {
using namespace inspect::testing;
using TestingBase = l2cap::testing::FakeChannelTest;
constexpr hci_spec::ConnectionHandle kTestHandle1 = 1;
constexpr hci_spec::ConnectionHandle kTestHandle2 = 2;
void NopConnectCallback(fbl::RefPtr<l2cap::Channel>, const DataElement&) {}
constexpr l2cap::ChannelParameters kChannelParams;
class ServerTest : public TestingBase {
public:
ServerTest() = default;
~ServerTest() = default;
protected:
void SetUp() override {
l2cap_ = std::make_unique<l2cap::testing::FakeL2cap>();
l2cap_->set_channel_callback([this](auto fake_chan) {
channel_ = std::move(fake_chan);
set_fake_chan(channel_->AsWeakPtr());
});
l2cap_->AddACLConnection(kTestHandle1, hci_spec::ConnectionRole::kPeripheral, nullptr, nullptr);
l2cap_->AddACLConnection(kTestHandle2, hci_spec::ConnectionRole::kPeripheral, nullptr, nullptr);
server_ = std::make_unique<Server>(l2cap_.get());
}
void TearDown() override {
channel_ = nullptr;
server_ = nullptr;
l2cap_ = nullptr;
}
Server* server() const { return server_.get(); }
l2cap::testing::FakeL2cap* l2cap() const { return l2cap_.get(); }
RegistrationHandle AddSPP(sdp::Server::ConnectCallback cb = NopConnectCallback) {
ServiceRecord record;
record.SetServiceClassUUIDs({profile::kSerialPort});
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement());
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kRFCOMM,
DataElement(uint8_t{0}));
record.AddProfile(profile::kSerialPort, 1, 2);
record.AddInfo("en", "FAKE", "", "");
std::vector<ServiceRecord> records;
records.emplace_back(std::move(record));
RegistrationHandle handle =
server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_TRUE(handle);
return handle;
}
RegistrationHandle AddA2DPSink(sdp::Server::ConnectCallback cb = NopConnectCallback) {
ServiceRecord record;
record.SetServiceClassUUIDs({profile::kAudioSink});
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(l2cap::kAVDTP));
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kAVDTP,
DataElement(uint16_t{0x0103})); // Version
record.AddProfile(profile::kAdvancedAudioDistribution, 1, 3);
record.SetAttribute(kA2DP_SupportedFeatures, DataElement(uint16_t{0x0001})); // Headphones
std::vector<ServiceRecord> records;
records.emplace_back(std::move(record));
RegistrationHandle handle =
server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_TRUE(handle);
return handle;
}
RegistrationHandle AddL2capService(l2cap::PSM channel,
l2cap::ChannelParameters chan_params = kChannelParams,
sdp::Server::ConnectCallback cb = NopConnectCallback) {
ServiceRecord record;
record.SetServiceClassUUIDs({profile::kAudioSink});
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(channel));
record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kAVDTP,
DataElement(uint16_t{0x0103})); // Version
record.AddProfile(profile::kAdvancedAudioDistribution, 1, 3);
record.SetAttribute(kA2DP_SupportedFeatures, DataElement(uint16_t{0x0001})); // Headphones
std::vector<ServiceRecord> records;
records.emplace_back(std::move(record));
RegistrationHandle handle =
server()->RegisterService(std::move(records), chan_params, std::move(cb));
EXPECT_TRUE(handle);
return handle;
}
private:
fbl::RefPtr<l2cap::testing::FakeChannel> channel_;
std::unique_ptr<l2cap::testing::FakeL2cap> l2cap_;
std::unique_ptr<Server> server_;
};
constexpr l2cap::ChannelId kSdpChannel = 0x0041;
#define SDP_ERROR_RSP(t_id, code) \
StaticByteBuffer(0x01, UpperBits(t_id), LowerBits(t_id), 0x00, 0x02, UpperBits(uint16_t(code)), \
LowerBits(uint16_t(code)));
// Test:
// - Accepts channels and holds channel open correctly.
// - More than one channel from the same peer can be open at once.
// - Packets that are the wrong length are responded to with kInvalidSize
// - Answers with the same TransactionID as sent
TEST_F(ServerTest, BasicError) {
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
ASSERT_TRUE(fake_chan());
EXPECT_TRUE(fake_chan()->activated());
const auto kRspErrSize = SDP_ERROR_RSP(0x1001, ErrorCode::kInvalidSize);
const StaticByteBuffer kTooSmall(0x01, // SDP_ServiceSearchRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x09 // Parameter length (9 bytes)
);
const auto kRspTooSmall = SDP_ERROR_RSP(0x1001, ErrorCode::kInvalidSize);
const StaticByteBuffer kTooBig(0x01, // SDP_ServiceSearchRequest
0x20, 0x10, // Transaction ID (0x2010)
0x00, 0x02, // Parameter length (2 bytes)
0x01, 0x02, 0x03 // 3 bytes of parameters
);
const auto kRspTooBig = SDP_ERROR_RSP(0x2010, ErrorCode::kInvalidSize);
EXPECT_TRUE(ReceiveAndExpect(kTooSmall, kRspTooSmall));
EXPECT_TRUE(ReceiveAndExpect(kTooBig, kRspTooBig));
const auto kRspInvalidSyntax = SDP_ERROR_RSP(0x2010, ErrorCode::kInvalidRequestSyntax);
// Responses aren't valid requests
EXPECT_TRUE(ReceiveAndExpect(kRspTooBig, kRspInvalidSyntax));
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel + 1, 0x0bad));
RunLoopUntilIdle();
ASSERT_TRUE(fake_chan());
EXPECT_TRUE(fake_chan()->activated());
EXPECT_TRUE(ReceiveAndExpect(kTooSmall, kRspTooSmall));
}
// Test:
// - Passes an initialized ServiceRecord that has a matching ServiceHandle
// - Doesn't add a service that doesn't contain a ServiceClassIDList
// - Adds a service that is valid.
// - Services can be Unregistered.
TEST_F(ServerTest, RegisterService) {
std::vector<ServiceRecord> records;
EXPECT_FALSE(server()->RegisterService(std::move(records), kChannelParams, {}));
ServiceRecord record = ServiceRecord();
std::vector<ServiceRecord> records0;
records0.emplace_back(std::move(record));
EXPECT_FALSE(server()->RegisterService(std::move(records0), kChannelParams, {}));
ServiceRecord record1;
record1.SetAttribute(kServiceClassIdList, DataElement(uint16_t{42}));
std::vector<ServiceRecord> records1;
records1.emplace_back(std::move(record1));
EXPECT_FALSE(server()->RegisterService(std::move(records1), kChannelParams, {}));
ServiceRecord has_handle;
has_handle.SetHandle(42);
std::vector<ServiceRecord> records2;
records2.emplace_back(std::move(has_handle));
EXPECT_FALSE(server()->RegisterService(std::move(records2), kChannelParams, {}));
ServiceRecord valid;
valid.SetServiceClassUUIDs({profile::kAVRemoteControl});
std::vector<ServiceRecord> records3;
records3.emplace_back(std::move(valid));
RegistrationHandle handle = server()->RegisterService(std::move(records3), kChannelParams, {});
EXPECT_TRUE(handle);
EXPECT_TRUE(server()->UnregisterService(handle));
EXPECT_FALSE(server()->UnregisterService(handle));
}
// Test:
// - Adds a primary protocol to the service defintion.
// - Adds multiple additional protocols to the service definition.
// - Tests registration and removal are successful.
// - Tests callback correctness when inbound l2cap channels are connected.
TEST_F(ServerTest, RegisterServiceWithAdditionalProtocol) {
std::vector<l2cap::PSM> psms{500, 27, 29};
ServiceRecord psm_additional;
psm_additional.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_additional.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t(psms[0])));
psm_additional.AddProtocolDescriptor(1, protocol::kL2CAP, DataElement(uint16_t(psms[1])));
psm_additional.AddProtocolDescriptor(2, protocol::kL2CAP, DataElement(uint16_t(psms[2])));
std::vector<uint16_t> protocols_discovered;
auto cb = [&](auto /*channel*/, auto& protocol_list) {
EXPECT_EQ(DataElement::Type::kSequence, protocol_list.type());
auto* psm = protocol_list.At(0);
EXPECT_EQ(DataElement::Type::kSequence, psm->type());
psm = psm->At(1);
EXPECT_EQ(DataElement::Type::kUnsignedInt, psm->type());
protocols_discovered.emplace_back(*psm->template Get<uint16_t>());
};
std::vector<ServiceRecord> records;
records.emplace_back(std::move(psm_additional));
auto handle = server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_TRUE(handle);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, psms[0], 0x40, 0x41));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, psms[1], 0x42, 0x43));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, psms[2], 0x44, 0x44));
RunLoopUntilIdle();
ASSERT_EQ(3u, protocols_discovered.size());
// There should be one connection (and therefore protocol_list) per psm registered.
for (auto& psm : psms) {
ASSERT_EQ(1u, std::count(protocols_discovered.begin(), protocols_discovered.end(), psm));
}
EXPECT_TRUE(server()->UnregisterService(handle));
}
// Test:
// - Adds a primary protocol to the service defintion.
// - Adds an additional protocol to the service definition.
// - Adds an additional protocol with missing information.
// - Tests that none of protocols are registered.
TEST_F(ServerTest, RegisterServiceWithIncompleteAdditionalProtocol) {
ServiceRecord psm_additional;
psm_additional.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_additional.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{500}));
psm_additional.AddProtocolDescriptor(1, protocol::kL2CAP, DataElement(uint16_t{27}));
psm_additional.AddProtocolDescriptor(2, protocol::kL2CAP, DataElement());
size_t cb_count = 0;
auto cb = [&](auto /*channel*/, auto& /* protocol_list */) { cb_count++; };
std::vector<ServiceRecord> records;
records.emplace_back(std::move(psm_additional));
RegistrationHandle handle =
server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_FALSE(handle);
EXPECT_FALSE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 500, 0x40, 0x41));
RunLoopUntilIdle();
// Despite an incoming L2CAP connection, the callback should never be triggered since
// no services should be registered.
EXPECT_EQ(0u, cb_count);
EXPECT_FALSE(server()->UnregisterService(handle));
}
TEST_F(ServerTest, PSMVerification) {
ServiceRecord no_psm;
no_psm.SetServiceClassUUIDs({profile::kAVRemoteControl});
no_psm.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement());
std::vector<ServiceRecord> records;
records.emplace_back(std::move(no_psm));
EXPECT_FALSE(server()->RegisterService(std::move(records), kChannelParams, {}));
ServiceRecord psm_wrong_argtype;
psm_wrong_argtype.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_wrong_argtype.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(bool(true)));
std::vector<ServiceRecord> records2;
records2.emplace_back(std::move(psm_wrong_argtype));
EXPECT_FALSE(server()->RegisterService(std::move(records2), kChannelParams, {}));
ServiceRecord psm_wrong_intsize;
psm_wrong_intsize.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_wrong_intsize.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint8_t{5}));
std::vector<ServiceRecord> records_wrong_intsize;
records_wrong_intsize.emplace_back(std::move(psm_wrong_intsize));
EXPECT_FALSE(server()->RegisterService(std::move(records_wrong_intsize), kChannelParams, {}));
ServiceRecord psm_rfcomm;
psm_rfcomm.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_rfcomm.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement());
psm_rfcomm.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kRFCOMM,
DataElement(uint16_t{5}));
std::vector<ServiceRecord> records3;
records3.emplace_back(std::move(psm_rfcomm));
EXPECT_TRUE(server()->RegisterService(std::move(records3), kChannelParams, NopConnectCallback));
// Another RFCOMM should fail, even with a different channel.
ServiceRecord psm_rfcomm2;
psm_rfcomm2.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_rfcomm2.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement());
psm_rfcomm2.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kRFCOMM,
DataElement(uint16_t{7}));
std::vector<ServiceRecord> records4;
records4.emplace_back(std::move(psm_rfcomm2));
EXPECT_FALSE(server()->RegisterService(std::move(records4), kChannelParams, NopConnectCallback));
ServiceRecord psm_ok;
psm_ok.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_ok.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{500}));
std::vector<ServiceRecord> records5;
records5.emplace_back(std::move(psm_ok));
auto handle = server()->RegisterService(std::move(records5), kChannelParams, NopConnectCallback);
EXPECT_TRUE(handle);
ServiceRecord psm_same;
psm_same.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_same.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{500}));
std::vector<ServiceRecord> records6;
records6.emplace_back(std::move(psm_same));
EXPECT_FALSE(server()->RegisterService(std::move(records6), kChannelParams, NopConnectCallback));
// Unregistering allows us to re-register with PSM.
server()->UnregisterService(handle);
ServiceRecord psm_readd;
psm_readd.SetServiceClassUUIDs({profile::kAVRemoteControl});
psm_readd.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{500}));
std::vector<ServiceRecord> records7;
records7.emplace_back(std::move(psm_readd));
EXPECT_TRUE(server()->RegisterService(std::move(records7), kChannelParams, NopConnectCallback));
}
// Test:
// - Registering multiple ServiceRecords from the same client is successful.
// - Inbound L2CAP connections on the registered PSMs trigger the callback.
TEST_F(ServerTest, RegisterServiceMultipleRecordsSuccess) {
ServiceRecord record1;
record1.SetServiceClassUUIDs({profile::kAVRemoteControl});
record1.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{7}));
record1.AddProtocolDescriptor(1, protocol::kL2CAP, DataElement(uint16_t{8}));
ServiceRecord record2;
record2.SetServiceClassUUIDs({profile::kAudioSink});
record2.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{9}));
record2.AddProtocolDescriptor(1, protocol::kL2CAP, DataElement(uint16_t{10}));
std::vector<ServiceRecord> records;
records.emplace_back(std::move(record1));
records.emplace_back(std::move(record2));
size_t cb_count = 0;
auto cb = [&](auto /*channel*/, auto& protocol_list) { cb_count++; };
auto handle = server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_TRUE(handle);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 7, 0x40, 0x41));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 8, 0x42, 0x43));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 9, 0x44, 0x44));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 10, 0x45, 0x46));
RunLoopUntilIdle();
EXPECT_EQ(4u, cb_count);
EXPECT_TRUE(server()->UnregisterService(handle));
}
// Test:
// - Registering multiple ServiceRecords with the same PSM from the same client
// is successful.
// - Inbound L2CAP connections on the registered PSMs trigger the same callback.
// - Attempting to register a record with an already taken PSM will fail, and not
// register any of the other records in the set of records.
TEST_F(ServerTest, RegisterServiceMultipleRecordsSamePSM) {
ServiceRecord target_browse_record;
target_browse_record.SetServiceClassUUIDs({profile::kAVRemoteControlTarget});
target_browse_record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{25}));
target_browse_record.AddProtocolDescriptor(1, protocol::kL2CAP, DataElement(uint16_t{27}));
ServiceRecord controller_record;
controller_record.SetServiceClassUUIDs({profile::kAVRemoteControlController});
controller_record.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{25}));
std::vector<ServiceRecord> records;
records.emplace_back(std::move(target_browse_record));
records.emplace_back(std::move(controller_record));
std::vector<uint16_t> protocols_discovered;
auto cb = [&](auto /*channel*/, auto& protocol_list) {
EXPECT_EQ(DataElement::Type::kSequence, protocol_list.type());
auto* psm = protocol_list.At(0);
EXPECT_EQ(DataElement::Type::kSequence, psm->type());
psm = psm->At(1);
EXPECT_EQ(DataElement::Type::kUnsignedInt, psm->type());
protocols_discovered.emplace_back(*psm->template Get<uint16_t>());
};
auto handle = server()->RegisterService(std::move(records), kChannelParams, std::move(cb));
EXPECT_TRUE(handle);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 25, 0x40, 0x41));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 27, 0x44, 0x44));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, 25, 0x42, 0x43));
RunLoopUntilIdle();
// We expect two calls to the callback for psm=25, and one for psm=27.
ASSERT_EQ(2u, std::count(protocols_discovered.begin(), protocols_discovered.end(), 25));
ASSERT_EQ(1u, std::count(protocols_discovered.begin(), protocols_discovered.end(), 27));
// Attempt to register existing PSM.
ServiceRecord duplicate_psm;
duplicate_psm.SetServiceClassUUIDs({profile::kAVRemoteControlTarget});
duplicate_psm.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{25}));
ServiceRecord valid_psm;
valid_psm.SetServiceClassUUIDs({profile::kAudioSource});
valid_psm.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{31}));
std::vector<ServiceRecord> invalid_records;
invalid_records.emplace_back(std::move(duplicate_psm));
invalid_records.emplace_back(std::move(valid_psm));
EXPECT_FALSE(
server()->RegisterService(std::move(invalid_records), kChannelParams, NopConnectCallback));
EXPECT_TRUE(server()->UnregisterService(handle));
}
#define UINT32_AS_BE_BYTES(x) \
UpperBits(x >> 16), LowerBits(x >> 16), UpperBits(x & 0xFFFF), LowerBits(x & 0xFFFF)
// Test ServiceSearchRequest:
// - returns services with the UUID included
// - doesn't return services that don't have the UUID
// - fails when there are no items or too many items in the search
// - doesn't return more than the max requested
TEST_F(ServerTest, ServiceSearchRequest) {
RegistrationHandle spp_handle = AddSPP();
RegistrationHandle a2dp_handle = AddA2DPSink();
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
const StaticByteBuffer kL2capSearch(0x02, // SDP_ServiceSearchRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x08, // Parameter length (8 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0xFF, 0xFF, // MaximumServiceRecordCount: (none)
0x00 // Contunuation State: none
);
ServiceSearchRequest search_req;
EXPECT_FALSE(search_req.valid());
EXPECT_EQ(nullptr, search_req.GetPDU(0x1001));
search_req.set_search_pattern({protocol::kL2CAP});
auto pdu = search_req.GetPDU(0x1001);
EXPECT_NE(nullptr, pdu);
EXPECT_TRUE(ContainersEqual(kL2capSearch, *pdu));
const StaticByteBuffer kL2capSearchResponse(
0x03, // SDP_ServicesearchResponse
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x0D, // Parameter length (13 bytes)
0x00, 0x02, // Total service record count: 2
0x00, 0x02, // Current service record count: 2
UINT32_AS_BE_BYTES(spp_handle), // This list isn't specifically ordered
UINT32_AS_BE_BYTES(a2dp_handle),
0x00 // No continuation state
);
bool recv = false;
std::vector<ServiceHandle> handles;
TransactionId tid;
auto cb = [&recv, &handles, &tid](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<Header> packet(cb_packet.get());
EXPECT_EQ(kServiceSearchResponse, packet.header().pdu_id);
tid = betoh16(packet.header().tid);
uint16_t len = betoh16(packet.header().param_length);
bt_log(TRACE, "unittest", "resize packet to %d", len);
packet.Resize(len);
ServiceSearchResponse resp;
auto status = resp.Parse(packet.payload_data());
EXPECT_EQ(fitx::ok(), status);
handles = resp.service_record_handle_list();
recv = true;
};
fake_chan()->SetSendCallback(cb, dispatcher());
fake_chan()->Receive(kL2capSearch);
RunLoopUntilIdle();
EXPECT_TRUE(recv);
EXPECT_EQ(0x1001, tid);
EXPECT_EQ(2u, handles.size());
EXPECT_NE(handles.end(), std::find(handles.begin(), handles.end(), spp_handle));
EXPECT_NE(handles.end(), std::find(handles.begin(), handles.end(), a2dp_handle));
const StaticByteBuffer kInvalidNoItems(0x02, // SDP_ServiceSearchRequest
0x10, 0xA1, // Transaction ID (0x10A1)
0x00, 0x05, // Parameter length (5 bytes)
// ServiceSearchPattern
0x35, 0x00, // Sequence uint8 0 bytes
0xFF, 0xFF, // MaximumServiceRecordCount: (none)
0x00 // Contunuation State: none
);
const auto kRspErrSyntax = SDP_ERROR_RSP(0x10A1, ErrorCode::kInvalidRequestSyntax);
EXPECT_TRUE(ReceiveAndExpect(kInvalidNoItems, kRspErrSyntax));
const StaticByteBuffer kInvalidTooManyItems(
0x02, // SDP_ServiceSearchRequest
0x10, 0xA1, // Transaction ID (0x10B1)
0x00, 0x2C, // Parameter length (44 bytes)
// 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 // Contunuation State: none
);
EXPECT_TRUE(ReceiveAndExpect(kInvalidTooManyItems, kRspErrSyntax));
}
// Test ServiceSearchRequest:
// - doesn't return more than the max requested
TEST_F(ServerTest, ServiceSearchRequestOneOfMany) {
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
RegistrationHandle spp_handle = AddSPP();
RegistrationHandle a2dp_handle = AddA2DPSink();
bool recv = false;
std::vector<ServiceHandle> handles;
TransactionId tid;
auto cb = [&recv, &handles, &tid](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<Header> packet(cb_packet.get());
EXPECT_EQ(kServiceSearchResponse, packet.header().pdu_id);
tid = betoh16(packet.header().tid);
uint16_t len = betoh16(packet.header().param_length);
bt_log(TRACE, "unittests", "resizing packet to %d", len);
packet.Resize(len);
ServiceSearchResponse resp;
auto status = resp.Parse(packet.payload_data());
EXPECT_EQ(fitx::ok(), status);
handles = resp.service_record_handle_list();
recv = true;
};
const StaticByteBuffer kL2capSearchOne(0x02, // SDP_ServiceSearchRequest
0x10, 0xC1, // Transaction ID (0x10C1)
0x00, 0x08, // Parameter length (8 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0x00, 0x01, // MaximumServiceRecordCount: 1
0x00 // Contunuation State: none
);
handles.clear();
recv = false;
fake_chan()->SetSendCallback(cb, dispatcher());
fake_chan()->Receive(kL2capSearchOne);
RunLoopUntilIdle();
EXPECT_TRUE(recv);
EXPECT_EQ(0x10C1, tid);
EXPECT_EQ(1u, handles.size());
bool found_spp = std::find(handles.begin(), handles.end(), spp_handle) != handles.end();
bool found_a2dp = std::find(handles.begin(), handles.end(), a2dp_handle) != handles.end();
EXPECT_TRUE(found_spp || found_a2dp);
}
// Test ServiceSearchRequest:
// - returns continuation state if too many services match
// - continuation state in request works correctly
TEST_F(ServerTest, ServiceSearchContinuationState) {
// Set the TX MTU to the lowest that's allowed (48)
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad,
48 /* tx_mtu */));
RunLoopUntilIdle();
// Add enough services to generate a continuation state.
AddL2capService(0x1001);
AddL2capService(0x1003);
AddL2capService(0x1005);
AddL2capService(0x1007);
AddL2capService(0x1009);
AddL2capService(0x100B);
AddL2capService(0x100D);
AddL2capService(0x100F);
AddL2capService(0x1011);
AddL2capService(0x1013);
AddL2capService(0x1015);
size_t received = 0;
ServiceSearchResponse rsp;
auto send_cb = [this, &rsp, &received](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<sdp::Header> packet(cb_packet.get());
ASSERT_EQ(0x03, packet.header().pdu_id);
uint16_t len = betoh16(packet.header().param_length);
EXPECT_LE(len,
0x2F); // 10 records (4 * 10) + 2 (total count) + 2 (current count) + 3 (cont state)
packet.Resize(len);
fitx::result<Error<>> result = rsp.Parse(packet.payload_data());
if (received == 0) {
// Server should have split this into more than one response.
EXPECT_EQ(ToResult(HostError::kInProgress), result);
EXPECT_FALSE(rsp.complete());
}
received++;
if (result.is_error() && !result.error_value().is(HostError::kInProgress)) {
// This isn't a valid packet and we shouldn't try to get
// a continuation.
return;
}
if (!rsp.complete()) {
// Repeat the request with the continuation state if it was returned.
auto continuation = rsp.ContinuationState();
uint8_t cont_size = continuation.size();
EXPECT_NE(0u, cont_size);
// Make another request with the continutation data.
size_t param_size = 8 + cont_size;
StaticByteBuffer kContinuedRequestStart(0x02, // SDP_ServiceSearchRequest
0x10, 0xC1, // Transaction ID (0x10C1)
UpperBits(param_size),
LowerBits(param_size), // Parameter length
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0x00, 0xFF // MaximumServiceRecordCount: 256
);
DynamicByteBuffer req(kContinuedRequestStart.size() + sizeof(uint8_t) + cont_size);
kContinuedRequestStart.Copy(&req);
req.Write(&cont_size, sizeof(uint8_t), kContinuedRequestStart.size());
req.Write(continuation, kContinuedRequestStart.size() + sizeof(uint8_t));
fake_chan()->Receive(req);
}
};
const StaticByteBuffer kL2capSearch(0x02, // SDP_ServiceSearchRequest
0x10, 0xC1, // Transaction ID (0x10C1)
0x00, 0x08, // Parameter length (8 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0x00, 0xFF, // MaximumServiceRecordCount: 256
0x00 // Contunuation State: none
);
fake_chan()->SetSendCallback(send_cb, dispatcher());
fake_chan()->Receive(kL2capSearch);
RunLoopUntilIdle();
EXPECT_GE(received, 1u);
EXPECT_EQ(11u, rsp.service_record_handle_list().size());
}
// Test:
// - Answers ServiceAttributeRequest correctly
// - Continuation state is generated correctly re:
// MaximumAttributeListByteCount
// - Valid Continuation state continues response
TEST_F(ServerTest, ServiceAttributeRequest) {
ServiceRecord record;
record.SetServiceClassUUIDs({profile::kAVRemoteControl});
record.SetAttribute(0xf00d, DataElement(uint32_t{0xfeedbeef}));
record.SetAttribute(0xf000, DataElement(uint32_t{0x01234567}));
std::vector<ServiceRecord> records;
records.emplace_back(std::move(record));
RegistrationHandle handle = server()->RegisterService(std::move(records), kChannelParams, {});
EXPECT_TRUE(handle);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
const auto kRequestAttr =
StaticByteBuffer(0x04, // SDP_ServiceAttritbuteRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x11, // Parameter length (17 bytes)
UINT32_AS_BE_BYTES(handle), // ServiceRecordHandle
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0x30, 0x00, // low end of range
0xf0, 0x00, // high end of range
0x00 // Contunuation State: none
);
size_t received = 0;
ServiceAttributeResponse rsp;
auto send_cb = [this, handle, &rsp, &received](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<sdp::Header> packet(cb_packet.get());
ASSERT_EQ(0x05, packet.header().pdu_id);
uint16_t len = betoh16(packet.header().param_length);
EXPECT_LE(len, 0x11); // 10 + 2 (byte count) + 5 (cont state)
packet.Resize(len);
fitx::result<Error<>> result = rsp.Parse(packet.payload_data());
if (received == 0) {
// Server should have split this into more than one response.
EXPECT_EQ(ToResult(HostError::kInProgress), result);
EXPECT_FALSE(rsp.complete());
}
received++;
if (result.is_error() && !result.error_value().is(HostError::kInProgress)) {
// This isn't a valid packet and we shouldn't try to get
// a continuation.
return;
}
if (!rsp.complete()) {
// Repeat the request with the continuation state if it was returned.
auto continuation = rsp.ContinuationState();
uint8_t cont_size = continuation.size();
EXPECT_NE(0u, cont_size);
// Make another request with the continutation data.
size_t param_size = 17 + cont_size;
auto kContinuedRequestAttrStart =
StaticByteBuffer(0x04, // SDP_ServiceAttributeRequest
0x10, 0x01, // Transaction ID (reused)
UpperBits(param_size), LowerBits(param_size), // Parameter length
UINT32_AS_BE_BYTES(handle), // ServiceRecordHandle
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0x30, 0x00, // low end of range
0xf0, 0x00 // high end of range
);
DynamicByteBuffer req(kContinuedRequestAttrStart.size() + sizeof(uint8_t) + cont_size);
kContinuedRequestAttrStart.Copy(&req);
req.Write(&cont_size, sizeof(uint8_t), kContinuedRequestAttrStart.size());
req.Write(continuation, kContinuedRequestAttrStart.size() + sizeof(uint8_t));
fake_chan()->Receive(req);
}
};
fake_chan()->SetSendCallback(send_cb, dispatcher());
fake_chan()->Receive(kRequestAttr);
RunLoopUntilIdle();
EXPECT_GE(received, 1u);
const auto& attrs = rsp.attributes();
EXPECT_EQ(2u, attrs.size());
EXPECT_NE(attrs.end(), attrs.find(kServiceClassIdList));
EXPECT_NE(attrs.end(), attrs.find(0xf000));
const auto kInvalidRangeOrder =
StaticByteBuffer(0x04, // SDP_ServiceAttritbuteRequest
0xE0, 0x01, // Transaction ID (0xE001)
0x00, 0x11, // Parameter length (17 bytes)
UINT32_AS_BE_BYTES(handle), // ServiceRecordHandle
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0xf0, 0x00, // low end of range
0x30, 0x00, // high end of range
0x00 // Contunuation State: none
);
const auto kRspErrSyntax = SDP_ERROR_RSP(0xE001, ErrorCode::kInvalidRequestSyntax);
EXPECT_TRUE(ReceiveAndExpect(kInvalidRangeOrder, kRspErrSyntax));
const auto kInvalidMaxBytes =
StaticByteBuffer(0x04, // SDP_ServiceAttritbuteRequest
0xE0, 0x02, // Transaction ID (0xE001)
0x00, 0x0C, // Parameter length (12 bytes)
UINT32_AS_BE_BYTES(handle), // ServiceRecordHandle
0x00, 0x05, // MaximumAttributeByteCount (5 bytes max)
// AttributeIDList
0x35, 0x03, // Sequence uint8 3 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x00 // Contunuation State: none
);
const auto kRspErrSyntax2 = SDP_ERROR_RSP(0xE002, ErrorCode::kInvalidRequestSyntax);
EXPECT_TRUE(ReceiveAndExpect(kInvalidMaxBytes, kRspErrSyntax2));
}
// Test:
// - Answers ServiceSearchAttributeRequest correctly
// - Continuation state is generated correctly re:
// MaximumAttributeListsByteCount
// - Valid Continuation state continues response
TEST_F(ServerTest, SearchAttributeRequest) {
ServiceRecord record1;
record1.SetServiceClassUUIDs({profile::kAVRemoteControl});
record1.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{500}));
record1.SetAttribute(0xf00d, DataElement(uint32_t{0xfeedbeef}));
record1.SetAttribute(0xf000, DataElement(uint32_t{0x01234567}));
std::vector<ServiceRecord> records1;
records1.emplace_back(std::move(record1));
RegistrationHandle handle1 =
server()->RegisterService(std::move(records1), kChannelParams, NopConnectCallback);
EXPECT_TRUE(handle1);
ServiceRecord record2;
record2.SetServiceClassUUIDs({profile::kAVRemoteControl});
record2.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(uint16_t{501}));
std::vector<ServiceRecord> records2;
records2.emplace_back(std::move(record2));
RegistrationHandle handle2 =
server()->RegisterService(std::move(records2), kChannelParams, NopConnectCallback);
EXPECT_TRUE(handle2);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
const auto kRequestAttr =
StaticByteBuffer(0x06, // SDP_ServiceAttritbuteRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x12, // Parameter length (18 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x00, // ServiceRecordHandle
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0x30, 0x00, // low end of range
0xf0, 0x00, // high end of range
0x00 // Contunuation State: none
);
size_t received = 0;
ServiceSearchAttributeResponse rsp;
auto send_cb = [this, &rsp, &received](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<sdp::Header> packet(cb_packet.get());
ASSERT_EQ(0x07, packet.header().pdu_id);
uint16_t len = betoh16(packet.header().param_length);
EXPECT_LE(len, 0x11); // 2 (byte count) + 10 (max len) + 5 (cont state)
packet.Resize(len);
fitx::result<Error<>> result = rsp.Parse(packet.payload_data());
if (received == 0) {
// Server should have split this into more than one response.
EXPECT_EQ(ToResult(HostError::kInProgress), result);
EXPECT_FALSE(rsp.complete());
}
received++;
if (result.is_error() && !result.error_value().is(HostError::kInProgress)) {
// This isn't a valid packet and we shouldn't try to get
// a continuation.
return;
}
if (!rsp.complete()) {
// Repeat the request with the continuation state if it was returned.
auto continuation = rsp.ContinuationState();
uint8_t cont_size = continuation.size();
EXPECT_NE(0u, cont_size);
// Make another request with the continutation data.
size_t param_size = 18 + cont_size;
auto kContinuedRequestAttrStart =
StaticByteBuffer(0x06, // SDP_ServiceAttributeRequest
0x10, static_cast<uint8_t>(received + 1), // Transaction ID
UpperBits(param_size), LowerBits(param_size), // Parameter length
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // SearchPattern: L2CAP
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x00, // ServiceRecordHandle
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0x30, 0x00, // low end of range
0xf0, 0x00 // high end of range
);
DynamicByteBuffer req(kContinuedRequestAttrStart.size() + sizeof(uint8_t) + cont_size);
kContinuedRequestAttrStart.Copy(&req);
req.Write(&cont_size, sizeof(uint8_t), kContinuedRequestAttrStart.size());
req.Write(continuation, kContinuedRequestAttrStart.size() + sizeof(uint8_t));
fake_chan()->Receive(req);
}
};
fake_chan()->SetSendCallback(send_cb, dispatcher());
fake_chan()->Receive(kRequestAttr);
RunLoopUntilIdle();
EXPECT_GE(received, 1u);
// We should receive both of our entered records.
EXPECT_EQ(2u, rsp.num_attribute_lists());
for (size_t i = 0; i < rsp.num_attribute_lists(); i++) {
const auto& attrs = rsp.attributes(i);
// Every service has a record handle
auto handle_it = attrs.find(kServiceRecordHandle);
EXPECT_NE(attrs.end(), handle_it);
ServiceHandle received_handle = *handle_it->second.Get<uint32_t>();
if (received_handle == handle1) {
// The first service also has another attribute we should find.
EXPECT_EQ(2u, attrs.size());
EXPECT_NE(attrs.end(), attrs.find(0xf000));
}
}
const auto kInvalidRangeOrder =
StaticByteBuffer(0x06, // SDP_ServiceAttritbuteRequest
0xE0, 0x01, // Transaction ID (0xE001)
0x00, 0x12, // Parameter length (18 bytes)
0x35, 0x03, 0x19, 0x01, 0x00, // SearchPattern: L2CAP
0x00, 0x0A, // MaximumAttributeByteCount (10 bytes max)
// AttributeIDList
0x35, 0x08, // Sequence uint8 8 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x0A, // uint32_t, which is a range (0x3000 - 0xf000)
0xf0, 0x00, // low end of range
0x30, 0x00, // high end of range
0x00 // Contunuation State: none
);
const auto kRspErrSyntax = SDP_ERROR_RSP(0xE001, ErrorCode::kInvalidRequestSyntax);
EXPECT_TRUE(ReceiveAndExpect(kInvalidRangeOrder, kRspErrSyntax));
const auto kInvalidMaxBytes =
StaticByteBuffer(0x04, // SDP_ServiceAttritbuteRequest
0xE0, 0x02, // Transaction ID (0xE002)
0x00, 0x0D, // Parameter length (13 bytes)
0x35, 0x03, 0x19, 0x01, 0x00, // SearchPattern: L2CAP
0x00, 0x05, // MaximumAttributeByteCount (5 bytes max)
// AttributeIDList
0x35, 0x03, // Sequence uint8 3 bytes
0x09, // uint16_t, single attribute
0x00, 0x01, // ServiceClassIDList
0x00 // Contunuation State: none
);
const auto kRspErrSyntax2 = SDP_ERROR_RSP(0xE002, ErrorCode::kInvalidRequestSyntax);
EXPECT_TRUE(ReceiveAndExpect(kInvalidMaxBytes, kRspErrSyntax2));
}
TEST_F(ServerTest, ConnectionCallbacks) {
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
std::vector<fbl::RefPtr<l2cap::Channel>> channels;
hci_spec::ConnectionHandle latest_handle;
// Register a service
AddA2DPSink([&channels, &latest_handle](auto chan, const auto& protocol) {
bt_log(TRACE, "test", "Got channel for the a2dp sink");
latest_handle = chan->link_handle();
channels.emplace_back(std::move(chan));
});
// Connect to the service
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kAVDTP, kSdpChannel + 1, 0x0b00));
RunLoopUntilIdle();
// It should get a callback with a channel
EXPECT_EQ(1u, channels.size());
EXPECT_EQ(kTestHandle1, latest_handle);
// Connect to the same service again with the same PSM (on a different
// connection, it should still work)
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(kTestHandle2, l2cap::kAVDTP, kSdpChannel + 2, 0x0b01));
RunLoopUntilIdle();
ASSERT_EQ(2u, channels.size());
EXPECT_EQ(kTestHandle2, latest_handle);
EXPECT_NE(channels.front(), channels.back());
// Connect to the service again, on the first connection.
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kAVDTP, kSdpChannel + 3, 0x0b00));
RunLoopUntilIdle();
// It should get a callback with a channel
EXPECT_EQ(3u, channels.size());
EXPECT_EQ(kTestHandle1, latest_handle);
}
// Browse Group gets set correctly
TEST_F(ServerTest, BrowseGroup) {
AddA2DPSink();
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, l2cap::kSDP, kSdpChannel, 0x0bad));
RunLoopUntilIdle();
const auto kRequestAttr = StaticByteBuffer(0x06, // SDP_ServiceAttritbuteRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x0D, // Parameter length (12 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0xFF, 0xFF, // MaximumAttributeByteCount (no max)
// AttributeIDList
0x35, 0x03, // Sequence uint8 3 bytes
0x09, // uint16_t, single attribute
0x00, 0x05, // BrowseGroupList
0x00 // Contunuation State: none
);
ServiceSearchAttributeResponse rsp;
auto send_cb = [&rsp](auto cb_packet) {
EXPECT_LE(sizeof(Header), cb_packet->size());
PacketView<sdp::Header> packet(cb_packet.get());
ASSERT_EQ(0x07, packet.header().pdu_id);
uint16_t len = betoh16(packet.header().param_length);
packet.Resize(len);
auto status = rsp.Parse(packet.payload_data());
EXPECT_EQ(fitx::ok(), status);
};
fake_chan()->SetSendCallback(send_cb, dispatcher());
fake_chan()->Receive(kRequestAttr);
RunLoopUntilIdle();
EXPECT_EQ(1u, rsp.num_attribute_lists());
auto& attributes = rsp.attributes(0);
auto group_attr_it = attributes.find(kBrowseGroupList);
ASSERT_EQ(DataElement::Type::kSequence, group_attr_it->second.type());
ASSERT_EQ(DataElement::Type::kUuid, group_attr_it->second.At(0)->type());
EXPECT_NE(attributes.end(), group_attr_it);
EXPECT_EQ(kPublicBrowseRootUuid, *group_attr_it->second.At(0)->Get<UUID>());
}
// Channels created for a service registered with channel parameters should be configured with that
// service's channel parameters.
TEST_F(ServerTest, RegisterServiceWithChannelParameters) {
l2cap::PSM kPSM = l2cap::kAVDTP;
l2cap::ChannelParameters preferred_params;
preferred_params.mode = l2cap::ChannelMode::kEnhancedRetransmission;
preferred_params.max_rx_sdu_size = l2cap::kMinACLMTU;
std::optional<l2cap::ChannelInfo> params;
size_t chan_cb_count = 0;
ASSERT_TRUE(AddL2capService(kPSM, preferred_params, [&](auto chan, auto& /*protocol*/) {
chan_cb_count++;
params = chan->info();
}));
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(kTestHandle1, kPSM, 0x40, 0x41));
RunLoopUntilIdle();
EXPECT_EQ(1u, chan_cb_count);
ASSERT_TRUE(params);
EXPECT_EQ(*preferred_params.mode, params->mode);
EXPECT_EQ(*preferred_params.max_rx_sdu_size, params->max_rx_sdu_size);
}
// Test:
// - Creation of ServiceDiscoveryService is valid.
TEST_F(ServerTest, MakeServiceDiscoveryServiceIsValid) {
auto sdp_def = server()->MakeServiceDiscoveryService();
const DataElement& version_number_list = sdp_def.GetAttribute(kSDP_VersionNumberList);
EXPECT_EQ(DataElement::Type::kSequence, version_number_list.type());
auto* it = version_number_list.At(0);
EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type());
EXPECT_EQ(0x0100u, it->Get<uint16_t>());
}
// Test:
// - The inspect hierarchy for the initial server is valid. It should
// only contain the registered PSM for SDP.
TEST_F(ServerTest, InspectHierarchy) {
inspect::Inspector inspector;
server()->AttachInspect(inspector.GetRoot());
auto psm_matcher = AllOf(NodeMatches(
AllOf(NameMatches("psm0x1"), PropertyList(UnorderedElementsAre(StringIs("psm", "SDP"))))));
auto reg_psm_matcher = AllOf(NodeMatches(AllOf(NameMatches("registered_psms"))),
ChildrenMatch(UnorderedElementsAre(psm_matcher)));
auto record_matcher = AllOf(
NodeMatches(AllOf(
NameMatches("record0x0"),
PropertyList(UnorderedElementsAre(StringIs(
"record",
"Service Class Id List: Sequence { UUID(00001000-0000-1000-8000-00805f9b34fb) }"))))),
ChildrenMatch(UnorderedElementsAre(reg_psm_matcher)));
auto sdp_matcher = AllOf(NodeMatches(AllOf(NameMatches("sdp_server"))),
ChildrenMatch(UnorderedElementsAre(record_matcher)));
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo());
ASSERT_TRUE(hierarchy);
EXPECT_THAT(hierarchy.take_value(), AllOf(NodeMatches(NameMatches("root")),
ChildrenMatch(UnorderedElementsAre(sdp_matcher))));
}
// Test:
// - The inspect hierarchy is updated correctly after registering another service.
// - The inspect hierarchy is updated correctly after unregistering a service.
// Note: None of the matchers test the name of the node. This is because the ordering of
// the std::unordered_map of PSMs is not guaranteed. Asserting on the name of the node
// is not feasible due to the usage of inspect::UniqueName, which assigns a new name
// to a node in every call. The contents of the node are verified.
TEST_F(ServerTest, InspectHierarchyAfterUnregisterService) {
inspect::Inspector inspector;
server()->AttachInspect(inspector.GetRoot());
auto handle = AddA2DPSink();
auto sdp_psm_matcher =
AllOf(NodeMatches(AllOf(PropertyList(UnorderedElementsAre(StringIs("psm", "SDP"))))));
auto sdp_matcher = AllOf(NodeMatches(AllOf(NameMatches("registered_psms"))),
ChildrenMatch(UnorderedElementsAre(sdp_psm_matcher)));
auto a2dp_psm_matcher =
AllOf(NodeMatches(AllOf(PropertyList(UnorderedElementsAre(StringIs("psm", "AVDTP"))))));
auto a2dp_matcher = AllOf(NodeMatches(AllOf(NameMatches("registered_psms"))),
ChildrenMatch(UnorderedElementsAre(a2dp_psm_matcher)));
auto sdp_record_matcher = AllOf(
NodeMatches(AllOf(PropertyList(UnorderedElementsAre(StringIs(
"record",
"Service Class Id List: Sequence { UUID(00001000-0000-1000-8000-00805f9b34fb) }"))))),
ChildrenMatch(UnorderedElementsAre(sdp_matcher)));
auto a2dp_record_matcher =
AllOf(NodeMatches(AllOf(PropertyList(UnorderedElementsAre(StringIs(
"record",
"Profile Descriptor: Sequence { Sequence { "
"UUID(0000110d-0000-1000-8000-00805f9b34fb) UnsignedInt:2(259) } }\nService Class "
"Id List: Sequence { UUID(0000110b-0000-1000-8000-00805f9b34fb) }"))))),
ChildrenMatch(UnorderedElementsAre(a2dp_matcher)));
auto sdp_server_matcher =
AllOf(NodeMatches(AllOf(NameMatches("sdp_server"))),
ChildrenMatch(UnorderedElementsAre(sdp_record_matcher, a2dp_record_matcher)));
// Hierarchy should contain ServiceRecords and PSMs for SDP and A2DP Sink.
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo());
ASSERT_TRUE(hierarchy);
EXPECT_THAT(hierarchy.take_value(),
AllOf(NodeMatches(NameMatches("root")),
ChildrenMatch(UnorderedElementsAre(sdp_server_matcher))));
// Unregister the A2DP Sink service.
EXPECT_TRUE(server()->UnregisterService(handle));
auto sdp_server_matcher2 = AllOf(NodeMatches(AllOf(NameMatches("sdp_server"))),
ChildrenMatch(UnorderedElementsAre(sdp_record_matcher)));
// The ServiceRecords and PSMs associated with A2DP Sink should be removed
// after the service has been registered. Only SDP's data should still exist.
auto hierarchy2 = inspect::ReadFromVmo(inspector.DuplicateVmo());
ASSERT_TRUE(hierarchy2);
EXPECT_THAT(hierarchy2.take_value(),
AllOf(NodeMatches(NameMatches("root")),
ChildrenMatch(UnorderedElementsAre(sdp_server_matcher2))));
}
// Test:
// Server::HandleRequest() provides expected responses when called without
// a corresponding l2cap::channel for both successful requests and errors.
TEST_F(ServerTest, HandleRequestWithoutChannel) {
const auto kRspErrSize = SDP_ERROR_RSP(0x1001, ErrorCode::kInvalidSize);
const StaticByteBuffer kTooSmall(0x01, // SDP_ServiceSearchRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x09 // Parameter length (9 bytes)
);
const auto kRspTooSmall = SDP_ERROR_RSP(0x1001, ErrorCode::kInvalidSize);
auto too_small_rsp = server()->HandleRequest(
std::unique_ptr<ByteBuffer>(new StaticByteBuffer(kTooSmall)), l2cap::kDefaultMTU);
EXPECT_TRUE(ContainersEqual(*too_small_rsp.value(), kRspTooSmall));
RegistrationHandle spp_handle = AddSPP();
RegistrationHandle a2dp_handle = AddA2DPSink();
const StaticByteBuffer kL2capSearch(0x02, // SDP_ServiceSearchRequest
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x08, // Parameter length (8 bytes)
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x01, 0x00, // UUID: Protocol: L2CAP
0xFF, 0xFF, // MaximumServiceRecordCount: (none)
0x00 // Contunuation State: none
);
const StaticByteBuffer kL2capSearchResponse(
0x03, // SDP_ServicesearchResponse
0x10, 0x01, // Transaction ID (0x1001)
0x00, 0x0D, // Parameter length (13 bytes)
0x00, 0x02, // Total service record count: 2
0x00, 0x02, // Current service record count: 2
UINT32_AS_BE_BYTES(a2dp_handle), // This list isn't specifically ordered
UINT32_AS_BE_BYTES(spp_handle),
0x00 // No continuation state
);
auto search_rsp = server()->HandleRequest(
std::unique_ptr<ByteBuffer>(new StaticByteBuffer(kL2capSearch)), l2cap::kDefaultMTU);
EXPECT_TRUE(ContainersEqual(*search_rsp.value(), kL2capSearchResponse));
}
#undef SDP_ERROR_RSP
#undef UINT32_AS_LE_BYTES
} // namespace
} // namespace bt::sdp