diff --git a/presence/implementation/advertisement_decoder.cc b/presence/implementation/advertisement_decoder.cc index 2739af41..da3f3c7e 100644 --- a/presence/implementation/advertisement_decoder.cc +++ b/presence/implementation/advertisement_decoder.cc @@ -14,6 +14,7 @@ #include "presence/implementation/advertisement_decoder.h" +#include #include #include #include @@ -157,6 +158,35 @@ void DecodeBaseTxAndAction(absl::string_view serialized_action, ActionFactory::DecodeAction(action, output); } +bool Contains(const std::vector& data_elements, + const DataElement& data_element) { + return std::find(data_elements.begin(), data_elements.end(), data_element) != + data_elements.end(); +} + +bool ContainsAll(const std::vector& data_elements, + const std::vector& extended_properties) { + for (const auto& filter_element : extended_properties) { + if (!Contains(data_elements, filter_element)) { + return false; + } + } + return true; +} + +bool ContainsAny(const std::vector& data_elements, + const std::vector& actions) { + if (actions.empty()) { + return true; + } + for (int action : actions) { + if (Contains(data_elements, DataElement(ActionBit(action)))) { + return true; + } + } + return false; +} + } // namespace absl::Status AdvertisementDecoder::DecryptDataElements( @@ -222,6 +252,35 @@ absl::StatusOr AdvertisementDecoder::Decrypt( salt, encrypted); } +void AdvertisementDecoder::AddBannedDataTypes() { + // The scan request has information what identity types the client is + // interested in. We'll ban all other idenitity data types. + banned_data_types_ = {DataElement::kPrivateIdentityFieldType, + DataElement::kTrustedIdentityFieldType, + DataElement::kPublicIdentityFieldType, + DataElement::kProvisionedIdentityFieldType}; + for (nearby::internal::IdentityType identity_type : + scan_request_.identity_types) { + switch (identity_type) { + case internal::IDENTITY_TYPE_PRIVATE: + banned_data_types_.erase(DataElement::kPrivateIdentityFieldType); + break; + case internal::IDENTITY_TYPE_TRUSTED: + banned_data_types_.erase(DataElement::kTrustedIdentityFieldType); + break; + case internal::IDENTITY_TYPE_PUBLIC: + banned_data_types_.erase(DataElement::kPublicIdentityFieldType); + break; + case internal::IDENTITY_TYPE_PROVISIONED: + banned_data_types_.erase(DataElement::kProvisionedIdentityFieldType); + break; + default: + // Nothing to do + break; + } + } +} + absl::StatusOr> AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) { std::vector result; @@ -245,6 +304,14 @@ AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) { << elem.status(); return elem.status(); } + // This checks allows us to bail before decryption when, for example, the + // client is scanning for advertisements with private identity but the + // advertisement uses trusted identity. + if (banned_data_types_.contains(elem->GetType())) { + return absl::FailedPreconditionError( + absl::StrFormat("Ignoring advertisement with data element type: %d", + elem->GetType())); + } if (IsEncryptedIdentity(elem->GetType())) { absl::Status status = DecryptDataElements(*elem, result); if (!status.ok()) { @@ -262,5 +329,45 @@ AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) { return result; } +bool AdvertisementDecoder::MatchesScanFilter( + const std::vector& data_elements) { + // The advertisement matches the scan request when it matches at least + // one of the filters in the request. + if (scan_request_.scan_filters.empty()) { + return true; + } + for (const auto& filter : scan_request_.scan_filters) { + if (absl::holds_alternative(filter)) { + if (MatchesScanFilter(data_elements, + absl::get(filter))) { + return true; + } + } else if (absl::holds_alternative(filter)) { + if (MatchesScanFilter(data_elements, + absl::get(filter))) { + return true; + } + } + } + return false; +} + +bool AdvertisementDecoder::MatchesScanFilter( + const std::vector& data_elements, + const PresenceScanFilter& filter) { + // The advertisement must contain all Data Elements in scan request. + return ContainsAll(data_elements, filter.extended_properties); +} + +bool AdvertisementDecoder::MatchesScanFilter( + const std::vector& data_elements, + const LegacyPresenceScanFilter& filter) { + // The advertisement must: + // * contain any Action from scan request, + // * contain all Data Elements in scan request. + return ContainsAny(data_elements, filter.actions) && + ContainsAll(data_elements, filter.extended_properties); +} + } // namespace presence } // namespace nearby diff --git a/presence/implementation/advertisement_decoder.h b/presence/implementation/advertisement_decoder.h index 120a6c9a..17eddc2c 100644 --- a/presence/implementation/advertisement_decoder.h +++ b/presence/implementation/advertisement_decoder.h @@ -34,7 +34,9 @@ class AdvertisementDecoder { AdvertisementDecoder(CredentialManager* credential_manager, ScanRequest scan_request) : credential_manager_(*ABSL_DIE_IF_NULL(credential_manager)), - scan_request_(scan_request) {} + scan_request_(scan_request) { + AddBannedDataTypes(); + } // Returns a list of Data Elements decoded from the advertisement. // Returns an error if the advertisement is misformatted or if it couldn't be @@ -42,6 +44,10 @@ class AdvertisementDecoder { absl::StatusOr> DecodeAdvertisement( absl::string_view advertisement); + // Returns true if the decoded advertisement in `data_elements` matches the + // filters in `scan_request`. + bool MatchesScanFilter(const std::vector& data_elements); + private: // Decrypts data elements stored inside encrypted `elem` and appends them to // `result`. @@ -49,9 +55,15 @@ class AdvertisementDecoder { std::vector& result); absl::StatusOr Decrypt(absl::string_view salt, absl::string_view encrypted); + void AddBannedDataTypes(); + bool MatchesScanFilter(const std::vector& data_elements, + const PresenceScanFilter& filter); + bool MatchesScanFilter(const std::vector& data_elements, + const LegacyPresenceScanFilter& filter); CredentialManager& credential_manager_; ScanRequest scan_request_; + absl::flat_hash_set banned_data_types_; }; } // namespace presence diff --git a/presence/implementation/advertisement_decoder_test.cc b/presence/implementation/advertisement_decoder_test.cc index d68da2e2..2391e153 100644 --- a/presence/implementation/advertisement_decoder_test.cc +++ b/presence/implementation/advertisement_decoder_test.cc @@ -42,7 +42,11 @@ using ::testing::status::StatusIs; constexpr absl::string_view kAccountName = "test account"; ScanRequest GetScanRequest() { - return {.account_name = std::string(kAccountName)}; + return {.account_name = std::string(kAccountName), + .identity_types = {internal::IDENTITY_TYPE_PRIVATE, + internal::IDENTITY_TYPE_TRUSTED, + internal::IDENTITY_TYPE_PUBLIC, + internal::IDENTITY_TYPE_PROVISIONED}}; } ScanRequest GetScanRequest( @@ -51,6 +55,10 @@ ScanRequest GetScanRequest( credentials}; return ScanRequestBuilder() .SetAccountName(kAccountName) + .AddIdentityType(internal::IDENTITY_TYPE_PRIVATE) + .AddIdentityType(internal::IDENTITY_TYPE_TRUSTED) + .AddIdentityType(internal::IDENTITY_TYPE_PUBLIC) + .AddIdentityType(internal::IDENTITY_TYPE_PROVISIONED) .AddScanFilter(scan_filter) .Build(); } @@ -276,6 +284,22 @@ TEST(AdvertisementDecoder, DecodeBaseNpWithTxActionField) { DataElement(DataElement(ActionBit::kNearbyShareAction)))); } +TEST(AdvertisementDecoder, + ScanForEncryptedIdentityIgnoresPublicIdentityAdvertisement) { + std::string salt = "AB"; + MockCredentialManager credential_manager; + AdvertisementDecoder decoder( + &credential_manager, + {.account_name = std::string(kAccountName), + .identity_types = {internal::IDENTITY_TYPE_PRIVATE, + internal::IDENTITY_TYPE_TRUSTED, + internal::IDENTITY_TYPE_PROVISIONED}}); + + EXPECT_THAT(decoder.DecodeAdvertisement( + absl::HexStringToBytes("00204142034650B04180")), + StatusIs(absl::StatusCode::kFailedPrecondition)); +} + TEST(AdvertisementDecoder, DecodeEddystone) { MockCredentialManager credential_manager; AdvertisementDecoder decoder(&credential_manager, GetScanRequest()); @@ -361,6 +385,104 @@ TEST(AdvertisementDecoder, UnsupportedAdvertisementVersion) { StatusIs(absl::StatusCode::kUnimplemented)); } +TEST(AdvertisementDecoder, MatchesScanFilterNoFilterPasses) { + std::vector adv = { + DataElement(DataElement::kPrivateIdentityFieldType, "payload")}; + MockCredentialManager credential_manager; + ScanRequest empty_scan_request = {}; + AdvertisementDecoder decoder(&credential_manager, empty_scan_request); + + // A scan request without scan filters matches any advertisement + EXPECT_TRUE(decoder.MatchesScanFilter( + {DataElement(DataElement::kPrivateIdentityFieldType, "payload")})); + EXPECT_TRUE(decoder.MatchesScanFilter({})); +} + +TEST(AdvertisementDecoder, MatchesPresenceScanFilter) { + std::vector adv = { + DataElement(DataElement::kPrivateIdentityFieldType, "payload")}; + MockCredentialManager credential_manager; + DataElement model_id = + DataElement(DataElement::kModelIdFieldType, "model id"); + DataElement salt = DataElement(DataElement::kSaltFieldType, "salt"); + DataElement salt2 = DataElement(DataElement::kSaltFieldType, "salt 2"); + PresenceScanFilter filter = {.extended_properties = {model_id, salt}}; + + AdvertisementDecoder decoder( + &credential_manager, ScanRequestBuilder().AddScanFilter(filter).Build()); + + EXPECT_FALSE(decoder.MatchesScanFilter({})); + EXPECT_FALSE(decoder.MatchesScanFilter({salt})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, model_id})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, salt2, model_id})); + EXPECT_FALSE(decoder.MatchesScanFilter({salt2, model_id})); +} + +TEST(AdvertisementDecoder, MatchesLegacyPresenceScanFilter) { + std::vector adv = { + DataElement(DataElement::kPrivateIdentityFieldType, "payload")}; + MockCredentialManager credential_manager; + DataElement model_id = + DataElement(DataElement::kModelIdFieldType, "model id"); + DataElement salt = DataElement(DataElement::kSaltFieldType, "salt"); + DataElement salt2 = DataElement(DataElement::kSaltFieldType, "salt 2"); + LegacyPresenceScanFilter filter = {.extended_properties = {model_id, salt}}; + + AdvertisementDecoder decoder( + &credential_manager, ScanRequestBuilder().AddScanFilter(filter).Build()); + + EXPECT_FALSE(decoder.MatchesScanFilter({})); + EXPECT_FALSE(decoder.MatchesScanFilter({salt})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, model_id})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, salt2, model_id})); + EXPECT_FALSE(decoder.MatchesScanFilter({salt2, model_id})); +} + +TEST(AdvertisementDecoder, MatchesLegacyPresenceScanFilterWithActions) { + std::vector adv = { + DataElement(DataElement::kPrivateIdentityFieldType, "payload")}; + MockCredentialManager credential_manager; + DataElement model_id = + DataElement(DataElement::kModelIdFieldType, "model id"); + DataElement salt = DataElement(DataElement::kSaltFieldType, "salt"); + DataElement eddystone_action = DataElement(ActionBit::kEddystoneAction); + LegacyPresenceScanFilter filter = { + .actions = {static_cast(ActionBit::kActiveUnlockAction), + static_cast(ActionBit::kEddystoneAction)}, + .extended_properties = {model_id, salt}}; + + AdvertisementDecoder decoder( + &credential_manager, ScanRequestBuilder().AddScanFilter(filter).Build()); + + EXPECT_FALSE(decoder.MatchesScanFilter({salt, model_id})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, eddystone_action, model_id})); +} + +TEST(AdvertisementDecoder, MatchesMultipleFilters) { + std::vector adv = { + DataElement(DataElement::kPrivateIdentityFieldType, "payload")}; + MockCredentialManager credential_manager; + DataElement model_id = + DataElement(DataElement::kModelIdFieldType, "model id"); + DataElement salt = DataElement(DataElement::kSaltFieldType, "salt"); + DataElement eddystone_action = DataElement(ActionBit::kEddystoneAction); + PresenceScanFilter presence_filter = {.extended_properties = {model_id}}; + LegacyPresenceScanFilter legacy_filter = { + .actions = {static_cast(ActionBit::kActiveUnlockAction), + static_cast(ActionBit::kEddystoneAction)}, + .extended_properties = {salt}}; + + AdvertisementDecoder decoder(&credential_manager, + ScanRequestBuilder() + .AddScanFilter(presence_filter) + .AddScanFilter(legacy_filter) + .Build()); + + EXPECT_TRUE(decoder.MatchesScanFilter({model_id})); + EXPECT_TRUE(decoder.MatchesScanFilter({salt, eddystone_action})); + EXPECT_FALSE(decoder.MatchesScanFilter({eddystone_action})); +} + } // namespace } // namespace presence } // namespace nearby