blob: ff5c6218cd84520420a0e251fe5b8272272173f6 [file] [log] [blame]
// Copyright 2022 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 "gatt2_server_server.h"
#include <fuchsia/bluetooth/gatt2/cpp/fidl_test_base.h>
#include <zircon/status.h>
#include <algorithm>
#include <optional>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "fuchsia/bluetooth/cpp/fidl.h"
#include "fuchsia/bluetooth/gatt2/cpp/fidl.h"
#include "lib/fidl/cpp/interface_handle.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/fake_gatt_fixture.h"
namespace bthost {
namespace {
namespace fbg = fuchsia::bluetooth::gatt2;
namespace fbt = fuchsia::bluetooth;
const std::vector<uint8_t> kBuffer = {0x00, 0x01, 0x02};
fbg::Characteristic BuildSimpleCharacteristic(uint64_t handle) {
fbg::Characteristic chrc;
fbg::Handle chrc_handle{handle};
chrc.set_handle(chrc_handle);
chrc.set_type(fbt::Uuid{{6}});
chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
fbg::AttributePermissions permissions;
fbg::SecurityRequirements security;
security.set_encryption_required(true);
permissions.set_read(std::move(security));
chrc.set_permissions(std::move(permissions));
return chrc;
}
fbg::ServiceInfo BuildSimpleService(uint64_t svc_handle = 1, bt::UInt128 svc_type = {5},
uint64_t chrc_handle = 2) {
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{svc_handle});
svc_info.set_type(fbt::Uuid{svc_type});
std::vector<fbg::Characteristic> chrcs;
chrcs.push_back(BuildSimpleCharacteristic(chrc_handle));
svc_info.set_characteristics(std::move(chrcs));
return svc_info;
}
class MockLocalService final : private ServerBase<fbg::LocalService> {
public:
using ReadValueFunction =
fit::function<void(fbt::PeerId, fbg::Handle, int32_t, ReadValueCallback)>;
using WriteValueFunction =
fit::function<void(fbg::LocalServiceWriteValueRequest, WriteValueCallback)>;
using CccFunction = fit::function<void(fbt::PeerId, fbg::Handle, bool, bool,
CharacteristicConfigurationCallback)>;
MockLocalService(fidl::InterfaceRequest<fbg::LocalService> request)
: ServerBase<fbg::LocalService>(this, std::move(request)) {
set_error_handler([this](zx_status_t status) { error_ = status; });
}
void set_read_value_function(ReadValueFunction func) { read_value_func_ = std::move(func); }
void set_write_value_function(WriteValueFunction func) { write_value_func_ = std::move(func); }
void set_ccc_function(CccFunction func) { ccc_func_ = std::move(func); }
int credits() const { return credits_; }
const std::vector<uint8_t>& credit_log() const { return credits_log_; }
void NotifyValue(fbg::ValueChangedParameters update) {
credits_--;
binding()->events().OnNotifyValue(std::move(update));
}
void IndicateValue(fbg::ValueChangedParameters update, zx::eventpair confirmation) {
credits_--;
binding()->events().OnIndicateValue(std::move(update), std::move(confirmation));
}
std::optional<zx_status_t> error() const { return error_; }
private:
// fbg::LocalService overrides:
void CharacteristicConfiguration(fbt::PeerId peer_id, fbg::Handle handle, bool notify,
bool indicate,
CharacteristicConfigurationCallback callback) override {
if (ccc_func_) {
ccc_func_(peer_id, handle, notify, indicate, std::move((callback)));
}
}
void ReadValue(fbt::PeerId peer_id, fbg::Handle handle, int32_t offset,
ReadValueCallback callback) override {
if (read_value_func_) {
read_value_func_(peer_id, handle, offset, std::move(callback));
}
}
void WriteValue(fbg::LocalServiceWriteValueRequest req, WriteValueCallback callback) override {
if (write_value_func_) {
write_value_func_(std::move(req), std::move(callback));
}
}
void PeerUpdate(fbg::LocalServicePeerUpdateRequest req, PeerUpdateCallback callback) override {}
void ValueChangedCredit(uint8_t additional_credit) override {
credits_ += additional_credit;
credits_log_.push_back(additional_credit);
}
ReadValueFunction read_value_func_;
WriteValueFunction write_value_func_;
CccFunction ccc_func_;
// Use signed integer because in tests it is possible to have negative credits.
int credits_ = fbg::INITIAL_VALUE_CHANGED_CREDITS;
std::vector<uint8_t> credits_log_;
std::optional<zx_status_t> error_;
};
class Gatt2ServerServerTest : public bt::fidl::testing::FakeGattFixture {
public:
~Gatt2ServerServerTest() override = default;
Gatt2ServerServerTest() {}
void SetUp() override {
// Create and connect to production GATT2 Server implementation
fidl::InterfaceHandle<fbg::Server> server_handle;
server_ = std::make_unique<Gatt2ServerServer>(gatt()->GetWeakPtr(), server_handle.NewRequest());
server_ptr_ = server_handle.Bind();
}
void TearDown() override {}
protected:
fbg::ServerPtr& server_ptr() { return server_ptr_; }
void DestroyServer() { server_.reset(); }
private:
// Proxy interface to the GATT2 Server implementation.
fbg::ServerPtr server_ptr_;
// Raw GATT2 Server implementation
std::unique_ptr<Gatt2ServerServer> server_;
};
TEST_F(Gatt2ServerServerTest, PublishAndRemoveServiceWithTwoCharacteristicsSuccess) {
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{1});
bt::UInt128 svc_type = {5};
svc_info.set_type(fbt::Uuid{svc_type});
std::vector<fbg::Characteristic> characteristics;
characteristics.push_back(BuildSimpleCharacteristic(/*handle=*/2));
characteristics.push_back(BuildSimpleCharacteristic(/*handle=*/3));
svc_info.set_characteristics(std::move(characteristics));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
fidl::InterfaceRequest<fbg::LocalService> request = local_service_handle_0.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle_0),
std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
bt::gatt::Service* service = fake_gatt()->local_services().begin()->second.service.get();
EXPECT_EQ(service->type(), svc_type);
EXPECT_TRUE(service->primary());
ASSERT_EQ(service->characteristics().size(), 2u);
EXPECT_EQ(service->characteristics()[0]->id(), 2u);
EXPECT_EQ(service->characteristics()[1]->id(), 3u);
request.Close(ZX_ERR_PEER_CLOSED);
RunLoopUntilIdle();
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, DestroyingServerUnregistersService) {
bt::UInt128 svc_type = {5};
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
fidl::InterfaceRequest<fbg::LocalService> request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
EXPECT_EQ(fake_gatt()->local_services().begin()->second.service->type().value(), svc_type);
DestroyServer();
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithoutHandleFails) {
fbg::ServiceInfo svc_info = BuildSimpleService();
svc_info.clear_handle();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
auto request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_SERVICE_HANDLE);
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithoutTypeFails) {
fbg::ServiceInfo svc_info = BuildSimpleService();
svc_info.clear_type();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
auto request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_UUID);
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithoutCharacteristicsFails) {
fbg::ServiceInfo svc_info = BuildSimpleService();
svc_info.clear_characteristics();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
auto request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_CHARACTERISTICS);
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithReusedHandleFails) {
fbg::ServiceInfo svc_info_0 = BuildSimpleService();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
auto request_0 = local_service_handle_0.NewRequest();
int cb_count_0 = 0;
auto cb_0 = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count_0++;
};
server_ptr()->PublishService(std::move(svc_info_0), std::move(local_service_handle_0),
std::move(cb_0));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_0, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 1u);
fbg::ServiceInfo svc_info_1 = BuildSimpleService();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
auto request_1 = local_service_handle_1.NewRequest();
int cb_count_1 = 0;
auto cb_1 = [&](fbg::Server_PublishService_Result res) {
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_SERVICE_HANDLE);
cb_count_1++;
};
server_ptr()->PublishService(std::move(svc_info_1), std::move(local_service_handle_1),
std::move(cb_1));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_1, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 1u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithReusedHandleAcrossTwoGattServersSuccceeds) {
// Both services are identical.
fbg::ServiceInfo svc_info_0 = BuildSimpleService();
fbg::ServiceInfo svc_info_1 = BuildSimpleService();
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
auto request_0 = local_service_handle_0.NewRequest();
int cb_count_0 = 0;
auto cb_0 = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count_0++;
};
server_ptr()->PublishService(std::move(svc_info_0), std::move(local_service_handle_0),
std::move(cb_0));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_0, 1);
EXPECT_EQ(fake_gatt()->local_services().size(), 1u);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
auto request_1 = local_service_handle_1.NewRequest();
int cb_count_1 = 0;
auto cb_1 = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count_1++;
};
// Create and connect to a second GATT Server implementation
fidl::InterfaceHandle<fbg::Server> server_handle_1;
auto server_1 =
std::make_unique<Gatt2ServerServer>(gatt()->GetWeakPtr(), server_handle_1.NewRequest());
fidl::InterfacePtr<fuchsia::bluetooth::gatt2::Server> server_ptr_1 = server_handle_1.Bind();
// Publish an identical service.
server_ptr_1->PublishService(std::move(svc_info_1), std::move(local_service_handle_1),
std::move(cb_1));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_1, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 2u);
}
TEST_F(Gatt2ServerServerTest, PublishTwoServicesSuccess) {
bt::UInt128 svc_type_0 = {5};
fbg::ServiceInfo svc_info_0 = BuildSimpleService(/*svc_handle=*/1, svc_type_0);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
fidl::InterfaceRequest<fbg::LocalService> request_0 = local_service_handle_0.NewRequest();
int cb_count_0 = 0;
auto cb_0 = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count_0++;
};
server_ptr()->PublishService(std::move(svc_info_0), std::move(local_service_handle_0),
std::move(cb_0));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_0, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
bt::gatt::Service* service = fake_gatt()->local_services().begin()->second.service.get();
EXPECT_EQ(service->type(), svc_type_0);
bt::UInt128 svc_type_1 = {6};
fbg::ServiceInfo svc_info_1 = BuildSimpleService(/*svc_handle=*/9, svc_type_1);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_1;
fidl::InterfaceRequest<fbg::LocalService> request_1 = local_service_handle_1.NewRequest();
int cb_count_1 = 0;
auto cb_1 = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count_1++;
};
server_ptr()->PublishService(std::move(svc_info_1), std::move(local_service_handle_1),
std::move(cb_1));
RunLoopUntilIdle();
EXPECT_EQ(cb_count_1, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 2u);
auto svc_iter = fake_gatt()->local_services().begin();
EXPECT_EQ(svc_iter->second.service->type(), svc_type_0);
svc_iter++;
EXPECT_EQ(svc_iter->second.service->type(), svc_type_1);
request_0.Close(ZX_ERR_PEER_CLOSED);
request_1.Close(ZX_ERR_PEER_CLOSED);
RunLoopUntilIdle();
EXPECT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishSecondaryService) {
bt::UInt128 svc_type = {5};
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);
svc_info.set_kind(fbg::ServiceKind::SECONDARY);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
fidl::InterfaceRequest<fbg::LocalService> request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
EXPECT_EQ(svc_iter->second.service->type(), svc_type);
EXPECT_FALSE(svc_iter->second.service->primary());
}
TEST_F(Gatt2ServerServerTest, ReadSuccess) {
uint64_t svc_handle = 1;
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{svc_handle});
svc_info.set_type(fbt::Uuid{{5}});
fbg::Characteristic chrc;
fbg::Handle chrc_handle{2};
chrc.set_handle(chrc_handle);
chrc.set_type(fbt::Uuid{{0}});
chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
chrc.set_permissions(fbg::AttributePermissions());
std::vector<fbg::Characteristic> chrcs;
chrcs.push_back(std::move(chrc));
svc_info.set_characteristics(std::move(chrcs));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc(local_service_handle.NewRequest());
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);
bt::gatt::IdType svc_id = svc_iter->first;
bt::gatt::IdType chrc_id = svc_iter->second.service->characteristics()[0]->id();
bt::PeerId peer_id(99);
int read_value_count = 0;
local_svc.set_read_value_function([&](fbt::PeerId cb_peer_id, fbg::Handle handle, int32_t offset,
fbg::LocalService::ReadValueCallback callback) {
read_value_count++;
EXPECT_EQ(peer_id.value(), cb_peer_id.value);
EXPECT_EQ(handle.value, chrc_handle.value);
EXPECT_EQ(offset, 3);
callback(fpromise::ok(kBuffer));
});
int read_responder_count = 0;
auto read_responder = [&](fit::result<bt::att::ErrorCode> status, const bt::ByteBuffer& value) {
read_responder_count++;
EXPECT_TRUE(status.is_ok());
EXPECT_THAT(value, ::testing::ElementsAreArray(kBuffer));
};
svc_iter->second.read_handler(peer_id, svc_id, chrc_id, /*offset=*/3, read_responder);
RunLoopUntilIdle();
EXPECT_EQ(read_value_count, 1);
EXPECT_EQ(read_responder_count, 1);
}
TEST_F(Gatt2ServerServerTest, ReadErrorResponse) {
uint64_t svc_handle = 1;
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{svc_handle});
svc_info.set_type(fbt::Uuid{{5}});
fbg::Characteristic chrc;
fbg::Handle chrc_handle{2};
chrc.set_handle(chrc_handle);
chrc.set_type(fbt::Uuid{{0}});
chrc.set_properties(fbg::CharacteristicPropertyBits::READ);
chrc.set_permissions(fbg::AttributePermissions());
std::vector<fbg::Characteristic> chrcs;
chrcs.push_back(std::move(chrc));
svc_info.set_characteristics(std::move(chrcs));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc(local_service_handle.NewRequest());
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);
bt::gatt::IdType svc_id = svc_iter->first;
bt::gatt::IdType chrc_id = svc_iter->second.service->characteristics()[0]->id();
int read_value_count = 0;
local_svc.set_read_value_function([&](fbt::PeerId cb_peer_id, fbg::Handle handle, int32_t offset,
fbg::LocalService::ReadValueCallback callback) {
read_value_count++;
callback(fpromise::error(fbg::Error::READ_NOT_PERMITTED));
});
int read_responder_count = 0;
auto read_responder = [&](fit::result<bt::att::ErrorCode> status, const bt::ByteBuffer& value) {
read_responder_count++;
ASSERT_TRUE(status.is_error());
EXPECT_EQ(status.error_value(), bt::att::ErrorCode::kReadNotPermitted);
EXPECT_EQ(value.size(), 0u);
};
svc_iter->second.read_handler(bt::PeerId(42), svc_id, chrc_id, /*offset=*/0, read_responder);
RunLoopUntilIdle();
EXPECT_EQ(read_value_count, 1);
EXPECT_EQ(read_responder_count, 1);
}
TEST_F(Gatt2ServerServerTest, WriteSuccess) {
uint64_t svc_handle = 1;
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{svc_handle});
svc_info.set_type(fbt::Uuid{{5}});
fbg::Characteristic chrc;
fbg::Handle chrc_handle{2};
chrc.set_handle(chrc_handle);
chrc.set_type(fbt::Uuid{{0}});
chrc.set_properties(fbg::CharacteristicPropertyBits::WRITE);
chrc.set_permissions(fbg::AttributePermissions());
std::vector<fbg::Characteristic> chrcs;
chrcs.push_back(std::move(chrc));
svc_info.set_characteristics(std::move(chrcs));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc(local_service_handle.NewRequest());
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);
bt::gatt::IdType svc_id = svc_iter->first;
bt::gatt::IdType chrc_id = svc_iter->second.service->characteristics()[0]->id();
bt::PeerId peer_id(99);
bt::StaticByteBuffer value_buffer(0x00, 0x01, 0x02);
int write_value_count = 0;
local_svc.set_write_value_function(
[&](fbg::LocalServiceWriteValueRequest req, fbg::LocalService::WriteValueCallback cb) {
write_value_count++;
ASSERT_TRUE(req.has_peer_id());
EXPECT_EQ(req.peer_id().value, peer_id.value());
ASSERT_TRUE(req.has_handle());
EXPECT_EQ(req.handle().value, chrc_handle.value);
ASSERT_TRUE(req.has_offset());
EXPECT_EQ(req.offset(), 3u);
ASSERT_TRUE(req.has_value());
EXPECT_THAT(req.value(), ::testing::ElementsAreArray(value_buffer));
cb(fpromise::ok());
});
int write_responder_count = 0;
auto write_responder = [&](fit::result<bt::att::ErrorCode> status) {
write_responder_count++;
EXPECT_TRUE(status.is_ok());
};
svc_iter->second.write_handler(peer_id, svc_id, chrc_id, /*offset=*/3, value_buffer,
std::move(write_responder));
RunLoopUntilIdle();
EXPECT_EQ(write_value_count, 1);
EXPECT_EQ(write_responder_count, 1);
}
TEST_F(Gatt2ServerServerTest, WriteError) {
uint64_t svc_handle = 1;
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{svc_handle});
svc_info.set_type(fbt::Uuid{{5}});
fbg::Characteristic chrc;
fbg::Handle chrc_handle{2};
chrc.set_handle(chrc_handle);
chrc.set_type(fbt::Uuid{{0}});
chrc.set_properties(fbg::CharacteristicPropertyBits::WRITE);
chrc.set_permissions(fbg::AttributePermissions());
std::vector<fbg::Characteristic> chrcs;
chrcs.push_back(std::move(chrc));
svc_info.set_characteristics(std::move(chrcs));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc(local_service_handle.NewRequest());
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
ASSERT_EQ(svc_iter->second.service->characteristics().size(), 1u);
bt::gatt::IdType svc_id = svc_iter->first;
bt::gatt::IdType chrc_id = svc_iter->second.service->characteristics()[0]->id();
int write_value_count = 0;
local_svc.set_write_value_function(
[&](fbg::LocalServiceWriteValueRequest req, fbg::LocalService::WriteValueCallback cb) {
write_value_count++;
cb(fpromise::error(fbg::Error::WRITE_NOT_PERMITTED));
});
int write_responder_count = 0;
auto write_responder = [&](fit::result<bt::att::ErrorCode> status) {
write_responder_count++;
ASSERT_TRUE(status.is_error());
EXPECT_EQ(status.error_value(), bt::att::ErrorCode::kWriteNotPermitted);
};
svc_iter->second.write_handler(bt::PeerId(4), svc_id, chrc_id, /*offset=*/0, bt::BufferView(),
std::move(write_responder));
RunLoopUntilIdle();
EXPECT_EQ(write_value_count, 1);
EXPECT_EQ(write_responder_count, 1);
}
TEST_F(Gatt2ServerServerTest, ClientCharacteristicConfiguration) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
bt::gatt::IdType svc_id = svc_iter->first;
bt::PeerId peer_id(4);
int ccc_count = 0;
local_svc.set_ccc_function([&](fbt::PeerId cb_peer_id, fbg::Handle handle, bool notify,
bool indicate,
fbg::LocalService::CharacteristicConfigurationCallback cb) {
ccc_count++;
EXPECT_EQ(peer_id.value(), cb_peer_id.value);
EXPECT_EQ(handle.value, chrc_handle);
EXPECT_TRUE(notify);
EXPECT_FALSE(indicate);
cb();
});
svc_iter->second.ccc_callback(svc_id, chrc_handle, peer_id, /*notify=*/true, /*indicate=*/false);
RunLoopUntilIdle();
EXPECT_EQ(ccc_count, 1);
}
TEST_F(Gatt2ServerServerTest, IndicateAllPeers) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params_0;
params_0.set_handle(fbg::Handle{chrc_handle});
std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
params_0.set_value(buffer);
zx::eventpair confirm_ours_0;
zx::eventpair confirm_theirs_0;
zx::eventpair::create(/*options=*/0, &confirm_ours_0, &confirm_theirs_0);
local_svc.IndicateValue(std::move(params_0), std::move(confirm_theirs_0));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 1u);
EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[0].value, buffer);
// Eventpair should not be signalled until indicate_cb called.
zx_signals_t observed;
zx_status_t status = confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
EXPECT_EQ(observed, 0u);
svc_iter->second.updates[0].indicate_cb(fit::ok());
status = confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
// The eventpair should have been signaled before it was closed.
EXPECT_EQ(observed, ZX_EVENTPAIR_SIGNALED | ZX_EVENTPAIR_PEER_CLOSED);
observed = 0;
// Also test with an empty peer_ids vector
fbg::ValueChangedParameters params_1;
params_1.set_peer_ids({});
params_1.set_handle(fbg::Handle{chrc_handle});
params_1.set_value(buffer);
zx::eventpair confirm_ours_1;
zx::eventpair confirm_theirs_1;
zx::eventpair::create(/*options=*/0, &confirm_ours_1, &confirm_theirs_1);
local_svc.IndicateValue(std::move(params_1), std::move(confirm_theirs_1));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
status = confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
EXPECT_EQ(observed, 0u);
svc_iter->second.updates[1].indicate_cb(fit::ok());
status = confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_SIGNALED | ZX_EVENTPAIR_PEER_CLOSED);
}
TEST_F(Gatt2ServerServerTest, IndicateAllPeersError) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
params.set_value(buffer);
zx::eventpair confirm_ours;
zx::eventpair confirm_theirs;
zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 1u);
EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[0].value, buffer);
// Eventpair should not be signalled until indicate_cb called.
zx_signals_t observed;
zx_status_t status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
EXPECT_EQ(observed, 0u);
svc_iter->second.updates[0].indicate_cb(fit::error(bt::att::ErrorCode::kUnlikelyError));
status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}
TEST_F(Gatt2ServerServerTest, IndicateValueChangedParametersMissingHandleClosesService) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
ASSERT_FALSE(local_svc.error());
// No handle is set.
fbg::ValueChangedParameters params_0;
std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
params_0.set_value(buffer);
zx::eventpair confirm_ours_0;
zx::eventpair confirm_theirs_0;
zx::eventpair::create(/*options=*/0, &confirm_ours_0, &confirm_theirs_0);
local_svc.IndicateValue(std::move(params_0), std::move(confirm_theirs_0));
RunLoopUntilIdle();
ASSERT_TRUE(local_svc.error());
EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
EXPECT_TRUE(fake_gatt()->local_services().empty());
zx_signals_t observed;
zx_status_t status = confirm_ours_0.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}
TEST_F(Gatt2ServerServerTest, IndicateValueChangedParametersMissingValueClosesService) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_FALSE(local_svc.error());
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
// No value is set.
fbg::ValueChangedParameters params_1;
params_1.set_handle(fbg::Handle{chrc_handle});
zx::eventpair confirm_ours_1;
zx::eventpair confirm_theirs_1;
zx::eventpair::create(/*options=*/0, &confirm_ours_1, &confirm_theirs_1);
local_svc.IndicateValue(std::move(params_1), std::move(confirm_theirs_1));
RunLoopUntilIdle();
ASSERT_TRUE(local_svc.error());
EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
EXPECT_TRUE(fake_gatt()->local_services().empty());
zx_signals_t observed;
zx_status_t status = confirm_ours_1.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
}
TEST_F(Gatt2ServerServerTest, Indicate2PeersSuccess) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
bt::PeerId peer_0(0);
bt::PeerId peer_1(1);
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
std::vector<uint8_t> buffer = {0x00, 0x01, 0x02};
params.set_value(buffer);
params.set_peer_ids({fbt::PeerId{peer_0.value()}, fbt::PeerId{peer_1.value()}});
zx::eventpair confirm_ours;
zx::eventpair confirm_theirs;
zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[0].value, buffer);
EXPECT_THAT(svc_iter->second.updates[0].peer, ::testing::Optional(peer_0));
EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[1].value, buffer);
EXPECT_THAT(svc_iter->second.updates[1].peer, ::testing::Optional(peer_1));
// Eventpair should not be signaled until indicate_cb called for both peers.
zx_signals_t observed;
zx_status_t status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
svc_iter->second.updates[1].indicate_cb(fit::ok());
status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
svc_iter->second.updates[0].indicate_cb(fit::ok());
status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED);
}
TEST_F(Gatt2ServerServerTest, Indicate2PeersFirstOneFails) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
params.set_value(kBuffer);
params.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
zx::eventpair confirm_ours;
zx::eventpair confirm_theirs;
zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
zx_signals_t observed;
zx_status_t status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
svc_iter->second.updates[0].indicate_cb(fit::error(bt::att::ErrorCode::kUnlikelyError));
status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
svc_iter->second.updates[1].indicate_cb(fit::ok());
}
TEST_F(Gatt2ServerServerTest, Indicate2PeersBothFail) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
params.set_value(kBuffer);
params.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
zx::eventpair confirm_ours;
zx::eventpair confirm_theirs;
zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
zx_signals_t observed;
zx_status_t status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_ERR_TIMED_OUT);
svc_iter->second.updates[1].indicate_cb(fit::error(bt::att::ErrorCode::kUnlikelyError));
status = confirm_ours.wait_one(ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED,
/*deadline=*/zx::time(0), &observed);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(observed, ZX_EVENTPAIR_PEER_CLOSED);
svc_iter->second.updates[0].indicate_cb(fit::error(bt::att::ErrorCode::kUnlikelyError));
}
TEST_F(Gatt2ServerServerTest, NotifyAllPeers) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params_0;
params_0.set_handle(fbg::Handle{chrc_handle});
params_0.set_value(kBuffer);
local_svc.NotifyValue(std::move(params_0));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 1u);
EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[0].value, kBuffer);
EXPECT_FALSE(svc_iter->second.updates[0].peer);
EXPECT_FALSE(svc_iter->second.updates[0].indicate_cb);
// Also test with an empty peer_ids vector
fbg::ValueChangedParameters params_1;
params_1.set_peer_ids({});
params_1.set_handle(fbg::Handle{chrc_handle});
params_1.set_value(kBuffer);
local_svc.NotifyValue(std::move(params_1));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[1].value, kBuffer);
EXPECT_FALSE(svc_iter->second.updates[1].peer);
EXPECT_FALSE(svc_iter->second.updates[1].indicate_cb);
}
TEST_F(Gatt2ServerServerTest, NotifyTwoPeers) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
fbg::ValueChangedParameters params_0;
params_0.set_handle(fbg::Handle{chrc_handle});
params_0.set_value(kBuffer);
params_0.set_peer_ids({fbt::PeerId{0}, fbt::PeerId{1}});
local_svc.NotifyValue(std::move(params_0));
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 2u);
EXPECT_EQ(svc_iter->second.updates[0].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[0].value, kBuffer);
EXPECT_THAT(svc_iter->second.updates[0].peer, ::testing::Optional(bt::PeerId(0)));
EXPECT_FALSE(svc_iter->second.updates[0].indicate_cb);
EXPECT_EQ(svc_iter->second.updates[1].chrc_id, chrc_handle);
EXPECT_EQ(svc_iter->second.updates[1].value, kBuffer);
EXPECT_THAT(svc_iter->second.updates[1].peer, ::testing::Optional(bt::PeerId(1)));
EXPECT_FALSE(svc_iter->second.updates[1].indicate_cb);
}
TEST_F(Gatt2ServerServerTest, NotifyInvalidParametersMissingHandleClosesService) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
ASSERT_FALSE(local_svc.error());
// Missing handle.
fbg::ValueChangedParameters params_0;
params_0.set_value(kBuffer);
local_svc.NotifyValue(std::move(params_0));
RunLoopUntilIdle();
ASSERT_TRUE(local_svc.error());
EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
EXPECT_TRUE(fake_gatt()->local_services().empty());
}
TEST_F(Gatt2ServerServerTest, NotifyInvalidParametersMissingValueClosesService) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
ASSERT_FALSE(local_svc.error());
// Missing value.
fbg::ValueChangedParameters params_1;
params_1.set_handle(fbg::Handle{chrc_handle});
local_svc.NotifyValue(std::move(params_1));
RunLoopUntilIdle();
ASSERT_TRUE(local_svc.error());
EXPECT_EQ(local_svc.error().value(), ZX_ERR_PEER_CLOSED);
EXPECT_TRUE(fake_gatt()->local_services().empty());
}
TEST_F(Gatt2ServerServerTest, ValueChangedFlowControl) {
const uint64_t chrc_handle = 2;
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, /*svc_type=*/{5}, chrc_handle);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
MockLocalService local_svc = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
EXPECT_TRUE(res.is_response());
cb_count++;
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 1u);
auto svc_iter = fake_gatt()->local_services().begin();
for (size_t i = 0; i < 3 * fbg::INITIAL_VALUE_CHANGED_CREDITS; i++) {
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
params.set_value(kBuffer);
local_svc.NotifyValue(std::move(params));
}
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 3 * fbg::INITIAL_VALUE_CHANGED_CREDITS);
EXPECT_GT(local_svc.credits(), 0);
EXPECT_LE(local_svc.credits(), static_cast<int>(fbg::INITIAL_VALUE_CHANGED_CREDITS));
EXPECT_GE(local_svc.credit_log().size(), 2u);
for (size_t i = 0; i < 3 * fbg::INITIAL_VALUE_CHANGED_CREDITS; i++) {
fbg::ValueChangedParameters params;
params.set_handle(fbg::Handle{chrc_handle});
params.set_value(kBuffer);
zx::eventpair confirm_ours;
zx::eventpair confirm_theirs;
zx::eventpair::create(/*options=*/0, &confirm_ours, &confirm_theirs);
local_svc.IndicateValue(std::move(params), std::move(confirm_theirs));
}
RunLoopUntilIdle();
ASSERT_EQ(svc_iter->second.updates.size(), 6 * fbg::INITIAL_VALUE_CHANGED_CREDITS);
EXPECT_GT(local_svc.credits(), 0);
EXPECT_LE(local_svc.credits(), static_cast<int>(fbg::INITIAL_VALUE_CHANGED_CREDITS));
EXPECT_GE(local_svc.credit_log().size(), 5u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceWithInvalidCharacteristic) {
fbg::ServiceInfo svc_info;
svc_info.set_handle(fbg::ServiceHandle{1});
bt::UInt128 svc_type = {5};
svc_info.set_type(fbt::Uuid{svc_type});
fbg::Characteristic chrc_0;
fbg::Handle chrc_handle_0{2};
chrc_0.set_handle(chrc_handle_0);
std::vector<fbg::Characteristic> characteristics;
characteristics.push_back(std::move(chrc_0));
svc_info.set_characteristics(std::move(characteristics));
fidl::InterfaceHandle<fbg::LocalService> local_service_handle_0;
fidl::InterfaceRequest<fbg::LocalService> request = local_service_handle_0.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
cb_count++;
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::INVALID_CHARACTERISTICS);
};
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle_0),
std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 0u);
}
TEST_F(Gatt2ServerServerTest, PublishServiceReturnsInvalidId) {
bt::UInt128 svc_type = {5};
fbg::ServiceInfo svc_info = BuildSimpleService(/*svc_handle=*/1, svc_type);
fidl::InterfaceHandle<fbg::LocalService> local_service_handle;
fidl::InterfaceRequest<fbg::LocalService> request = local_service_handle.NewRequest();
int cb_count = 0;
auto cb = [&](fbg::Server_PublishService_Result res) {
cb_count++;
ASSERT_TRUE(res.is_err());
EXPECT_EQ(res.err(), fbg::PublishServiceError::UNLIKELY_ERROR);
};
fake_gatt()->set_register_service_fails(true);
server_ptr()->PublishService(std::move(svc_info), std::move(local_service_handle), std::move(cb));
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 1);
ASSERT_EQ(fake_gatt()->local_services().size(), 0u);
}
} // namespace
} // namespace bthost