diff --git a/presence/BUILD b/presence/BUILD index a34e26ea..bbcf5a7e 100644 --- a/presence/BUILD +++ b/presence/BUILD @@ -133,6 +133,7 @@ cc_library( ":broadcast_request", ":types", "//internal/platform:logging", + "@com_google_absl//absl/types:optional", ], ) diff --git a/presence/action_factory.cc b/presence/action_factory.cc index 8c0de49a..32274f1e 100644 --- a/presence/action_factory.cc +++ b/presence/action_factory.cc @@ -17,6 +17,7 @@ #include #include +#include "absl/types/optional.h" #include "internal/platform/logging.h" namespace nearby { @@ -51,9 +52,26 @@ int GetActionMask(int action) { return kEmptyMask; } -} // namespace +// The reverse of `GetActionMask()` +absl::optional GetActionFromBitMask(int mask) { + switch (mask) { + case kTapToTransferMask: + return action::kTapToTransferAction; + case kActiveUnlockMask: + return action::kActiveUnlockAction; + case kNearbyShareMask: + return action::kNearbyShareAction; + case kFastPairMask: + return action::kFastPairAction; + case kFitCastMask: + return action::kFitCastAction; + default: + NEARBY_LOG(WARNING, "Unsupported action for bit mask 0x%x", mask); + return absl::nullopt; + } +} -int ActionFactory::GetMask(const DataElement& element) { +int GetMask(const DataElement& element) { int type = element.GetType(); switch (type) { case DataElement::kContextTimestampFieldType: { @@ -78,7 +96,9 @@ int ActionFactory::GetMask(const DataElement& element) { return kEmptyMask; } -Action ActionFactory::createAction( +} // namespace + +Action ActionFactory::CreateAction( const std::vector& data_elements) { Action action = {.action = 0}; std::for_each(data_elements.begin(), data_elements.end(), @@ -89,5 +109,24 @@ Action ActionFactory::createAction( return action; } +void ActionFactory::DecodeAction(const Action& action, + std::vector& output) { + uint8_t context_timestamp = + (action.action >> kContentTimestampShift) & kContentTimestampMask; + if (context_timestamp) { + output.emplace_back(DataElement::kContextTimestampFieldType, + context_timestamp); + } + for (int i = 0; i < kContentTimestampShift; i++) { + int bit_mask = 1 << i; + if (action.action & bit_mask) { + absl::optional action_value = GetActionFromBitMask(bit_mask); + if (action_value) { + output.emplace_back(DataElement::kActionFieldType, *action_value); + } + } + } +} + } // namespace presence } // namespace nearby diff --git a/presence/action_factory.h b/presence/action_factory.h index 24c0a146..dbb1bff9 100644 --- a/presence/action_factory.h +++ b/presence/action_factory.h @@ -23,18 +23,21 @@ namespace nearby { namespace presence { -/** Defines the mapping between Data Elements and Actions in the Base NP - * advertisement. */ +// Defines the mapping between Data Elements and Actions in the Base NP +// advertisement. class ActionFactory { public: - /** Returns an Action for Base NP advertisement from a collection of Data - * Elements. Data Elements unsupported in the Base NP advertisement are - * ignored. - */ - static Action createAction(const std::vector& data_elements); + // Returns an Action for Base NP advertisement from a collection of Data + // Elements. Data Elements unsupported in the Base NP advertisement are + // ignored. + static Action CreateAction(const std::vector& data_elements); - private: - static int GetMask(const DataElement& element); + // Decodes a Base NP Action into a list of Data Elements. The Data Elements + // are appended to the `output` list. + // + // DecodeAction is effectively a reverse operation of CreateAction. + static void DecodeAction(const Action& action, + std::vector& output); }; } // namespace presence } // namespace nearby diff --git a/presence/action_factory_test.cc b/presence/action_factory_test.cc index 041e4c35..d3cdebcf 100644 --- a/presence/action_factory_test.cc +++ b/presence/action_factory_test.cc @@ -28,12 +28,14 @@ namespace nearby { namespace presence { namespace { +using ::testing::ElementsAre; + TEST(ActionFactory, CreateActiveUnlockAction) { std::vector data_elements; data_elements.emplace_back(DataElement::kActionFieldType, action::kActiveUnlockAction); - Action action = ActionFactory::createAction(data_elements); + Action action = ActionFactory::CreateAction(data_elements); EXPECT_EQ(action.action, 1 << 7); } @@ -45,7 +47,7 @@ TEST(ActionFactory, CreateContextTimestamp) { data_elements.emplace_back(DataElement::kContextTimestampFieldType, kTimestamp); - Action action = ActionFactory::createAction(data_elements); + Action action = ActionFactory::CreateAction(data_elements); EXPECT_EQ(action.action, 0x0B << 12); } @@ -59,11 +61,35 @@ TEST(ActionFactory, CreateContextTimestampAndFastPair) { data_elements.emplace_back(DataElement::kActionFieldType, action::kFastPairAction); - Action action = ActionFactory::createAction(data_elements); + Action action = ActionFactory::CreateAction(data_elements); EXPECT_EQ(action.action, (0x0B << 12) | 0x20); } +TEST(ActionFactory, DecodeActiveUnlockAction) { + constexpr Action kAction = {.action = 1 << 7}; + std::vector data_elements; + + ActionFactory::DecodeAction(kAction, data_elements); + + EXPECT_THAT(data_elements, + ElementsAre(DataElement(DataElement::kActionFieldType, + action::kActiveUnlockAction))); +} + +TEST(ActionFactory, DecodeContextTimestampAndFastPair) { + constexpr Action kAction = {.action = (0x0B << 12) | 0x20}; + std::vector data_elements; + + ActionFactory::DecodeAction(kAction, data_elements); + + EXPECT_THAT(data_elements, + ElementsAre(DataElement(DataElement::kContextTimestampFieldType, + absl::HexStringToBytes("0B")), + DataElement(DataElement::kActionFieldType, + action::kFastPairAction))); +} + } // namespace } // namespace presence } // namespace nearby diff --git a/presence/advertisement_factory_test.cc b/presence/advertisement_factory_test.cc index 7c229d07..e3bbcf98 100644 --- a/presence/advertisement_factory_test.cc +++ b/presence/advertisement_factory_test.cc @@ -52,7 +52,7 @@ TEST(AdvertisementFactory, CreateAdvertisementFromPrivateIdentity) { std::vector data_elements; data_elements.emplace_back(DataElement::kActionFieldType, action::kActiveUnlockAction); - Action action = ActionFactory::createAction(data_elements); + Action action = ActionFactory::CreateAction(data_elements); BroadcastRequest request = BroadcastRequest(BasePresenceRequestBuilder(identity) .SetSalt(salt) @@ -87,7 +87,7 @@ TEST(AdvertisementFactory, std::vector data_elements; data_elements.emplace_back(DataElement::kActionFieldType, action::kActiveUnlockAction); - Action action = ActionFactory::createAction(data_elements); + Action action = ActionFactory::CreateAction(data_elements); BroadcastRequest request = BroadcastRequest(BasePresenceRequestBuilder(identity) .SetSalt("AB") diff --git a/presence/data_element.h b/presence/data_element.h index 511be724..4a633d50 100644 --- a/presence/data_element.h +++ b/presence/data_element.h @@ -17,8 +17,10 @@ #include +#include #include +#include "absl/strings/escaping.h" #include "absl/strings/string_view.h" namespace nearby { namespace presence { @@ -77,6 +79,11 @@ inline bool operator==(const DataElement& i1, const DataElement& i2) { return i1.GetType() == i2.GetType() && i1.GetValue() == i2.GetValue(); } +inline std::ostream& operator<<(std::ostream& os, const DataElement& elem) { + return os << "DataElement(" << elem.GetType() << ", " + << absl::BytesToHexString(elem.GetValue()) << ")"; +} + } // namespace presence } // namespace nearby diff --git a/presence/implementation/BUILD b/presence/implementation/BUILD index 048295ac..773f2357 100644 --- a/presence/implementation/BUILD +++ b/presence/implementation/BUILD @@ -33,6 +33,7 @@ cc_library( "//internal/platform:comm", "//internal/platform:logging", "//internal/platform/implementation:comm", + "//third_party/nearby/presence:action_factory", "//third_party/nearby/presence:advertisement_factory", "//third_party/nearby/presence:credential", "//third_party/nearby/presence:types", diff --git a/presence/implementation/advertisement_decoder.cc b/presence/implementation/advertisement_decoder.cc index dd51e875..3d9221a3 100644 --- a/presence/implementation/advertisement_decoder.cc +++ b/presence/implementation/advertisement_decoder.cc @@ -14,6 +14,7 @@ #include "third_party/nearby/presence/implementation/advertisement_decoder.h" +#include #include #include #include @@ -24,6 +25,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "internal/platform/logging.h" +#include "third_party/nearby/presence/action_factory.h" #include "third_party/nearby/presence/data_element.h" namespace nearby { @@ -65,6 +67,23 @@ bool IsIdentity(int data_type) { data_type <= DataElement::kProvisionedIdentityFieldType; } +void DecodeBaseAction(absl::string_view serialized_action, + std::vector& output) { + if (serialized_action.size() != sizeof(uint16_t)) { + NEARBY_LOGS(WARNING) << "Base NP action \'" + << absl::BytesToHexString(serialized_action) + << "\' has wrong length " << serialized_action.size() + << " , expected " << sizeof(uint16_t); + return; + } + // Two bytes in Big Endian order + uint8_t high = serialized_action[0]; + uint8_t low = serialized_action[1]; + Action action = {.action = static_cast((high << 8) | low)}; + + ActionFactory::DecodeAction(action, output); +} + } // namespace absl::StatusOr> @@ -114,7 +133,11 @@ AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) { advertisement = *decrypted; index = 0; } - result.push_back(*std::move(elem)); + if (elem->GetType() == DataElement::kActionFieldType) { + DecodeBaseAction(elem->GetValue(), result); + } else { + result.push_back(*std::move(elem)); + } } return result; } diff --git a/presence/implementation/advertisement_decoder_test.cc b/presence/implementation/advertisement_decoder_test.cc index 160037c4..979f7ad7 100644 --- a/presence/implementation/advertisement_decoder_test.cc +++ b/presence/implementation/advertisement_decoder_test.cc @@ -30,6 +30,7 @@ namespace presence { namespace { using ::testing::ElementsAre; using ::testing::Return; +using ::testing::UnorderedElementsAre; using ::testing::status::StatusIs; class MockCredentialManager : public CredentialManagerImpl { @@ -83,6 +84,27 @@ TEST(AdvertisementDecoder, DecodeBaseNpPublicAdvertisement) { DataElement(9, absl::HexStringToBytes("EEFF")))); } +TEST(AdvertisementDecoder, DecodeBaseNpPWithActionField) { + std::string salt = "AB"; + MockCredentialManager credential_manager; + AdvertisementDecoder decoder(&credential_manager); + + auto result = + decoder.DecodeAdvertisement(absl::HexStringToBytes("002041420326B840")); + + EXPECT_OK(result); + EXPECT_THAT(*result, + UnorderedElementsAre( + DataElement(DataElement::kSaltFieldType, salt), + DataElement(DataElement::kPublicIdentityFieldType, ""), + DataElement(DataElement::kContextTimestampFieldType, + absl::HexStringToBytes("0B")), + DataElement(DataElement::kActionFieldType, + action::kTapToTransferAction), + DataElement(DataElement::kActionFieldType, + action::kNearbyShareAction))); +} + TEST(AdvertisementDecoder, InvalidAdvertisementFieldTooShort) { MockCredentialManager credential_manager; AdvertisementDecoder decoder(&credential_manager);