blob: 1c3d6a2f75ed1d40fbc5e72dc2de8b0b260375cd [file] [log] [blame]
// Copyright 2019 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/client.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/sdp/service_record.h"
namespace bt::sdp {
namespace {
using TestingBase = bt::l2cap::testing::FakeChannelTest;
constexpr l2cap::ChannelId kTestChannelId = 0x0041;
constexpr uint16_t kResponseMaxSize = 672;
class SDP_ClientTest : public TestingBase {
public:
SDP_ClientTest() = default;
~SDP_ClientTest() = default;
protected:
void SetUp() override {
ChannelOptions options(kTestChannelId);
options.link_type = hci::Connection::LinkType::kACL;
channel_ = CreateFakeChannel(options);
}
void TearDown() override { channel_ = nullptr; }
fbl::RefPtr<l2cap::testing::FakeChannel> channel() { return channel_; }
private:
fbl::RefPtr<l2cap::testing::FakeChannel> channel_;
};
// Flower Path Test:
// - sends correctly formatted request
// - receives response in the callback
// - receives kNotFound at the end of the callbacks
// - closes SDP channel when client is deallocated
TEST_F(SDP_ClientTest, ConnectAndQuery) {
{
auto client = Client::Create(channel());
EXPECT_TRUE(fake_chan()->activated());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
if (cb_count == 3) {
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kNotFound, status.error());
return true;
}
// All results should have the ServiceClassIdList.
EXPECT_EQ(1u, attrs.count(kServiceClassIdList));
// The first result has a kProtocolDescriptorList and the second has a
// kBluetoothProfileDescriptorList
if (cb_count == 1) {
EXPECT_EQ(1u, attrs.count(kProtocolDescriptorList));
EXPECT_EQ(0u, attrs.count(kBluetoothProfileDescriptorList));
} else if (cb_count == 2) {
EXPECT_EQ(0u, attrs.count(kProtocolDescriptorList));
EXPECT_EQ(1u, attrs.count(kBluetoothProfileDescriptorList));
}
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x09, // Sequence uint8 9 bytes
0x09, 0x00, 0x01, // uint16_t (kServiceClassIdList)
0x09, 0x00, 0x04, // uint16_t (kProtocolDescriptorList)
0x09, 0x00, 0x09, // uint16_t (kBluetoothProfileDescriptorList)
0x00 // No continuation state
);
uint32_t request_tid;
bool success = false;
fake_chan()->SetSendCallback(
[&request_tid, &success, &kSearchExpectedParams](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
request_tid = (*packet)[1] << 8 || (*packet)[2];
success = true;
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes(
{profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList, kBluetoothProfileDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(success);
// Receive the response
// Record makes building the response easier.
ServiceRecord rec;
rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(l2cap::kAVDTP));
// The second element here indicates version 1.3 (specified in A2DP spec)
rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kAVDTP,
DataElement(uint16_t{0x0103}));
rec.AddProfile(profile::kAudioSink, 1, 3);
ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
rsp.SetAttribute(0, kProtocolDescriptorList, rec.GetAttribute(kProtocolDescriptorList).Clone());
rsp.SetAttribute(1, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
rsp.SetAttribute(1, kBluetoothProfileDescriptorList,
rec.GetAttribute(kBluetoothProfileDescriptorList).Clone());
auto rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(3u, cb_count);
}
EXPECT_FALSE(fake_chan()->activated());
}
TEST_F(SDP_ClientTest, TwoQueriesSubsequent) {
{
auto client = Client::Create(channel());
EXPECT_TRUE(fake_chan()->activated());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
// We return no results for both queries.
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kNotFound, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x03, // Sequence uint8 3 bytes
0x09, 0x00, 0x01, // uint16_t (kServiceClassIdList)
0x00 // No continuation state
);
uint32_t request_tid;
bool success = false;
fake_chan()->SetSendCallback(
[&request_tid, &success, &kSearchExpectedParams](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
request_tid = (*packet)[1] << 8 || (*packet)[2];
success = true;
},
dispatcher());
// Search for all A2DP sinks, get the:
// - Service Class ID list
client->ServiceSearchAttributes({profile::kAudioSink}, {kServiceClassIdList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(success);
// Receive the response (empty response)
// Record makes building the response easier.
ServiceSearchAttributeResponse rsp;
auto rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
// Twice
success = false;
client->ServiceSearchAttributes({profile::kAudioSink}, {kServiceClassIdList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(success);
rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(2u, cb_count);
}
EXPECT_FALSE(fake_chan()->activated());
}
TEST_F(SDP_ClientTest, TwoQueriesQueued) {
{
auto client = Client::Create(channel());
EXPECT_TRUE(fake_chan()->activated());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
// We return no results for both queries.
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kNotFound, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x03, // Sequence uint8 3 bytes
0x09, 0x00, 0x01, // uint16_t (kServiceClassIdList)
0x00 // No continuation state
);
uint32_t request_tid;
size_t sent_packets = 0;
fake_chan()->SetSendCallback(
[&request_tid, &sent_packets, &kSearchExpectedParams](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
request_tid = (*packet)[1] << 8 || (*packet)[2];
sent_packets++;
},
dispatcher());
// Search for all A2DP sinks, get the:
// - Service Class ID list
client->ServiceSearchAttributes({profile::kAudioSink}, {kServiceClassIdList}, result_cb,
dispatcher());
// Twice (without waiting)
client->ServiceSearchAttributes({profile::kAudioSink}, {kServiceClassIdList}, result_cb,
dispatcher());
RunLoopUntilIdle();
// Only one request should have been sent.
EXPECT_EQ(1u, sent_packets);
// Receive the response (empty response)
// Record makes building the response easier.
ServiceSearchAttributeResponse rsp;
auto rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
// The second request should have been sent when the first completed.
EXPECT_EQ(2u, sent_packets);
// Respond to the second request.
rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(2u, cb_count);
EXPECT_EQ(2u, sent_packets);
}
EXPECT_FALSE(fake_chan()->activated());
}
// Continuing response test:
// - send correctly formatted request
// - receives a response with a continuing response
// - sends a second request to get the rest of the response
// - receives the continued response
// - responds with the results
// - gives up when callback returns false
TEST_F(SDP_ClientTest, ContinuingResponseRequested) {
auto client = Client::Create(channel());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
if (cb_count == 3) {
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kNotFound, status.error());
return true;
}
// All results should have the ServiceClassIdList.
EXPECT_EQ(1u, attrs.count(kServiceClassIdList));
EXPECT_EQ(1u, attrs.count(kProtocolDescriptorList));
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x06, // Sequence uint8 6 bytes
0x09, 0x00, 0x01, // uint16_t (0x0001 = kServiceClassIdList)
0x09, 0x00, 0x04 // uint16_t (0x0004 = kProtocolDescriptorList)
);
size_t requests_made = 0;
// Record makes building the response easier.
ServiceRecord rec;
rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kL2CAP,
DataElement(l2cap::kAVDTP));
// The second element here indicates version 1.3 (specified in A2DP spec)
rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList, protocol::kAVDTP,
DataElement(uint16_t{0x0103}));
rec.AddProfile(profile::kAudioSink, 1, 3);
ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
rsp.SetAttribute(0, kProtocolDescriptorList, rec.GetAttribute(kProtocolDescriptorList).Clone());
rsp.SetAttribute(1, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
rsp.SetAttribute(1, kProtocolDescriptorList, rec.GetAttribute(kProtocolDescriptorList).Clone());
fake_chan()->SetSendCallback(
[&](auto packet) {
requests_made++;
// First byte should be type.
ASSERT_LE(5u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
uint16_t request_tid = (*packet)[1] << 8 || (*packet)[2];
ASSERT_EQ(kSearchExpectedParams, packet->view(5, kSearchExpectedParams.size()));
// The stuff after the params is the continuation state.
auto rsp_ptr = rsp.GetPDU(16 /* Max attribute bytes */, request_tid, kResponseMaxSize,
packet->view(5 + kSearchExpectedParams.size() + 1));
fake_chan()->Receive(*rsp_ptr);
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes({profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_EQ(3u, cb_count);
EXPECT_EQ(4u, requests_made);
}
// No results test:
// - send correctly formatted request
// - receives response with no results
// - callback with no results (kNotFound right away)
TEST_F(SDP_ClientTest, NoResults) {
auto client = Client::Create(channel());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kNotFound, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x06, // Sequence uint8 6 bytes
0x09, 0x00, 0x01, // uint16_t (0x0001 = kServiceClassIdList)
0x09, 0x00, 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
0x00 // No continuation state
);
uint32_t request_tid;
bool success = false;
fake_chan()->SetSendCallback(
[&request_tid, &success, &kSearchExpectedParams](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
request_tid = (*packet)[1] << 8 || (*packet)[2];
success = true;
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes({profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(success);
// Receive an empty response
ServiceSearchAttributeResponse rsp;
auto rsp_ptr =
rsp.GetPDU(0xFFFF /* Max attribute bytes */, request_tid, kResponseMaxSize, BufferView());
fake_chan()->Receive(*rsp_ptr);
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
}
// Disconnect early test:
// - send request
// - remote end disconnects
// - result should be called with kLinkDisconnected
TEST_F(SDP_ClientTest, Disconnected) {
auto client = Client::Create(channel());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kLinkDisconnected, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x06, // Sequence uint8 6 bytes
0x09, 0x00, 0x01, // uint16_t (0x0001 = kServiceClassIdList)
0x09, 0x00, 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
0x00 // No continuation state
);
bool requested = false;
fake_chan()->SetSendCallback(
[&](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
requested = true;
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes({profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(requested);
EXPECT_EQ(0u, cb_count);
// Remote end closes the channel.
fake_chan()->Close();
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
}
// Malformed reply test:
// - remote end sends wrong packet type in response (dropped)
// - remote end sends invalid response
// - callback receives no response with a malformed packet error
TEST_F(SDP_ClientTest, InvalidResponse) {
auto client = Client::Create(channel());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kPacketMalformed, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x06, // Sequence uint8 6 bytes
0x09, 0x00, 0x01, // uint16_t (0x0001 = kServiceClassIdList)
0x09, 0x00, 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
0x00 // No continuation state
);
uint32_t request_tid;
bool requested = false;
fake_chan()->SetSendCallback(
[&](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
request_tid = (*packet)[1] << 8 || (*packet)[2];
requested = true;
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes({profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(requested);
EXPECT_EQ(0u, cb_count);
// Remote end sends some unparseable stuff for the packet.
fake_chan()->Receive(CreateStaticByteBuffer(0x07, UpperBits(request_tid), LowerBits(request_tid),
0x00, 0x03, 0x05, 0x06, 0x07));
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
}
// Time out (or possibly dropped packets that were malformed)
TEST_F(SDP_ClientTest, Timeout) {
constexpr uint32_t kTimeoutMs = 10000;
auto client = Client::Create(channel());
size_t cb_count = 0;
auto result_cb = [&](auto status, const std::map<AttributeId, DataElement> &attrs) {
cb_count++;
EXPECT_FALSE(status);
EXPECT_EQ(HostError::kTimedOut, status.error());
return true;
};
const auto kSearchExpectedParams = CreateStaticByteBuffer(
// ServiceSearchPattern
0x35, 0x03, // Sequence uint8 3 bytes
0x19, 0x11, 0x0B, // UUID (kAudioSink)
0xFF, 0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35, 0x06, // Sequence uint8 6 bytes
0x09, 0x00, 0x01, // uint16_t (0x0001 = kServiceClassIdList)
0x09, 0x00, 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
0x00 // No continuation state
);
bool requested = false;
fake_chan()->SetSendCallback(
[&](auto packet) {
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(5));
requested = true;
},
dispatcher());
// Seartch for all A2DP sinks, get the:
// - Service Class ID list
// - Descriptor List
// - Bluetooth Profile Descriptor List
client->ServiceSearchAttributes({profile::kAudioSink},
{kServiceClassIdList, kProtocolDescriptorList}, result_cb,
dispatcher());
RunLoopUntilIdle();
EXPECT_TRUE(requested);
EXPECT_EQ(0u, cb_count);
// Wait until the timeout happens
RunLoopFor(zx::msec(kTimeoutMs + 1));
EXPECT_EQ(1u, cb_count);
}
} // namespace
} // namespace bt::sdp