// Copyright 2022 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_profiles/device_info_service.h" #include #include "pw_bluetooth/gatt/server.h" #include "pw_unit_test/framework.h" using namespace std::string_view_literals; namespace pw::bluetooth_profiles { namespace { class FakeGattServer final : public bluetooth::gatt::Server { public: // Server overrides: void PublishService( const bluetooth::gatt::LocalServiceInfo& info, bluetooth::gatt::LocalServiceDelegate* delegate, Function&& result_callback) override { ASSERT_EQ(delegate_, nullptr); delegate_ = delegate; ASSERT_EQ(published_info_, nullptr); published_info_ = &info; local_service_.emplace(this); result_callback( PublishServiceResult(std::in_place, &local_service_.value())); } // PublishService call argument getters: const bluetooth::gatt::LocalServiceInfo* published_info() const { return published_info_; } bluetooth::gatt::LocalServiceDelegate* delegate() const { return delegate_; } private: class FakeLocalService final : public bluetooth::gatt::LocalService { public: explicit FakeLocalService(FakeGattServer* fake_server) : fake_server_(fake_server) {} // LocalService overrides: void NotifyValue( const ValueChangedParameters& /* parameters */, ValueChangedCallback&& /* completion_callback */) override { FAIL(); // Unimplemented } void IndicateValue( const ValueChangedParameters& /* parameters */, Function)>&& /* confirmation */) override { FAIL(); // Unimplemented } private: void UnpublishService() override { fake_server_->local_service_.reset(); } FakeGattServer* fake_server_; }; // The LocalServiceInfo passed when PublishService was called. const bluetooth::gatt::LocalServiceInfo* published_info_ = nullptr; bluetooth::gatt::LocalServiceDelegate* delegate_ = nullptr; std::optional local_service_; }; TEST(DeviceInfoServiceTest, PublishAndReadTest) { FakeGattServer fake_server; constexpr auto kUsedFields = DeviceInfo::Field::kModelNumber | DeviceInfo::Field::kSerialNumber | DeviceInfo::Field::kSoftwareRevision; DeviceInfo device_info = {}; const auto kModelNumber = "model"sv; device_info.model_number = as_bytes(span{kModelNumber}); device_info.serial_number = as_bytes(span{"parallel_number"sv}); device_info.software_revision = as_bytes(span{"rev123"sv}); DeviceInfoService device_info_service(device_info); bool called = false; device_info_service.PublishService( &fake_server, [&called]( bluetooth::Result res) { EXPECT_TRUE(res.ok()); called = true; }); // The FakeGattServer calls the PublishService callback right away so our // callback should have been called already. EXPECT_TRUE(called); ASSERT_NE(fake_server.delegate(), nullptr); ASSERT_NE(fake_server.published_info(), nullptr); // Test that the published info looks correct. EXPECT_EQ(3u, fake_server.published_info()->characteristics.size()); // Test that we can read the characteristics. for (auto& characteristic : fake_server.published_info()->characteristics) { bool read_callback_called = false; fake_server.delegate()->ReadValue( bluetooth::PeerId{1234}, characteristic.handle, /*offset=*/0, [&read_callback_called](bluetooth::Result> res) { EXPECT_TRUE(res.ok()); EXPECT_NE(0u, res.value().size()); read_callback_called = true; }); // The DeviceInfoService always calls the callback from within ReadValue(). EXPECT_TRUE(read_callback_called); } // Check the actual values. // The order of the characteristics in the LocalServiceInfo must be the order // in which the fields are listed in kCharacteristicFields, so the first // characteristic is the Model Number. span read_value; fake_server.delegate()->ReadValue( bluetooth::PeerId{1234}, fake_server.published_info()->characteristics[0].handle, /*offset=*/0, [&read_value](bluetooth::Result> res) { EXPECT_TRUE(res.ok()); read_value = res.value(); }); EXPECT_EQ(read_value.size(), kModelNumber.size()); // "model" string. // DeviceInfoService keeps references to the values provides in the // DeviceInfo struct, not copies. EXPECT_EQ(read_value.data(), reinterpret_cast(kModelNumber.data())); // Read with an offset. const size_t kReadOffset = 3; fake_server.delegate()->ReadValue( bluetooth::PeerId{1234}, fake_server.published_info()->characteristics[0].handle, kReadOffset, [&read_value](bluetooth::Result> res) { EXPECT_TRUE(res.ok()); read_value = res.value(); }); EXPECT_EQ(read_value.size(), kModelNumber.size() - kReadOffset); EXPECT_EQ( read_value.data(), reinterpret_cast(kModelNumber.data()) + kReadOffset); } } // namespace } // namespace pw::bluetooth_profiles