mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Add advertisement decoder
The decoder allows us to parse incoming Nearby Presence advertisements. PiperOrigin-RevId: 465146528
This commit is contained in:
committed by
Copybara-Service
parent
ecf51ff13d
commit
319e5abea4
@@ -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
|
||||
Reference in New Issue
Block a user