From 40b279aa668e4eaa5bf24459aeabeb99dc26100a Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 18 Aug 2026 06:39:14 +0000 Subject: [PATCH 1/4] feat(ble)!: take address and connection parameters in GapCentral::Connect Connect took a MAC address and an address type as separate arguments, and gave the caller no say over the link layer parameters of the connection it was about to open. The connection parameters were only reachable afterwards, through the peripheral side of the interface, so a central had to accept whatever its implementation hard-coded. That is a problem for the supervision timeout in particular, because a peripheral without a hardware ECC engine can block for well over a second while computing LE Secure Connections key material and gets supervised out by a central that assumes a short timeout. Connect now takes the address as a single GapAddress, the requested GapConnectionParameters, and the initiating timeout. Gap.proto follows the same shape: ConnectionParameters now describes the link layer parameters, and the Connect argument is renamed to ConnectRequest. BREAKING CHANGE: GapCentral::Connect takes a GapAddress and GapConnectionParameters instead of a MAC address and address type, and the Connect rpc argument is renamed from ConnectionParameters to ConnectRequest with initiatingTimeoutInMs moved to field 3. --- services/ble/Gap.cpp | 4 ++-- services/ble/Gap.hpp | 4 ++-- services/ble/Gap.proto | 25 +++++++++++++++++--- services/ble/test/TestGapCentral.cpp | 11 +++++++-- services/ble/test_doubles/GapCentralMock.hpp | 2 +- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/services/ble/Gap.cpp b/services/ble/Gap.cpp index 8ff3db9e5..a29c2b6d8 100644 --- a/services/ble/Gap.cpp +++ b/services/ble/Gap.cpp @@ -197,9 +197,9 @@ namespace services }); } - void GapCentralDecorator::Connect(hal::MacAddress macAddress, GapDeviceAddressType addressType, infra::Duration initiatingTimeout) + void GapCentralDecorator::Connect(GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout) { - GapCentralObserver::Subject().Connect(macAddress, addressType, initiatingTimeout); + GapCentralObserver::Subject().Connect(address, connectionParameters, initiatingTimeout); } void GapCentralDecorator::Standby() diff --git a/services/ble/Gap.hpp b/services/ble/Gap.hpp index 312631cb6..ccd563e96 100644 --- a/services/ble/Gap.hpp +++ b/services/ble/Gap.hpp @@ -399,7 +399,7 @@ namespace services : public infra::Subject { public: - virtual void Connect(hal::MacAddress macAddress, GapDeviceAddressType addressType, infra::Duration initiatingTimeout) = 0; + virtual void Connect(GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout) = 0; virtual void Standby() = 0; virtual void SetIdentityAddress(hal::MacAddress macAddress, GapDeviceAddressType addressType) = 0; virtual void StartDeviceDiscovery() = 0; @@ -419,7 +419,7 @@ namespace services void StateChanged(GapState state) override; // Implementation of GapCentral - void Connect(hal::MacAddress macAddress, GapDeviceAddressType addressType, infra::Duration initiatingTimeout) override; + void Connect(GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout) override; void Standby() override; void SetIdentityAddress(hal::MacAddress macAddress, GapDeviceAddressType addressType) override; void StartDeviceDiscovery() override; diff --git a/services/ble/Gap.proto b/services/ble/Gap.proto index 59410719c..881039c2c 100644 --- a/services/ble/Gap.proto +++ b/services/ble/Gap.proto @@ -392,17 +392,36 @@ message SecurityModeAndLevel SecurityLevelEnum level = 2; } +// Link layer connection parameters, as defined in Bluetooth Core Specification v6.3, Vol 6, Part B, Section 4.5.1. message ConnectionParameters +{ + // Minimum and maximum connection interval, in units of 1.25 ms. + uint32 minConnectionIntervalMultiplier = 1; + uint32 maxConnectionIntervalMultiplier = 2; + + // Number of consecutive connection events the peripheral is allowed to skip. + uint32 peripheralLatency = 3; + + // Time without a successful connection event after which the link is considered lost, in milliseconds. + // Must outlast the slowest operation the peer performs while connected. A peripheral without a hardware + // ECC engine can block for well over a second computing LE Secure Connections key material. + uint32 supervisionTimeoutInMs = 4; +} + +message ConnectRequest { // The address of the peripheral to connect to, as discovered during scanning (e.g. from DiscoveredDevice). AddressWithType addressWithType = 1; + // The link layer parameters requested for this connection. + ConnectionParameters connectionParameters = 2; + // How long the central will wait for the peripheral to respond to the connection request, in milliseconds. // After the central sends the connection request, the peripheral may take time to process and confirm it. // If the peripheral does not respond within this timeout, the connection attempt is aborted. // A value of zero waits indefinitely. // Recommended: set to at least several multiples of the peripheral's advertising interval. - uint32 initiatingTimeoutInMs = 2; + uint32 initiatingTimeoutInMs = 3; } message AdvertisingReportType @@ -422,7 +441,7 @@ message AdvertisingReportType message DiscoveredDevice { - // The address as received in the advertising packet. Use this address directly when connecting via ConnectionParameters. + // The address as received in the advertising packet. Use this address directly when connecting via ConnectRequest. // If the peripheral uses a resolvable private address (RPA), the controller will resolve it automatically // after a bond has been established and the peer's IRK is stored. AddressWithType addressWithType = 1; @@ -518,7 +537,7 @@ service GapCentral // For successful connection establishment, state will follow: standby -> initiating -> connected // For failed connection establishment, state will follow: standby -> initiating -> standby // Results in GapCentralResponse.CurrentState - rpc Connect(ConnectionParameters) returns (Nothing) { option (method_id) = 4; } + rpc Connect(ConnectRequest) returns (Nothing) { option (method_id) = 4; } // Allowed states: scanning, initiating, connected // Terminates the current (or initiating) connection. diff --git a/services/ble/test/TestGapCentral.cpp b/services/ble/test/TestGapCentral.cpp index 1b218ed14..67b4f9ecb 100644 --- a/services/ble/test/TestGapCentral.cpp +++ b/services/ble/test/TestGapCentral.cpp @@ -30,6 +30,11 @@ namespace services return x.reportType == arg.reportType && x.gapAddress == arg.gapAddress && x.rssi == arg.rssi; } + MATCHER_P(ConnectionParametersEqual, x, negation ? "Contents not equal" : "Contents are equal") + { + return x.minConnIntMultiplier == arg.minConnIntMultiplier && x.maxConnIntMultiplier == arg.maxConnIntMultiplier && x.slaveLatency == arg.slaveLatency && x.supervisorTimeoutMs == arg.supervisorTimeoutMs; + } + TEST_F(GapCentralDecoratorTest, forward_all_state_changed_events_to_observers) { EXPECT_CALL(gapObserver, StateChanged(GapState::connected)); @@ -66,9 +71,11 @@ namespace services TEST_F(GapCentralDecoratorTest, forward_all_calls_to_subject) { hal::MacAddress macAddress{ 0, 1, 2, 3, 4, 5 }; + const GapAddress address{ macAddress, GapDeviceAddressType::publicAddress }; + const GapConnectionParameters connectionParameters{ 6, 6, 0, 500 }; - EXPECT_CALL(gap, Connect(MacAddressContentsEqual(macAddress), services::GapDeviceAddressType::publicAddress, infra::Duration{ 0 })); - decorator.Connect(macAddress, services::GapDeviceAddressType::publicAddress, std::chrono::seconds(0)); + EXPECT_CALL(gap, Connect(address, ConnectionParametersEqual(connectionParameters), infra::Duration{ 0 })); + decorator.Connect(address, connectionParameters, std::chrono::seconds(0)); EXPECT_CALL(gap, Standby()); decorator.Standby(); diff --git a/services/ble/test_doubles/GapCentralMock.hpp b/services/ble/test_doubles/GapCentralMock.hpp index 10a3e6007..bd7daa620 100644 --- a/services/ble/test_doubles/GapCentralMock.hpp +++ b/services/ble/test_doubles/GapCentralMock.hpp @@ -10,7 +10,7 @@ namespace services : public GapCentral { public: - MOCK_METHOD(void, Connect, (hal::MacAddress macAddress, GapDeviceAddressType addressType, infra::Duration initiatingTimeout)); + MOCK_METHOD(void, Connect, (GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout)); MOCK_METHOD(void, Standby, ()); MOCK_METHOD(void, SetIdentityAddress, (hal::MacAddress macAddress, GapDeviceAddressType addressType)); MOCK_METHOD(void, StartDeviceDiscovery, ()); From ef60b38d174571d79a7c8fea68acb12def5b133a Mon Sep 17 00:00:00 2001 From: Gabriel Santos <118445638+gabrielsantosphilips@users.noreply.github.com> Date: Tue, 18 Aug 2026 08:44:06 +0200 Subject: [PATCH 2/4] Update Gap.proto --- services/ble/Gap.proto | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/ble/Gap.proto b/services/ble/Gap.proto index 881039c2c..dd790c3e1 100644 --- a/services/ble/Gap.proto +++ b/services/ble/Gap.proto @@ -403,8 +403,6 @@ message ConnectionParameters uint32 peripheralLatency = 3; // Time without a successful connection event after which the link is considered lost, in milliseconds. - // Must outlast the slowest operation the peer performs while connected. A peripheral without a hardware - // ECC engine can block for well over a second computing LE Secure Connections key material. uint32 supervisionTimeoutInMs = 4; } From 13b90dc5534d821b0667e003f317cc8754478f83 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 18 Aug 2026 07:41:14 +0000 Subject: [PATCH 3/4] refactor(ble)!: name the connection parameters after what they hold supervisorTimeoutMs claimed to be milliseconds while every implementation feeds it to a controller api that takes units of 10 ms, and that mismatch already produced a real defect: GapPeripheralTiCC35 divided the value by ten while its own central did not. slaveLatency uses terminology the core specification replaced with peripheral, and the interval fields were abbreviated to the point of hiding that they are multipliers as well. The names now state the unit, so callers no longer need a comment to use them: minConnIntMultiplier -> minConnectionIntervalMultiplier maxConnIntMultiplier -> maxConnectionIntervalMultiplier slaveLatency -> peripheralLatency supervisorTimeoutMs -> supervisionTimeoutMultiplier BREAKING CHANGE: the members of GapConnectionParameters are renamed, and the Connect rpc field supervisionTimeoutInMs is renamed to supervisionTimeoutMultiplier. --- services/ble/Gap.hpp | 8 ++-- services/ble/Gap.proto | 4 +- services/ble/test/TestGapCentral.cpp | 2 +- services/ble/test/TestGapPeripheral.cpp | 8 ++-- .../TestGapPeripheralIntervalDecorator.cpp | 48 +++++++++---------- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/services/ble/Gap.hpp b/services/ble/Gap.hpp index ccd563e96..77e52b392 100644 --- a/services/ble/Gap.hpp +++ b/services/ble/Gap.hpp @@ -59,10 +59,10 @@ namespace services struct GapConnectionParameters { - uint16_t minConnIntMultiplier; - uint16_t maxConnIntMultiplier; - uint16_t slaveLatency; - uint16_t supervisorTimeoutMs; + uint16_t minConnectionIntervalMultiplier; + uint16_t maxConnectionIntervalMultiplier; + uint16_t peripheralLatency; + uint16_t supervisionTimeoutMultiplier; static constexpr uint16_t connectionInitialMaxTxOctets = 251; static constexpr uint16_t connectionInitialMaxTxTime = 2120; // (connectionInitialMaxTxOctets + 14) * 8 diff --git a/services/ble/Gap.proto b/services/ble/Gap.proto index dd790c3e1..14b26a94b 100644 --- a/services/ble/Gap.proto +++ b/services/ble/Gap.proto @@ -402,8 +402,8 @@ message ConnectionParameters // Number of consecutive connection events the peripheral is allowed to skip. uint32 peripheralLatency = 3; - // Time without a successful connection event after which the link is considered lost, in milliseconds. - uint32 supervisionTimeoutInMs = 4; + // Time without a successful connection event after which the link is considered lost, in units of 10 ms. + uint32 supervisionTimeoutMultiplier = 4; } message ConnectRequest diff --git a/services/ble/test/TestGapCentral.cpp b/services/ble/test/TestGapCentral.cpp index 67b4f9ecb..0a4cf1a0e 100644 --- a/services/ble/test/TestGapCentral.cpp +++ b/services/ble/test/TestGapCentral.cpp @@ -32,7 +32,7 @@ namespace services MATCHER_P(ConnectionParametersEqual, x, negation ? "Contents not equal" : "Contents are equal") { - return x.minConnIntMultiplier == arg.minConnIntMultiplier && x.maxConnIntMultiplier == arg.maxConnIntMultiplier && x.slaveLatency == arg.slaveLatency && x.supervisorTimeoutMs == arg.supervisorTimeoutMs; + return x.minConnectionIntervalMultiplier == arg.minConnectionIntervalMultiplier && x.maxConnectionIntervalMultiplier == arg.maxConnectionIntervalMultiplier && x.peripheralLatency == arg.peripheralLatency && x.supervisionTimeoutMultiplier == arg.supervisionTimeoutMultiplier; } TEST_F(GapCentralDecoratorTest, forward_all_state_changed_events_to_observers) diff --git a/services/ble/test/TestGapPeripheral.cpp b/services/ble/test/TestGapPeripheral.cpp index 95bb12bb9..f010df442 100644 --- a/services/ble/test/TestGapPeripheral.cpp +++ b/services/ble/test/TestGapPeripheral.cpp @@ -64,10 +64,10 @@ namespace services services::GapConnectionParameters connParam{ 10, 20, 30, 40 }; EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([connParam](const services::GapConnectionParameters& param) { - EXPECT_EQ(param.maxConnIntMultiplier, connParam.maxConnIntMultiplier); - EXPECT_EQ(param.minConnIntMultiplier, connParam.minConnIntMultiplier); - EXPECT_EQ(param.slaveLatency, connParam.slaveLatency); - EXPECT_EQ(param.supervisorTimeoutMs, connParam.supervisorTimeoutMs); + EXPECT_EQ(param.maxConnectionIntervalMultiplier, connParam.maxConnectionIntervalMultiplier); + EXPECT_EQ(param.minConnectionIntervalMultiplier, connParam.minConnectionIntervalMultiplier); + EXPECT_EQ(param.peripheralLatency, connParam.peripheralLatency); + EXPECT_EQ(param.supervisionTimeoutMultiplier, connParam.supervisionTimeoutMultiplier); })); decorator.SetConnectionParameters(connParam); } diff --git a/services/ble/test/TestGapPeripheralIntervalDecorator.cpp b/services/ble/test/TestGapPeripheralIntervalDecorator.cpp index d48d361b4..b7b3948c4 100644 --- a/services/ble/test/TestGapPeripheralIntervalDecorator.cpp +++ b/services/ble/test/TestGapPeripheralIntervalDecorator.cpp @@ -71,10 +71,10 @@ TEST_F(GapPeripheralIntervalDecoratorTest, use_default_connection_parameter_when EXPECT_CALL(gapObserver, StateChanged(services::GapState::connected)); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 6); - EXPECT_EQ(connParam.maxConnIntMultiplier, 6); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.StateChanged(services::GapState::connected); } @@ -87,10 +87,10 @@ TEST_F(GapPeripheralIntervalDecoratorTest, use_user_connection_parameter_when_co EXPECT_CALL(gapObserver, StateChanged(services::GapState::connected)); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 10); - EXPECT_EQ(connParam.maxConnIntMultiplier, 10); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 10); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 10); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.StateChanged(services::GapState::connected); } @@ -102,10 +102,10 @@ TEST_F(GapPeripheralIntervalDecoratorTest, use_long_connection_parameter_when_co EXPECT_CALL(gapObserver, StateChanged(services::GapState::connected)); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 50); - EXPECT_EQ(connParam.maxConnIntMultiplier, 50); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 50); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 50); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.StateChanged(services::GapState::connected); } @@ -115,28 +115,28 @@ TEST_F(GapPeripheralIntervalDecoratorTest, switch_to_long_connection_interval_an EXPECT_CALL(gapObserver, StateChanged(services::GapState::connected)); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 6); - EXPECT_EQ(connParam.maxConnIntMultiplier, 6); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.StateChanged(services::GapState::connected); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 50); - EXPECT_EQ(connParam.maxConnIntMultiplier, 50); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 50); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 50); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.SwitchToLongInterval(); EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([](const services::GapConnectionParameters& connParam) { - EXPECT_EQ(connParam.minConnIntMultiplier, 6); - EXPECT_EQ(connParam.maxConnIntMultiplier, 6); - EXPECT_EQ(connParam.slaveLatency, 0); - EXPECT_EQ(connParam.supervisorTimeoutMs, 500); + EXPECT_EQ(connParam.minConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.maxConnectionIntervalMultiplier, 6); + EXPECT_EQ(connParam.peripheralLatency, 0); + EXPECT_EQ(connParam.supervisionTimeoutMultiplier, 500); })); gapPeripheralIntervalDecorator.SwitchToUserInterval(); } From fdf61c300fe4d83c658d9d26dea02cb2d3f6712f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 08:26:36 +0000 Subject: [PATCH 4/4] fix: normalize CRLF line endings to LF in Gap.cpp and TestGapPeripheral.cpp Co-authored-by: gabrielsantosphilips <118445638+gabrielsantosphilips@users.noreply.github.com> --- services/ble/Gap.cpp | 924 ++++++++++++------------ services/ble/test/TestGapPeripheral.cpp | 148 ++-- 2 files changed, 536 insertions(+), 536 deletions(-) diff --git a/services/ble/Gap.cpp b/services/ble/Gap.cpp index a29c2b6d8..97c4af770 100644 --- a/services/ble/Gap.cpp +++ b/services/ble/Gap.cpp @@ -1,462 +1,462 @@ -#include "services/ble/Gap.hpp" -#include "infra/stream/ByteInputStream.hpp" -#include "infra/util/BoundedString.hpp" -#include "infra/util/LogAndAbort.hpp" -#include "infra/util/MemoryRange.hpp" -#include - -namespace -{ - void AddHeader(infra::BoundedVector& payload, std::size_t length, services::GapAdvertisementDataType type) - { - payload.push_back(static_cast(length + 1)); - payload.push_back(static_cast(type)); - } - - void AddData(infra::BoundedVector& payload, infra::ConstByteRange data) - { - payload.insert(payload.end(), data.begin(), data.end()); - } -} - -namespace services -{ - void GapPairingDecorator::AuthenticationRequired(bool isNumericComparison, uint32_t passkey) - { - GapPairing::NotifyObservers([&passkey, &isNumericComparison](auto& obs) - { - obs.AuthenticationRequired(isNumericComparison, passkey); - }); - } - - void GapPairingDecorator::PairingResult(bool pairedSuccessfully, PairingFailedReason pairingFailedReason) - { - GapPairing::NotifyObservers([&pairedSuccessfully, &pairingFailedReason](auto& obs) - { - obs.PairingResult(pairedSuccessfully, pairingFailedReason); - }); - } - - void GapPairingDecorator::OutOfBandDataGenerated(const GapOutOfBandData& outOfBandData) - { - GapPairing::NotifyObservers([outOfBandData](auto& obs) - { - obs.OutOfBandDataGenerated(outOfBandData); - }); - } - - void GapPairingDecorator::PairAndBond() - { - GapPairingObserver::Subject().PairAndBond(); - } - - void GapPairingDecorator::AllowPairing(bool allow) - { - GapPairingObserver::Subject().AllowPairing(allow); - } - - void GapPairingDecorator::SetSecurityMode(SecurityMode mode, SecurityLevel level) - { - GapPairingObserver::Subject().SetSecurityMode(mode, level); - } - - void GapPairingDecorator::SetIoCapabilities(IoCapabilities caps) - { - GapPairingObserver::Subject().SetIoCapabilities(caps); - } - - void GapPairingDecorator::GenerateOutOfBandData() - { - GapPairingObserver::Subject().GenerateOutOfBandData(); - } - - void GapPairingDecorator::SetOutOfBandData(const GapOutOfBandData& outOfBandData) - { - GapPairingObserver::Subject().SetOutOfBandData(outOfBandData); - } - - void GapPairingDecorator::AuthenticateWithPasskey(uint32_t passkey) - { - GapPairingObserver::Subject().AuthenticateWithPasskey(passkey); - } - - void GapPairingDecorator::NumericComparisonConfirm(bool accept) - { - GapPairingObserver::Subject().NumericComparisonConfirm(accept); - } - - void GapBondingDecorator::NumberOfBondsChanged(std::size_t nrBonds) - { - GapBonding::NotifyObservers([&nrBonds](auto& obs) - { - obs.NumberOfBondsChanged(nrBonds); - }); - } - - void GapBondingDecorator::RemoveAllBonds() - { - GapBondingObserver::Subject().RemoveAllBonds(); - } - - void GapBondingDecorator::RemoveOldestBond() - { - GapBondingObserver::Subject().RemoveOldestBond(); - } - - void GapBondingDecorator::RemoveBondWithAddress(GapAddress gapAddress) - { - GapBondingObserver::Subject().RemoveBondWithAddress(gapAddress); - } - - std::size_t GapBondingDecorator::GetMaxNumberOfBonds() const - { - return GapBondingObserver::Subject().GetMaxNumberOfBonds(); - } - - std::size_t GapBondingDecorator::GetNumberOfBonds() const - { - return GapBondingObserver::Subject().GetNumberOfBonds(); - } - - bool GapBondingDecorator::IsDeviceBonded(hal::MacAddress address, GapDeviceAddressType addressType) const - { - return GapBondingObserver::Subject().IsDeviceBonded(address, addressType); - } - - infra::MemoryRange GapBondingDecorator::GetBondList() const - { - return GapBondingObserver::Subject().GetBondList(); - } - - void GapPeripheralDecorator::StateChanged(GapState state) - { - GapPeripheral::NotifyObservers([&state](auto& obs) - { - obs.StateChanged(state); - }); - } - - GapAddress GapPeripheralDecorator::GetAddress() const - { - return GapPeripheralObserver::Subject().GetAddress(); - } - - GapAddress GapPeripheralDecorator::GetIdentityAddress() const - { - return GapPeripheralObserver::Subject().GetIdentityAddress(); - } - - void GapPeripheralDecorator::SetAdvertisementData(infra::ConstByteRange data) - { - GapPeripheralObserver::Subject().SetAdvertisementData(data); - } - - infra::ConstByteRange GapPeripheralDecorator::GetAdvertisementData() const - { - return GapPeripheralObserver::Subject().GetAdvertisementData(); - } - - void GapPeripheralDecorator::SetScanResponseData(infra::ConstByteRange data) - { - GapPeripheralObserver::Subject().SetScanResponseData(data); - } - - infra::ConstByteRange GapPeripheralDecorator::GetScanResponseData() const - { - return GapPeripheralObserver::Subject().GetScanResponseData(); - } - - void GapPeripheralDecorator::Advertise(GapAdvertisementType type, AdvertisementIntervalMultiplier multiplier) - { - GapPeripheralObserver::Subject().Advertise(type, multiplier); - } - - void GapPeripheralDecorator::Standby() - { - GapPeripheralObserver::Subject().Standby(); - } - - void GapPeripheralDecorator::SetConnectionParameters(const services::GapConnectionParameters& connParam) - { - GapPeripheralObserver::Subject().SetConnectionParameters(connParam); - } - - void GapCentralDecorator::DeviceDiscovered(const GapAdvertisingReport& deviceDiscovered) - { - GapCentralObserver::SubjectType::NotifyObservers([&deviceDiscovered](auto& obs) - { - obs.DeviceDiscovered(deviceDiscovered); - }); - } - - void GapCentralDecorator::StateChanged(GapState state) - { - GapCentralObserver::SubjectType::NotifyObservers([&state](auto& obs) - { - obs.StateChanged(state); - }); - } - - void GapCentralDecorator::Connect(GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout) - { - GapCentralObserver::Subject().Connect(address, connectionParameters, initiatingTimeout); - } - - void GapCentralDecorator::Standby() - { - GapCentralObserver::Subject().Standby(); - } - - void GapCentralDecorator::SetIdentityAddress(hal::MacAddress macAddress, GapDeviceAddressType addressType) - { - GapCentralObserver::Subject().SetIdentityAddress(macAddress, addressType); - } - - void GapCentralDecorator::StartDeviceDiscovery() - { - GapCentralObserver::Subject().StartDeviceDiscovery(); - } - - std::optional GapCentralDecorator::ResolvePrivateAddress(hal::MacAddress address) const - { - return GapCentralObserver::Subject().ResolvePrivateAddress(address); - } - - void GapCentralDecorator::SetPrivacyMode(bool enabled) - { - GapCentralObserver::Subject().SetPrivacyMode(enabled); - } - - GapAdvertisingDataParser::GapAdvertisingDataParser(infra::ConstByteRange data) - : data(data) - {} - - infra::ConstByteRange GapAdvertisingDataParser::LocalName() const - { - auto localName = ParserAdvertisingData(GapAdvertisementDataType::completeLocalName); - - if (localName.empty()) - return ParserAdvertisingData(GapAdvertisementDataType::shortenedLocalName); - else - return localName; - } - - std::optional> GapAdvertisingDataParser::ManufacturerSpecificData() const - { - infra::ByteInputStream stream(ParserAdvertisingData(GapAdvertisementDataType::manufacturerSpecificData), infra::softFail); - auto manufacturerCode = stream.Extract(); - auto manufacturerData = stream.Reader().Remaining(); - - if (stream.Failed()) - return std::nullopt; - - return std::make_optional(std::make_pair(manufacturerCode, manufacturerData)); - } - - std::optional GapAdvertisingDataParser::Flags() const - { - auto flagsData = ParserAdvertisingData(GapAdvertisementDataType::flags); - - if (flagsData.empty()) - return std::nullopt; - - if (flagsData.size() != 1) - return std::nullopt; - - return std::make_optional(static_cast(flagsData[0])); - } - - infra::MemoryRange GapAdvertisingDataParser::CompleteListOf16BitUuids() const - { - auto uuidData = ParserAdvertisingData(GapAdvertisementDataType::completeListOf16BitUuids); - - if (uuidData.size() % sizeof(AttAttribute::Uuid16) != 0) - return {}; - - return infra::ConstCastMemoryRange(infra::ReinterpretCastMemoryRange(uuidData)); - } - - infra::MemoryRange GapAdvertisingDataParser::CompleteListOf128BitUuids() const - { - auto uuidData = ParserAdvertisingData(GapAdvertisementDataType::completeListOf128BitUuids); - - if (uuidData.size() % sizeof(AttAttribute::Uuid128) != 0) - return {}; - - return infra::ConstCastMemoryRange(infra::ReinterpretCastMemoryRange(uuidData)); - } - - std::optional GapAdvertisingDataParser::Appearance() const - { - auto appearanceData = ParserAdvertisingData(GapAdvertisementDataType::appearance); - - infra::ByteInputStream stream(appearanceData, infra::softFail); - auto appearance = stream.Extract(); - - if (stream.Failed()) - return std::nullopt; - - return std::make_optional(appearance); - } - - infra::ConstByteRange GapAdvertisingDataParser::ParserAdvertisingData(GapAdvertisementDataType type) const - { - const uint8_t lengthOffset = 0; - const uint8_t advertisingTypeOffset = 1; - const uint8_t headerSize = 2; - - infra::ConstByteRange advData = data; - - while (!advData.empty()) - { - size_t elementSize = std::min(advData[lengthOffset] + 1, advData.size()); - - auto element = infra::Head(advData, elementSize); - - if (element.size() == 1 || (element.size() != element[lengthOffset] + 1)) - return infra::ConstByteRange(); - - if (element[advertisingTypeOffset] == static_cast(type)) - return infra::DiscardHead(element, headerSize); - - advData = infra::DiscardHead(advData, element.size()); - } - - return infra::ConstByteRange(); - } - - GapAdvertisementFormatter::GapAdvertisementFormatter(infra::BoundedVector& payload) - : payload(payload) - {} - - void GapAdvertisementFormatter::AppendFlags(GapPeripheral::AdvertisementFlags flags) - { - really_assert(headerSize + sizeof(flags) <= RemainingSpaceAvailable()); - - auto flagsByte = static_cast(flags); - AddHeader(payload, sizeof(flags), GapAdvertisementDataType::flags); - AddData(payload, infra::MakeRangeFromSingleObject(flagsByte)); - } - - void GapAdvertisementFormatter::AppendCompleteLocalName(const infra::BoundedConstString& name) - { - really_assert(name.size() + headerSize <= RemainingSpaceAvailable() && name.size() > 0); - - AddHeader(payload, name.size(), GapAdvertisementDataType::completeLocalName); - AddData(payload, infra::StringAsByteRange(name)); - } - - void GapAdvertisementFormatter::AppendShortenedLocalName(const infra::BoundedConstString& name) - { - really_assert(name.size() + headerSize <= RemainingSpaceAvailable() && name.size() > 0); - - AddHeader(payload, name.size(), GapAdvertisementDataType::shortenedLocalName); - AddData(payload, infra::StringAsByteRange(name)); - } - - void GapAdvertisementFormatter::AppendManufacturerData(uint16_t manufacturerCode, infra::ConstByteRange data) - { - really_assert(data.size() + headerSize + sizeof(manufacturerCode) <= RemainingSpaceAvailable()); - - AddHeader(payload, data.size() + sizeof(manufacturerCode), GapAdvertisementDataType::manufacturerSpecificData); - AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(manufacturerCode))); - AddData(payload, data); - } - - void GapAdvertisementFormatter::AppendListOfServicesUuid(infra::MemoryRange services) - { - really_assert(services.size() * sizeof(AttAttribute::Uuid16) + headerSize <= RemainingSpaceAvailable() && !services.empty()); - - AddHeader(payload, services.size() * sizeof(AttAttribute::Uuid16), GapAdvertisementDataType::completeListOf16BitUuids); - - for (const auto& service : services) - AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(service))); - } - - void GapAdvertisementFormatter::AppendListOfServicesUuid(infra::MemoryRange services) - { - really_assert(services.size() * sizeof(AttAttribute::Uuid128) + headerSize <= RemainingSpaceAvailable() && !services.empty()); - - AddHeader(payload, services.size() * sizeof(AttAttribute::Uuid128), GapAdvertisementDataType::completeListOf128BitUuids); - - for (auto& service : services) - AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(service))); - } - - void GapAdvertisementFormatter::AppendPublicTargetAddress(hal::MacAddress address) - { - really_assert(sizeof(address) + headerSize <= RemainingSpaceAvailable()); - - AddHeader(payload, sizeof(address), GapAdvertisementDataType::publicTargetAddress); - AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(address))); - } - - void GapAdvertisementFormatter::AppendAppearance(uint16_t appearance) - { - really_assert(sizeof(appearance) + headerSize <= RemainingSpaceAvailable()); - - AddHeader(payload, sizeof(appearance), GapAdvertisementDataType::appearance); - AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(appearance))); - } - - infra::ConstByteRange GapAdvertisementFormatter::FormattedAdvertisementData() const - { - return infra::MakeRange(payload); - } - - std::size_t GapAdvertisementFormatter::RemainingSpaceAvailable() const - { - return payload.max_size() - payload.size(); - } -} - -namespace infra -{ - TextOutputStream& operator<<(TextOutputStream& stream, const services::AdvertisingReportType& reportType) - { - if (reportType == services::AdvertisingReportType::advInd) - stream << "ADV_IND"; - else if (reportType == services::AdvertisingReportType::advDirectInd) - stream << "ADV_DIRECT_IND"; - else if (reportType == services::AdvertisingReportType::advScanInd) - stream << "ADV_SCAN_IND"; - else if (reportType == services::AdvertisingReportType::scanResponse) - stream << "SCAN_RESPONSE"; - else if (reportType == services::AdvertisingReportType::advNonconnInd) - stream << "ADV_NONCONN_IND"; - else - LOG_AND_ABORT_ENUM(reportType); - - return stream; - } - - TextOutputStream& operator<<(TextOutputStream& stream, const services::GapDeviceAddressType& addressType) - { - if (addressType == services::GapDeviceAddressType::publicAddress) - stream << "Public"; - else if (addressType == services::GapDeviceAddressType::randomAddress) - stream << "Random"; - else - LOG_AND_ABORT_ENUM(addressType); - - return stream; - } - - TextOutputStream& operator<<(TextOutputStream& stream, const services::GapState& state) - { - if (state == services::GapState::standby) - stream << "Standby"; - else if (state == services::GapState::scanning) - stream << "Scanning"; - else if (state == services::GapState::advertising) - stream << "Advertising"; - else if (state == services::GapState::initiating) - stream << "Initiating"; - else if (state == services::GapState::connected) - stream << "Connected"; - else - LOG_AND_ABORT_ENUM(state); - - return stream; - } -} +#include "services/ble/Gap.hpp" +#include "infra/stream/ByteInputStream.hpp" +#include "infra/util/BoundedString.hpp" +#include "infra/util/LogAndAbort.hpp" +#include "infra/util/MemoryRange.hpp" +#include + +namespace +{ + void AddHeader(infra::BoundedVector& payload, std::size_t length, services::GapAdvertisementDataType type) + { + payload.push_back(static_cast(length + 1)); + payload.push_back(static_cast(type)); + } + + void AddData(infra::BoundedVector& payload, infra::ConstByteRange data) + { + payload.insert(payload.end(), data.begin(), data.end()); + } +} + +namespace services +{ + void GapPairingDecorator::AuthenticationRequired(bool isNumericComparison, uint32_t passkey) + { + GapPairing::NotifyObservers([&passkey, &isNumericComparison](auto& obs) + { + obs.AuthenticationRequired(isNumericComparison, passkey); + }); + } + + void GapPairingDecorator::PairingResult(bool pairedSuccessfully, PairingFailedReason pairingFailedReason) + { + GapPairing::NotifyObservers([&pairedSuccessfully, &pairingFailedReason](auto& obs) + { + obs.PairingResult(pairedSuccessfully, pairingFailedReason); + }); + } + + void GapPairingDecorator::OutOfBandDataGenerated(const GapOutOfBandData& outOfBandData) + { + GapPairing::NotifyObservers([outOfBandData](auto& obs) + { + obs.OutOfBandDataGenerated(outOfBandData); + }); + } + + void GapPairingDecorator::PairAndBond() + { + GapPairingObserver::Subject().PairAndBond(); + } + + void GapPairingDecorator::AllowPairing(bool allow) + { + GapPairingObserver::Subject().AllowPairing(allow); + } + + void GapPairingDecorator::SetSecurityMode(SecurityMode mode, SecurityLevel level) + { + GapPairingObserver::Subject().SetSecurityMode(mode, level); + } + + void GapPairingDecorator::SetIoCapabilities(IoCapabilities caps) + { + GapPairingObserver::Subject().SetIoCapabilities(caps); + } + + void GapPairingDecorator::GenerateOutOfBandData() + { + GapPairingObserver::Subject().GenerateOutOfBandData(); + } + + void GapPairingDecorator::SetOutOfBandData(const GapOutOfBandData& outOfBandData) + { + GapPairingObserver::Subject().SetOutOfBandData(outOfBandData); + } + + void GapPairingDecorator::AuthenticateWithPasskey(uint32_t passkey) + { + GapPairingObserver::Subject().AuthenticateWithPasskey(passkey); + } + + void GapPairingDecorator::NumericComparisonConfirm(bool accept) + { + GapPairingObserver::Subject().NumericComparisonConfirm(accept); + } + + void GapBondingDecorator::NumberOfBondsChanged(std::size_t nrBonds) + { + GapBonding::NotifyObservers([&nrBonds](auto& obs) + { + obs.NumberOfBondsChanged(nrBonds); + }); + } + + void GapBondingDecorator::RemoveAllBonds() + { + GapBondingObserver::Subject().RemoveAllBonds(); + } + + void GapBondingDecorator::RemoveOldestBond() + { + GapBondingObserver::Subject().RemoveOldestBond(); + } + + void GapBondingDecorator::RemoveBondWithAddress(GapAddress gapAddress) + { + GapBondingObserver::Subject().RemoveBondWithAddress(gapAddress); + } + + std::size_t GapBondingDecorator::GetMaxNumberOfBonds() const + { + return GapBondingObserver::Subject().GetMaxNumberOfBonds(); + } + + std::size_t GapBondingDecorator::GetNumberOfBonds() const + { + return GapBondingObserver::Subject().GetNumberOfBonds(); + } + + bool GapBondingDecorator::IsDeviceBonded(hal::MacAddress address, GapDeviceAddressType addressType) const + { + return GapBondingObserver::Subject().IsDeviceBonded(address, addressType); + } + + infra::MemoryRange GapBondingDecorator::GetBondList() const + { + return GapBondingObserver::Subject().GetBondList(); + } + + void GapPeripheralDecorator::StateChanged(GapState state) + { + GapPeripheral::NotifyObservers([&state](auto& obs) + { + obs.StateChanged(state); + }); + } + + GapAddress GapPeripheralDecorator::GetAddress() const + { + return GapPeripheralObserver::Subject().GetAddress(); + } + + GapAddress GapPeripheralDecorator::GetIdentityAddress() const + { + return GapPeripheralObserver::Subject().GetIdentityAddress(); + } + + void GapPeripheralDecorator::SetAdvertisementData(infra::ConstByteRange data) + { + GapPeripheralObserver::Subject().SetAdvertisementData(data); + } + + infra::ConstByteRange GapPeripheralDecorator::GetAdvertisementData() const + { + return GapPeripheralObserver::Subject().GetAdvertisementData(); + } + + void GapPeripheralDecorator::SetScanResponseData(infra::ConstByteRange data) + { + GapPeripheralObserver::Subject().SetScanResponseData(data); + } + + infra::ConstByteRange GapPeripheralDecorator::GetScanResponseData() const + { + return GapPeripheralObserver::Subject().GetScanResponseData(); + } + + void GapPeripheralDecorator::Advertise(GapAdvertisementType type, AdvertisementIntervalMultiplier multiplier) + { + GapPeripheralObserver::Subject().Advertise(type, multiplier); + } + + void GapPeripheralDecorator::Standby() + { + GapPeripheralObserver::Subject().Standby(); + } + + void GapPeripheralDecorator::SetConnectionParameters(const services::GapConnectionParameters& connParam) + { + GapPeripheralObserver::Subject().SetConnectionParameters(connParam); + } + + void GapCentralDecorator::DeviceDiscovered(const GapAdvertisingReport& deviceDiscovered) + { + GapCentralObserver::SubjectType::NotifyObservers([&deviceDiscovered](auto& obs) + { + obs.DeviceDiscovered(deviceDiscovered); + }); + } + + void GapCentralDecorator::StateChanged(GapState state) + { + GapCentralObserver::SubjectType::NotifyObservers([&state](auto& obs) + { + obs.StateChanged(state); + }); + } + + void GapCentralDecorator::Connect(GapAddress address, const GapConnectionParameters& connectionParameters, infra::Duration initiatingTimeout) + { + GapCentralObserver::Subject().Connect(address, connectionParameters, initiatingTimeout); + } + + void GapCentralDecorator::Standby() + { + GapCentralObserver::Subject().Standby(); + } + + void GapCentralDecorator::SetIdentityAddress(hal::MacAddress macAddress, GapDeviceAddressType addressType) + { + GapCentralObserver::Subject().SetIdentityAddress(macAddress, addressType); + } + + void GapCentralDecorator::StartDeviceDiscovery() + { + GapCentralObserver::Subject().StartDeviceDiscovery(); + } + + std::optional GapCentralDecorator::ResolvePrivateAddress(hal::MacAddress address) const + { + return GapCentralObserver::Subject().ResolvePrivateAddress(address); + } + + void GapCentralDecorator::SetPrivacyMode(bool enabled) + { + GapCentralObserver::Subject().SetPrivacyMode(enabled); + } + + GapAdvertisingDataParser::GapAdvertisingDataParser(infra::ConstByteRange data) + : data(data) + {} + + infra::ConstByteRange GapAdvertisingDataParser::LocalName() const + { + auto localName = ParserAdvertisingData(GapAdvertisementDataType::completeLocalName); + + if (localName.empty()) + return ParserAdvertisingData(GapAdvertisementDataType::shortenedLocalName); + else + return localName; + } + + std::optional> GapAdvertisingDataParser::ManufacturerSpecificData() const + { + infra::ByteInputStream stream(ParserAdvertisingData(GapAdvertisementDataType::manufacturerSpecificData), infra::softFail); + auto manufacturerCode = stream.Extract(); + auto manufacturerData = stream.Reader().Remaining(); + + if (stream.Failed()) + return std::nullopt; + + return std::make_optional(std::make_pair(manufacturerCode, manufacturerData)); + } + + std::optional GapAdvertisingDataParser::Flags() const + { + auto flagsData = ParserAdvertisingData(GapAdvertisementDataType::flags); + + if (flagsData.empty()) + return std::nullopt; + + if (flagsData.size() != 1) + return std::nullopt; + + return std::make_optional(static_cast(flagsData[0])); + } + + infra::MemoryRange GapAdvertisingDataParser::CompleteListOf16BitUuids() const + { + auto uuidData = ParserAdvertisingData(GapAdvertisementDataType::completeListOf16BitUuids); + + if (uuidData.size() % sizeof(AttAttribute::Uuid16) != 0) + return {}; + + return infra::ConstCastMemoryRange(infra::ReinterpretCastMemoryRange(uuidData)); + } + + infra::MemoryRange GapAdvertisingDataParser::CompleteListOf128BitUuids() const + { + auto uuidData = ParserAdvertisingData(GapAdvertisementDataType::completeListOf128BitUuids); + + if (uuidData.size() % sizeof(AttAttribute::Uuid128) != 0) + return {}; + + return infra::ConstCastMemoryRange(infra::ReinterpretCastMemoryRange(uuidData)); + } + + std::optional GapAdvertisingDataParser::Appearance() const + { + auto appearanceData = ParserAdvertisingData(GapAdvertisementDataType::appearance); + + infra::ByteInputStream stream(appearanceData, infra::softFail); + auto appearance = stream.Extract(); + + if (stream.Failed()) + return std::nullopt; + + return std::make_optional(appearance); + } + + infra::ConstByteRange GapAdvertisingDataParser::ParserAdvertisingData(GapAdvertisementDataType type) const + { + const uint8_t lengthOffset = 0; + const uint8_t advertisingTypeOffset = 1; + const uint8_t headerSize = 2; + + infra::ConstByteRange advData = data; + + while (!advData.empty()) + { + size_t elementSize = std::min(advData[lengthOffset] + 1, advData.size()); + + auto element = infra::Head(advData, elementSize); + + if (element.size() == 1 || (element.size() != element[lengthOffset] + 1)) + return infra::ConstByteRange(); + + if (element[advertisingTypeOffset] == static_cast(type)) + return infra::DiscardHead(element, headerSize); + + advData = infra::DiscardHead(advData, element.size()); + } + + return infra::ConstByteRange(); + } + + GapAdvertisementFormatter::GapAdvertisementFormatter(infra::BoundedVector& payload) + : payload(payload) + {} + + void GapAdvertisementFormatter::AppendFlags(GapPeripheral::AdvertisementFlags flags) + { + really_assert(headerSize + sizeof(flags) <= RemainingSpaceAvailable()); + + auto flagsByte = static_cast(flags); + AddHeader(payload, sizeof(flags), GapAdvertisementDataType::flags); + AddData(payload, infra::MakeRangeFromSingleObject(flagsByte)); + } + + void GapAdvertisementFormatter::AppendCompleteLocalName(const infra::BoundedConstString& name) + { + really_assert(name.size() + headerSize <= RemainingSpaceAvailable() && name.size() > 0); + + AddHeader(payload, name.size(), GapAdvertisementDataType::completeLocalName); + AddData(payload, infra::StringAsByteRange(name)); + } + + void GapAdvertisementFormatter::AppendShortenedLocalName(const infra::BoundedConstString& name) + { + really_assert(name.size() + headerSize <= RemainingSpaceAvailable() && name.size() > 0); + + AddHeader(payload, name.size(), GapAdvertisementDataType::shortenedLocalName); + AddData(payload, infra::StringAsByteRange(name)); + } + + void GapAdvertisementFormatter::AppendManufacturerData(uint16_t manufacturerCode, infra::ConstByteRange data) + { + really_assert(data.size() + headerSize + sizeof(manufacturerCode) <= RemainingSpaceAvailable()); + + AddHeader(payload, data.size() + sizeof(manufacturerCode), GapAdvertisementDataType::manufacturerSpecificData); + AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(manufacturerCode))); + AddData(payload, data); + } + + void GapAdvertisementFormatter::AppendListOfServicesUuid(infra::MemoryRange services) + { + really_assert(services.size() * sizeof(AttAttribute::Uuid16) + headerSize <= RemainingSpaceAvailable() && !services.empty()); + + AddHeader(payload, services.size() * sizeof(AttAttribute::Uuid16), GapAdvertisementDataType::completeListOf16BitUuids); + + for (const auto& service : services) + AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(service))); + } + + void GapAdvertisementFormatter::AppendListOfServicesUuid(infra::MemoryRange services) + { + really_assert(services.size() * sizeof(AttAttribute::Uuid128) + headerSize <= RemainingSpaceAvailable() && !services.empty()); + + AddHeader(payload, services.size() * sizeof(AttAttribute::Uuid128), GapAdvertisementDataType::completeListOf128BitUuids); + + for (auto& service : services) + AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(service))); + } + + void GapAdvertisementFormatter::AppendPublicTargetAddress(hal::MacAddress address) + { + really_assert(sizeof(address) + headerSize <= RemainingSpaceAvailable()); + + AddHeader(payload, sizeof(address), GapAdvertisementDataType::publicTargetAddress); + AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(address))); + } + + void GapAdvertisementFormatter::AppendAppearance(uint16_t appearance) + { + really_assert(sizeof(appearance) + headerSize <= RemainingSpaceAvailable()); + + AddHeader(payload, sizeof(appearance), GapAdvertisementDataType::appearance); + AddData(payload, infra::ReinterpretCastMemoryRange(infra::MakeRangeFromSingleObject(appearance))); + } + + infra::ConstByteRange GapAdvertisementFormatter::FormattedAdvertisementData() const + { + return infra::MakeRange(payload); + } + + std::size_t GapAdvertisementFormatter::RemainingSpaceAvailable() const + { + return payload.max_size() - payload.size(); + } +} + +namespace infra +{ + TextOutputStream& operator<<(TextOutputStream& stream, const services::AdvertisingReportType& reportType) + { + if (reportType == services::AdvertisingReportType::advInd) + stream << "ADV_IND"; + else if (reportType == services::AdvertisingReportType::advDirectInd) + stream << "ADV_DIRECT_IND"; + else if (reportType == services::AdvertisingReportType::advScanInd) + stream << "ADV_SCAN_IND"; + else if (reportType == services::AdvertisingReportType::scanResponse) + stream << "SCAN_RESPONSE"; + else if (reportType == services::AdvertisingReportType::advNonconnInd) + stream << "ADV_NONCONN_IND"; + else + LOG_AND_ABORT_ENUM(reportType); + + return stream; + } + + TextOutputStream& operator<<(TextOutputStream& stream, const services::GapDeviceAddressType& addressType) + { + if (addressType == services::GapDeviceAddressType::publicAddress) + stream << "Public"; + else if (addressType == services::GapDeviceAddressType::randomAddress) + stream << "Random"; + else + LOG_AND_ABORT_ENUM(addressType); + + return stream; + } + + TextOutputStream& operator<<(TextOutputStream& stream, const services::GapState& state) + { + if (state == services::GapState::standby) + stream << "Standby"; + else if (state == services::GapState::scanning) + stream << "Scanning"; + else if (state == services::GapState::advertising) + stream << "Advertising"; + else if (state == services::GapState::initiating) + stream << "Initiating"; + else if (state == services::GapState::connected) + stream << "Connected"; + else + LOG_AND_ABORT_ENUM(state); + + return stream; + } +} diff --git a/services/ble/test/TestGapPeripheral.cpp b/services/ble/test/TestGapPeripheral.cpp index f010df442..47fa5afe8 100644 --- a/services/ble/test/TestGapPeripheral.cpp +++ b/services/ble/test/TestGapPeripheral.cpp @@ -1,74 +1,74 @@ -#include "infra/util/test_helper/MemoryRangeMatcher.hpp" -#include "services/ble/Gap.hpp" -#include "services/ble/test_doubles/GapCentralMock.hpp" -#include "services/ble/test_doubles/GapCentralObserverMock.hpp" -#include "services/ble/test_doubles/GapPeripheralMock.hpp" -#include "services/ble/test_doubles/GapPeripheralObserverMock.hpp" -#include "gmock/gmock.h" - -namespace services -{ - namespace - { - class GapPeripheralDecoratorTest - : public testing::Test - { - public: - GapPeripheralMock gap; - GapPeripheralDecorator decorator{ gap }; - GapPeripheralObserverMock gapObserver{ decorator }; - }; - } - - TEST_F(GapPeripheralDecoratorTest, forward_all_events_to_observers) - { - EXPECT_CALL(gapObserver, StateChanged(GapState::connected)); - EXPECT_CALL(gapObserver, StateChanged(GapState::advertising)); - - gap.NotifyObservers([](GapPeripheralObserver& obs) - { - obs.StateChanged(GapState::connected); - obs.StateChanged(GapState::advertising); - }); - } - - TEST_F(GapPeripheralDecoratorTest, forward_all_calls_to_subject) - { - services::GapAddress address = { hal::MacAddress({ 5, 4, 3, 2, 1, 0 }), services::GapDeviceAddressType::publicAddress }; - EXPECT_CALL(gap, GetAddress()).WillOnce(testing::Return(address)); - EXPECT_THAT(decorator.GetAddress(), testing::Eq(address)); - - services::GapAddress identityAddress = { hal::MacAddress({ 0, 1, 2, 3, 4, 5 }), services::GapDeviceAddressType::publicAddress }; - EXPECT_CALL(gap, GetIdentityAddress()).WillOnce(testing::Return(identityAddress)); - EXPECT_THAT(decorator.GetIdentityAddress(), testing::Eq(identityAddress)); - - std::array data{ 0, 1, 2, 3, 4, 5 }; - EXPECT_CALL(gap, SetAdvertisementData(infra::ContentsEqual(data))); - decorator.SetAdvertisementData(data); - - EXPECT_CALL(gap, GetAdvertisementData()); - decorator.GetAdvertisementData(); - - EXPECT_CALL(gap, SetScanResponseData(infra::ContentsEqual(data))); - decorator.SetScanResponseData(data); - - EXPECT_CALL(gap, GetScanResponseData()); - decorator.GetScanResponseData(); - - EXPECT_CALL(gap, Advertise(services::GapAdvertisementType::advNonconnInd, 32)); - decorator.Advertise(services::GapAdvertisementType::advNonconnInd, 32); - - EXPECT_CALL(gap, Standby()); - decorator.Standby(); - - services::GapConnectionParameters connParam{ 10, 20, 30, 40 }; - EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([connParam](const services::GapConnectionParameters& param) - { - EXPECT_EQ(param.maxConnectionIntervalMultiplier, connParam.maxConnectionIntervalMultiplier); - EXPECT_EQ(param.minConnectionIntervalMultiplier, connParam.minConnectionIntervalMultiplier); - EXPECT_EQ(param.peripheralLatency, connParam.peripheralLatency); - EXPECT_EQ(param.supervisionTimeoutMultiplier, connParam.supervisionTimeoutMultiplier); - })); - decorator.SetConnectionParameters(connParam); - } -} +#include "infra/util/test_helper/MemoryRangeMatcher.hpp" +#include "services/ble/Gap.hpp" +#include "services/ble/test_doubles/GapCentralMock.hpp" +#include "services/ble/test_doubles/GapCentralObserverMock.hpp" +#include "services/ble/test_doubles/GapPeripheralMock.hpp" +#include "services/ble/test_doubles/GapPeripheralObserverMock.hpp" +#include "gmock/gmock.h" + +namespace services +{ + namespace + { + class GapPeripheralDecoratorTest + : public testing::Test + { + public: + GapPeripheralMock gap; + GapPeripheralDecorator decorator{ gap }; + GapPeripheralObserverMock gapObserver{ decorator }; + }; + } + + TEST_F(GapPeripheralDecoratorTest, forward_all_events_to_observers) + { + EXPECT_CALL(gapObserver, StateChanged(GapState::connected)); + EXPECT_CALL(gapObserver, StateChanged(GapState::advertising)); + + gap.NotifyObservers([](GapPeripheralObserver& obs) + { + obs.StateChanged(GapState::connected); + obs.StateChanged(GapState::advertising); + }); + } + + TEST_F(GapPeripheralDecoratorTest, forward_all_calls_to_subject) + { + services::GapAddress address = { hal::MacAddress({ 5, 4, 3, 2, 1, 0 }), services::GapDeviceAddressType::publicAddress }; + EXPECT_CALL(gap, GetAddress()).WillOnce(testing::Return(address)); + EXPECT_THAT(decorator.GetAddress(), testing::Eq(address)); + + services::GapAddress identityAddress = { hal::MacAddress({ 0, 1, 2, 3, 4, 5 }), services::GapDeviceAddressType::publicAddress }; + EXPECT_CALL(gap, GetIdentityAddress()).WillOnce(testing::Return(identityAddress)); + EXPECT_THAT(decorator.GetIdentityAddress(), testing::Eq(identityAddress)); + + std::array data{ 0, 1, 2, 3, 4, 5 }; + EXPECT_CALL(gap, SetAdvertisementData(infra::ContentsEqual(data))); + decorator.SetAdvertisementData(data); + + EXPECT_CALL(gap, GetAdvertisementData()); + decorator.GetAdvertisementData(); + + EXPECT_CALL(gap, SetScanResponseData(infra::ContentsEqual(data))); + decorator.SetScanResponseData(data); + + EXPECT_CALL(gap, GetScanResponseData()); + decorator.GetScanResponseData(); + + EXPECT_CALL(gap, Advertise(services::GapAdvertisementType::advNonconnInd, 32)); + decorator.Advertise(services::GapAdvertisementType::advNonconnInd, 32); + + EXPECT_CALL(gap, Standby()); + decorator.Standby(); + + services::GapConnectionParameters connParam{ 10, 20, 30, 40 }; + EXPECT_CALL(gap, SetConnectionParameters(testing::_)).WillOnce(testing::Invoke([connParam](const services::GapConnectionParameters& param) + { + EXPECT_EQ(param.maxConnectionIntervalMultiplier, connParam.maxConnectionIntervalMultiplier); + EXPECT_EQ(param.minConnectionIntervalMultiplier, connParam.minConnectionIntervalMultiplier); + EXPECT_EQ(param.peripheralLatency, connParam.peripheralLatency); + EXPECT_EQ(param.supervisionTimeoutMultiplier, connParam.supervisionTimeoutMultiplier); + })); + decorator.SetConnectionParameters(connParam); + } +}