Add advertisement decoder

The decoder allows us to parse incoming Nearby Presence advertisements.

PiperOrigin-RevId: 465146528
This commit is contained in:
jsobczak
2022-08-03 14:18:12 -07:00
committed by Copybara-Service
parent ecf51ff13d
commit 319e5abea4
4 changed files with 338 additions and 1 deletions
+20 -1
View File
@@ -15,8 +15,9 @@ licenses(["notice"])
cc_library(
name = "internal",
srcs = [],
srcs = ["advertisement_decoder.cc"],
hdrs = [
"advertisement_decoder.h",
"broadcast_manager.h",
"credential_manager.h",
"credential_manager_impl.h",
@@ -30,12 +31,30 @@ cc_library(
],
deps = [
"//internal/platform:comm",
"//internal/platform:logging",
"//internal/platform/implementation:comm",
"//third_party/nearby/presence:advertisement_factory",
"//third_party/nearby/presence:credential",
"//third_party/nearby/presence:types",
"//third_party/nearby/presence/implementation/mediums",
"//third_party/nearby/presence/proto:credential_cc_proto",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
],
)
cc_test(
name = "advertisement_decoder_test",
size = "small",
srcs = ["advertisement_decoder_test.cc"],
deps = [
":internal",
"//internal/platform/implementation/g3", # build_cleaner: keep
"//third_party/nearby/presence:types",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
@@ -0,0 +1,123 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "third_party/nearby/presence/implementation/advertisement_decoder.h"
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "internal/platform/logging.h"
#include "third_party/nearby/presence/data_element.h"
namespace nearby {
namespace presence {
constexpr uint8_t kDataTypeMask =
(1 << DataElement::kDataElementLengthShift) - 1;
constexpr int kAdvertisementVersion = 0;
namespace {
absl::StatusOr<DataElement> ParseDataElement(const absl::string_view input,
size_t& index) {
if (index >= input.size()) {
return absl::OutOfRangeError(absl::StrFormat(
"Data element (%s) is %d bytes long. Expected more than %d",
absl::BytesToHexString(input), input.size(), index));
}
uint8_t header = input[index];
uint8_t data_type = header & kDataTypeMask;
size_t length = header >> DataElement::kDataElementLengthShift;
index++;
size_t start = index;
index += length;
if (index > input.size()) {
return absl::OutOfRangeError(absl::StrFormat(
"Data element (%s) is %d bytes long. Expected at least %d",
absl::BytesToHexString(input), input.size(), index));
}
NEARBY_LOGS(VERBOSE) << "Type: " << static_cast<int>(data_type)
<< " length: " << static_cast<int>(length) << " DE: "
<< absl::BytesToHexString(input.substr(start, length));
return DataElement(data_type, input.substr(start, length));
}
bool IsIdentity(int data_type) {
return data_type >= DataElement::kPrivateIdentityFieldType &&
data_type <= DataElement::kProvisionedIdentityFieldType;
}
} // namespace
absl::StatusOr<std::vector<DataElement>>
AdvertisementDecoder::DecodeAdvertisement(absl::string_view advertisement) {
std::vector<DataElement> result;
NEARBY_LOGS(VERBOSE) << "Advertisement: "
<< absl::BytesToHexString(advertisement);
if (advertisement.empty()) {
return absl::OutOfRangeError("Empty advertisement");
}
int version = advertisement[0];
NEARBY_LOGS(VERBOSE) << "Version: " << version;
if (version != kAdvertisementVersion) {
return absl::UnimplementedError(
absl::StrFormat("Advertisment version (%d) is not supported", version));
}
size_t index = 1;
std::string salt;
absl::StatusOr<std::string> decrypted;
while (index < advertisement.size()) {
absl::StatusOr<DataElement> elem = ParseDataElement(advertisement, index);
if (!elem.ok()) {
NEARBY_LOGS(WARNING) << "Failed to read data element, status: "
<< elem.status();
return elem.status();
}
if (elem->GetType() == DataElement::kSaltFieldType) {
salt = elem->GetValue();
}
bool need_decryption =
IsIdentity(elem->GetType()) && !elem->GetValue().empty();
if (need_decryption && index < advertisement.size()) {
NEARBY_LOGS(VERBOSE) << "Metadata: "
<< absl::BytesToHexString(elem->GetValue())
<< "Salt: " << absl::BytesToHexString(salt)
<< "Encrypted: "
<< absl::BytesToHexString(
advertisement.substr(index));
decrypted = credential_manager_.DecryptDataElements(
elem->GetValue(), salt, advertisement.substr(index));
if (!decrypted.ok()) {
NEARBY_LOGS(WARNING) << "Failed to decrypt advertisement, status: "
<< decrypted.status();
return decrypted.status();
}
// Restart the loop and iterate over the decrypted content
advertisement = *decrypted;
index = 0;
}
result.push_back(*std::move(elem));
}
return result;
}
} // namespace presence
} // namespace nearby
@@ -0,0 +1,50 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_PRESENCE_ADVERTISEMENT_DECODER_H_
#define THIRD_PARTY_NEARBY_PRESENCE_ADVERTISEMENT_DECODER_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "third_party/nearby/presence/advertisement_factory.h"
#include "third_party/nearby/presence/data_element.h"
#include "third_party/nearby/presence/implementation/credential_manager.h"
#include "third_party/nearby/presence/presence_identity.h"
namespace nearby {
namespace presence {
// Decodes BLE NP advertisements
class AdvertisementDecoder {
public:
explicit AdvertisementDecoder(CredentialManager* credential_manager)
: credential_manager_(*ABSL_DIE_IF_NULL(credential_manager)) {}
// Returns a list of Data Elements decoded from the advertisement.
// Returns an error if the advertisement is misformatted or if it couldn't be
// decrypted.
absl::StatusOr<std::vector<DataElement>> DecodeAdvertisement(
absl::string_view advertisement);
private:
CredentialManager& credential_manager_;
};
} // namespace presence
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_PRESENCE_ADVERTISEMENT_DECODER_H_
@@ -0,0 +1,145 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "third_party/nearby/presence/implementation/advertisement_decoder.h"
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/strings/escaping.h"
#include "third_party/nearby/presence/data_element.h"
#include "third_party/nearby/presence/implementation/credential_manager_impl.h"
namespace nearby {
namespace presence {
namespace {
using ::testing::ElementsAre;
using ::testing::Return;
using ::testing::status::StatusIs;
class MockCredentialManager : public CredentialManagerImpl {
public:
MOCK_METHOD(absl::StatusOr<std::string>, DecryptDataElements,
(absl::string_view metadata_key, absl::string_view salt,
absl::string_view data_elements),
(override));
};
TEST(AdvertisementDecoder, DecodeBaseNpPrivateAdvertisement) {
const std::string salt = "AB";
const std::string metadata =
absl::HexStringToBytes("1011121314151617181920212223");
MockCredentialManager credential_manager;
EXPECT_CALL(
credential_manager,
DecryptDataElements(metadata, salt, absl::HexStringToBytes("5051525354")))
.WillOnce(Return(absl::HexStringToBytes("18CD29EEFF")));
AdvertisementDecoder decoder(&credential_manager);
const absl::StatusOr<std::vector<DataElement>> result =
decoder.DecodeAdvertisement(absl::HexStringToBytes(
"00204142e110111213141516171819202122235051525354"));
ASSERT_OK(result);
EXPECT_THAT(
*result,
ElementsAre(DataElement(DataElement::kSaltFieldType, salt),
DataElement(DataElement::kPrivateIdentityFieldType, metadata),
DataElement(8, absl::HexStringToBytes("CD")),
DataElement(9, absl::HexStringToBytes("EEFF"))));
}
TEST(AdvertisementDecoder, DecodeBaseNpPublicAdvertisement) {
const std::string salt = "AB";
MockCredentialManager credential_manager;
AdvertisementDecoder decoder(&credential_manager);
const absl::StatusOr<std::vector<DataElement>> result =
decoder.DecodeAdvertisement(
absl::HexStringToBytes("002041420318CD29EEFF"));
ASSERT_OK(result);
EXPECT_THAT(
*result,
ElementsAre(DataElement(DataElement::kSaltFieldType, salt),
DataElement(DataElement::kPublicIdentityFieldType, ""),
DataElement(8, absl::HexStringToBytes("CD")),
DataElement(9, absl::HexStringToBytes("EEFF"))));
}
TEST(AdvertisementDecoder, InvalidAdvertisementFieldTooShort) {
MockCredentialManager credential_manager;
AdvertisementDecoder decoder(&credential_manager);
// 0x50 header means 5 bytes long salt but only 4 bytes follow.
EXPECT_THAT(
decoder.DecodeAdvertisement(absl::HexStringToBytes("0050A0A1A2A3")),
StatusIs(absl::StatusCode::kOutOfRange));
}
TEST(AdvertisementDecoder, ZeroLengthPayload) {
MockCredentialManager credential_manager;
AdvertisementDecoder decoder(&credential_manager);
// A action with type 0xA and no payload
const absl::StatusOr<std::vector<DataElement>> result =
decoder.DecodeAdvertisement(absl::HexStringToBytes("000A"));
ASSERT_OK(result);
EXPECT_THAT(*result, ElementsAre(DataElement(0xA, "")));
}
TEST(AdvertisementDecoder, EmptyAdvertisement) {
MockCredentialManager credential_manager;
AdvertisementDecoder decoder(&credential_manager);
EXPECT_THAT(decoder.DecodeAdvertisement(""),
StatusIs(absl::StatusCode::kOutOfRange));
}
TEST(AdvertisementDecoder, InvalidEncryptedContent) {
const std::string salt = "AB";
const std::string metadata =
absl::HexStringToBytes("1011121314151617181920212223");
MockCredentialManager credential_manager;
// 0x28CD is an invalid DE, 0x28 means a 2 byte payload with type 8 alas only
// one byte is given (0xCD)
EXPECT_CALL(
credential_manager,
DecryptDataElements(metadata, salt, absl::HexStringToBytes("5051525354")))
.WillOnce(Return(absl::HexStringToBytes("28CD")));
AdvertisementDecoder decoder(&credential_manager);
EXPECT_THAT(decoder.DecodeAdvertisement(absl::HexStringToBytes(
"00204142e110111213141516171819202122235051525354")),
StatusIs(absl::StatusCode::kOutOfRange));
}
TEST(AdvertisementDecoder, UnsupportedAdvertisementVersion) {
MockCredentialManager credential_manager;
AdvertisementDecoder decoder(&credential_manager);
EXPECT_THAT(decoder.DecodeAdvertisement(
absl::HexStringToBytes("012041420318CD29EEFF")),
StatusIs(absl::StatusCode::kUnimplemented));
}
} // namespace
} // namespace presence
} // namespace nearby