Add advertisement filtering

PiperOrigin-RevId: 484616133
This commit is contained in:
Janusz Sobczak
2022-10-28 14:07:29 -07:00
committed by Copybara-Service
parent bc9a6ec55b
commit 5d742558a4
3 changed files with 243 additions and 2 deletions
@@ -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