mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 07:36:10 -04:00
Add advertisement filtering
PiperOrigin-RevId: 484616133
This commit is contained in:
committed by
Copybara-Service
parent
bc9a6ec55b
commit
5d742558a4
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "presence/implementation/advertisement_decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -157,6 +158,35 @@ void DecodeBaseTxAndAction(absl::string_view serialized_action,
|
||||
ActionFactory::DecodeAction(action, output);
|
||||
}
|
||||
|
||||
bool Contains(const std::vector<DataElement>& 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<DataElement>& data_elements,
|
||||
const std::vector<DataElement>& extended_properties) {
|
||||
for (const auto& filter_element : extended_properties) {
|
||||
if (!Contains(data_elements, filter_element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContainsAny(const std::vector<DataElement>& data_elements,
|
||||
const std::vector<int>& 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<std::string> 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<std::vector<DataElement>>
|
||||
AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) {
|
||||
std::vector<DataElement> 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<DataElement>& 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<PresenceScanFilter>(filter)) {
|
||||
if (MatchesScanFilter(data_elements,
|
||||
absl::get<PresenceScanFilter>(filter))) {
|
||||
return true;
|
||||
}
|
||||
} else if (absl::holds_alternative<LegacyPresenceScanFilter>(filter)) {
|
||||
if (MatchesScanFilter(data_elements,
|
||||
absl::get<LegacyPresenceScanFilter>(filter))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AdvertisementDecoder::MatchesScanFilter(
|
||||
const std::vector<DataElement>& 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<DataElement>& 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
|
||||
|
||||
@@ -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<std::vector<DataElement>> 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<DataElement>& data_elements);
|
||||
|
||||
private:
|
||||
// Decrypts data elements stored inside encrypted `elem` and appends them to
|
||||
// `result`.
|
||||
@@ -49,9 +55,15 @@ class AdvertisementDecoder {
|
||||
std::vector<DataElement>& result);
|
||||
absl::StatusOr<std::string> Decrypt(absl::string_view salt,
|
||||
absl::string_view encrypted);
|
||||
void AddBannedDataTypes();
|
||||
bool MatchesScanFilter(const std::vector<DataElement>& data_elements,
|
||||
const PresenceScanFilter& filter);
|
||||
bool MatchesScanFilter(const std::vector<DataElement>& data_elements,
|
||||
const LegacyPresenceScanFilter& filter);
|
||||
|
||||
CredentialManager& credential_manager_;
|
||||
ScanRequest scan_request_;
|
||||
absl::flat_hash_set<int> banned_data_types_;
|
||||
};
|
||||
|
||||
} // namespace presence
|
||||
|
||||
@@ -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<DataElement> 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<DataElement> 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<DataElement> 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<DataElement> 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<int>(ActionBit::kActiveUnlockAction),
|
||||
static_cast<int>(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<DataElement> 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<int>(ActionBit::kActiveUnlockAction),
|
||||
static_cast<int>(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
|
||||
|
||||
Reference in New Issue
Block a user