mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Add more sharing code to github
PiperOrigin-RevId: 601277252
This commit is contained in:
committed by
Copybara-Service
parent
809c36139f
commit
e9ae655916
@@ -32,11 +32,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build Connections
|
||||
run: CC=clang CXX=clang++ bazel build //connections:core --spawn_strategy=standalone
|
||||
run: CC=clang CXX=clang++ bazel build --copt='-DGITHUB_BUILD' //connections:core --spawn_strategy=standalone
|
||||
- name: Build Presence
|
||||
run: CC=clang CXX=clang++ bazel build //presence --spawn_strategy=standalone
|
||||
run: CC=clang CXX=clang++ bazel build --copt='-DGITHUB_BUILD' //presence --spawn_strategy=standalone
|
||||
- name: Build Sharing
|
||||
run: CC=clang CXX=clang++ bazel build //sharing/proto/... //sharing/internal/public:nearby_context //sharing/common:all //sharing/scheduling //sharing/fast_initiation:nearby_fast_initiation //sharing/analytics --spawn_strategy=standalone
|
||||
run: CC=clang CXX=clang++ bazel build --copt='-DGITHUB_BUILD' //sharing/certificates //sharing/contacts //sharing/local_device_data //sharing/proto/... //sharing/internal/public:nearby_context //sharing/common:all //sharing/scheduling //sharing/fast_initiation:nearby_fast_initiation //sharing/analytics --spawn_strategy=standalone
|
||||
|
||||
build-rust-linux:
|
||||
name: Build Rust on Linux
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "certificates",
|
||||
srcs = [
|
||||
"common.cc",
|
||||
"nearby_share_certificate_manager.cc",
|
||||
"nearby_share_certificate_manager_impl.cc",
|
||||
"nearby_share_certificate_storage.cc",
|
||||
"nearby_share_certificate_storage_impl.cc",
|
||||
"nearby_share_decrypted_public_certificate.cc",
|
||||
"nearby_share_encrypted_metadata_key.cc",
|
||||
"nearby_share_private_certificate.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"common.h",
|
||||
"constants.h",
|
||||
"nearby_share_certificate_manager.h",
|
||||
"nearby_share_certificate_manager_impl.h",
|
||||
"nearby_share_certificate_storage.h",
|
||||
"nearby_share_certificate_storage_impl.h",
|
||||
"nearby_share_decrypted_public_certificate.h",
|
||||
"nearby_share_encrypted_metadata_key.h",
|
||||
"nearby_share_private_certificate.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/base",
|
||||
"//internal/crypto_cros",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation:types",
|
||||
"//sharing/common",
|
||||
"//sharing/contacts",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/base",
|
||||
"//sharing/internal/public:logging",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/local_device_data",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"@com_google_absl//absl/container:btree",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/functional:bind_front",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:span",
|
||||
"@com_google_protobuf//:protobuf_lite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "test_support",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"fake_nearby_share_certificate_manager.cc",
|
||||
"fake_nearby_share_certificate_storage.cc",
|
||||
"test_util.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"fake_nearby_share_certificate_manager.h",
|
||||
"fake_nearby_share_certificate_storage.h",
|
||||
"test_util.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":certificates",
|
||||
"//internal/account",
|
||||
"//internal/base:bluetooth_address",
|
||||
"//internal/crypto_cros",
|
||||
"//sharing/common",
|
||||
"//sharing/contacts",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/local_device_data",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "certificates_test",
|
||||
srcs = [
|
||||
"common_test.cc",
|
||||
"nearby_share_certificate_manager_impl_test.cc",
|
||||
"nearby_share_certificate_storage_impl_test.cc",
|
||||
"nearby_share_decrypted_public_certificate_test.cc",
|
||||
"nearby_share_private_certificate_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":certificates",
|
||||
":test_support",
|
||||
"//internal/data:data_manager",
|
||||
"//internal/platform/implementation:types",
|
||||
"//internal/platform/implementation/g3", # fixdeps: keep
|
||||
"//internal/test",
|
||||
"//sharing/common",
|
||||
"//sharing/contacts:test_support",
|
||||
"//sharing/internal/api:mock_sharing_platform",
|
||||
"//sharing/internal/public:logging",
|
||||
"//sharing/internal/test:nearby_test",
|
||||
"//sharing/local_device_data:test_support",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"//sharing/scheduling:test_support",
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/meta:type_traits",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:span",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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 "sharing/certificates/common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/encryptor.h"
|
||||
#include "internal/crypto_cros/hkdf.h"
|
||||
#include "internal/crypto_cros/random.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
bool IsNearbyShareCertificateExpired(absl::Time current_time,
|
||||
absl::Time not_after,
|
||||
bool use_public_certificate_tolerance) {
|
||||
absl::Duration tolerance =
|
||||
use_public_certificate_tolerance
|
||||
? kNearbySharePublicCertificateValidityBoundOffsetTolerance
|
||||
: absl::ZeroDuration();
|
||||
|
||||
return current_time >= not_after + tolerance;
|
||||
}
|
||||
|
||||
bool IsNearbyShareCertificateWithinValidityPeriod(
|
||||
absl::Time current_time, absl::Time not_before, absl::Time not_after,
|
||||
bool use_public_certificate_tolerance) {
|
||||
absl::Duration tolerance =
|
||||
use_public_certificate_tolerance
|
||||
? kNearbySharePublicCertificateValidityBoundOffsetTolerance
|
||||
: absl::ZeroDuration();
|
||||
|
||||
return current_time >= not_before - tolerance &&
|
||||
!IsNearbyShareCertificateExpired(current_time, not_after,
|
||||
use_public_certificate_tolerance);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DeriveNearbyShareKey(absl::Span<const uint8_t> key,
|
||||
size_t new_num_bytes) {
|
||||
return crypto::HkdfSha256(key,
|
||||
/*salt=*/absl::Span<const uint8_t>(),
|
||||
/*info=*/absl::Span<const uint8_t>(),
|
||||
new_num_bytes);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ComputeAuthenticationTokenHash(
|
||||
absl::Span<const uint8_t> authentication_token,
|
||||
absl::Span<const uint8_t> secret_key) {
|
||||
return crypto::HkdfSha256(authentication_token, secret_key,
|
||||
/*info=*/absl::Span<const uint8_t>(),
|
||||
kNearbyShareNumBytesAuthenticationTokenHash);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GenerateRandomBytes(size_t num_bytes) {
|
||||
std::vector<uint8_t> bytes(num_bytes);
|
||||
crypto::RandBytes(absl::Span<uint8_t>(bytes));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::unique_ptr<crypto::Encryptor> CreateNearbyShareCtrEncryptor(
|
||||
const crypto::SymmetricKey* secret_key, absl::Span<const uint8_t> salt) {
|
||||
NL_DCHECK(secret_key);
|
||||
NL_DCHECK_EQ(kNearbyShareNumBytesSecretKey, secret_key->key().size());
|
||||
NL_DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt, salt.size());
|
||||
|
||||
auto encryptor = std::make_unique<crypto::Encryptor>();
|
||||
|
||||
// For CTR mode, the iv input to Init() must be empty. Instead, the iv is
|
||||
// set via SetCounter().
|
||||
if (!encryptor->Init(secret_key, crypto::Encryptor::Mode::CTR,
|
||||
/*iv=*/absl::Span<const uint8_t>())) {
|
||||
NL_LOG(ERROR) << "Encryptor could not be initialized.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> iv =
|
||||
DeriveNearbyShareKey(salt, kNearbyShareNumBytesAesCtrIv);
|
||||
if (!encryptor->SetCounter(iv)) {
|
||||
NL_LOG(ERROR) << "Could not set encryptor counter.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
absl::Time FromJavaTime(int64_t ms_since_epoch) {
|
||||
return absl::UnixEpoch() + absl::Milliseconds(ms_since_epoch);
|
||||
}
|
||||
|
||||
int64_t ToJavaTime(absl::Time time) {
|
||||
// Preserve 0 so the invalid result doesn't depend on the platform.
|
||||
if (time == absl::InfiniteFuture()) {
|
||||
return std::numeric_limits<int64_t>::max();
|
||||
} else if (time == absl::InfinitePast()) {
|
||||
return std::numeric_limits<int64_t>::min();
|
||||
} else {
|
||||
return (time - absl::UnixEpoch()) / absl::Milliseconds(1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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_SHARING_CERTIFICATES_COMMON_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_COMMON_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/encryptor.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Returns true if the |current_time| exceeds |not_after| by more than the
|
||||
// public certificate clock-skew tolerance if applicable.
|
||||
bool IsNearbyShareCertificateExpired(absl::Time current_time,
|
||||
absl::Time not_after,
|
||||
bool use_public_certificate_tolerance);
|
||||
|
||||
// Returns true if the |current_time| is in the interval
|
||||
// [|not_before| - tolerance, |not_after| + tolerance), where a clock-skew
|
||||
// tolerance is only non-zero if |use_public_certificate_tolerance| is true.
|
||||
bool IsNearbyShareCertificateWithinValidityPeriod(
|
||||
absl::Time current_time, absl::Time not_before, absl::Time not_after,
|
||||
bool use_public_certificate_tolerance);
|
||||
|
||||
// Uses HKDF to create a hash of the |authentication_token|, using the
|
||||
// |secret_key|. A trivial info parameter is used, and the output length is
|
||||
// fixed to be kNearbyShareNumBytesAuthenticationTokenHash to conform with the
|
||||
// GmsCore implementation.
|
||||
std::vector<uint8_t> ComputeAuthenticationTokenHash(
|
||||
absl::Span<const uint8_t> authentication_token,
|
||||
absl::Span<const uint8_t> secret_key);
|
||||
|
||||
// Uses HKDF to generate a new key of length |new_num_bytes| from |key|. To
|
||||
// conform with the GmsCore implementation, trivial salt and info are used.
|
||||
std::vector<uint8_t> DeriveNearbyShareKey(absl::Span<const uint8_t> key,
|
||||
size_t new_num_bytes);
|
||||
|
||||
// Generates a random byte array with size |num_bytes|.
|
||||
std::vector<uint8_t> GenerateRandomBytes(size_t num_bytes);
|
||||
|
||||
// Creates a CTR Encryptor used for metadata key encryption/decryption.
|
||||
std::unique_ptr<crypto::Encryptor> CreateNearbyShareCtrEncryptor(
|
||||
const crypto::SymmetricKey* secret_key, absl::Span<const uint8_t> salt);
|
||||
|
||||
// Generates Time from JAVA Time
|
||||
absl::Time FromJavaTime(int64_t ms_since_epoch);
|
||||
int64_t ToJavaTime(absl::Time time);
|
||||
|
||||
template <typename T>
|
||||
absl::Span<const uint8_t> as_bytes(absl::Span<T> s) noexcept {
|
||||
return {reinterpret_cast<const uint8_t*>(s.data()), s.size() * sizeof(T)};
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_COMMON_H_
|
||||
@@ -0,0 +1,184 @@
|
||||
// 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 "sharing/certificates/common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
TEST(NearbyShareCertificatesCommonTest, AuthenticationTokenHash) {
|
||||
EXPECT_EQ(
|
||||
GetNearbyShareTestPayloadHashUsingSecretKey(),
|
||||
ComputeAuthenticationTokenHash(
|
||||
GetNearbyShareTestPayloadToSign(),
|
||||
as_bytes(absl::MakeSpan(GetNearbyShareTestSecretKey()->key()))));
|
||||
}
|
||||
|
||||
TEST(NearbyShareCertificatesCommonTest, ValidityPeriod_PrivateCertificate) {
|
||||
NearbySharePrivateCertificate cert = GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
const bool use_public_certificate_tolerance = false;
|
||||
|
||||
// Set time before validity period.
|
||||
absl::Time now = cert.not_before() - absl::Milliseconds(1);
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time at inclusive lower bound of validity period.
|
||||
now = cert.not_before();
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time in the middle of the validity period.
|
||||
now = cert.not_before() + (cert.not_after() - cert.not_before()) / 2;
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time at non-inclusive upper bound of validity period.
|
||||
now = cert.not_after();
|
||||
EXPECT_TRUE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time after validity period.
|
||||
now = cert.not_after() + absl::Milliseconds(1);
|
||||
EXPECT_TRUE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
}
|
||||
|
||||
TEST(NearbyShareCertificatesCommonTest, ValidityPeriod_PublicCertificate) {
|
||||
NearbyShareDecryptedPublicCertificate cert =
|
||||
*NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS),
|
||||
GetNearbyShareTestEncryptedMetadataKey());
|
||||
const bool use_public_certificate_tolerance = true;
|
||||
|
||||
// Set time before validity period, outside of tolerance.
|
||||
absl::Time now = cert.not_before() -
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance -
|
||||
absl::Milliseconds(1);
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time before validity period, at inclusive bound with tolerance.
|
||||
now = cert.not_before() -
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time before validity period, inside of tolerance.
|
||||
now = cert.not_before() -
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance / 2;
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time at inclusive lower bound of validity period.
|
||||
now = cert.not_before();
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time in the middle of the validity period.
|
||||
now = cert.not_before() + (cert.not_after() - cert.not_before()) / 2;
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time at upper bound of validity period.
|
||||
now = cert.not_after();
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time after validity period, inside of tolerance.
|
||||
now = cert.not_after() +
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance / 2;
|
||||
EXPECT_FALSE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time after validity period, at non-inclusive tolerance bound.
|
||||
now = cert.not_after() +
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
|
||||
EXPECT_TRUE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
|
||||
// Set time after validity period, outside of tolerance.
|
||||
now = cert.not_after() +
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance +
|
||||
absl::Milliseconds(1);
|
||||
EXPECT_TRUE(IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(), use_public_certificate_tolerance));
|
||||
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
|
||||
now, cert.not_before(), cert.not_after(),
|
||||
use_public_certificate_tolerance));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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_SHARING_CERTIFICATES_CONSTANTS_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_CONSTANTS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// The number of days a certificate is valid.
|
||||
constexpr absl::Duration kNearbyShareCertificateValidityPeriod =
|
||||
absl::Hours(72);
|
||||
|
||||
// The maximum offset for obfuscating a private certificate's not before/after
|
||||
// timestamps when converting to a public certificate.
|
||||
constexpr absl::Duration kNearbyShareMaxPrivateCertificateValidityBoundOffset =
|
||||
absl::Hours(2);
|
||||
|
||||
// To account for clock skew between the local device and remote devices, public
|
||||
// certificates will be considered valid if the current time is within the
|
||||
// bounds [not-before - tolerance, not-after + tolerance).
|
||||
constexpr absl::Duration
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance =
|
||||
absl::Minutes(30);
|
||||
|
||||
// The number of private certificates for a given visibility to be stored and
|
||||
// rotated on the local device.
|
||||
constexpr size_t kNearbyShareNumPrivateCertificates = 3;
|
||||
|
||||
// The number of bytes comprising the hash of the authentication token using the
|
||||
// secret key.
|
||||
constexpr size_t kNearbyShareNumBytesAuthenticationTokenHash = 6;
|
||||
|
||||
// Length of key in bytes required by AES-GCM encryption.
|
||||
constexpr size_t kNearbyShareNumBytesAesGcmKey = 32;
|
||||
|
||||
// Length of salt in bytes required by AES-GCM encryption.
|
||||
constexpr size_t kNearbyShareNumBytesAesGcmIv = 12;
|
||||
|
||||
// Length of salt in bytes required by AES-CTR encryption.
|
||||
constexpr size_t kNearbyShareNumBytesAesCtrIv = 16;
|
||||
|
||||
// The number of bytes of the AES secret key used to encrypt/decrypt the
|
||||
// metadata encryption key.
|
||||
constexpr size_t kNearbyShareNumBytesSecretKey = 32;
|
||||
|
||||
// The number of the bytes of the AES key used to encrypt personal info
|
||||
// metadata. For example, name and picture data. These bytes are broadcast in an
|
||||
// advertisement to other devices, thus the smaller byte size.
|
||||
constexpr size_t kNearbyShareNumBytesMetadataEncryptionKey = 14;
|
||||
|
||||
// The number of bytes for the salt used for encryption of the metadata
|
||||
// encryption key. These bytes are broadcast in the advertisement to other
|
||||
// devices.
|
||||
constexpr size_t kNearbyShareNumBytesMetadataEncryptionKeySalt = 2;
|
||||
|
||||
// The number of bytes used for the hash of the metadata encryption key.
|
||||
constexpr size_t kNearbyShareNumBytesMetadataEncryptionKeyTag = 32;
|
||||
|
||||
// The number of bytes in a certificate's identifier.
|
||||
constexpr size_t kNearbyShareNumBytesCertificateId = 32;
|
||||
|
||||
// Half of the possible 2-byte salt values.
|
||||
//
|
||||
// Note: Static identifiers can be tracked over time by setting up persistent
|
||||
// scanners at known locations (e.g. at different isles within a supermarket).
|
||||
// As the scanners’ location is already known, anyone who walks past the scanner
|
||||
// has their location recorded too. This can be used for heuristics (e.g. number
|
||||
// of customers in a store, customers who prefer product X also prefer product
|
||||
// Y, dwell time), or can be attached to an identity (e.g. rewards card when
|
||||
// checking out at the cashier). By rotating our identifiers, we prevent
|
||||
// inadvertently leaking location. However, even rotations can be tracked as we
|
||||
// get closer to running out of salts. If tracked over a long enough time, the
|
||||
// device that avoids salts that you’ve seen in the past is statistically likely
|
||||
// to be the device you’re tracking. Therefore, we only use half of the
|
||||
// available 2-byte salts.
|
||||
constexpr size_t kNearbyShareMaxNumMetadataEncryptionKeySalts = 32768;
|
||||
|
||||
// The max number of retries allowed to generate a salt. This is a sanity check
|
||||
// that will never be hit.
|
||||
constexpr size_t kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries =
|
||||
128;
|
||||
|
||||
// The prefix prepended to the UKEY2 authentication token by the sender before
|
||||
// signing.
|
||||
constexpr char kNearbyShareSenderVerificationPrefix = 0x01;
|
||||
|
||||
// The prefix prepended to the UKEY2 authentication token by the receiver before
|
||||
// signing.
|
||||
constexpr char kNearbyShareReceiverVerificationPrefix = 0x02;
|
||||
|
||||
// The maximum number of attempts to initialize LevelDB in Certificate Storage.
|
||||
constexpr size_t kNearbyShareCertificateStorageMaxNumInitializeAttempts = 3;
|
||||
|
||||
// The frequency with which to download public certificates.
|
||||
constexpr absl::Duration kNearbySharePublicCertificateDownloadPeriod =
|
||||
absl::Hours(12);
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_CONSTANTS_H_
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2022-2023 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 "sharing/certificates/fake_nearby_share_certificate_manager.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
class NearbyShareClientFactory;
|
||||
|
||||
FakeNearbyShareCertificateManager::Factory::Factory() = default;
|
||||
|
||||
FakeNearbyShareCertificateManager::Factory::~Factory() = default;
|
||||
|
||||
std::unique_ptr<NearbyShareCertificateManager>
|
||||
FakeNearbyShareCertificateManager::Factory::CreateInstance(
|
||||
nearby::Context* context,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager, absl::string_view profile_path,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory) {
|
||||
auto instance = std::make_unique<FakeNearbyShareCertificateManager>();
|
||||
instances_.push_back(instance.get());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
FakeNearbyShareCertificateManager::GetDecryptedPublicCertificateCall::
|
||||
GetDecryptedPublicCertificateCall(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback)
|
||||
: encrypted_metadata_key(std::move(encrypted_metadata_key)),
|
||||
callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareCertificateManager::GetDecryptedPublicCertificateCall::
|
||||
GetDecryptedPublicCertificateCall(
|
||||
GetDecryptedPublicCertificateCall&& other) = default;
|
||||
|
||||
FakeNearbyShareCertificateManager::GetDecryptedPublicCertificateCall&
|
||||
FakeNearbyShareCertificateManager::GetDecryptedPublicCertificateCall::operator=(
|
||||
GetDecryptedPublicCertificateCall&& other) = default;
|
||||
|
||||
FakeNearbyShareCertificateManager::GetDecryptedPublicCertificateCall::
|
||||
~GetDecryptedPublicCertificateCall() = default;
|
||||
|
||||
FakeNearbyShareCertificateManager::FakeNearbyShareCertificateManager()
|
||||
: next_salt_(GetNearbyShareTestSalt()) {}
|
||||
|
||||
FakeNearbyShareCertificateManager::~FakeNearbyShareCertificateManager() =
|
||||
default;
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
FakeNearbyShareCertificateManager::GetPrivateCertificatesAsPublicCertificates(
|
||||
DeviceVisibility visibility) {
|
||||
++num_get_private_certificates_as_public_certificates_calls_;
|
||||
return GetNearbyShareTestPublicCertificateList(visibility);
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateManager::GetDecryptedPublicCertificate(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback) {
|
||||
get_decrypted_public_certificate_calls_.emplace_back(encrypted_metadata_key,
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateManager::DownloadPublicCertificates() {
|
||||
++num_download_public_certificates_calls_;
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateManager::ClearPublicCertificates(
|
||||
std::function<void(bool)> callback) {
|
||||
++num_clear_public_certificates_calls_;
|
||||
callback(true);
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateManager::OnStart() {}
|
||||
|
||||
void FakeNearbyShareCertificateManager::OnStop() {}
|
||||
|
||||
std::optional<NearbySharePrivateCertificate>
|
||||
FakeNearbyShareCertificateManager::GetValidPrivateCertificate(
|
||||
DeviceVisibility visibility) const {
|
||||
auto cert = GetNearbyShareTestPrivateCertificate(visibility);
|
||||
cert.next_salts_for_testing() = std::queue<std::vector<uint8_t>>();
|
||||
cert.next_salts_for_testing().push(next_salt_);
|
||||
return cert;
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateManager::UpdatePrivateCertificateInStorage(
|
||||
const NearbySharePrivateCertificate& private_certificate) {}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2022-2023 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_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager_impl.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// A fake implementation of NearbyShareCertificateManager, along with a fake
|
||||
// factory, to be used in tests.
|
||||
class FakeNearbyShareCertificateManager : public NearbyShareCertificateManager {
|
||||
public:
|
||||
// Factory that creates FakeNearbyShareCertificateManager instances. Use in
|
||||
// NearbyShareCertificateManagerImpl::Factor::SetFactoryForTesting() in unit
|
||||
// tests.
|
||||
class Factory : public NearbyShareCertificateManagerImpl::Factory {
|
||||
public:
|
||||
Factory();
|
||||
~Factory() override;
|
||||
|
||||
// Returns all FakeNearbyShareCertificateManager instances created by
|
||||
// CreateInstance().
|
||||
std::vector<FakeNearbyShareCertificateManager*>& instances() {
|
||||
return instances_;
|
||||
}
|
||||
|
||||
private:
|
||||
// NearbyShareCertificateManagerImpl::Factory:
|
||||
std::unique_ptr<NearbyShareCertificateManager> CreateInstance(
|
||||
Context* context,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager,
|
||||
absl::string_view profile_path,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory) override;
|
||||
|
||||
std::vector<FakeNearbyShareCertificateManager*> instances_;
|
||||
};
|
||||
|
||||
struct GetDecryptedPublicCertificateCall {
|
||||
public:
|
||||
GetDecryptedPublicCertificateCall(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback);
|
||||
GetDecryptedPublicCertificateCall(
|
||||
GetDecryptedPublicCertificateCall&& other);
|
||||
GetDecryptedPublicCertificateCall& operator=(
|
||||
GetDecryptedPublicCertificateCall&& other);
|
||||
GetDecryptedPublicCertificateCall(
|
||||
const GetDecryptedPublicCertificateCall&) = delete;
|
||||
GetDecryptedPublicCertificateCall& operator=(
|
||||
const GetDecryptedPublicCertificateCall&) = delete;
|
||||
~GetDecryptedPublicCertificateCall();
|
||||
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key;
|
||||
CertDecryptedCallback callback;
|
||||
};
|
||||
|
||||
FakeNearbyShareCertificateManager();
|
||||
~FakeNearbyShareCertificateManager() override;
|
||||
|
||||
// NearbyShareCertificateManager:
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
GetPrivateCertificatesAsPublicCertificates(
|
||||
proto::DeviceVisibility visibility) override;
|
||||
void GetDecryptedPublicCertificate(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback) override;
|
||||
void DownloadPublicCertificates() override;
|
||||
void ClearPublicCertificates(std::function<void(bool)> callback) override;
|
||||
std::string Dump() const override { return ""; }
|
||||
|
||||
// Make protected methods from base class public in this fake class.
|
||||
using NearbyShareCertificateManager::NotifyPrivateCertificatesChanged;
|
||||
using NearbyShareCertificateManager::NotifyPublicCertificatesDownloaded;
|
||||
|
||||
void set_next_salt(const std::vector<uint8_t>& salt) { next_salt_ = salt; }
|
||||
|
||||
size_t num_get_private_certificates_as_public_certificates_calls() {
|
||||
return num_get_private_certificates_as_public_certificates_calls_;
|
||||
}
|
||||
|
||||
size_t num_download_public_certificates_calls() {
|
||||
return num_download_public_certificates_calls_;
|
||||
}
|
||||
|
||||
size_t num_clear_public_certificates_calls() {
|
||||
return num_clear_public_certificates_calls_;
|
||||
}
|
||||
|
||||
std::vector<GetDecryptedPublicCertificateCall>&
|
||||
get_decrypted_public_certificate_calls() {
|
||||
return get_decrypted_public_certificate_calls_;
|
||||
}
|
||||
|
||||
private:
|
||||
// NearbyShareCertificateManager:
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
std::optional<NearbySharePrivateCertificate> GetValidPrivateCertificate(
|
||||
proto::DeviceVisibility visibility) const override;
|
||||
void UpdatePrivateCertificateInStorage(
|
||||
const NearbySharePrivateCertificate& private_certificate) override;
|
||||
|
||||
size_t num_get_private_certificates_as_public_certificates_calls_ = 0;
|
||||
size_t num_download_public_certificates_calls_ = 0;
|
||||
size_t num_clear_public_certificates_calls_ = 0;
|
||||
std::vector<GetDecryptedPublicCertificateCall>
|
||||
get_decrypted_public_certificate_calls_;
|
||||
std::vector<uint8_t> next_salt_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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 "sharing/certificates/fake_nearby_share_certificate_storage.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
using ::nearby::sharing::proto::PublicCertificate;
|
||||
|
||||
FakeNearbyShareCertificateStorage::Factory::Factory() = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::Factory::~Factory() = default;
|
||||
|
||||
std::shared_ptr<NearbyShareCertificateStorage>
|
||||
FakeNearbyShareCertificateStorage::Factory::CreateInstance(
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database) {
|
||||
auto instance = std::make_shared<FakeNearbyShareCertificateStorage>();
|
||||
instances_.push_back(instance.get());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
FakeNearbyShareCertificateStorage::ReplacePublicCertificatesCall::
|
||||
ReplacePublicCertificatesCall(
|
||||
const std::vector<PublicCertificate>& public_certificates,
|
||||
ResultCallback callback)
|
||||
: public_certificates(public_certificates), callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareCertificateStorage::ReplacePublicCertificatesCall::
|
||||
ReplacePublicCertificatesCall(ReplacePublicCertificatesCall&& other) =
|
||||
default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::ReplacePublicCertificatesCall::
|
||||
~ReplacePublicCertificatesCall() = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::AddPublicCertificatesCall::
|
||||
AddPublicCertificatesCall(
|
||||
const std::vector<PublicCertificate>& public_certificates,
|
||||
ResultCallback callback)
|
||||
: public_certificates(public_certificates), callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareCertificateStorage::AddPublicCertificatesCall::
|
||||
AddPublicCertificatesCall(AddPublicCertificatesCall&& other) = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::AddPublicCertificatesCall::
|
||||
~AddPublicCertificatesCall() = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::RemoveExpiredPublicCertificatesCall::
|
||||
RemoveExpiredPublicCertificatesCall(absl::Time now, ResultCallback callback)
|
||||
: now(now), callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareCertificateStorage::RemoveExpiredPublicCertificatesCall::
|
||||
RemoveExpiredPublicCertificatesCall(
|
||||
RemoveExpiredPublicCertificatesCall&& other) = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::RemoveExpiredPublicCertificatesCall::
|
||||
~RemoveExpiredPublicCertificatesCall() = default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::FakeNearbyShareCertificateStorage() =
|
||||
default;
|
||||
|
||||
FakeNearbyShareCertificateStorage::~FakeNearbyShareCertificateStorage() =
|
||||
default;
|
||||
|
||||
std::vector<std::string>
|
||||
FakeNearbyShareCertificateStorage::GetPublicCertificateIds() const {
|
||||
return public_certificate_ids_;
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::GetPublicCertificates(
|
||||
PublicCertificateCallback callback) {
|
||||
get_public_certificates_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
FakeNearbyShareCertificateStorage::GetPrivateCertificates() const {
|
||||
return private_certificates_;
|
||||
}
|
||||
|
||||
std::optional<absl::Time>
|
||||
FakeNearbyShareCertificateStorage::NextPublicCertificateExpirationTime() const {
|
||||
return next_public_certificate_expiration_time_;
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::ReplacePrivateCertificates(
|
||||
absl::Span<const NearbySharePrivateCertificate> private_certificates) {
|
||||
private_certificates_ = std::vector<NearbySharePrivateCertificate>(
|
||||
private_certificates.begin(), private_certificates.end());
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::ReplacePublicCertificates(
|
||||
absl::Span<const PublicCertificate> public_certificates,
|
||||
ResultCallback callback) {
|
||||
replace_public_certificates_calls_.emplace_back(
|
||||
std::vector<PublicCertificate>(public_certificates.begin(),
|
||||
public_certificates.end()),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::AddPublicCertificates(
|
||||
absl::Span<const PublicCertificate> public_certificates,
|
||||
ResultCallback callback) {
|
||||
add_public_certificates_calls_.emplace_back(
|
||||
std::vector<PublicCertificate>(public_certificates.begin(),
|
||||
public_certificates.end()),
|
||||
callback);
|
||||
if (is_sync_mode_) {
|
||||
callback(add_public_certificates_result_);
|
||||
}
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::RemoveExpiredPublicCertificates(
|
||||
absl::Time now, ResultCallback callback) {
|
||||
remove_expired_public_certificates_calls_.emplace_back(now, callback);
|
||||
if (is_sync_mode_) {
|
||||
callback(remove_expired_public_certificates_result_);
|
||||
}
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::ClearPublicCertificates(
|
||||
ResultCallback callback) {
|
||||
clear_public_certificates_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::SetPublicCertificateIds(
|
||||
absl::Span<const absl::string_view> ids) {
|
||||
public_certificate_ids_ = std::vector<std::string>(ids.begin(), ids.end());
|
||||
}
|
||||
|
||||
void FakeNearbyShareCertificateStorage::SetNextPublicCertificateExpirationTime(
|
||||
std::optional<absl::Time> time) {
|
||||
next_public_certificate_expiration_time_ = time;
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage_impl.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// A fake implementation of NearbyShareCertificateStorage, along with a fake
|
||||
// factory, to be used in tests.
|
||||
class FakeNearbyShareCertificateStorage : public NearbyShareCertificateStorage {
|
||||
public:
|
||||
// Factory that creates FakeNearbyShareCertificateStorage instances. Use
|
||||
// in NearbyShareCertificateStorageImpl::Factory::SetFactoryForTesting()
|
||||
// in unit tests.
|
||||
class Factory : public NearbyShareCertificateStorageImpl::Factory {
|
||||
public:
|
||||
Factory();
|
||||
~Factory() override;
|
||||
|
||||
// Returns all FakeNearbyShareCertificateStorage instances created by
|
||||
// CreateInstance().
|
||||
std::vector<FakeNearbyShareCertificateStorage*>& instances() {
|
||||
return instances_;
|
||||
}
|
||||
|
||||
private:
|
||||
// NearbyShareCertificateStorageImpl::Factory:
|
||||
std::shared_ptr<NearbyShareCertificateStorage> CreateInstance(
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database) override;
|
||||
|
||||
std::vector<FakeNearbyShareCertificateStorage*> instances_;
|
||||
};
|
||||
|
||||
struct ReplacePublicCertificatesCall {
|
||||
ReplacePublicCertificatesCall(
|
||||
const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
public_certificates,
|
||||
ResultCallback callback);
|
||||
ReplacePublicCertificatesCall(ReplacePublicCertificatesCall&& other);
|
||||
~ReplacePublicCertificatesCall();
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> public_certificates;
|
||||
ResultCallback callback;
|
||||
};
|
||||
|
||||
struct AddPublicCertificatesCall {
|
||||
AddPublicCertificatesCall(
|
||||
const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
public_certificates,
|
||||
ResultCallback callback);
|
||||
AddPublicCertificatesCall(AddPublicCertificatesCall&& other);
|
||||
~AddPublicCertificatesCall();
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> public_certificates;
|
||||
ResultCallback callback;
|
||||
};
|
||||
|
||||
struct RemoveExpiredPublicCertificatesCall {
|
||||
RemoveExpiredPublicCertificatesCall(absl::Time now,
|
||||
ResultCallback callback);
|
||||
RemoveExpiredPublicCertificatesCall(
|
||||
RemoveExpiredPublicCertificatesCall&& other);
|
||||
~RemoveExpiredPublicCertificatesCall();
|
||||
|
||||
absl::Time now;
|
||||
ResultCallback callback;
|
||||
};
|
||||
|
||||
FakeNearbyShareCertificateStorage();
|
||||
~FakeNearbyShareCertificateStorage() override;
|
||||
|
||||
// NearbyShareCertificateStorage:
|
||||
std::vector<std::string> GetPublicCertificateIds() const override;
|
||||
void GetPublicCertificates(PublicCertificateCallback callback) override;
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
GetPrivateCertificates() const override;
|
||||
std::optional<absl::Time> NextPublicCertificateExpirationTime()
|
||||
const override;
|
||||
void ReplacePrivateCertificates(
|
||||
absl::Span<const NearbySharePrivateCertificate> private_certificates)
|
||||
override;
|
||||
void ReplacePublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) override;
|
||||
void AddPublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) override;
|
||||
void RemoveExpiredPublicCertificates(absl::Time now,
|
||||
ResultCallback callback) override;
|
||||
void ClearPublicCertificates(ResultCallback callback) override;
|
||||
|
||||
void SetPublicCertificateIds(absl::Span<const absl::string_view> ids);
|
||||
void SetNextPublicCertificateExpirationTime(std::optional<absl::Time> time);
|
||||
|
||||
std::vector<PublicCertificateCallback>& get_public_certificates_callbacks() {
|
||||
return get_public_certificates_callbacks_;
|
||||
}
|
||||
|
||||
std::vector<ReplacePublicCertificatesCall>&
|
||||
replace_public_certificates_calls() {
|
||||
return replace_public_certificates_calls_;
|
||||
}
|
||||
|
||||
std::vector<AddPublicCertificatesCall>& add_public_certificates_calls() {
|
||||
return add_public_certificates_calls_;
|
||||
}
|
||||
|
||||
std::vector<RemoveExpiredPublicCertificatesCall>&
|
||||
remove_expired_public_certificates_calls() {
|
||||
return remove_expired_public_certificates_calls_;
|
||||
}
|
||||
|
||||
std::vector<ResultCallback>& clear_public_certificates_callbacks() {
|
||||
return clear_public_certificates_callbacks_;
|
||||
}
|
||||
|
||||
void set_is_sync_mode(bool is_sync_mode) { is_sync_mode_ = is_sync_mode; }
|
||||
|
||||
void SetAddPublicCertificatesResult(bool result) {
|
||||
add_public_certificates_result_ = result;
|
||||
}
|
||||
|
||||
void SetRemoveExpiredPublicCertificatesResult(bool result) {
|
||||
remove_expired_public_certificates_result_ = result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<absl::Time> next_public_certificate_expiration_time_;
|
||||
std::vector<std::string> public_certificate_ids_;
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
private_certificates_;
|
||||
std::vector<PublicCertificateCallback> get_public_certificates_callbacks_;
|
||||
std::vector<ReplacePublicCertificatesCall> replace_public_certificates_calls_;
|
||||
std::vector<AddPublicCertificatesCall> add_public_certificates_calls_;
|
||||
std::vector<RemoveExpiredPublicCertificatesCall>
|
||||
remove_expired_public_certificates_calls_;
|
||||
std::vector<ResultCallback> clear_public_certificates_callbacks_;
|
||||
bool is_sync_mode_ = false;
|
||||
bool add_public_certificates_result_ = false;
|
||||
bool remove_expired_public_certificates_result_ = false;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_FAKE_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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 "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
NearbyShareCertificateManager::NearbyShareCertificateManager() = default;
|
||||
|
||||
NearbyShareCertificateManager::~NearbyShareCertificateManager() = default;
|
||||
|
||||
void NearbyShareCertificateManager::AddObserver(Observer* observer) {
|
||||
observers_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManager::RemoveObserver(Observer* observer) {
|
||||
observers_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManager::Start() {
|
||||
if (is_running_) return;
|
||||
|
||||
is_running_ = true;
|
||||
OnStart();
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManager::Stop() {
|
||||
if (!is_running_) return;
|
||||
|
||||
is_running_ = false;
|
||||
OnStop();
|
||||
}
|
||||
|
||||
std::optional<NearbyShareEncryptedMetadataKey>
|
||||
NearbyShareCertificateManager::EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility visibility) {
|
||||
std::optional<NearbySharePrivateCertificate> cert =
|
||||
GetValidPrivateCertificate(visibility);
|
||||
if (!cert) return std::nullopt;
|
||||
|
||||
std::optional<NearbyShareEncryptedMetadataKey> encrypted_key =
|
||||
cert->EncryptMetadataKey();
|
||||
|
||||
// Every salt consumed to encrypt the metadata encryption key is tracked by
|
||||
// the NearbySharePrivateCertificate. Update the private certificate in
|
||||
// storage to reflect the new list of consumed salts.
|
||||
UpdatePrivateCertificateInStorage(*cert);
|
||||
|
||||
return encrypted_key;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>>
|
||||
NearbyShareCertificateManager::SignWithPrivateCertificate(
|
||||
DeviceVisibility visibility, absl::Span<const uint8_t> payload) const {
|
||||
std::optional<NearbySharePrivateCertificate> cert =
|
||||
GetValidPrivateCertificate(visibility);
|
||||
if (!cert) return std::nullopt;
|
||||
|
||||
return cert->Sign(payload);
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>>
|
||||
NearbyShareCertificateManager::HashAuthenticationTokenWithPrivateCertificate(
|
||||
DeviceVisibility visibility,
|
||||
absl::Span<const uint8_t> authentication_token) const {
|
||||
std::optional<NearbySharePrivateCertificate> cert =
|
||||
GetValidPrivateCertificate(visibility);
|
||||
if (!cert) return std::nullopt;
|
||||
|
||||
return cert->HashAuthenticationToken(authentication_token);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManager::NotifyPublicCertificatesDownloaded() {
|
||||
for (const auto& observer : observers_.GetObservers())
|
||||
observer->OnPublicCertificatesDownloaded();
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManager::NotifyPrivateCertificatesChanged() {
|
||||
for (const auto& observer : observers_.GetObservers())
|
||||
observer->OnPrivateCertificatesChanged();
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/base/observer_list.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// The Nearby Share certificate manager maintains the local device's private
|
||||
// certificates and contacts' public certificates. The manager communicates with
|
||||
// the Nearby server to 1) download contacts' public certificates and 2) upload
|
||||
// local device public certificates to be distributed to contacts.
|
||||
//
|
||||
// The class contains methods for performing crypto operations with the
|
||||
// currently valid private certificate of a given visibility, such as signing a
|
||||
// payload or generating an encrypted metadata key for an advertisement. For
|
||||
// crypto operations related to public certificates, such as verifying a
|
||||
// payload, find and decrypt the relevant certificate with
|
||||
// DecryptPublicCertificate(), then use the
|
||||
// NearbyShareDecryptedPublicCertificate class to perform the crypto operations.
|
||||
// NOTE: The NearbySharePrivateCertificate class is not directly returned
|
||||
// because storage needs to be updated whenever salts are consumed for metadata
|
||||
// key encryption.
|
||||
//
|
||||
// Observers are notified of any changes to private/public certificates.
|
||||
class NearbyShareCertificateManager {
|
||||
public:
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void OnPublicCertificatesDownloaded() = 0;
|
||||
virtual void OnPrivateCertificatesChanged() = 0;
|
||||
};
|
||||
|
||||
using CertDecryptedCallback =
|
||||
std::function<void(std::optional<NearbyShareDecryptedPublicCertificate>)>;
|
||||
|
||||
NearbyShareCertificateManager();
|
||||
virtual ~NearbyShareCertificateManager();
|
||||
|
||||
void AddObserver(Observer* observer);
|
||||
void RemoveObserver(Observer* observer);
|
||||
|
||||
// Starts/Stops certificate task scheduling.
|
||||
void Start();
|
||||
void Stop();
|
||||
bool is_running() { return is_running_; }
|
||||
|
||||
// Encrypts the metadata encryption key of the currently valid private
|
||||
// certificate with |visibility|. Returns absl::nullopt if there is no valid
|
||||
// private certificate with |visibility|, if the encryption fails, or if
|
||||
// there are no remaining salts.
|
||||
std::optional<NearbyShareEncryptedMetadataKey>
|
||||
EncryptPrivateCertificateMetadataKey(proto::DeviceVisibility visibility);
|
||||
|
||||
// Signs the input |payload| using the currently valid private certificate
|
||||
// with |visibility|. Returns absl::nullopt if there is no valid private
|
||||
// certificate with |visibility| or if the signing was unsuccessful.
|
||||
std::optional<std::vector<uint8_t>> SignWithPrivateCertificate(
|
||||
proto::DeviceVisibility visibility,
|
||||
absl::Span<const uint8_t> payload) const;
|
||||
|
||||
// Creates a hash of the |authentication_token| using the currently valid
|
||||
// private certificate. Returns absl::nullopt if there is no valid private
|
||||
// certificate with |visibility|.
|
||||
std::optional<std::vector<uint8_t>>
|
||||
HashAuthenticationTokenWithPrivateCertificate(
|
||||
proto::DeviceVisibility visibility,
|
||||
absl::Span<const uint8_t> authentication_token) const;
|
||||
|
||||
// Returns all local device private certificates of |visibility| converted to
|
||||
// public certificates. The public certificates' for_selected_contacts fields
|
||||
// will be set to reflect the |visibility|. NOTE: Only certificates with the
|
||||
// requested visibility will be returned; if selected-contacts visibility is
|
||||
// passed in, the all-contacts visibility certificates will *not* be returned
|
||||
// as well.
|
||||
virtual std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
GetPrivateCertificatesAsPublicCertificates(
|
||||
proto::DeviceVisibility visibility) = 0;
|
||||
|
||||
// Returns in |callback| the public certificate that is able to be decrypted
|
||||
// using |encrypted_metadata_key|, and returns absl::nullopt if no such public
|
||||
// certificate exists.
|
||||
virtual void GetDecryptedPublicCertificate(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback) = 0;
|
||||
|
||||
// Makes an RPC call to the Nearby server to retrieve all public certificates
|
||||
// available to the local device. These are also downloaded periodically.
|
||||
// Observers are notified when all public certificate downloads succeed via
|
||||
// OnPublicCertificatesDownloaded().
|
||||
virtual void DownloadPublicCertificates() = 0;
|
||||
|
||||
// Clears all public certificates. when account logout,the public certificates
|
||||
// should be cleared.
|
||||
virtual void ClearPublicCertificates(std::function<void(bool)> callback) = 0;
|
||||
|
||||
// Dump certificates ID information for troubleshooting.
|
||||
virtual std::string Dump() const = 0;
|
||||
|
||||
protected:
|
||||
virtual void OnStart() = 0;
|
||||
virtual void OnStop() = 0;
|
||||
|
||||
// Returns the currently valid private certificate with |visibility|, or
|
||||
// returns std::nullopt if one does not exist.
|
||||
virtual std::optional<NearbySharePrivateCertificate>
|
||||
GetValidPrivateCertificate(proto::DeviceVisibility visibility) const = 0;
|
||||
|
||||
// Updates the existing record for |private_certificate|. If no such record
|
||||
// exists, this function does nothing.
|
||||
virtual void UpdatePrivateCertificateInStorage(
|
||||
const NearbySharePrivateCertificate& private_certificate) = 0;
|
||||
|
||||
void NotifyPublicCertificatesDownloaded();
|
||||
void NotifyPrivateCertificatesChanged();
|
||||
|
||||
private:
|
||||
bool is_running_ = false;
|
||||
nearby::ObserverList<Observer> observers_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_H_
|
||||
@@ -0,0 +1,706 @@
|
||||
// Copyright 2023 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 "sharing/certificates/nearby_share_certificate_manager_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/functional/bind_front.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage_impl.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/bluetooth_adapter.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/internal/api/sharing_platform.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/base/encode.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/certificate_rpc.pb.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
#include "google/protobuf/repeated_ptr_field.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::api::PreferenceManager;
|
||||
using ::nearby::sharing::api::PublicCertificateDatabase;
|
||||
using ::nearby::sharing::api::SharingPlatform;
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
using ::nearby::sharing::proto::EncryptedMetadata;
|
||||
using ::nearby::sharing::proto::ListPublicCertificatesRequest;
|
||||
using ::nearby::sharing::proto::ListPublicCertificatesResponse;
|
||||
using ::nearby::sharing::proto::PublicCertificate;
|
||||
|
||||
constexpr char kDeviceIdPrefix[] = "users/me/devices/";
|
||||
|
||||
constexpr std::array<DeviceVisibility, 3> kVisibilities = {
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS,
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE,
|
||||
};
|
||||
|
||||
const absl::string_view kPublicCertificateDatabaseName =
|
||||
"NearbySharePublicCertificateDatabase";
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum class GetDecryptedPublicCertificateResult {
|
||||
kSuccess = 0,
|
||||
kNoMatch = 1,
|
||||
kStorageFailure = 2,
|
||||
kMaxValue = kStorageFailure
|
||||
};
|
||||
|
||||
size_t NumExpectedPrivateCertificates() {
|
||||
return kVisibilities.size() * kNearbyShareNumPrivateCertificates;
|
||||
}
|
||||
|
||||
std::optional<EncryptedMetadata> BuildMetadata(
|
||||
std::string device_name, std::optional<std::string> full_name,
|
||||
std::optional<std::string> icon_url,
|
||||
std::optional<std::string> account_name, Context* context) {
|
||||
EncryptedMetadata metadata;
|
||||
if (device_name.empty()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": Failed to create private certificate metadata; "
|
||||
<< "missing device name.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
metadata.set_device_name(device_name);
|
||||
if (full_name.has_value()) {
|
||||
metadata.set_full_name(*full_name);
|
||||
}
|
||||
if (icon_url.has_value()) {
|
||||
metadata.set_icon_url(*icon_url);
|
||||
}
|
||||
if (account_name.has_value()) {
|
||||
metadata.set_account_name(*account_name);
|
||||
}
|
||||
|
||||
auto bluetooth_mac_address = context->GetBluetoothAdapter().GetAddress();
|
||||
if (!bluetooth_mac_address) return std::nullopt;
|
||||
|
||||
metadata.set_bluetooth_mac_address(bluetooth_mac_address->data(), 6u);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
void TryDecryptPublicCertificates(
|
||||
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
|
||||
NearbyShareCertificateManager::CertDecryptedCallback callback, bool success,
|
||||
std::unique_ptr<std::vector<PublicCertificate>> public_certificates) {
|
||||
if (!success || !public_certificates) {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Failed to read public certificates from storage.";
|
||||
std::move(callback)(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& cert : *public_certificates) {
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> decrypted =
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
cert, encrypted_metadata_key);
|
||||
if (decrypted) {
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Successfully decrypted public certificate with ID "
|
||||
<< nearby::utils::HexEncode(decrypted->id());
|
||||
std::move(callback)(std::move(decrypted));
|
||||
return;
|
||||
}
|
||||
}
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Metadata key could not decrypt any public certificates.";
|
||||
std::move(callback)(std::nullopt);
|
||||
}
|
||||
|
||||
void DumpCertificateId(std::stringstream& sstream, absl::string_view cert_id,
|
||||
bool is_public_cert) {
|
||||
if (is_public_cert) {
|
||||
sstream << " Public certificates:[";
|
||||
} else {
|
||||
sstream << " Private certificates:[";
|
||||
}
|
||||
for (int i = 0; i < cert_id.size() - 1; ++i) {
|
||||
sstream << static_cast<int>(static_cast<int8_t>(cert_id[i])) << ", ";
|
||||
}
|
||||
sstream << static_cast<int>(static_cast<int8_t>(cert_id[cert_id.size() - 1]))
|
||||
<< "]" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
NearbyShareCertificateManagerImpl::Factory*
|
||||
NearbyShareCertificateManagerImpl::Factory::test_factory_ = nullptr;
|
||||
|
||||
// static
|
||||
std::unique_ptr<NearbyShareCertificateManager>
|
||||
NearbyShareCertificateManagerImpl::Factory::Create(
|
||||
Context* context, SharingPlatform& sharing_platform,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager, absl::string_view profile_path,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory) {
|
||||
NL_DCHECK(context);
|
||||
|
||||
if (test_factory_) {
|
||||
return test_factory_->CreateInstance(context, local_device_data_manager,
|
||||
contact_manager, profile_path,
|
||||
client_factory);
|
||||
}
|
||||
|
||||
return absl::WrapUnique(new NearbyShareCertificateManagerImpl(
|
||||
context, sharing_platform.GetPreferenceManager(),
|
||||
sharing_platform.GetAccountManager(),
|
||||
sharing_platform.CreatePublicCertificateDatabase(
|
||||
absl::StrCat(profile_path, "/", kPublicCertificateDatabaseName)),
|
||||
local_device_data_manager, contact_manager, client_factory));
|
||||
}
|
||||
|
||||
// static
|
||||
void NearbyShareCertificateManagerImpl::Factory::SetFactoryForTesting(
|
||||
Factory* test_factory) {
|
||||
test_factory_ = test_factory;
|
||||
}
|
||||
|
||||
NearbyShareCertificateManagerImpl::Factory::~Factory() = default;
|
||||
|
||||
NearbyShareCertificateManagerImpl::NearbyShareCertificateManagerImpl(
|
||||
Context* context, PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
std::unique_ptr<PublicCertificateDatabase> public_certificate_database,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory)
|
||||
: context_(context),
|
||||
account_manager_(account_manager),
|
||||
local_device_data_manager_(local_device_data_manager),
|
||||
contact_manager_(contact_manager),
|
||||
nearby_client_(client_factory->CreateInstance()),
|
||||
certificate_storage_(NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager, std::move(public_certificate_database))),
|
||||
private_certificate_expiration_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreateExpirationScheduler(
|
||||
context, preference_manager,
|
||||
[&] { return NextPrivateCertificateExpirationTime(); },
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/false,
|
||||
prefs::kNearbySharingSchedulerPrivateCertificateExpirationName,
|
||||
[&] {
|
||||
NL_LOG(INFO)
|
||||
<< ": Private certificate expiration scheduler is called.";
|
||||
OnPrivateCertificateExpiration();
|
||||
})),
|
||||
public_certificate_expiration_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreateExpirationScheduler(
|
||||
context, preference_manager,
|
||||
[&] { return NextPublicCertificateExpirationTime(); },
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/false,
|
||||
prefs::kNearbySharingSchedulerPublicCertificateExpirationName,
|
||||
[&] {
|
||||
NL_LOG(INFO)
|
||||
<< ": Public certificate expiration scheduler is called.";
|
||||
OnPublicCertificateExpiration();
|
||||
})),
|
||||
upload_local_device_certificates_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreateOnDemandScheduler(
|
||||
context, preference_manager,
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/true,
|
||||
prefs::kNearbySharingSchedulerUploadLocalDeviceCertificatesName,
|
||||
[&] {
|
||||
NL_LOG(INFO) << ": Upload local device certificates scheduler "
|
||||
"is called.";
|
||||
UploadLocalDeviceCertificates();
|
||||
})),
|
||||
download_public_certificates_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreatePeriodicScheduler(
|
||||
context, preference_manager,
|
||||
kNearbySharePublicCertificateDownloadPeriod,
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/true,
|
||||
prefs::kNearbySharingSchedulerDownloadPublicCertificatesName,
|
||||
[&] {
|
||||
NL_LOG(INFO)
|
||||
<< ": Download public certificates scheduler is called.";
|
||||
DownloadPublicCertificates();
|
||||
})),
|
||||
executor_(context->CreateSequencedTaskRunner()) {
|
||||
local_device_data_manager_->AddObserver(this);
|
||||
contact_manager_->AddObserver(this);
|
||||
}
|
||||
|
||||
NearbyShareCertificateManagerImpl::~NearbyShareCertificateManagerImpl() {
|
||||
local_device_data_manager_->RemoveObserver(this);
|
||||
contact_manager_->RemoveObserver(this);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::CertificateDownloadContext::
|
||||
FetchNextPage() {
|
||||
NL_LOG(INFO) << __func__ << ": Downloading page=" << page_number_++;
|
||||
ListPublicCertificatesRequest request;
|
||||
request.set_parent(device_id_);
|
||||
if (next_page_token_.has_value()) {
|
||||
request.set_page_token(*next_page_token_);
|
||||
}
|
||||
nearby_share_client_->ListPublicCertificates(
|
||||
request, [this](
|
||||
const absl::StatusOr<ListPublicCertificatesResponse>&
|
||||
response) mutable {
|
||||
if (!response.ok()) {
|
||||
NL_LOG(ERROR) << __func__ << ": Failed to download certificates.";
|
||||
std::move(download_failure_callback_)();
|
||||
return;
|
||||
}
|
||||
|
||||
certificates_.insert(certificates_.end(),
|
||||
response->public_certificates().begin(),
|
||||
response->public_certificates().end());
|
||||
|
||||
if (response->next_page_token().empty()) {
|
||||
NL_LOG(INFO) << __func__ << ": Completed to download "
|
||||
<< certificates_.size()
|
||||
<< " certificates from backend";
|
||||
std::move(download_success_callback_)(certificates_);
|
||||
return;
|
||||
}
|
||||
next_page_token_ = response->next_page_token();
|
||||
FetchNextPage();
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnPublicCertificatesDownloadSuccess(
|
||||
const std::vector<PublicCertificate>& certificates) {
|
||||
// Save certificates to store.
|
||||
absl::Notification notification;
|
||||
bool is_added_to_store = false;
|
||||
certificate_storage_->AddPublicCertificates(
|
||||
absl::MakeSpan(certificates.data(),
|
||||
certificates.size()),
|
||||
[&](bool success) {
|
||||
is_added_to_store = success;
|
||||
notification.Notify();
|
||||
});
|
||||
notification.WaitForNotification();
|
||||
if (!is_added_to_store) {
|
||||
NL_LOG(ERROR) << __func__ << ": Failed to add certificates to store.";
|
||||
OnPublicCertificatesDownloadFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Succeeded to download public certificates.
|
||||
NotifyPublicCertificatesDownloaded();
|
||||
|
||||
// Recompute the expiration timer to account for new certificates.
|
||||
public_certificate_expiration_scheduler_->Reschedule();
|
||||
download_public_certificates_scheduler_->HandleResult(true);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnPublicCertificatesDownloadFailure() {
|
||||
download_public_certificates_scheduler_->HandleResult(false);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::DownloadPublicCertificates() {
|
||||
executor_->PostTask([&]() {
|
||||
NL_LOG(INFO) << __func__ << ": Start to download certificates.";
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": Ignore to download certificates due to manager is "
|
||||
"not running.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING)
|
||||
<< __func__
|
||||
<< ": Ignore to download certificates due to no login account.";
|
||||
download_public_certificates_scheduler_->HandleResult(/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently certificates download is synchronous. It completes after
|
||||
// FetchNextPage() returns.
|
||||
auto context = std::make_unique<CertificateDownloadContext>(
|
||||
nearby_client_.get(),
|
||||
kDeviceIdPrefix + local_device_data_manager_->GetId(),
|
||||
absl::bind_front(&NearbyShareCertificateManagerImpl::
|
||||
OnPublicCertificatesDownloadFailure,
|
||||
this),
|
||||
absl::bind_front(&NearbyShareCertificateManagerImpl::
|
||||
OnPublicCertificatesDownloadSuccess,
|
||||
this));
|
||||
context->FetchNextPage();
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::UploadLocalDeviceCertificates() {
|
||||
executor_->PostTask([&]() {
|
||||
NL_LOG(INFO) << __func__ << ": Start to upload local device certificates.";
|
||||
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING)
|
||||
<< __func__
|
||||
<< ": Ignore to upload local device certificates due to manager is "
|
||||
"not running.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING)
|
||||
<< __func__
|
||||
<< ": Ignore to upload local device certificates due to no "
|
||||
"login account.";
|
||||
upload_local_device_certificates_scheduler_->HandleResult(
|
||||
/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<PublicCertificate> public_certs;
|
||||
std::vector<NearbySharePrivateCertificate> private_certs =
|
||||
*certificate_storage_->GetPrivateCertificates();
|
||||
public_certs.reserve(private_certs.size());
|
||||
for (const NearbySharePrivateCertificate& private_cert : private_certs) {
|
||||
public_certs.push_back(*private_cert.ToPublicCertificate());
|
||||
}
|
||||
|
||||
NL_LOG(INFO) << __func__ << ": Uploading " << public_certs.size()
|
||||
<< " local device certificates.";
|
||||
bool upload_certificates_result = false;
|
||||
absl::Notification notification;
|
||||
local_device_data_manager_->UploadCertificates(
|
||||
std::move(public_certs), [&](bool success) {
|
||||
upload_certificates_result = success;
|
||||
notification.Notify();
|
||||
});
|
||||
notification.WaitForNotification();
|
||||
NL_LOG(INFO) << __func__ << ": Upload of local device certificates "
|
||||
<< (upload_certificates_result ? "succeeded" : "failed.");
|
||||
upload_local_device_certificates_scheduler_->HandleResult(
|
||||
upload_certificates_result);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<PublicCertificate>
|
||||
NearbyShareCertificateManagerImpl::GetPrivateCertificatesAsPublicCertificates(
|
||||
DeviceVisibility visibility) {
|
||||
return std::vector<PublicCertificate>();
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::GetDecryptedPublicCertificate(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback) {
|
||||
certificate_storage_->GetPublicCertificates(
|
||||
[encrypted_metadata_key = std::move(encrypted_metadata_key),
|
||||
callback = std::move(callback)](
|
||||
bool success,
|
||||
std::unique_ptr<std::vector<PublicCertificate>> result) {
|
||||
TryDecryptPublicCertificates(encrypted_metadata_key,
|
||||
std::move(callback), success,
|
||||
std::move(result));
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::ClearPublicCertificates(
|
||||
std::function<void(bool)> callback) {
|
||||
certificate_storage_->ClearPublicCertificates(std::move(callback));
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnStart() {
|
||||
private_certificate_expiration_scheduler_->Start();
|
||||
public_certificate_expiration_scheduler_->Start();
|
||||
upload_local_device_certificates_scheduler_->Start();
|
||||
download_public_certificates_scheduler_->Start();
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnStop() {
|
||||
private_certificate_expiration_scheduler_->Stop();
|
||||
public_certificate_expiration_scheduler_->Stop();
|
||||
upload_local_device_certificates_scheduler_->Stop();
|
||||
download_public_certificates_scheduler_->Stop();
|
||||
}
|
||||
|
||||
std::optional<NearbySharePrivateCertificate>
|
||||
NearbyShareCertificateManagerImpl::GetValidPrivateCertificate(
|
||||
DeviceVisibility visibility) const {
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
|
||||
*certificate_storage_->GetPrivateCertificates();
|
||||
for (auto& cert : *certs) {
|
||||
if (IsNearbyShareCertificateWithinValidityPeriod(
|
||||
context_->GetClock()->Now(), cert.not_before(), cert.not_after(),
|
||||
/*use_public_certificate_tolerance=*/false) &&
|
||||
cert.visibility() == visibility) {
|
||||
return std::move(cert);
|
||||
}
|
||||
}
|
||||
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": No valid private certificate found with visibility "
|
||||
<< static_cast<int>(visibility);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::UpdatePrivateCertificateInStorage(
|
||||
const NearbySharePrivateCertificate& private_certificate) {
|
||||
certificate_storage_->UpdatePrivateCertificate(private_certificate);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) {
|
||||
NL_LOG(INFO) << __func__ << ": Contacts downloaded.";
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnContactsUploaded(
|
||||
bool did_contacts_change_since_last_upload) {
|
||||
executor_->PostTask([&, did_contacts_change_since_last_upload]() {
|
||||
NL_LOG(INFO) << __func__ << ": Handle to Contacts uploaded.";
|
||||
if (!did_contacts_change_since_last_upload) return;
|
||||
|
||||
// If any of the uploaded contact data - the contact list or the allowlist -
|
||||
// has changed since the previous successful upload, recreate certificates.
|
||||
// We do not want to continue using the current certificates because they
|
||||
// might have been shared with contacts no longer on the contact list or
|
||||
// allowlist. NOTE: Ideally, we would only recreate all-contacts visibility
|
||||
// certificates when contacts are removed from the contact list, and we
|
||||
// would only recreate selected-contacts visibility certificates when
|
||||
// contacts are removed from the allowlist, but our information is not that
|
||||
// granular.
|
||||
certificate_storage_->ClearPrivateCertificates();
|
||||
private_certificate_expiration_scheduler_->MakeImmediateRequest();
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnLocalDeviceDataChanged(
|
||||
bool did_device_name_change, bool did_full_name_change,
|
||||
bool did_icon_change) {
|
||||
executor_->PostTask([&, did_device_name_change, did_full_name_change,
|
||||
did_icon_change]() {
|
||||
NL_LOG(INFO) << __func__ << ": Handle to local device data changed.";
|
||||
if (!did_device_name_change && !did_full_name_change && !did_icon_change)
|
||||
return;
|
||||
|
||||
// Recreate all private certificates to ensure up-to-date metadata.
|
||||
certificate_storage_->ClearPrivateCertificates();
|
||||
private_certificate_expiration_scheduler_->MakeImmediateRequest();
|
||||
});
|
||||
}
|
||||
|
||||
std::string NearbyShareCertificateManagerImpl::Dump() const {
|
||||
std::stringstream sstream;
|
||||
sstream << "Public Certificates" << std::endl;
|
||||
std::vector<std::string> ids =
|
||||
certificate_storage_->GetPublicCertificateIds();
|
||||
sstream << " Total count:" << ids.size() << std::endl;
|
||||
for (const auto& id : ids) {
|
||||
DumpCertificateId(sstream, id, true);
|
||||
}
|
||||
sstream << std::endl;
|
||||
|
||||
sstream << "Private Certificates" << std::endl;
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> private_certs =
|
||||
certificate_storage_->GetPrivateCertificates();
|
||||
if (private_certs.has_value()) {
|
||||
sstream << " Total count:" << private_certs->size() << std::endl;
|
||||
for (const auto& cert : *private_certs) {
|
||||
std::string id(cert.id().begin(), cert.id().end());
|
||||
DumpCertificateId(sstream, id, false);
|
||||
}
|
||||
} else {
|
||||
sstream << " Total count: 0" << std::endl;
|
||||
}
|
||||
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
std::optional<absl::Time>
|
||||
NearbyShareCertificateManagerImpl::NextPrivateCertificateExpirationTime() {
|
||||
// We enforce that a fixed number--kNearbyShareNumPrivateCertificates for each
|
||||
// visibility--of private certificates be present at all times. This might not
|
||||
// be true the first time the user enables Nearby Share or after certificates
|
||||
// are revoked. For simplicity, consider the case of missing certificates an
|
||||
// "expired" state. Return the minimum time to immediately trigger the private
|
||||
// certificate creation flow.
|
||||
if (certificate_storage_->GetPrivateCertificates()->size() <
|
||||
NumExpectedPrivateCertificates()) {
|
||||
return absl::InfinitePast();
|
||||
}
|
||||
|
||||
std::optional<absl::Time> expiration_time =
|
||||
certificate_storage_->NextPrivateCertificateExpirationTime();
|
||||
NL_DCHECK(expiration_time);
|
||||
|
||||
return *expiration_time;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnPrivateCertificateExpiration() {
|
||||
NL_VLOG(1)
|
||||
<< __func__
|
||||
<< ": Private certificate expiration detected; refreshing certificates.";
|
||||
|
||||
FinishPrivateCertificateRefresh();
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::FinishPrivateCertificateRefresh() {
|
||||
executor_->PostTask([&]() {
|
||||
NL_LOG(INFO) << __func__ << ": Refresh private certificates.";
|
||||
absl::Time now = context_->GetClock()->Now();
|
||||
certificate_storage_->RemoveExpiredPrivateCertificates(now);
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> certs =
|
||||
*certificate_storage_->GetPrivateCertificates();
|
||||
if (certs.size() == NumExpectedPrivateCertificates()) {
|
||||
NL_VLOG(1) << __func__ << ": All private certificates are still valid.";
|
||||
private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine how many private certificates of each visibility need to be
|
||||
// created, and determine the validity period for the new certificates.
|
||||
absl::flat_hash_map<DeviceVisibility, size_t> num_valid_certs;
|
||||
absl::flat_hash_map<DeviceVisibility, absl::Time> latest_not_after;
|
||||
for (DeviceVisibility visibility : kVisibilities) {
|
||||
num_valid_certs[visibility] = 0;
|
||||
latest_not_after[visibility] = now;
|
||||
}
|
||||
for (const NearbySharePrivateCertificate& cert : certs) {
|
||||
++num_valid_certs[cert.visibility()];
|
||||
latest_not_after[cert.visibility()] =
|
||||
std::max(latest_not_after[cert.visibility()], cert.not_after());
|
||||
}
|
||||
|
||||
std::optional<AccountManager::Account> account =
|
||||
account_manager_.GetCurrentAccount();
|
||||
std::optional<std::string> email =
|
||||
account.has_value()
|
||||
? account->email
|
||||
: static_cast<std::optional<std::string>>(std::nullopt);
|
||||
|
||||
std::optional<EncryptedMetadata> metadata = BuildMetadata(
|
||||
local_device_data_manager_->GetDeviceName(),
|
||||
local_device_data_manager_->GetFullName(),
|
||||
local_device_data_manager_->GetIconUrl(), email, context_);
|
||||
if (!metadata.has_value()) {
|
||||
NL_LOG(WARNING)
|
||||
<< __func__
|
||||
<< "Failed to create private certificates; cannot create metadata";
|
||||
private_certificate_expiration_scheduler_->HandleResult(
|
||||
/*success=*/false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new certificates if necessary. Each visibility should have
|
||||
// kNearbyShareNumPrivateCertificates.
|
||||
NL_LOG(INFO)
|
||||
<< __func__ << ": Creating "
|
||||
<< kNearbyShareNumPrivateCertificates -
|
||||
num_valid_certs[DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS]
|
||||
<< " all-contacts visibility and "
|
||||
<< kNearbyShareNumPrivateCertificates -
|
||||
num_valid_certs
|
||||
[DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS]
|
||||
<< " selected-contacts visibility private certificates.";
|
||||
|
||||
for (DeviceVisibility visibility : kVisibilities) {
|
||||
while (num_valid_certs[visibility] < kNearbyShareNumPrivateCertificates) {
|
||||
certs.emplace_back(visibility,
|
||||
/*not_before=*/latest_not_after[visibility],
|
||||
*metadata);
|
||||
++num_valid_certs[visibility];
|
||||
latest_not_after[visibility] = certs.back().not_after();
|
||||
}
|
||||
}
|
||||
|
||||
certificate_storage_->ReplacePrivateCertificates(
|
||||
absl::MakeSpan(certs.data(), certs.size()));
|
||||
NotifyPrivateCertificatesChanged();
|
||||
private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
|
||||
|
||||
upload_local_device_certificates_scheduler_->MakeImmediateRequest();
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<absl::Time>
|
||||
NearbyShareCertificateManagerImpl::NextPublicCertificateExpirationTime() {
|
||||
std::optional<absl::Time> next_expiration_time =
|
||||
certificate_storage_->NextPublicCertificateExpirationTime();
|
||||
|
||||
// Supposedly there are no store public certificates.
|
||||
if (!next_expiration_time) return std::nullopt;
|
||||
|
||||
// To account for clock skew between devices, we accept public certificates
|
||||
// that are slightly past their validity period. This conforms with the
|
||||
// GmsCore implementation.
|
||||
return *next_expiration_time +
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateManagerImpl::OnPublicCertificateExpiration() {
|
||||
executor_->PostTask([&]() {
|
||||
NL_LOG(INFO) << __func__ << ": Removing expired public certificates.";
|
||||
absl::Notification notification;
|
||||
bool result = false;
|
||||
certificate_storage_->RemoveExpiredPublicCertificates(
|
||||
context_->GetClock()->Now(), [&](bool success) {
|
||||
result = success;
|
||||
notification.Notify();
|
||||
});
|
||||
notification.WaitForNotification();
|
||||
if (!result) {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Failed to remove expired public certificates.";
|
||||
}
|
||||
public_certificate_expiration_scheduler_->HandleResult(result);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright 2023 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_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_IMPL_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_IMPL_H_
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/platform/task_runner.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/internal/api/sharing_platform.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
class NearbyShareScheduler;
|
||||
|
||||
// An implementation of the NearbyShareCertificateManager that handles
|
||||
// 1) creating, storing, and uploading local device certificates, as well as
|
||||
// removing expired/revoked local device certificates;
|
||||
// 2) downloading, storing, and decrypting public certificates from trusted
|
||||
// contacts, as well as removing expired public certificates.
|
||||
//
|
||||
// This implementation destroys and recreates all private certificates if there
|
||||
// are any changes to the user's contact list or allowlist, or if there are any
|
||||
// changes to the local device data, such as the device name.
|
||||
class NearbyShareCertificateManagerImpl
|
||||
: public NearbyShareCertificateManager,
|
||||
public NearbyShareContactManager::Observer,
|
||||
public NearbyShareLocalDeviceDataManager::Observer {
|
||||
public:
|
||||
class Factory {
|
||||
public:
|
||||
static std::unique_ptr<NearbyShareCertificateManager> Create(
|
||||
Context* context,
|
||||
nearby::sharing::api::SharingPlatform& sharing_platform,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager,
|
||||
absl::string_view profile_path,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory);
|
||||
static void SetFactoryForTesting(Factory* test_factory);
|
||||
|
||||
protected:
|
||||
virtual ~Factory();
|
||||
virtual std::unique_ptr<NearbyShareCertificateManager> CreateInstance(
|
||||
Context* context,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager,
|
||||
absl::string_view profile_path,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory) = 0;
|
||||
|
||||
private:
|
||||
static Factory* test_factory_;
|
||||
};
|
||||
|
||||
~NearbyShareCertificateManagerImpl() override;
|
||||
|
||||
private:
|
||||
// Class for maintaining a single instance of public certificate download
|
||||
// request. It is responsible for downloading all available pages and making
|
||||
// the results or error available.
|
||||
class CertificateDownloadContext {
|
||||
public:
|
||||
CertificateDownloadContext(
|
||||
nearby::sharing::api::SharingRpcClient* nearby_share_client,
|
||||
std::string device_id,
|
||||
absl::AnyInvocable<void() &&> download_failure_callback,
|
||||
absl::AnyInvocable<
|
||||
void(const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
certificates) &&>
|
||||
download_success_callback)
|
||||
: nearby_share_client_(nearby_share_client),
|
||||
device_id_(std::move(device_id)),
|
||||
download_failure_callback_(std::move(download_failure_callback)),
|
||||
download_success_callback_(std::move(download_success_callback)) {}
|
||||
|
||||
// Fetches the next page of certificates.
|
||||
// If |next_page_token_| is empty, it fetches the first page.
|
||||
// On successful download, if page token in the response is empty, the
|
||||
// |download_success_callback_| is invoked with all downloaded certificates.
|
||||
void FetchNextPage();
|
||||
|
||||
private:
|
||||
nearby::sharing::api::SharingRpcClient* const nearby_share_client_;
|
||||
std::string device_id_;
|
||||
std::optional<std::string> next_page_token_;
|
||||
int page_number_ = 1;
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates_;
|
||||
absl::AnyInvocable<void() &&> download_failure_callback_;
|
||||
absl::AnyInvocable<
|
||||
void(const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
certificates) &&>
|
||||
download_success_callback_;
|
||||
};
|
||||
|
||||
NearbyShareCertificateManagerImpl(
|
||||
Context* context,
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager,
|
||||
NearbyShareContactManager* contact_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* client_factory);
|
||||
|
||||
// NearbyShareCertificateManager:
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
GetPrivateCertificatesAsPublicCertificates(
|
||||
proto::DeviceVisibility visibility) override;
|
||||
void GetDecryptedPublicCertificate(
|
||||
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
|
||||
CertDecryptedCallback callback) override;
|
||||
void DownloadPublicCertificates() override;
|
||||
void ClearPublicCertificates(std::function<void(bool)> callback) override;
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
std::optional<NearbySharePrivateCertificate> GetValidPrivateCertificate(
|
||||
proto::DeviceVisibility visibility) const override;
|
||||
void UpdatePrivateCertificateInStorage(
|
||||
const NearbySharePrivateCertificate& private_certificate) override;
|
||||
|
||||
// NearbyShareContactManager::Observer:
|
||||
void OnContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) override;
|
||||
void OnContactsUploaded(bool did_contacts_change_since_last_upload) override;
|
||||
|
||||
// NearbyShareLocalDeviceDataManager::Observer:
|
||||
void OnLocalDeviceDataChanged(bool did_device_name_change,
|
||||
bool did_full_name_change,
|
||||
bool did_icon_change) override;
|
||||
|
||||
// Dump certs information.
|
||||
std::string Dump() const override;
|
||||
|
||||
// Used by the private certificate expiration scheduler to determine the next
|
||||
// private certificate expiration time. Returns base::Time::Min() if
|
||||
// certificates are missing. This function never returns absl::nullopt.
|
||||
std::optional<absl::Time> NextPrivateCertificateExpirationTime();
|
||||
|
||||
// Used by the public certificate expiration scheduler to determine the next
|
||||
// public certificate expiration time. Returns absl::nullopt if no public
|
||||
// certificates are present, and no expiration event is scheduled.
|
||||
std::optional<absl::Time> NextPublicCertificateExpirationTime();
|
||||
|
||||
// Invoked by the private certificate expiration scheduler when an expired
|
||||
// private certificate needs to be removed or if no private certificates exist
|
||||
// yet. New certificate(s) will be created, and an upload to the Nearby Share
|
||||
// server will be requested.
|
||||
void OnPrivateCertificateExpiration();
|
||||
void FinishPrivateCertificateRefresh();
|
||||
|
||||
// Invoked by the public certificate expiration scheduler when an expired
|
||||
// public certificate needs to be removed from storage.
|
||||
void OnPublicCertificateExpiration();
|
||||
|
||||
void UploadLocalDeviceCertificates();
|
||||
|
||||
void OnPublicCertificatesDownloadSuccess(
|
||||
const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
certificates);
|
||||
void OnPublicCertificatesDownloadFailure();
|
||||
|
||||
Context* const context_;
|
||||
AccountManager& account_manager_;
|
||||
NearbyShareLocalDeviceDataManager* const local_device_data_manager_;
|
||||
NearbyShareContactManager* const contact_manager_;
|
||||
std::unique_ptr< nearby::sharing::api::SharingRpcClient> nearby_client_;
|
||||
|
||||
std::shared_ptr<NearbyShareCertificateStorage> certificate_storage_;
|
||||
std::unique_ptr<NearbyShareScheduler>
|
||||
private_certificate_expiration_scheduler_;
|
||||
std::unique_ptr<NearbyShareScheduler>
|
||||
public_certificate_expiration_scheduler_;
|
||||
std::unique_ptr<NearbyShareScheduler>
|
||||
upload_local_device_certificates_scheduler_;
|
||||
std::unique_ptr<NearbyShareScheduler> download_public_certificates_scheduler_;
|
||||
|
||||
std::unique_ptr<TaskRunner> executor_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_MANAGER_IMPL_H_
|
||||
@@ -0,0 +1,807 @@
|
||||
// Copyright 2023 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 "sharing/certificates/nearby_share_certificate_manager_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/test/fake_account_manager.h"
|
||||
#include "internal/test/fake_task_runner.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/fake_nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_manager.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage_impl.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/contacts/fake_nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/fake_nearby_share_client.h"
|
||||
#include "sharing/internal/api/mock_sharing_platform.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/internal/test/fake_bluetooth_adapter.h"
|
||||
#include "sharing/internal/test/fake_context.h"
|
||||
#include "sharing/internal/test/fake_preference_manager.h"
|
||||
#include "sharing/local_device_data/fake_nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/certificate_rpc.pb.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler_factory.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
using ::nearby::sharing::proto::PublicCertificate;
|
||||
using ::testing::ReturnRef;
|
||||
|
||||
const absl::Time t0 = absl::UnixEpoch() + absl::Hours(365 * 50 * 24);
|
||||
|
||||
constexpr char kPageTokenPrefix[] = "page_token_";
|
||||
constexpr char kSecretIdPrefix[] = "secret_id_";
|
||||
constexpr char kDeviceId[] = "123456789A";
|
||||
constexpr char kDefaultDeviceName[] = "Josh's Chromebook";
|
||||
|
||||
constexpr absl::string_view kPublicCertificateIds[3] = {"id1", "id2", "id3"};
|
||||
|
||||
void CaptureDecryptedPublicCertificateCallback(
|
||||
std::optional<NearbyShareDecryptedPublicCertificate>* dest,
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> src) {
|
||||
*dest = std::move(src);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class NearbyShareCertificateManagerImplTest
|
||||
: public ::testing::Test,
|
||||
public NearbyShareCertificateManager::Observer {
|
||||
public:
|
||||
NearbyShareCertificateManagerImplTest() = default;
|
||||
~NearbyShareCertificateManagerImplTest() override = default;
|
||||
|
||||
void SetUp() override {
|
||||
ON_CALL(mock_sharing_platform_, GetPreferenceManager)
|
||||
.WillByDefault(ReturnRef(preference_manager_));
|
||||
ON_CALL(mock_sharing_platform_, GetAccountManager)
|
||||
.WillByDefault(ReturnRef(fake_account_manager_));
|
||||
// Set time to t0.
|
||||
FastForward(t0 - fake_context_.GetClock()->Now());
|
||||
|
||||
local_device_data_manager_ =
|
||||
std::make_unique<FakeNearbyShareLocalDeviceDataManager>(
|
||||
kDefaultDeviceName);
|
||||
local_device_data_manager_->set_is_sync_mode(true);
|
||||
local_device_data_manager_->SetId(kDeviceId);
|
||||
|
||||
contact_manager_ = std::make_unique<FakeNearbyShareContactManager>();
|
||||
|
||||
AccountManager::Account account{
|
||||
.email = kTestMetadataAccountName,
|
||||
};
|
||||
|
||||
fake_account_manager_.SetAccount(account);
|
||||
|
||||
fake_account_manager_.Login([](AccountManager::Account account) {},
|
||||
[]() {});
|
||||
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(&scheduler_factory_);
|
||||
NearbyShareCertificateStorageImpl::Factory::SetFactoryForTesting(
|
||||
&cert_store_factory_);
|
||||
|
||||
// Set default device data.
|
||||
local_device_data_manager_->SetDeviceName(
|
||||
GetNearbyShareTestMetadata().device_name());
|
||||
local_device_data_manager_->SetFullName(
|
||||
GetNearbyShareTestMetadata().full_name());
|
||||
local_device_data_manager_->SetIconUrl(
|
||||
GetNearbyShareTestMetadata().icon_url());
|
||||
SetBluetoothMacAddress(kTestUnparsedBluetoothMacAddress);
|
||||
SetMockBluetoothAddress(kTestUnparsedBluetoothMacAddress);
|
||||
|
||||
cert_manager_ = NearbyShareCertificateManagerImpl::Factory::Create(
|
||||
&fake_context_, mock_sharing_platform_,
|
||||
local_device_data_manager_.get(), contact_manager_.get(), std::string(),
|
||||
&client_factory_);
|
||||
cert_manager_->AddObserver(this);
|
||||
|
||||
cert_store_ = cert_store_factory_.instances().back();
|
||||
cert_store_->set_is_sync_mode(true);
|
||||
|
||||
private_cert_exp_scheduler_ =
|
||||
scheduler_factory_.pref_name_to_expiration_instance()
|
||||
.find(
|
||||
prefs::kNearbySharingSchedulerPrivateCertificateExpirationName)
|
||||
->second.fake_scheduler;
|
||||
public_cert_exp_scheduler_ =
|
||||
scheduler_factory_.pref_name_to_expiration_instance()
|
||||
.find(prefs::kNearbySharingSchedulerPublicCertificateExpirationName)
|
||||
->second.fake_scheduler;
|
||||
upload_scheduler_ =
|
||||
scheduler_factory_.pref_name_to_on_demand_instance()
|
||||
.find(
|
||||
prefs::
|
||||
kNearbySharingSchedulerUploadLocalDeviceCertificatesName) // NOLINT
|
||||
->second.fake_scheduler;
|
||||
download_scheduler_ =
|
||||
scheduler_factory_.pref_name_to_periodic_instance()
|
||||
.find(prefs::kNearbySharingSchedulerDownloadPublicCertificatesName)
|
||||
->second.fake_scheduler;
|
||||
|
||||
PopulatePrivateCertificates();
|
||||
PopulatePublicCertificates();
|
||||
cert_manager_->Start();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
cert_manager_->RemoveObserver(this);
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(nullptr);
|
||||
NearbyShareCertificateStorageImpl::Factory::SetFactoryForTesting(nullptr);
|
||||
}
|
||||
|
||||
void SetBluetoothMacAddress(absl::string_view bluetooth_mac_address) {
|
||||
bluetooth_mac_address_ = bluetooth_mac_address;
|
||||
}
|
||||
|
||||
// NearbyShareCertificateManager::Observer:
|
||||
void OnPublicCertificatesDownloaded() override {
|
||||
++num_public_certs_downloaded_notifications_;
|
||||
}
|
||||
void OnPrivateCertificatesChanged() override {
|
||||
++num_private_certs_changed_notifications_;
|
||||
}
|
||||
|
||||
protected:
|
||||
enum class DownloadPublicCertificatesResult {
|
||||
kSuccess,
|
||||
kTimeout,
|
||||
kHttpError,
|
||||
kStorageError
|
||||
};
|
||||
|
||||
absl::Time Now() { return fake_context_.GetClock()->Now(); }
|
||||
|
||||
// Fast-forwards mock time by |delta| and fires relevant timers.
|
||||
void FastForward(absl::Duration delta) {
|
||||
fake_context_.fake_clock()->FastForward(delta);
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
EXPECT_TRUE(FakeTaskRunner::WaitForRunningTasksWithTimeout(
|
||||
absl::Milliseconds(1000)));
|
||||
}
|
||||
|
||||
void SetMockBluetoothAddress(absl::string_view bluetooth_mac_address) {
|
||||
FakeBluetoothAdapter& bluetooth_adapter =
|
||||
dynamic_cast<FakeBluetoothAdapter&>(
|
||||
fake_context_.GetBluetoothAdapter());
|
||||
bluetooth_adapter.SetAddress(bluetooth_mac_address);
|
||||
}
|
||||
|
||||
void SetBluetoothAdapterIsPresent(bool is_present) {
|
||||
if (!is_present) {
|
||||
FakeBluetoothAdapter& bluetooth_adapter =
|
||||
dynamic_cast<FakeBluetoothAdapter&>(
|
||||
fake_context_.GetBluetoothAdapter());
|
||||
bluetooth_adapter.SetAddress("");
|
||||
}
|
||||
}
|
||||
|
||||
void GetPublicCertificatesCallback(
|
||||
bool success, const std::vector<PublicCertificate>& certs) {
|
||||
auto& callbacks = cert_store_->get_public_certificates_callbacks();
|
||||
auto callback = std::move(callbacks.back());
|
||||
callbacks.pop_back();
|
||||
auto pub_certs = std::make_unique<std::vector<PublicCertificate>>(
|
||||
certs.begin(), certs.end());
|
||||
std::move(callback)(success, std::move(pub_certs));
|
||||
}
|
||||
|
||||
void HandlePrivateCertificateRefresh(bool expect_private_cert_refresh,
|
||||
bool expected_success) {
|
||||
if (expect_private_cert_refresh) {
|
||||
private_cert_exp_scheduler_->InvokeRequestCallback();
|
||||
}
|
||||
Sync();
|
||||
EXPECT_EQ(expect_private_cert_refresh ? 1u : 0u,
|
||||
private_cert_exp_scheduler_->handled_results().size());
|
||||
if (expect_private_cert_refresh) {
|
||||
EXPECT_EQ(expected_success,
|
||||
private_cert_exp_scheduler_->handled_results().back());
|
||||
}
|
||||
EXPECT_EQ(expect_private_cert_refresh && expected_success ? 1u : 0u,
|
||||
num_private_certs_changed_notifications_);
|
||||
EXPECT_EQ(expect_private_cert_refresh && expected_success ? 1u : 0u,
|
||||
upload_scheduler_->num_immediate_requests());
|
||||
}
|
||||
|
||||
void VerifyPrivateCertificates(
|
||||
const nearby::sharing::proto::EncryptedMetadata& expected_metadata) {
|
||||
// Expect a full set of certificates for all-contacts, selected-contacts,
|
||||
// and self-share
|
||||
std::vector<NearbySharePrivateCertificate> certs =
|
||||
*cert_store_->GetPrivateCertificates();
|
||||
EXPECT_EQ(3 * kNearbyShareNumPrivateCertificates, certs.size());
|
||||
|
||||
absl::Time min_not_before_all_contacts = absl::InfiniteFuture();
|
||||
absl::Time min_not_before_selected_contacts = absl::InfiniteFuture();
|
||||
absl::Time min_not_before_self_share = absl::InfiniteFuture();
|
||||
|
||||
absl::Time max_not_after_all_contacts = absl::InfinitePast();
|
||||
absl::Time max_not_after_selected_contacts = absl::InfinitePast();
|
||||
absl::Time max_not_after_self_share = absl::InfinitePast();
|
||||
|
||||
for (const auto& cert : certs) {
|
||||
EXPECT_EQ(cert.not_after() - cert.not_before(),
|
||||
kNearbyShareCertificateValidityPeriod);
|
||||
switch (cert.visibility()) {
|
||||
case DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS:
|
||||
min_not_before_all_contacts =
|
||||
std::min(min_not_before_all_contacts, cert.not_before());
|
||||
max_not_after_all_contacts =
|
||||
std::max(max_not_after_all_contacts, cert.not_after());
|
||||
break;
|
||||
case DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS:
|
||||
min_not_before_selected_contacts =
|
||||
std::min(min_not_before_selected_contacts, cert.not_before());
|
||||
max_not_after_selected_contacts =
|
||||
std::max(max_not_after_selected_contacts, cert.not_after());
|
||||
break;
|
||||
case DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE:
|
||||
min_not_before_self_share =
|
||||
std::min(min_not_before_self_share, cert.not_before());
|
||||
max_not_after_self_share =
|
||||
std::max(max_not_after_self_share, cert.not_after());
|
||||
break;
|
||||
default:
|
||||
NL_DCHECK(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify metadata.
|
||||
EXPECT_EQ(expected_metadata.SerializeAsString(),
|
||||
cert.unencrypted_metadata().SerializeAsString());
|
||||
}
|
||||
|
||||
// Verify contiguous validity periods
|
||||
EXPECT_EQ(max_not_after_all_contacts - min_not_before_all_contacts,
|
||||
kNearbyShareNumPrivateCertificates *
|
||||
kNearbyShareCertificateValidityPeriod);
|
||||
EXPECT_EQ(
|
||||
max_not_after_selected_contacts - min_not_before_selected_contacts,
|
||||
kNearbyShareNumPrivateCertificates *
|
||||
kNearbyShareCertificateValidityPeriod);
|
||||
}
|
||||
|
||||
void RunUpload(bool success) {
|
||||
size_t initial_num_upload_calls =
|
||||
local_device_data_manager_->upload_certificates_calls().size();
|
||||
local_device_data_manager_->SetUploadCertificatesResult(success);
|
||||
size_t initial_num_handled_results =
|
||||
upload_scheduler_->handled_results().size();
|
||||
|
||||
upload_scheduler_->InvokeRequestCallback();
|
||||
Sync();
|
||||
EXPECT_EQ(local_device_data_manager_->upload_certificates_calls().size(),
|
||||
initial_num_upload_calls + 1);
|
||||
|
||||
EXPECT_EQ(local_device_data_manager_->upload_certificates_calls()
|
||||
.back()
|
||||
.certificates.size(),
|
||||
3 * kNearbyShareNumPrivateCertificates);
|
||||
|
||||
EXPECT_EQ(upload_scheduler_->handled_results().size(),
|
||||
initial_num_handled_results + 1);
|
||||
EXPECT_EQ(upload_scheduler_->handled_results().back(), success);
|
||||
}
|
||||
|
||||
// Test downloading public certificates with or without errors. The RPC is
|
||||
// paginated, and |num_pages| will be simulated. Any failures, as indicated by
|
||||
// |result|, will be simulated on the last page.
|
||||
void DownloadPublicCertificatesFlow(size_t num_pages,
|
||||
DownloadPublicCertificatesResult result) {
|
||||
size_t prev_num_results = download_scheduler_->handled_results().size();
|
||||
cert_store_->SetPublicCertificateIds(kPublicCertificateIds);
|
||||
|
||||
size_t initial_num_notifications =
|
||||
num_public_certs_downloaded_notifications_;
|
||||
size_t initial_num_public_cert_exp_reschedules =
|
||||
public_cert_exp_scheduler_->num_reschedule_calls();
|
||||
|
||||
// Build RPC responses.
|
||||
std::vector<absl::StatusOr<proto::ListPublicCertificatesResponse>>
|
||||
responses;
|
||||
std::string page_token;
|
||||
for (size_t page_number = 0; page_number < num_pages; ++page_number) {
|
||||
bool last_page = page_number == num_pages - 1;
|
||||
if (last_page && result == DownloadPublicCertificatesResult::kHttpError) {
|
||||
responses.push_back(absl::InternalError(""));
|
||||
break;
|
||||
}
|
||||
page_token = last_page ? std::string()
|
||||
: absl::StrCat(kPageTokenPrefix, page_number);
|
||||
responses.push_back(BuildRpcResponse(page_number, page_token));
|
||||
}
|
||||
|
||||
client_factory_.instances().back()->SetListPublicCertificatesResponses(
|
||||
responses);
|
||||
cert_store_->SetAddPublicCertificatesResult(
|
||||
result != DownloadPublicCertificatesResult::kStorageError);
|
||||
download_scheduler_->InvokeRequestCallback();
|
||||
Sync();
|
||||
|
||||
CheckRpcRequest(num_pages);
|
||||
ASSERT_EQ(download_scheduler_->handled_results().size(),
|
||||
prev_num_results + 1);
|
||||
|
||||
bool success = result == DownloadPublicCertificatesResult::kSuccess;
|
||||
EXPECT_EQ(download_scheduler_->handled_results().back(), success);
|
||||
EXPECT_EQ(num_public_certs_downloaded_notifications_,
|
||||
initial_num_notifications + (success ? 1u : 0u));
|
||||
EXPECT_EQ(public_cert_exp_scheduler_->num_reschedule_calls(),
|
||||
initial_num_public_cert_exp_reschedules + (success ? 1u : 0u));
|
||||
}
|
||||
|
||||
void CheckRpcRequest(int num_pages) {
|
||||
std::vector<proto::ListPublicCertificatesRequest> requests =
|
||||
client_factory_.instances().back()->list_public_certificates_requests();
|
||||
EXPECT_EQ(requests.size(), num_pages);
|
||||
}
|
||||
|
||||
nearby::sharing::proto::ListPublicCertificatesResponse BuildRpcResponse(
|
||||
size_t page_number, absl::string_view page_token) {
|
||||
nearby::sharing::proto::ListPublicCertificatesResponse response;
|
||||
for (size_t i = 0; i < public_certificates_.size(); ++i) {
|
||||
public_certificates_[i].set_secret_id(
|
||||
absl::StrCat(kSecretIdPrefix, page_number, "_", i));
|
||||
response.add_public_certificates();
|
||||
*response.mutable_public_certificates(i) = public_certificates_[i];
|
||||
}
|
||||
response.set_next_page_token(page_token);
|
||||
return response;
|
||||
}
|
||||
|
||||
void CheckStorageAddCertificates(
|
||||
const FakeNearbyShareCertificateStorage::AddPublicCertificatesCall&
|
||||
add_cert_call) {
|
||||
ASSERT_EQ(add_cert_call.public_certificates.size(),
|
||||
public_certificates_.size());
|
||||
for (size_t i = 0; i < public_certificates_.size(); ++i) {
|
||||
EXPECT_EQ(add_cert_call.public_certificates[i].secret_id(),
|
||||
public_certificates_[i].secret_id());
|
||||
}
|
||||
}
|
||||
|
||||
void PopulatePrivateCertificates() {
|
||||
private_certificates_.clear();
|
||||
const auto& metadata = GetNearbyShareTestMetadata();
|
||||
for (auto visibility :
|
||||
{DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS,
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE}) {
|
||||
private_certificates_.emplace_back(visibility, t0, metadata);
|
||||
private_certificates_.emplace_back(
|
||||
visibility, t0 + kNearbyShareCertificateValidityPeriod, metadata);
|
||||
private_certificates_.emplace_back(
|
||||
visibility, t0 + kNearbyShareCertificateValidityPeriod * 2, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void PopulatePublicCertificates() {
|
||||
public_certificates_.clear();
|
||||
metadata_encryption_keys_.clear();
|
||||
auto& metadata1 = GetNearbyShareTestMetadata();
|
||||
nearby::sharing::proto::EncryptedMetadata metadata2;
|
||||
metadata2.set_device_name("device_name2");
|
||||
metadata2.set_full_name("full_name2");
|
||||
metadata2.set_icon_url("icon_url2");
|
||||
metadata2.set_bluetooth_mac_address("bluetooth_mac_address2");
|
||||
for (auto metadata : {metadata1, metadata2}) {
|
||||
auto private_cert = NearbySharePrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS, t0, metadata);
|
||||
public_certificates_.push_back(*private_cert.ToPublicCertificate());
|
||||
metadata_encryption_keys_.push_back(*private_cert.EncryptMetadataKey());
|
||||
}
|
||||
}
|
||||
|
||||
nearby::sharing::api::MockSharingPlatform mock_sharing_platform_;
|
||||
nearby::FakePreferenceManager preference_manager_;
|
||||
FakeAccountManager fake_account_manager_;
|
||||
FakeContext fake_context_;
|
||||
FakeNearbyShareCertificateStorage* cert_store_ = nullptr;
|
||||
FakeNearbyShareScheduler* private_cert_exp_scheduler_ = nullptr;
|
||||
FakeNearbyShareScheduler* public_cert_exp_scheduler_ = nullptr;
|
||||
FakeNearbyShareScheduler* upload_scheduler_ = nullptr;
|
||||
FakeNearbyShareScheduler* download_scheduler_ = nullptr;
|
||||
std::string bluetooth_mac_address_ = kTestUnparsedBluetoothMacAddress;
|
||||
size_t num_public_certs_downloaded_notifications_ = 0;
|
||||
size_t num_private_certs_changed_notifications_ = 0;
|
||||
std::vector<NearbySharePrivateCertificate> private_certificates_;
|
||||
std::vector<PublicCertificate> public_certificates_;
|
||||
std::vector<NearbyShareEncryptedMetadataKey> metadata_encryption_keys_;
|
||||
|
||||
FakeNearbyShareClientFactory client_factory_;
|
||||
FakeNearbyShareSchedulerFactory scheduler_factory_;
|
||||
FakeNearbyShareCertificateStorage::Factory cert_store_factory_;
|
||||
std::unique_ptr<FakeNearbyShareLocalDeviceDataManager>
|
||||
local_device_data_manager_;
|
||||
std::unique_ptr<FakeNearbyShareContactManager> contact_manager_;
|
||||
std::unique_ptr<NearbyShareCertificateManager> cert_manager_;
|
||||
};
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
EncryptPrivateCertificateMetadataKey) {
|
||||
// No valid certificates exist.
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
EXPECT_FALSE(cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS));
|
||||
EXPECT_FALSE(cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS));
|
||||
|
||||
// Set up valid all-contacts visibility certificate.
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store_->ReplacePrivateCertificates({private_certificate});
|
||||
FastForward(GetNearbyShareTestNotBefore() +
|
||||
kNearbyShareCertificateValidityPeriod * 0.5 - Now());
|
||||
|
||||
// Sanity check that the cert storage is as expected.
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> stored_certs =
|
||||
cert_store_->GetPrivateCertificates();
|
||||
EXPECT_EQ(stored_certs->at(0).ToCertificateData(),
|
||||
private_certificate.ToCertificateData());
|
||||
|
||||
std::optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
|
||||
cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().encrypted_key(),
|
||||
encrypted_metadata_key->encrypted_key());
|
||||
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().salt(),
|
||||
encrypted_metadata_key->salt());
|
||||
EXPECT_FALSE(cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS));
|
||||
|
||||
// Verify that storage is updated when salts are consumed during encryption.
|
||||
EXPECT_NE(cert_store_->GetPrivateCertificates()->at(0).ToCertificateData(),
|
||||
private_certificate.ToCertificateData());
|
||||
|
||||
// No valid certificates exist.
|
||||
FastForward(kNearbyShareCertificateValidityPeriod);
|
||||
EXPECT_FALSE(cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS));
|
||||
EXPECT_FALSE(cert_manager_->EncryptPrivateCertificateMetadataKey(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest, SignWithPrivateCertificate) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store_->ReplacePrivateCertificates({private_certificate});
|
||||
FastForward(GetNearbyShareTestNotBefore() +
|
||||
kNearbyShareCertificateValidityPeriod * 0.5 - Now());
|
||||
|
||||
// Perform sign/verify round trip.
|
||||
EXPECT_TRUE(GetNearbyShareTestDecryptedPublicCertificate().VerifySignature(
|
||||
GetNearbyShareTestPayloadToSign(),
|
||||
*cert_manager_->SignWithPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
GetNearbyShareTestPayloadToSign())));
|
||||
|
||||
// No selected-contact visibility certificate in storage.
|
||||
EXPECT_FALSE(cert_manager_->SignWithPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS,
|
||||
GetNearbyShareTestPayloadToSign()));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
HashAuthenticationTokenWithPrivateCertificate) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store_->ReplacePrivateCertificates({private_certificate});
|
||||
FastForward(GetNearbyShareTestNotBefore() +
|
||||
kNearbyShareCertificateValidityPeriod * 0.5 - Now());
|
||||
|
||||
EXPECT_EQ(private_certificate.HashAuthenticationToken(
|
||||
GetNearbyShareTestPayloadToSign()),
|
||||
cert_manager_->HashAuthenticationTokenWithPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
GetNearbyShareTestPayloadToSign()));
|
||||
|
||||
// No selected-contact visibility certificate in storage.
|
||||
EXPECT_FALSE(cert_manager_->HashAuthenticationTokenWithPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS,
|
||||
GetNearbyShareTestPayloadToSign()));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
GetDecryptedPublicCertificateSuccess) {
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> decrypted_pub_cert;
|
||||
cert_manager_->GetDecryptedPublicCertificate(
|
||||
metadata_encryption_keys_[0],
|
||||
[&](std::optional<NearbyShareDecryptedPublicCertificate> cert) {
|
||||
CaptureDecryptedPublicCertificateCallback(&decrypted_pub_cert, cert);
|
||||
});
|
||||
GetPublicCertificatesCallback(true, public_certificates_);
|
||||
|
||||
ASSERT_TRUE(decrypted_pub_cert);
|
||||
std::vector<uint8_t> id(public_certificates_[0].secret_id().begin(),
|
||||
public_certificates_[0].secret_id().end());
|
||||
EXPECT_EQ(decrypted_pub_cert->id(), id);
|
||||
EXPECT_EQ(decrypted_pub_cert->unencrypted_metadata().SerializeAsString(),
|
||||
GetNearbyShareTestMetadata().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
GetDecryptedPublicCertificateCertNotFound) {
|
||||
auto private_cert = NearbySharePrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS, t0,
|
||||
GetNearbyShareTestMetadata());
|
||||
auto metadata_key = private_cert.EncryptMetadataKey();
|
||||
ASSERT_TRUE(metadata_key);
|
||||
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> decrypted_pub_cert;
|
||||
cert_manager_->GetDecryptedPublicCertificate(
|
||||
*metadata_key,
|
||||
[&](std::optional<NearbyShareDecryptedPublicCertificate> cert) {
|
||||
CaptureDecryptedPublicCertificateCallback(&decrypted_pub_cert, cert);
|
||||
});
|
||||
|
||||
GetPublicCertificatesCallback(true, public_certificates_);
|
||||
|
||||
EXPECT_FALSE(decrypted_pub_cert);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
GetDecryptedPublicCertificateGetPublicCertificatesFailure) {
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> decrypted_pub_cert;
|
||||
cert_manager_->GetDecryptedPublicCertificate(
|
||||
metadata_encryption_keys_[0],
|
||||
[&](std::optional<NearbyShareDecryptedPublicCertificate> cert) {
|
||||
CaptureDecryptedPublicCertificateCallback(&decrypted_pub_cert, cert);
|
||||
});
|
||||
|
||||
GetPublicCertificatesCallback(false, {});
|
||||
|
||||
EXPECT_FALSE(decrypted_pub_cert);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
DownloadPublicCertificatesSuccess) {
|
||||
ASSERT_NO_FATAL_FAILURE(DownloadPublicCertificatesFlow(
|
||||
/*num_pages=*/2, DownloadPublicCertificatesResult::kSuccess));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
DownloadPublicCertificatesRPCFailure) {
|
||||
ASSERT_NO_FATAL_FAILURE(DownloadPublicCertificatesFlow(
|
||||
/*num_pages=*/2, DownloadPublicCertificatesResult::kHttpError));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest, ClearPublicCertificates) {
|
||||
cert_manager_->ClearPublicCertificates([&](bool result) {});
|
||||
EXPECT_THAT(cert_store_->clear_public_certificates_callbacks(),
|
||||
::testing::SizeIs(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
DownloadPublicCertificatesStoreFailure) {
|
||||
ASSERT_NO_FATAL_FAILURE(DownloadPublicCertificatesFlow(
|
||||
/*num_pages=*/2, DownloadPublicCertificatesResult::kStorageError));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_ValidCertificates) {
|
||||
cert_store_->ReplacePrivateCertificates(private_certificates_);
|
||||
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/false,
|
||||
/*expected_success=*/true);
|
||||
VerifyPrivateCertificates(/*expected_metadata=*/GetNearbyShareTestMetadata());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_NoCertificates_UploadSuccess) {
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/true);
|
||||
RunUpload(/*success=*/true);
|
||||
VerifyPrivateCertificates(/*expected_metadata=*/GetNearbyShareTestMetadata());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_NoCertificates_UploadFailure) {
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/true);
|
||||
RunUpload(/*success=*/false);
|
||||
VerifyPrivateCertificates(/*expected_metadata=*/GetNearbyShareTestMetadata());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RevokePrivateCertificates_OnContactsUploaded) {
|
||||
// Destroy and recreate private certificates if contact data has changed since
|
||||
// the last successful upload.
|
||||
cert_manager_->Stop();
|
||||
size_t num_expected_calls = 0;
|
||||
for (bool did_contacts_change_since_last_upload : {true, false}) {
|
||||
cert_store_->ReplacePrivateCertificates(private_certificates_);
|
||||
contact_manager_->NotifyContactsUploaded(
|
||||
did_contacts_change_since_last_upload);
|
||||
|
||||
Sync();
|
||||
std::vector<NearbySharePrivateCertificate> certs =
|
||||
*cert_store_->GetPrivateCertificates();
|
||||
|
||||
if (did_contacts_change_since_last_upload) {
|
||||
++num_expected_calls;
|
||||
EXPECT_TRUE(certs.empty());
|
||||
} else {
|
||||
EXPECT_EQ(certs.size(), 9u);
|
||||
}
|
||||
|
||||
EXPECT_EQ(num_expected_calls,
|
||||
private_cert_exp_scheduler_->num_immediate_requests());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_OnLocalDeviceMetadataChanged) {
|
||||
cert_manager_->Start();
|
||||
|
||||
// Destroy and recreate private certificates if any metadata fields change.
|
||||
size_t num_expected_calls = 0;
|
||||
for (bool did_device_name_change : {true, false}) {
|
||||
for (bool did_full_name_change : {true, false}) {
|
||||
for (bool did_icon_change : {true, false}) {
|
||||
local_device_data_manager_->NotifyLocalDeviceDataChanged(
|
||||
did_device_name_change, did_full_name_change, did_icon_change);
|
||||
Sync();
|
||||
|
||||
if (did_device_name_change || did_full_name_change || did_icon_change) {
|
||||
++num_expected_calls;
|
||||
EXPECT_TRUE(cert_store_->GetPrivateCertificates()->empty());
|
||||
}
|
||||
|
||||
EXPECT_EQ(num_expected_calls,
|
||||
private_cert_exp_scheduler_->num_immediate_requests());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_ExpiredCertificate) {
|
||||
// First certificates are expired;
|
||||
FastForward(kNearbyShareCertificateValidityPeriod * 1.5);
|
||||
cert_store_->ReplacePrivateCertificates(private_certificates_);
|
||||
|
||||
cert_manager_->Start();
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/true);
|
||||
RunUpload(/*success=*/true);
|
||||
VerifyPrivateCertificates(/*expected_metadata=*/GetNearbyShareTestMetadata());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_InvalidDeviceName) {
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
|
||||
// Device name is missing in local device data manager.
|
||||
local_device_data_manager_->SetDeviceName(std::string());
|
||||
|
||||
cert_manager_->Start();
|
||||
|
||||
// Expect failure because a device name is required.
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/false);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_BluetoothAdapterNotPresent) {
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
|
||||
SetBluetoothAdapterIsPresent(false);
|
||||
|
||||
cert_manager_->Start();
|
||||
|
||||
// Expect failure because a Bluetooth MAC address is required.
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/false);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RefreshPrivateCertificates_MissingFullNameAndIconUrl) {
|
||||
cert_store_->ReplacePrivateCertificates({});
|
||||
|
||||
// Full name and icon URL are missing in the local device data manager.
|
||||
local_device_data_manager_->SetFullName(std::nullopt);
|
||||
local_device_data_manager_->SetIconUrl(std::nullopt);
|
||||
|
||||
cert_manager_->Start();
|
||||
HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
|
||||
/*expected_success=*/true);
|
||||
RunUpload(/*success=*/true);
|
||||
|
||||
// The full name and icon URL are not set.
|
||||
nearby::sharing::proto::EncryptedMetadata metadata =
|
||||
GetNearbyShareTestMetadata();
|
||||
metadata.clear_full_name();
|
||||
metadata.clear_icon_url();
|
||||
|
||||
VerifyPrivateCertificates(/*expected_metadata=*/metadata);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RemoveExpiredPublicCertificates_Success) {
|
||||
// The public certificate expiration scheduler notifies the certificate
|
||||
// manager that a public certificate has expired.
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().size(), 0u);
|
||||
EXPECT_EQ(public_cert_exp_scheduler_->handled_results().size(), 0u);
|
||||
cert_store_->SetRemoveExpiredPublicCertificatesResult(true);
|
||||
public_cert_exp_scheduler_->InvokeRequestCallback();
|
||||
Sync();
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().size(), 1u);
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().back().now,
|
||||
t0);
|
||||
EXPECT_EQ(public_cert_exp_scheduler_->handled_results().size(), 1u);
|
||||
EXPECT_TRUE(public_cert_exp_scheduler_->handled_results().back());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateManagerImplTest,
|
||||
RemoveExpiredPublicCertificates_Failure) {
|
||||
// The public certificate expiration scheduler notifies the certificate
|
||||
// manager that a public certificate has expired.
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().size(), 0u);
|
||||
cert_store_->SetRemoveExpiredPublicCertificatesResult(false);
|
||||
EXPECT_EQ(public_cert_exp_scheduler_->handled_results().size(), 0u);
|
||||
|
||||
public_cert_exp_scheduler_->InvokeRequestCallback();
|
||||
Sync();
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().size(), 1u);
|
||||
EXPECT_EQ(cert_store_->remove_expired_public_certificates_calls().back().now,
|
||||
t0);
|
||||
|
||||
EXPECT_EQ(public_cert_exp_scheduler_->handled_results().size(), 1u);
|
||||
EXPECT_FALSE(public_cert_exp_scheduler_->handled_results().back());
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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 "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/internal/base/encode.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
std::optional<absl::Time>
|
||||
NearbyShareCertificateStorage::NextPrivateCertificateExpirationTime() {
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
|
||||
GetPrivateCertificates();
|
||||
if (!certs || certs->empty()) return std::nullopt;
|
||||
|
||||
absl::Time min_time = absl::InfiniteFuture();
|
||||
for (const NearbySharePrivateCertificate& cert : *certs)
|
||||
min_time = std::min(min_time, cert.not_after());
|
||||
|
||||
return min_time;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorage::UpdatePrivateCertificate(
|
||||
const NearbySharePrivateCertificate& private_certificate) {
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
|
||||
GetPrivateCertificates();
|
||||
if (!certs) {
|
||||
NL_LOG(WARNING) << __func__ << ": No private certificates to update.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find_if(
|
||||
certs->begin(), certs->end(),
|
||||
[&private_certificate](const NearbySharePrivateCertificate& cert) {
|
||||
return cert.id() == private_certificate.id();
|
||||
});
|
||||
if (it == certs->end()) {
|
||||
NL_VLOG(1) << __func__ << ": No private certificate with id="
|
||||
<< nearby::utils::HexEncode(private_certificate.id());
|
||||
return;
|
||||
}
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Updating private certificate id="
|
||||
<< nearby::utils::HexEncode(private_certificate.id());
|
||||
*it = private_certificate;
|
||||
ReplacePrivateCertificates(*certs);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorage::RemoveExpiredPrivateCertificates(
|
||||
absl::Time now) {
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
|
||||
GetPrivateCertificates();
|
||||
if (!certs) return;
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> unexpired_certs;
|
||||
for (const NearbySharePrivateCertificate& cert : *certs) {
|
||||
if (!IsNearbyShareCertificateExpired(
|
||||
now, cert.not_after(),
|
||||
/*use_public_certificate_tolerance=*/false)) {
|
||||
unexpired_certs.push_back(cert);
|
||||
}
|
||||
}
|
||||
|
||||
size_t num_removed = certs->size() - unexpired_certs.size();
|
||||
if (num_removed == 0) return;
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Removing " << num_removed
|
||||
<< " expired private certificates.";
|
||||
ReplacePrivateCertificates(unexpired_certs);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorage::ClearPrivateCertificates() {
|
||||
NL_VLOG(1) << __func__ << ": Removing all private certificates.";
|
||||
ReplacePrivateCertificates({});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorage::ClearPrivateCertificatesOfVisibility(
|
||||
DeviceVisibility visibility) {
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
|
||||
GetPrivateCertificates();
|
||||
if (!certs) return;
|
||||
|
||||
bool were_certs_removed = false;
|
||||
std::vector<NearbySharePrivateCertificate> new_certs;
|
||||
for (const NearbySharePrivateCertificate& cert : *certs) {
|
||||
if (cert.visibility() == visibility) {
|
||||
were_certs_removed = true;
|
||||
} else {
|
||||
new_certs.push_back(cert);
|
||||
}
|
||||
}
|
||||
|
||||
if (were_certs_removed) {
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Removing all private certificates of visibility "
|
||||
<< static_cast<int>(visibility);
|
||||
ReplacePrivateCertificates(new_certs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Stores local-device private certificates and remote-device public
|
||||
// certificates. Provides methods to help manage certificate expiration. Due to
|
||||
// the potentially large number of public certificates, some methods are
|
||||
// asynchronous.
|
||||
class NearbyShareCertificateStorage {
|
||||
public:
|
||||
using ResultCallback = std::function<void(bool)>;
|
||||
using PublicCertificateCallback = std::function<void(
|
||||
bool,
|
||||
std::unique_ptr<std::vector<nearby::sharing::proto::PublicCertificate>>)>;
|
||||
|
||||
NearbyShareCertificateStorage() = default;
|
||||
virtual ~NearbyShareCertificateStorage() = default;
|
||||
|
||||
// Returns the secret ids of all stored public certificates
|
||||
virtual std::vector<std::string> GetPublicCertificateIds() const = 0;
|
||||
|
||||
// Returns all public certificates currently in storage. No RPC call is made.
|
||||
virtual void GetPublicCertificates(PublicCertificateCallback callback) = 0;
|
||||
|
||||
// Returns all private certificates currently in storage. Will return
|
||||
// absl::nullopt if deserialization from prefs fails -- not expected to happen
|
||||
// under normal circumstances.
|
||||
virtual std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
GetPrivateCertificates() const = 0;
|
||||
|
||||
// Returns the next time a certificate expires or absl::nullopt if no
|
||||
// certificates are present.
|
||||
std::optional<absl::Time> NextPrivateCertificateExpirationTime();
|
||||
virtual std::optional<absl::Time> NextPublicCertificateExpirationTime()
|
||||
const = 0;
|
||||
|
||||
// Deletes existing private certificates and replaces them with
|
||||
// |private_certificates|.
|
||||
virtual void ReplacePrivateCertificates(
|
||||
absl::Span<const NearbySharePrivateCertificate> private_certificates) = 0;
|
||||
|
||||
// Deletes existing public certificates and replaces them with
|
||||
// |public_certificates|.
|
||||
virtual void ReplacePublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) = 0;
|
||||
|
||||
// Overwrites an existing record with |private_certificate| if that record
|
||||
// has the same ID . If no such record exists in storage, no action is taken.
|
||||
// This method is necessary for updating the private certificate's list of
|
||||
// consumed salts.
|
||||
void UpdatePrivateCertificate(
|
||||
const NearbySharePrivateCertificate& private_certificate);
|
||||
|
||||
// Adds public certificates, or replaces existing certificates
|
||||
// by secret_id
|
||||
virtual void AddPublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) = 0;
|
||||
|
||||
// Removes all private certificates from storage with expiration date after
|
||||
// |now|.
|
||||
void RemoveExpiredPrivateCertificates(absl::Time now);
|
||||
|
||||
// Removes all public certificates from storage with expiration date after
|
||||
// |now|.
|
||||
virtual void RemoveExpiredPublicCertificates(absl::Time now,
|
||||
ResultCallback callback) = 0;
|
||||
|
||||
// Delete all private certificates from memory and persistent storage.
|
||||
void ClearPrivateCertificates();
|
||||
|
||||
// Delete private certificates with |visibility| from memory and persistent
|
||||
// storage.
|
||||
void ClearPrivateCertificatesOfVisibility(proto::DeviceVisibility visibility);
|
||||
|
||||
// Delete all public certificates from memory and persistent storage.
|
||||
virtual void ClearPublicCertificates(ResultCallback callback) = 0;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_H_
|
||||
@@ -0,0 +1,602 @@
|
||||
// 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 "sharing/certificates/nearby_share_certificate_storage_impl.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/btree_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/private_certificate_data.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
using ::nearby::sharing::api::PreferenceManager;
|
||||
using ::nearby::sharing::api::PrivateCertificateData;
|
||||
using ::nearby::sharing::api::PublicCertificateDatabase;
|
||||
|
||||
// Compare to leveldb_proto::Enums::InitStatus. Using a separate enum so that
|
||||
// the values don't change.
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum InitStatusMetric {
|
||||
kOK = 0,
|
||||
kNotInitialized = 1,
|
||||
kError = 2,
|
||||
kCorrupt = 3,
|
||||
kInvalidOperation = 4,
|
||||
kMaxValue = kInvalidOperation
|
||||
};
|
||||
|
||||
std::string EncodeString(absl::string_view unencoded_string) {
|
||||
std::string result;
|
||||
absl::WebSafeBase64Escape(unencoded_string, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> DecodeString(const std::string* encoded_string) {
|
||||
std::string result;
|
||||
if (!encoded_string) return std::nullopt;
|
||||
|
||||
if (absl::WebSafeBase64Unescape(*encoded_string, &result)) {
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SortBySecond(const std::pair<std::string, absl::Time>& pair1,
|
||||
const std::pair<std::string, absl::Time>& pair2) {
|
||||
return pair1.second < pair2.second;
|
||||
}
|
||||
|
||||
NearbyShareCertificateStorageImpl::ExpirationList MergeExpirations(
|
||||
const NearbyShareCertificateStorageImpl::ExpirationList& old_exp,
|
||||
const NearbyShareCertificateStorageImpl::ExpirationList& new_exp) {
|
||||
// Remove duplicates with a preference for new entries.
|
||||
absl::btree_map<std::string, absl::Time> merged_map(new_exp.begin(),
|
||||
new_exp.end());
|
||||
merged_map.insert(old_exp.begin(), old_exp.end());
|
||||
// Convert map to vector and sort by expiration time.
|
||||
NearbyShareCertificateStorageImpl::ExpirationList merged(merged_map.begin(),
|
||||
merged_map.end());
|
||||
std::sort(merged.begin(), merged.end(), SortBySecond);
|
||||
return merged;
|
||||
}
|
||||
|
||||
absl::Time TimestampToTime(nearby::sharing::proto::Timestamp timestamp) {
|
||||
return absl::UnixEpoch() + absl::Seconds(timestamp.seconds()) +
|
||||
absl::Nanoseconds(timestamp.nanos());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
NearbyShareCertificateStorageImpl::Factory*
|
||||
NearbyShareCertificateStorageImpl::Factory::test_factory_ = nullptr;
|
||||
|
||||
// static
|
||||
std::shared_ptr<NearbyShareCertificateStorage>
|
||||
NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
PreferenceManager& preference_manager,
|
||||
std::unique_ptr<PublicCertificateDatabase> public_certificate_database) {
|
||||
if (test_factory_) {
|
||||
return test_factory_->CreateInstance(
|
||||
preference_manager, std::move(public_certificate_database));
|
||||
}
|
||||
|
||||
auto storage = std::shared_ptr<NearbyShareCertificateStorageImpl>(
|
||||
new NearbyShareCertificateStorageImpl(
|
||||
preference_manager, std::move(public_certificate_database)));
|
||||
// Initialize cannot be called from c'tor since it tries to create a weak_ptr.
|
||||
storage->Initialize();
|
||||
return storage;
|
||||
}
|
||||
|
||||
// static
|
||||
void NearbyShareCertificateStorageImpl::Factory::SetFactoryForTesting(
|
||||
Factory* test_factory) {
|
||||
test_factory_ = test_factory;
|
||||
}
|
||||
|
||||
NearbyShareCertificateStorageImpl::Factory::~Factory() = default;
|
||||
|
||||
NearbyShareCertificateStorageImpl::NearbyShareCertificateStorageImpl(
|
||||
PreferenceManager& preference_manager,
|
||||
std::unique_ptr<PublicCertificateDatabase> public_certificate_database)
|
||||
: preference_manager_(preference_manager),
|
||||
public_certificate_database_(std::move(public_certificate_database)) {
|
||||
FetchPublicCertificateExpirations();
|
||||
}
|
||||
|
||||
NearbyShareCertificateStorageImpl::~NearbyShareCertificateStorageImpl() =
|
||||
default;
|
||||
|
||||
void NearbyShareCertificateStorageImpl::Initialize() {
|
||||
switch (init_status_) {
|
||||
case InitStatus::kUninitialized:
|
||||
case InitStatus::kFailed:
|
||||
num_initialize_attempts_++;
|
||||
if (num_initialize_attempts_ >
|
||||
kNearbyShareCertificateStorageMaxNumInitializeAttempts) {
|
||||
FinishInitialization(false);
|
||||
break;
|
||||
}
|
||||
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Attempting to initialize public certificate "
|
||||
"database. Number of attempts: "
|
||||
<< num_initialize_attempts_;
|
||||
public_certificate_database_->Initialize(
|
||||
[weak_this =
|
||||
weak_from_this()](PublicCertificateDatabase::InitStatus status) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->OnDatabaseInitialized(absl::Now(), status);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case InitStatus::kInitialized:
|
||||
NL_LOG(INFO) << __func__ << " already initialized.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::DestroyAndReinitialize() {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Public certificate database corrupt. Erasing and "
|
||||
"initializing new database.";
|
||||
init_status_ = InitStatus::kUninitialized;
|
||||
public_certificate_database_->Destroy(
|
||||
[weak_this = weak_from_this()](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->OnDatabaseDestroyedReinitialize(
|
||||
[&](bool result) {
|
||||
NL_LOG(INFO)
|
||||
<< "Destroy and reinitialize database. result: " << result;
|
||||
},
|
||||
success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::OnDatabaseInitialized(
|
||||
absl::Time initialize_start_time,
|
||||
PublicCertificateDatabase::InitStatus status) {
|
||||
NL_LOG(INFO) << "Database is initialized for certificates. status="
|
||||
<< static_cast<int>(status);
|
||||
switch (status) {
|
||||
case PublicCertificateDatabase::InitStatus::kOk:
|
||||
FinishInitialization(true);
|
||||
break;
|
||||
case PublicCertificateDatabase::InitStatus::kError:
|
||||
Initialize();
|
||||
break;
|
||||
case PublicCertificateDatabase::InitStatus::kCorrupt:
|
||||
DestroyAndReinitialize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::FinishInitialization(bool success) {
|
||||
init_status_ = success ? InitStatus::kInitialized : InitStatus::kFailed;
|
||||
if (success) {
|
||||
// Need to reset the initialize attempts.
|
||||
num_initialize_attempts_ = 0;
|
||||
|
||||
NL_VLOG(1) << __func__
|
||||
<< "Public certificate database initialization succeeded.";
|
||||
} else {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< "Public certificate database initialization failed.";
|
||||
}
|
||||
|
||||
// We run deferred callbacks even if initialization failed not to cause
|
||||
// possible client-side blocks of next calls to the database.
|
||||
while (!deferred_callbacks_.empty()) {
|
||||
auto deferred_task = std::move(deferred_callbacks_.front());
|
||||
deferred_callbacks_.pop();
|
||||
deferred_task();
|
||||
}
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::OnDatabaseDestroyedReinitialize(
|
||||
ResultCallback callback, bool success) {
|
||||
if (!success) {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Failed to destroy public certificate database.";
|
||||
FinishInitialization(false);
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
public_certificate_expirations_.clear();
|
||||
SavePublicCertificateExpirations();
|
||||
|
||||
Initialize();
|
||||
callback(true);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::OnDatabaseDestroyed(
|
||||
ResultCallback callback, bool success) {
|
||||
if (!success) {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Failed to destroy public certificate database.";
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
public_certificate_expirations_.clear();
|
||||
SavePublicCertificateExpirations();
|
||||
|
||||
std::move(callback)(true);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::
|
||||
ReplacePublicCertificatesDestroyCallback(
|
||||
const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
new_certificates,
|
||||
const ExpirationList& new_expirations, ResultCallback callback,
|
||||
bool proceed) {
|
||||
if (!proceed) {
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Inserting " << new_certificates.size()
|
||||
<< " public certificates.";
|
||||
|
||||
public_certificate_database_->AddCertificates(
|
||||
absl::MakeConstSpan(new_certificates),
|
||||
[weak_this = weak_from_this(), new_expirations,
|
||||
callback = std::move(callback)](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->ReplacePublicCertificatesUpdateEntriesCallback(
|
||||
std::make_unique<ExpirationList>(std::move(new_expirations)),
|
||||
std::move(callback), success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::
|
||||
ReplacePublicCertificatesUpdateEntriesCallback(
|
||||
std::unique_ptr<ExpirationList> expirations, ResultCallback callback,
|
||||
bool proceed) {
|
||||
if (!proceed) {
|
||||
NL_LOG(ERROR) << __func__ << ": Failed to replace public certificates.";
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
NL_VLOG(1) << __func__ << ": Successfully replaced public certificates.";
|
||||
|
||||
NL_CHECK(expirations);
|
||||
public_certificate_expirations_.swap(*expirations);
|
||||
SavePublicCertificateExpirations();
|
||||
std::move(callback)(true);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::AddPublicCertificatesCallback(
|
||||
std::unique_ptr<ExpirationList> new_expirations, ResultCallback callback,
|
||||
bool proceed) {
|
||||
if (!proceed) {
|
||||
NL_LOG(ERROR) << __func__ << ": Failed to add public certificates.";
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
NL_VLOG(1) << __func__ << ": Successfully added public certificates.";
|
||||
|
||||
public_certificate_expirations_ =
|
||||
MergeExpirations(public_certificate_expirations_, *new_expirations);
|
||||
SavePublicCertificateExpirations();
|
||||
std::move(callback)(true);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::RemoveExpiredPublicCertificatesCallback(
|
||||
const absl::flat_hash_set<std::string>& ids_to_remove,
|
||||
ResultCallback callback, bool proceed) {
|
||||
if (!proceed) {
|
||||
NL_LOG(ERROR) << __func__
|
||||
<< ": Failed to remove expired public certificates.";
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Expired public certificates successfully removed.";
|
||||
|
||||
auto should_remove =
|
||||
[&](const std::pair<std::string, absl::Time>& pair) -> bool {
|
||||
return ids_to_remove.contains(pair.first);
|
||||
};
|
||||
|
||||
public_certificate_expirations_.erase(
|
||||
std::remove_if(public_certificate_expirations_.begin(),
|
||||
public_certificate_expirations_.end(), should_remove),
|
||||
public_certificate_expirations_.end());
|
||||
SavePublicCertificateExpirations();
|
||||
std::move(callback)(true);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
NearbyShareCertificateStorageImpl::GetPublicCertificateIds() const {
|
||||
std::vector<std::string> ids;
|
||||
for (const auto& pair : public_certificate_expirations_) {
|
||||
ids.emplace_back(pair.first);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::GetPublicCertificates(
|
||||
PublicCertificateCallback callback) {
|
||||
if (init_status_ == InitStatus::kFailed) {
|
||||
std::move(callback)(false, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (init_status_ == InitStatus::kUninitialized) {
|
||||
deferred_callbacks_.push([this, callback = std::move(callback)] {
|
||||
GetPublicCertificates(std::move(callback));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Calling LoadEntries on database.";
|
||||
public_certificate_database_->LoadEntries(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
NearbyShareCertificateStorageImpl::GetPrivateCertificates() const {
|
||||
std::vector<PrivateCertificateData> list =
|
||||
preference_manager_.GetPrivateCertificateArray(
|
||||
prefs::kNearbySharingPrivateCertificateListName);
|
||||
std::vector<NearbySharePrivateCertificate> certs;
|
||||
certs.reserve(list.size());
|
||||
for (const PrivateCertificateData& cert_data : list) {
|
||||
std::optional<NearbySharePrivateCertificate> cert(
|
||||
NearbySharePrivateCertificate::FromCertificateData(cert_data));
|
||||
if (!cert) return std::nullopt;
|
||||
|
||||
certs.push_back(*std::move(cert));
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
|
||||
std::optional<absl::Time>
|
||||
NearbyShareCertificateStorageImpl::NextPublicCertificateExpirationTime() const {
|
||||
if (public_certificate_expirations_.empty()) return std::nullopt;
|
||||
|
||||
// |public_certificate_expirations_| is sorted by expiration date.
|
||||
return public_certificate_expirations_.front().second;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::ReplacePrivateCertificates(
|
||||
absl::Span<const NearbySharePrivateCertificate> private_certificates) {
|
||||
std::vector<PrivateCertificateData> list;
|
||||
list.reserve(private_certificates.size());
|
||||
for (const NearbySharePrivateCertificate& cert : private_certificates) {
|
||||
list.push_back(cert.ToCertificateData());
|
||||
}
|
||||
preference_manager_.SetPrivateCertificateArray(
|
||||
prefs::kNearbySharingPrivateCertificateListName, list);
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::ReplacePublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) {
|
||||
if (init_status_ == InitStatus::kFailed) {
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (init_status_ == InitStatus::kUninitialized) {
|
||||
deferred_callbacks_.push(
|
||||
[this, public_certificates, callback = std::move(callback)]() {
|
||||
ReplacePublicCertificates(public_certificates, std::move(callback));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_entries = std::vector<nearby::sharing::proto::PublicCertificate>();
|
||||
ExpirationList new_expirations;
|
||||
for (const nearby::sharing::proto::PublicCertificate& cert :
|
||||
public_certificates) {
|
||||
new_entries.emplace_back(cert);
|
||||
new_expirations.emplace_back(cert.secret_id(),
|
||||
TimestampToTime(cert.end_time()));
|
||||
}
|
||||
std::sort(new_expirations.begin(), new_expirations.end(), SortBySecond);
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Clearing public certificate database.";
|
||||
|
||||
public_certificate_database_->Destroy(
|
||||
[weak_this = weak_from_this(), new_entries, new_expirations,
|
||||
callback = std::move(callback)](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->ReplacePublicCertificatesDestroyCallback(
|
||||
new_entries, new_expirations, std::move(callback), success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::AddPublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) {
|
||||
if (init_status_ == InitStatus::kFailed) {
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (init_status_ == InitStatus::kUninitialized) {
|
||||
deferred_callbacks_.push(
|
||||
[this, public_certificates, callback = std::move(callback)]() {
|
||||
AddPublicCertificates(public_certificates, std::move(callback));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ExpirationList new_expirations;
|
||||
for (const nearby::sharing::proto::PublicCertificate& cert :
|
||||
public_certificates) {
|
||||
new_expirations.emplace_back(cert.secret_id(),
|
||||
TimestampToTime(cert.end_time()));
|
||||
}
|
||||
std::sort(new_expirations.begin(), new_expirations.end(), SortBySecond);
|
||||
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Calling UpdateEntries on public certificate database with "
|
||||
<< public_certificates.size() << " certificates.";
|
||||
public_certificate_database_->AddCertificates(
|
||||
public_certificates, [weak_this = weak_from_this(), new_expirations,
|
||||
callback = std::move(callback)](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->AddPublicCertificatesCallback(
|
||||
std::make_unique<ExpirationList>(new_expirations),
|
||||
std::move(callback), success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::RemoveExpiredPublicCertificates(
|
||||
absl::Time now, ResultCallback callback) {
|
||||
if (init_status_ == InitStatus::kFailed) {
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (init_status_ == InitStatus::kUninitialized) {
|
||||
deferred_callbacks_.push([this, now, callback = std::move(callback)]() {
|
||||
RemoveExpiredPublicCertificates(now, std::move(callback));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto ids_to_remove = std::vector<std::string>();
|
||||
for (const auto& pair : public_certificate_expirations_) {
|
||||
// Because the list is sorted by expiration time, break as soon as we
|
||||
// encounter an unexpired certificate. Apply a tolerance when evaluating
|
||||
// whether the certificate is expired to account for clock skew between
|
||||
// devices. This conforms this the GmsCore implementation.
|
||||
if (!IsNearbyShareCertificateExpired(
|
||||
now,
|
||||
/*not_after=*/pair.second,
|
||||
/*use_public_certificate_tolerance=*/true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ids_to_remove.emplace_back(pair.first);
|
||||
}
|
||||
if (ids_to_remove.empty()) {
|
||||
std::move(callback)(true);
|
||||
return;
|
||||
}
|
||||
|
||||
NL_VLOG(1)
|
||||
<< __func__
|
||||
<< ": Calling UpdateEntries on public certificate database to remove "
|
||||
<< ids_to_remove.size() << " expired certificates.";
|
||||
absl::flat_hash_set<std::string> remove_set(ids_to_remove.begin(),
|
||||
ids_to_remove.end());
|
||||
public_certificate_database_->RemoveCertificatesById(
|
||||
std::move(ids_to_remove),
|
||||
[weak_this = weak_from_this(), remove_set = std::move(remove_set),
|
||||
callback = std::move(callback)](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->RemoveExpiredPublicCertificatesCallback(
|
||||
remove_set, std::move(callback), success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::ClearPublicCertificates(
|
||||
ResultCallback callback) {
|
||||
if (init_status_ == InitStatus::kFailed) {
|
||||
std::move(callback)(false);
|
||||
return;
|
||||
}
|
||||
|
||||
NL_VLOG(1) << __func__ << ": Calling Destroy on public certificate database.";
|
||||
init_status_ = InitStatus::kUninitialized;
|
||||
public_certificate_database_->Destroy(
|
||||
[weak_this = weak_from_this(),
|
||||
callback = std::move(callback)](bool success) {
|
||||
if (auto storage = weak_this.lock()) {
|
||||
storage->OnDatabaseDestroyedReinitialize(callback, success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool NearbyShareCertificateStorageImpl::FetchPublicCertificateExpirations() {
|
||||
std::vector<std::pair<std::string, int64_t>> expirations =
|
||||
preference_manager_.GetCertificateExpirationArray(
|
||||
prefs::kNearbySharingPublicCertificateExpirationDictName);
|
||||
public_certificate_expirations_.clear();
|
||||
if (expirations.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public_certificate_expirations_.reserve(expirations.size());
|
||||
|
||||
for (auto it = expirations.begin(); it != expirations.end(); ++it) {
|
||||
std::optional<std::string> id = DecodeString(&it->first);
|
||||
std::optional<absl::Time> expiration = absl::FromUnixNanos(it->second);
|
||||
if (!id || !expiration) return false;
|
||||
|
||||
public_certificate_expirations_.emplace_back(*id, *expiration);
|
||||
}
|
||||
std::sort(public_certificate_expirations_.begin(),
|
||||
public_certificate_expirations_.end(), SortBySecond);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NearbyShareCertificateStorageImpl::SavePublicCertificateExpirations() {
|
||||
std::vector<std::pair<std::string, int64_t>> expirations;
|
||||
expirations.reserve(public_certificate_expirations_.size());
|
||||
for (const std::pair<std::string, absl::Time>& pair :
|
||||
public_certificate_expirations_) {
|
||||
expirations.emplace_back(EncodeString(pair.first),
|
||||
absl::ToUnixNanos(pair.second));
|
||||
}
|
||||
|
||||
preference_manager_.SetCertificateExpirationArray(
|
||||
prefs::kNearbySharingPublicCertificateExpirationDictName, expirations);
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_IMPL_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_IMPL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/public_certificate_database.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Implements NearbyShareCertificateStorage using Prefs to store private
|
||||
// certificates and LevelDB Proto to store public certificates. Must be
|
||||
// initialized by calling Initialize before retrieving or storing certificates.
|
||||
// Callbacks are guaranteed to not be invoked after
|
||||
// NearbyShareCertificateStorageImpl is destroyed.
|
||||
class NearbyShareCertificateStorageImpl : public NearbyShareCertificateStorage,
|
||||
public std::enable_shared_from_this<NearbyShareCertificateStorageImpl> {
|
||||
public:
|
||||
class Factory {
|
||||
public:
|
||||
static std::shared_ptr<NearbyShareCertificateStorage> Create(
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database);
|
||||
static void SetFactoryForTesting(Factory* test_factory);
|
||||
|
||||
protected:
|
||||
virtual ~Factory();
|
||||
virtual std::shared_ptr<NearbyShareCertificateStorage> CreateInstance(
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database) = 0;
|
||||
|
||||
private:
|
||||
static Factory* test_factory_;
|
||||
};
|
||||
|
||||
using ExpirationList = std::vector<std::pair<std::string, absl::Time>>;
|
||||
|
||||
~NearbyShareCertificateStorageImpl() override;
|
||||
NearbyShareCertificateStorageImpl(NearbyShareCertificateStorageImpl&) =
|
||||
delete;
|
||||
void operator=(NearbyShareCertificateStorageImpl&) = delete;
|
||||
|
||||
// NearbyShareCertificateStorage
|
||||
std::vector<std::string> GetPublicCertificateIds() const override;
|
||||
void GetPublicCertificates(PublicCertificateCallback callback) override;
|
||||
std::optional<std::vector<NearbySharePrivateCertificate>>
|
||||
GetPrivateCertificates() const override;
|
||||
std::optional<absl::Time> NextPublicCertificateExpirationTime()
|
||||
const override;
|
||||
void ReplacePrivateCertificates(
|
||||
absl::Span<const NearbySharePrivateCertificate> private_certificates)
|
||||
override;
|
||||
void ReplacePublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) override;
|
||||
void AddPublicCertificates(
|
||||
absl::Span<const nearby::sharing::proto::PublicCertificate>
|
||||
public_certificates,
|
||||
ResultCallback callback) override;
|
||||
void RemoveExpiredPublicCertificates(absl::Time now,
|
||||
ResultCallback callback) override;
|
||||
void ClearPublicCertificates(ResultCallback callback) override;
|
||||
|
||||
private:
|
||||
enum class InitStatus { kUninitialized, kInitialized, kFailed };
|
||||
|
||||
NearbyShareCertificateStorageImpl(
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database);
|
||||
void Initialize();
|
||||
void OnDatabaseInitialized(
|
||||
absl::Time initialize_start_time,
|
||||
nearby::sharing::api::PublicCertificateDatabase::InitStatus
|
||||
init_dataset_status);
|
||||
void FinishInitialization(bool success);
|
||||
|
||||
void OnDatabaseDestroyedReinitialize(ResultCallback callback, bool success);
|
||||
void OnDatabaseDestroyed(ResultCallback callback, bool success);
|
||||
|
||||
void DestroyAndReinitialize();
|
||||
|
||||
void ReplacePublicCertificatesDestroyCallback(
|
||||
const std::vector<nearby::sharing::proto::PublicCertificate>&
|
||||
new_certificates,
|
||||
const ExpirationList& new_expirations, ResultCallback callback,
|
||||
bool proceed);
|
||||
void ReplacePublicCertificatesUpdateEntriesCallback(
|
||||
std::unique_ptr<ExpirationList> expirations, ResultCallback callback,
|
||||
bool proceed);
|
||||
void AddPublicCertificatesCallback(
|
||||
std::unique_ptr<ExpirationList> new_expirations, ResultCallback callback,
|
||||
bool proceed);
|
||||
void RemoveExpiredPublicCertificatesCallback(
|
||||
const absl::flat_hash_set<std::string>& ids_to_remove,
|
||||
ResultCallback callback, bool proceed);
|
||||
|
||||
bool FetchPublicCertificateExpirations();
|
||||
void SavePublicCertificateExpirations();
|
||||
|
||||
nearby::sharing::api::PreferenceManager& preference_manager_;
|
||||
InitStatus init_status_ = InitStatus::kUninitialized;
|
||||
size_t num_initialize_attempts_ = 0;
|
||||
std::unique_ptr<nearby::sharing::api::PublicCertificateDatabase>
|
||||
public_certificate_database_;
|
||||
ExpirationList public_certificate_expirations_;
|
||||
|
||||
std::queue<std::function<void()>> deferred_callbacks_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_CERTIFICATE_STORAGE_IMPL_H_
|
||||
@@ -0,0 +1,810 @@
|
||||
// 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 "sharing/certificates/nearby_share_certificate_storage_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_certificate_storage.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/internal/api/mock_public_certificate_db.h"
|
||||
#include "sharing/internal/test/fake_preference_manager.h"
|
||||
#include "sharing/internal/test/fake_public_certificate_db.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
using ::nearby::sharing::api::MockPublicCertificateDb;
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
using ::nearby::sharing::proto::PublicCertificate;
|
||||
using ::testing::_;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
// NOTE: Make sure secret ID alphabetical ordering does not match the 1,2,3,...
|
||||
// ordering to test sorting expiration times.
|
||||
constexpr char kSecretId1[] = "b_secretid1";
|
||||
constexpr char kSecretKey1[] = "secretkey1";
|
||||
constexpr char kPublicKey1[] = "publickey1";
|
||||
constexpr int64_t kStartSeconds1 = 0;
|
||||
constexpr int32_t kStartNanos1 = 10;
|
||||
constexpr int64_t kEndSeconds1 = 100;
|
||||
constexpr int32_t kEndNanos1 = 30;
|
||||
constexpr bool kForSelectedContacts1 = false;
|
||||
constexpr char kMetadataEncryptionKey1[] = "metadataencryptionkey1";
|
||||
constexpr char kEncryptedMetadataBytes1[] = "encryptedmetadatabytes1";
|
||||
constexpr char kMetadataEncryptionKeyTag1[] = "metadataencryptionkeytag1";
|
||||
constexpr char kSecretId2[] = "c_secretid2";
|
||||
constexpr char kSecretKey2[] = "secretkey2";
|
||||
constexpr char kPublicKey2[] = "publickey2";
|
||||
constexpr int64_t kStartSeconds2 = 0;
|
||||
constexpr int32_t kStartNanos2 = 20;
|
||||
constexpr int64_t kEndSeconds2 = 200;
|
||||
constexpr int32_t kEndNanos2 = 30;
|
||||
constexpr bool kForSelectedContacts2 = false;
|
||||
constexpr char kMetadataEncryptionKey2[] = "metadataencryptionkey2";
|
||||
constexpr char kEncryptedMetadataBytes2[] = "encryptedmetadatabytes2";
|
||||
constexpr char kMetadataEncryptionKeyTag2[] = "metadataencryptionkeytag2";
|
||||
constexpr char kSecretId3[] = "a_secretid3";
|
||||
constexpr char kSecretKey3[] = "secretkey3";
|
||||
constexpr char kPublicKey3[] = "publickey3";
|
||||
constexpr int64_t kStartSeconds3 = 0;
|
||||
constexpr int32_t kStartNanos3 = 30;
|
||||
constexpr int64_t kEndSeconds3 = 300;
|
||||
constexpr int32_t kEndNanos3 = 30;
|
||||
constexpr bool kForSelectedContacts3 = false;
|
||||
constexpr char kMetadataEncryptionKey3[] = "metadataencryptionkey3";
|
||||
constexpr char kEncryptedMetadataBytes3[] = "encryptedmetadatabytes3";
|
||||
constexpr char kMetadataEncryptionKeyTag3[] = "metadataencryptionkeytag3";
|
||||
constexpr char kSecretId4[] = "d_secretid4";
|
||||
constexpr char kSecretKey4[] = "secretkey4";
|
||||
constexpr char kPublicKey4[] = "publickey4";
|
||||
constexpr int64_t kStartSeconds4 = 0;
|
||||
constexpr int32_t kStartNanos4 = 10;
|
||||
constexpr int64_t kEndSeconds4 = 100;
|
||||
constexpr int32_t kEndNanos4 = 30;
|
||||
constexpr bool kForSelectedContacts4 = false;
|
||||
constexpr char kMetadataEncryptionKey4[] = "metadataencryptionkey4";
|
||||
constexpr char kEncryptedMetadataBytes4[] = "encryptedmetadatabytes4";
|
||||
constexpr char kMetadataEncryptionKeyTag4[] = "metadataencryptionkeytag4";
|
||||
|
||||
std::string EncodeString(absl::string_view unencoded_string) {
|
||||
std::string result;
|
||||
absl::WebSafeBase64Escape(unencoded_string, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
PublicCertificate CreatePublicCertificate(
|
||||
absl::string_view secret_id, absl::string_view secret_key,
|
||||
absl::string_view public_key, int64_t start_seconds, int32_t start_nanos,
|
||||
int64_t end_seconds, int32_t end_nanos, bool for_selected_contacts,
|
||||
absl::string_view metadata_encryption_key,
|
||||
absl::string_view encrypted_metadata_bytes,
|
||||
absl::string_view metadata_encryption_key_tag) {
|
||||
PublicCertificate cert;
|
||||
cert.set_secret_id(secret_id);
|
||||
cert.set_secret_key(secret_key);
|
||||
cert.set_public_key(public_key);
|
||||
cert.mutable_start_time()->set_seconds(start_seconds);
|
||||
cert.mutable_start_time()->set_nanos(start_nanos);
|
||||
cert.mutable_end_time()->set_seconds(end_seconds);
|
||||
cert.mutable_end_time()->set_nanos(end_nanos);
|
||||
cert.set_for_selected_contacts(for_selected_contacts);
|
||||
cert.set_metadata_encryption_key(metadata_encryption_key);
|
||||
cert.set_encrypted_metadata_bytes(encrypted_metadata_bytes);
|
||||
cert.set_metadata_encryption_key_tag(metadata_encryption_key_tag);
|
||||
return cert;
|
||||
}
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> CreatePrivateCertificates(
|
||||
size_t n, DeviceVisibility visibility) {
|
||||
std::vector<NearbySharePrivateCertificate> certs;
|
||||
certs.reserve(n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
certs.emplace_back(visibility, absl::Now(), GetNearbyShareTestMetadata());
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
|
||||
absl::Time TimestampToTime(nearby::sharing::proto::Timestamp timestamp) {
|
||||
return absl::UnixEpoch() + absl::Seconds(timestamp.seconds()) +
|
||||
absl::Nanoseconds(timestamp.nanos());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class NearbyShareCertificateStorageImplTest : public ::testing::Test {
|
||||
public:
|
||||
NearbyShareCertificateStorageImplTest() = default;
|
||||
~NearbyShareCertificateStorageImplTest() override = default;
|
||||
NearbyShareCertificateStorageImplTest(
|
||||
NearbyShareCertificateStorageImplTest&) = delete;
|
||||
NearbyShareCertificateStorageImplTest& operator=(
|
||||
NearbyShareCertificateStorageImplTest&) = delete;
|
||||
|
||||
void SetUp() override {
|
||||
preference_manager_.Remove(
|
||||
prefs::kNearbySharingPublicCertificateExpirationDictName);
|
||||
preference_manager_.Remove(
|
||||
prefs::kNearbySharingPrivateCertificateListName);
|
||||
}
|
||||
|
||||
void PrepopulatePublicCertificates(nearby::FakePublicCertificateDb* db) {
|
||||
std::vector<PublicCertificate> pub_certs;
|
||||
pub_certs.emplace_back(CreatePublicCertificate(
|
||||
kSecretId1, kSecretKey1, kPublicKey1, kStartSeconds1, kStartNanos1,
|
||||
kEndSeconds1, kEndNanos1, kForSelectedContacts1,
|
||||
kMetadataEncryptionKey1, kEncryptedMetadataBytes1,
|
||||
kMetadataEncryptionKeyTag1));
|
||||
pub_certs.emplace_back(CreatePublicCertificate(
|
||||
kSecretId2, kSecretKey2, kPublicKey2, kStartSeconds2, kStartNanos2,
|
||||
kEndSeconds2, kEndNanos2, kForSelectedContacts2,
|
||||
kMetadataEncryptionKey2, kEncryptedMetadataBytes2,
|
||||
kMetadataEncryptionKeyTag2));
|
||||
pub_certs.emplace_back(CreatePublicCertificate(
|
||||
kSecretId3, kSecretKey3, kPublicKey3, kStartSeconds3, kStartNanos3,
|
||||
kEndSeconds3, kEndNanos3, kForSelectedContacts3,
|
||||
kMetadataEncryptionKey3, kEncryptedMetadataBytes3,
|
||||
kMetadataEncryptionKeyTag3));
|
||||
db->AddCertificates(pub_certs, [](bool) {});
|
||||
std::vector<std::pair<std::string, int64_t>> expirations;
|
||||
for (const auto& cert : pub_certs) {
|
||||
expirations.emplace_back(
|
||||
EncodeString(cert.secret_id()),
|
||||
absl::ToUnixNanos(TimestampToTime(cert.end_time())));
|
||||
}
|
||||
preference_manager_.SetCertificateExpirationArray(
|
||||
prefs::kNearbySharingPublicCertificateExpirationDictName,
|
||||
expirations);
|
||||
}
|
||||
|
||||
void CaptureBoolCallback(bool* dest, bool src) { *dest = src; }
|
||||
|
||||
void PublicCertificateCallback(
|
||||
std::vector<PublicCertificate>* public_certificates,
|
||||
std::function<void()> complete, bool success,
|
||||
std::unique_ptr<std::vector<PublicCertificate>> result) {
|
||||
if (success && result) {
|
||||
public_certificates->swap(*result);
|
||||
}
|
||||
std::move(complete)();
|
||||
}
|
||||
|
||||
protected:
|
||||
nearby::FakePreferenceManager preference_manager_;
|
||||
};
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, InitializeRetrySucceed) {
|
||||
// This test only makes sense if initialization will be attempted at least
|
||||
// twice.
|
||||
if (kNearbyShareCertificateStorageMaxNumInitializeAttempts < 2) return;
|
||||
|
||||
auto db = std::make_unique<StrictMock<MockPublicCertificateDb>>();
|
||||
MockPublicCertificateDb* mock_db = db.get();
|
||||
EXPECT_CALL(*mock_db, Initialize(_))
|
||||
.WillOnce(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kError);
|
||||
}))
|
||||
.WillRepeatedly(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kOk);
|
||||
}));
|
||||
EXPECT_CALL(*mock_db, Destroy(_))
|
||||
.WillOnce(Invoke([](absl::AnyInvocable<void(bool)&&> callback) {
|
||||
std::move(callback)(true);
|
||||
}));
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool clear_succeeded = false;
|
||||
// Use this to trigger call to Destroy.
|
||||
cert_store->ClearPublicCertificates([this, &clear_succeeded](bool success) {
|
||||
CaptureBoolCallback(&clear_succeeded, success);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(clear_succeeded);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, InitializeRetryFailed) {
|
||||
auto db = std::make_unique<StrictMock<MockPublicCertificateDb>>();
|
||||
MockPublicCertificateDb* mock_db = db.get();
|
||||
EXPECT_CALL(*mock_db, Initialize(_))
|
||||
.WillRepeatedly(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kError);
|
||||
}));
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool clear_succeeded = true;
|
||||
// Call to Destroy not made because of initialization failure.
|
||||
cert_store->ClearPublicCertificates([this, &clear_succeeded](bool success) {
|
||||
CaptureBoolCallback(&clear_succeeded, success);
|
||||
});
|
||||
|
||||
EXPECT_FALSE(clear_succeeded);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest,
|
||||
InitializeCorruptDestroySucceeds) {
|
||||
auto db = std::make_unique<StrictMock<MockPublicCertificateDb>>();
|
||||
MockPublicCertificateDb* mock_db = db.get();
|
||||
EXPECT_CALL(*mock_db, Initialize(_))
|
||||
.WillOnce(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kCorrupt);
|
||||
}))
|
||||
.WillRepeatedly(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kOk);
|
||||
}));
|
||||
// Destroy called once from corrupted initialization and once from cal to
|
||||
// ClearPublicCertificates.
|
||||
EXPECT_CALL(*mock_db, Destroy(_))
|
||||
.Times(2)
|
||||
.WillRepeatedly(Invoke([](absl::AnyInvocable<void(bool)&&> callback) {
|
||||
std::move(callback)(true);
|
||||
}));
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool clear_succeeded = false;
|
||||
cert_store->ClearPublicCertificates([this, &clear_succeeded](bool success) {
|
||||
CaptureBoolCallback(&clear_succeeded, success);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(clear_succeeded);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, InitializeCorruptDestroyFails) {
|
||||
auto db = std::make_unique<StrictMock<MockPublicCertificateDb>>();
|
||||
MockPublicCertificateDb* mock_db = db.get();
|
||||
EXPECT_CALL(*mock_db, Initialize(_))
|
||||
.WillOnce(Invoke(
|
||||
[](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) {
|
||||
std::move(callback)(MockPublicCertificateDb::InitStatus::kCorrupt);
|
||||
}));
|
||||
EXPECT_CALL(*mock_db, Destroy(_))
|
||||
.WillOnce(Invoke([](absl::AnyInvocable<void(bool)&&> callback) {
|
||||
std::move(callback)(false);
|
||||
}));
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool clear_succeeded = true;
|
||||
cert_store->ClearPublicCertificates([this, &clear_succeeded](bool success) {
|
||||
CaptureBoolCallback(&clear_succeeded, success);
|
||||
});
|
||||
|
||||
EXPECT_FALSE(clear_succeeded);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, DeferredCallbackQueue) {
|
||||
absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus) &&>
|
||||
init_status_callback;
|
||||
absl::AnyInvocable<void(bool) &&> destroy_callback;
|
||||
absl::AnyInvocable<void(bool,
|
||||
std::unique_ptr<std::vector<PublicCertificate>>) &&>
|
||||
load_callback;
|
||||
auto db = std::make_unique<StrictMock<MockPublicCertificateDb>>();
|
||||
MockPublicCertificateDb* mock_db = db.get();
|
||||
EXPECT_CALL(*mock_db, Initialize(_))
|
||||
.WillOnce(Invoke(
|
||||
[&](absl::AnyInvocable<void(MockPublicCertificateDb::InitStatus)&&>
|
||||
callback) { init_status_callback = std::move(callback); }));
|
||||
EXPECT_CALL(*mock_db, Destroy(_))
|
||||
.WillOnce(Invoke([&](absl::AnyInvocable<void(bool)&&> callback) {
|
||||
destroy_callback = std::move(callback);
|
||||
}));
|
||||
EXPECT_CALL(*mock_db, LoadEntries(_))
|
||||
.WillOnce(Invoke(
|
||||
[&](absl::AnyInvocable<void(
|
||||
bool, std::unique_ptr<std::vector<PublicCertificate>>)&&>
|
||||
callback) { load_callback = std::move(callback); }));
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool clear_succeeded = false;
|
||||
std::vector<PublicCertificate> public_certificates;
|
||||
|
||||
cert_store->ClearPublicCertificates([this, &clear_succeeded](bool success) {
|
||||
CaptureBoolCallback(&clear_succeeded, success);
|
||||
});
|
||||
cert_store->GetPublicCertificates(
|
||||
[this, &public_certificates, complete = []() {
|
||||
}](bool success, std::unique_ptr<std::vector<PublicCertificate>> result) {
|
||||
PublicCertificateCallback(&public_certificates, std::move(complete),
|
||||
success, std::move(result));
|
||||
});
|
||||
|
||||
std::move(init_status_callback)(MockPublicCertificateDb::InitStatus::kOk);
|
||||
|
||||
// These callbacks have to be posted to ensure that they run after the
|
||||
// deferred callbacks posted during initialization.
|
||||
std::move(destroy_callback)(true);
|
||||
std::move(load_callback)(true, nullptr);
|
||||
|
||||
EXPECT_TRUE(clear_succeeded);
|
||||
EXPECT_TRUE(public_certificates.empty());
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, GetPublicCertificateIds) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
auto ids = cert_store->GetPublicCertificateIds();
|
||||
ASSERT_EQ(ids.size(), 3u);
|
||||
EXPECT_EQ(ids[0], kSecretId1);
|
||||
EXPECT_EQ(ids[1], kSecretId2);
|
||||
EXPECT_EQ(ids[2], kSecretId3);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, GetPublicCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<PublicCertificate> public_certificates;
|
||||
cert_store->GetPublicCertificates([this, &public_certificates, complete = [] {
|
||||
}](bool success, std::unique_ptr<std::vector<PublicCertificate>> result) {
|
||||
PublicCertificateCallback(&public_certificates, std::move(complete),
|
||||
success, std::move(result));
|
||||
});
|
||||
|
||||
ASSERT_EQ(3u, public_certificates.size());
|
||||
for (const PublicCertificate& cert : public_certificates) {
|
||||
std::string expected_serialized, actual_serialized;
|
||||
ASSERT_TRUE(cert.SerializeToString(&actual_serialized));
|
||||
ASSERT_TRUE(fake_db->GetCertificatesMap()
|
||||
.find(cert.secret_id())
|
||||
->second.SerializeToString(&expected_serialized));
|
||||
ASSERT_EQ(expected_serialized, actual_serialized);
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, ReplacePublicCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
std::vector<PublicCertificate> new_certs = {
|
||||
CreatePublicCertificate(kSecretId4, kSecretKey4, kPublicKey4,
|
||||
kStartSeconds4, kStartNanos4, kEndSeconds4,
|
||||
kEndNanos4, kForSelectedContacts4,
|
||||
kMetadataEncryptionKey4, kEncryptedMetadataBytes4,
|
||||
kMetadataEncryptionKeyTag4),
|
||||
};
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool succeeded = false;
|
||||
cert_store->ReplacePublicCertificates(
|
||||
new_certs, [this, &succeeded](bool success) {
|
||||
CaptureBoolCallback(&succeeded, success);
|
||||
});
|
||||
|
||||
ASSERT_TRUE(succeeded);
|
||||
auto cert_map = fake_db->GetCertificatesMap();
|
||||
ASSERT_EQ(cert_map.size(), 1u);
|
||||
ASSERT_EQ(cert_map.count(kSecretId4), 1u);
|
||||
auto& cert = cert_map.find(kSecretId4)->second;
|
||||
EXPECT_EQ(cert.secret_key(), kSecretKey4);
|
||||
EXPECT_EQ(cert.public_key(), kPublicKey4);
|
||||
EXPECT_EQ(cert.start_time().seconds(), kStartSeconds4);
|
||||
EXPECT_EQ(cert.start_time().nanos(), kStartNanos4);
|
||||
EXPECT_EQ(cert.end_time().seconds(), kEndSeconds4);
|
||||
EXPECT_EQ(cert.end_time().nanos(), kEndNanos4);
|
||||
EXPECT_EQ(cert.for_selected_contacts(), kForSelectedContacts4);
|
||||
EXPECT_EQ(cert.metadata_encryption_key(), kMetadataEncryptionKey4);
|
||||
EXPECT_EQ(cert.encrypted_metadata_bytes(), kEncryptedMetadataBytes4);
|
||||
EXPECT_EQ(cert.metadata_encryption_key_tag(), kMetadataEncryptionKeyTag4);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, AddPublicCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
std::vector<PublicCertificate> new_certs = {
|
||||
CreatePublicCertificate(kSecretId3, kSecretKey2, kPublicKey2,
|
||||
kStartSeconds2, kStartNanos2, kEndSeconds2,
|
||||
kEndNanos2, kForSelectedContacts2,
|
||||
kMetadataEncryptionKey2, kEncryptedMetadataBytes2,
|
||||
kMetadataEncryptionKeyTag2),
|
||||
CreatePublicCertificate(kSecretId4, kSecretKey4, kPublicKey4,
|
||||
kStartSeconds4, kStartNanos4, kEndSeconds4,
|
||||
kEndNanos4, kForSelectedContacts4,
|
||||
kMetadataEncryptionKey4, kEncryptedMetadataBytes4,
|
||||
kMetadataEncryptionKeyTag4),
|
||||
};
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool succeeded = false;
|
||||
cert_store->AddPublicCertificates(new_certs,
|
||||
[this, &succeeded](bool success) {
|
||||
CaptureBoolCallback(&succeeded, success);
|
||||
});
|
||||
|
||||
ASSERT_TRUE(succeeded);
|
||||
auto cert_map = fake_db->GetCertificatesMap();
|
||||
ASSERT_EQ(cert_map.size(), 4u);
|
||||
ASSERT_EQ(cert_map.count(kSecretId3), 1u);
|
||||
ASSERT_EQ(cert_map.count(kSecretId4), 1u);
|
||||
auto& cert = cert_map.find(kSecretId3)->second;
|
||||
EXPECT_EQ(cert.secret_key(), kSecretKey2);
|
||||
EXPECT_EQ(cert.public_key(), kPublicKey2);
|
||||
EXPECT_EQ(cert.start_time().seconds(), kStartSeconds2);
|
||||
EXPECT_EQ(cert.start_time().nanos(), kStartNanos2);
|
||||
EXPECT_EQ(cert.end_time().seconds(), kEndSeconds2);
|
||||
EXPECT_EQ(cert.end_time().nanos(), kEndNanos2);
|
||||
EXPECT_EQ(cert.for_selected_contacts(), kForSelectedContacts2);
|
||||
EXPECT_EQ(cert.metadata_encryption_key(), kMetadataEncryptionKey2);
|
||||
EXPECT_EQ(cert.encrypted_metadata_bytes(), kEncryptedMetadataBytes2);
|
||||
EXPECT_EQ(cert.metadata_encryption_key_tag(), kMetadataEncryptionKeyTag2);
|
||||
cert = cert_map.find(kSecretId4)->second;
|
||||
EXPECT_EQ(cert.secret_key(), kSecretKey4);
|
||||
EXPECT_EQ(cert.public_key(), kPublicKey4);
|
||||
EXPECT_EQ(cert.start_time().seconds(), kStartSeconds4);
|
||||
EXPECT_EQ(cert.start_time().nanos(), kStartNanos4);
|
||||
EXPECT_EQ(cert.end_time().seconds(), kEndSeconds4);
|
||||
EXPECT_EQ(cert.end_time().nanos(), kEndNanos4);
|
||||
EXPECT_EQ(cert.for_selected_contacts(), kForSelectedContacts4);
|
||||
EXPECT_EQ(cert.metadata_encryption_key(), kMetadataEncryptionKey4);
|
||||
EXPECT_EQ(cert.encrypted_metadata_bytes(), kEncryptedMetadataBytes4);
|
||||
EXPECT_EQ(cert.metadata_encryption_key_tag(), kMetadataEncryptionKeyTag4);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, ClearPublicCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
bool succeeded = false;
|
||||
cert_store->ClearPublicCertificates([this, &succeeded](bool success) {
|
||||
CaptureBoolCallback(&succeeded, success);
|
||||
});
|
||||
|
||||
ASSERT_TRUE(succeeded);
|
||||
ASSERT_EQ(0u, fake_db->GetCertificatesMap().size());
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest,
|
||||
RemoveExpiredPrivateCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> certs = CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(certs);
|
||||
|
||||
std::vector<absl::Time> expiration_times;
|
||||
for (const NearbySharePrivateCertificate& cert : certs) {
|
||||
expiration_times.push_back(cert.not_after());
|
||||
}
|
||||
std::sort(expiration_times.begin(), expiration_times.end());
|
||||
|
||||
// Set the current time to exceed the expiration times of the first two
|
||||
// certificates.
|
||||
absl::Time now = expiration_times[1];
|
||||
|
||||
cert_store->RemoveExpiredPrivateCertificates(now);
|
||||
|
||||
certs = *cert_store->GetPrivateCertificates();
|
||||
ASSERT_EQ(1u, certs.size());
|
||||
for (const NearbySharePrivateCertificate& cert : certs) {
|
||||
EXPECT_LE(now, cert.not_after());
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, RemoveExpiredPublicCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<absl::Time> expiration_times;
|
||||
for (const auto& pair : fake_db->GetCertificatesMap()) {
|
||||
expiration_times.emplace_back(TimestampToTime(pair.second.end_time()));
|
||||
}
|
||||
std::sort(expiration_times.begin(), expiration_times.end());
|
||||
|
||||
// The current time exceeds the expiration times of the first two
|
||||
// certificates even accounting for the expiration time tolerance
|
||||
// applied to public certificates to account for clock skew.
|
||||
absl::Time now = expiration_times[1] +
|
||||
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
|
||||
|
||||
bool succeeded = false;
|
||||
cert_store->RemoveExpiredPublicCertificates(
|
||||
now, [this, &succeeded](bool success) {
|
||||
CaptureBoolCallback(&succeeded, success);
|
||||
});
|
||||
|
||||
ASSERT_TRUE(succeeded);
|
||||
auto cert_map = fake_db->GetCertificatesMap();
|
||||
ASSERT_EQ(cert_map.size(), 1u);
|
||||
for (const auto& pair : cert_map) {
|
||||
EXPECT_LE(now - kNearbySharePublicCertificateValidityBoundOffsetTolerance,
|
||||
TimestampToTime(pair.second.end_time()));
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, ReplaceGetPrivateCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
auto certs_before = CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(certs_before);
|
||||
auto certs_after = cert_store->GetPrivateCertificates();
|
||||
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
ASSERT_EQ(certs_before.size(), certs_after->size());
|
||||
for (size_t i = 0; i < certs_before.size(); ++i) {
|
||||
EXPECT_EQ(certs_before[i].ToCertificateData(),
|
||||
(*certs_after)[i].ToCertificateData());
|
||||
}
|
||||
|
||||
certs_before = CreatePrivateCertificates(
|
||||
1, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(certs_before);
|
||||
certs_after = cert_store->GetPrivateCertificates();
|
||||
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
ASSERT_EQ(certs_before.size(), certs_after->size());
|
||||
for (size_t i = 0; i < certs_before.size(); ++i) {
|
||||
EXPECT_EQ(certs_before[i].ToCertificateData(),
|
||||
(*certs_after)[i].ToCertificateData());
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, UpdatePrivateCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> initial_certs =
|
||||
CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(initial_certs);
|
||||
|
||||
NearbySharePrivateCertificate cert_to_update = initial_certs[1];
|
||||
EXPECT_EQ(initial_certs[1].ToCertificateData(),
|
||||
cert_to_update.ToCertificateData());
|
||||
cert_to_update.EncryptMetadataKey();
|
||||
EXPECT_NE(initial_certs[1].ToCertificateData(),
|
||||
cert_to_update.ToCertificateData());
|
||||
|
||||
cert_store->UpdatePrivateCertificate(cert_to_update);
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> new_certs =
|
||||
*cert_store->GetPrivateCertificates();
|
||||
EXPECT_EQ(initial_certs.size(), new_certs.size());
|
||||
for (size_t i = 0; i < new_certs.size(); ++i) {
|
||||
NearbySharePrivateCertificate expected_cert =
|
||||
i == 1 ? cert_to_update : initial_certs[i];
|
||||
EXPECT_EQ(expected_cert.ToCertificateData(),
|
||||
new_certs[i].ToCertificateData());
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest,
|
||||
NextPrivateCertificateExpirationTime) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
auto certs = CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(certs);
|
||||
std::optional<absl::Time> next_expiration =
|
||||
cert_store->NextPrivateCertificateExpirationTime();
|
||||
|
||||
ASSERT_TRUE(next_expiration.has_value());
|
||||
bool found = false;
|
||||
for (auto& cert : certs) {
|
||||
EXPECT_GE(cert.not_after(), *next_expiration);
|
||||
if (cert.not_after() == *next_expiration) found = true;
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest,
|
||||
NextPublicCertificateExpirationTime) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::optional<absl::Time> next_expiration =
|
||||
cert_store->NextPublicCertificateExpirationTime();
|
||||
|
||||
ASSERT_TRUE(next_expiration.has_value());
|
||||
bool found = false;
|
||||
for (const auto& pair : fake_db->GetCertificatesMap()) {
|
||||
absl::Time curr_expiration = TimestampToTime(pair.second.end_time());
|
||||
EXPECT_GE(curr_expiration, *next_expiration);
|
||||
if (curr_expiration == *next_expiration) found = true;
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest, ClearPrivateCertificates) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> certs_before =
|
||||
CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
cert_store->ReplacePrivateCertificates(certs_before);
|
||||
cert_store->ClearPrivateCertificates();
|
||||
auto certs_after = cert_store->GetPrivateCertificates();
|
||||
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
EXPECT_EQ(0u, certs_after->size());
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareCertificateStorageImplTest,
|
||||
ClearPrivateCertificatesOfVisibility) {
|
||||
auto db = std::make_unique<nearby::FakePublicCertificateDb>();
|
||||
nearby::FakePublicCertificateDb* fake_db = db.get();
|
||||
PrepopulatePublicCertificates(fake_db);
|
||||
|
||||
auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create(
|
||||
preference_manager_, std::move(db));
|
||||
|
||||
std::vector<NearbySharePrivateCertificate> certs_all_contacts =
|
||||
CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
std::vector<NearbySharePrivateCertificate> certs_selected_contacts =
|
||||
CreatePrivateCertificates(
|
||||
3, DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
std::vector<NearbySharePrivateCertificate> all_certs;
|
||||
all_certs.reserve(certs_all_contacts.size() + certs_selected_contacts.size());
|
||||
all_certs.insert(all_certs.end(), certs_all_contacts.begin(),
|
||||
certs_all_contacts.end());
|
||||
all_certs.insert(all_certs.end(), certs_selected_contacts.begin(),
|
||||
certs_selected_contacts.end());
|
||||
|
||||
// Remove all-contacts certs then selected-contacts certs.
|
||||
{
|
||||
cert_store->ReplacePrivateCertificates(all_certs);
|
||||
cert_store->ClearPrivateCertificatesOfVisibility(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
auto certs_after = cert_store->GetPrivateCertificates();
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
ASSERT_EQ(certs_selected_contacts.size(), certs_after->size());
|
||||
for (size_t i = 0; i < certs_selected_contacts.size(); ++i) {
|
||||
EXPECT_EQ(certs_selected_contacts[i].ToCertificateData(),
|
||||
(*certs_after)[i].ToCertificateData());
|
||||
}
|
||||
|
||||
cert_store->ClearPrivateCertificatesOfVisibility(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
certs_after = cert_store->GetPrivateCertificates();
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
EXPECT_EQ(certs_after->size(), 0u);
|
||||
}
|
||||
|
||||
// Remove selected-contacts certs then all-contacts certs.
|
||||
{
|
||||
cert_store->ReplacePrivateCertificates(all_certs);
|
||||
cert_store->ClearPrivateCertificatesOfVisibility(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
auto certs_after = cert_store->GetPrivateCertificates();
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
ASSERT_EQ(certs_all_contacts.size(), certs_after->size());
|
||||
for (size_t i = 0; i < certs_all_contacts.size(); ++i) {
|
||||
EXPECT_EQ(certs_all_contacts[i].ToCertificateData(),
|
||||
(*certs_after)[i].ToCertificateData());
|
||||
}
|
||||
|
||||
cert_store->ClearPrivateCertificatesOfVisibility(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
certs_after = cert_store->GetPrivateCertificates();
|
||||
ASSERT_TRUE(certs_after.has_value());
|
||||
EXPECT_EQ(certs_after->size(), 0u);
|
||||
}
|
||||
EXPECT_THAT(cert_store.use_count(), Eq(1));
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,261 @@
|
||||
// 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 "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/aead.h"
|
||||
#include "internal/crypto_cros/encryptor.h"
|
||||
#include "internal/crypto_cros/hmac.h"
|
||||
#include "internal/crypto_cros/signature_verifier.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
bool IsDataValid(absl::Time not_before, absl::Time not_after,
|
||||
absl::Span<const uint8_t> public_key,
|
||||
crypto::SymmetricKey* secret_key, absl::Span<const uint8_t> id,
|
||||
absl::Span<const uint8_t> encrypted_metadata,
|
||||
absl::Span<const uint8_t> metadata_encryption_key_tag) {
|
||||
return not_before < not_after && !public_key.empty() && secret_key &&
|
||||
secret_key->key().size() == kNearbyShareNumBytesSecretKey &&
|
||||
id.size() == kNearbyShareNumBytesCertificateId &&
|
||||
!encrypted_metadata.empty() &&
|
||||
metadata_encryption_key_tag.size() ==
|
||||
kNearbyShareNumBytesMetadataEncryptionKeyTag;
|
||||
}
|
||||
|
||||
// Attempts to decrypt |encrypted_metadata_key| using the |secret_key|.
|
||||
// Return std::nullopt if the decryption was unsuccessful.
|
||||
std::optional<std::vector<uint8_t>> DecryptMetadataKey(
|
||||
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
|
||||
const crypto::SymmetricKey* secret_key) {
|
||||
std::unique_ptr<crypto::Encryptor> encryptor =
|
||||
CreateNearbyShareCtrEncryptor(secret_key, encrypted_metadata_key.salt());
|
||||
if (!encryptor) {
|
||||
NL_LOG(ERROR)
|
||||
<< "Cannot decrypt metadata key: Could not create CTR encryptor.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decrypted_metadata_key;
|
||||
if (!encryptor->Decrypt(
|
||||
as_bytes(absl::MakeSpan(encrypted_metadata_key.encrypted_key())),
|
||||
&decrypted_metadata_key)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return decrypted_metadata_key;
|
||||
}
|
||||
|
||||
// Attempts to decrypt |encrypted_metadata| with |metadata_encryption_key|,
|
||||
// using |authentication_key| as the IV. Returns std::nullopt if the decryption
|
||||
// was unsuccessful.
|
||||
std::optional<std::vector<uint8_t>> DecryptMetadataPayload(
|
||||
absl::Span<const uint8_t> encrypted_metadata,
|
||||
absl::Span<const uint8_t> metadata_encryption_key,
|
||||
const crypto::SymmetricKey* secret_key) {
|
||||
// Init() keeps a reference to the input key, so that reference must outlive
|
||||
// the lifetime of |aead|.
|
||||
std::vector<uint8_t> derived_key = DeriveNearbyShareKey(
|
||||
metadata_encryption_key, kNearbyShareNumBytesAesGcmKey);
|
||||
|
||||
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
|
||||
aead.Init(derived_key);
|
||||
|
||||
auto result = aead.Open(
|
||||
encrypted_metadata,
|
||||
/*nonce=*/
|
||||
DeriveNearbyShareKey(as_bytes(absl::MakeSpan(secret_key->key())),
|
||||
kNearbyShareNumBytesAesGcmIv),
|
||||
/*additional_data=*/absl::Span<const uint8_t>());
|
||||
if (result) {
|
||||
return result.value();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Returns true if the HMAC of |decrypted_metadata_key| is
|
||||
// |metadata_encryption_key_tag|.
|
||||
bool VerifyMetadataEncryptionKeyTag(
|
||||
absl::Span<const uint8_t> decrypted_metadata_key,
|
||||
absl::Span<const uint8_t> metadata_encryption_key_tag) {
|
||||
// This array of 0x00 is used to conform with the GmsCore implementation.
|
||||
std::vector<uint8_t> key(kNearbyShareNumBytesMetadataEncryptionKeyTag, 0x00);
|
||||
|
||||
std::vector<uint8_t> result(kNearbyShareNumBytesMetadataEncryptionKeyTag);
|
||||
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
|
||||
return hmac.Init(key) &&
|
||||
hmac.Verify(decrypted_metadata_key, metadata_encryption_key_tag);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::optional<NearbyShareDecryptedPublicCertificate>
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
const nearby::sharing::proto::PublicCertificate& public_certificate,
|
||||
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key) {
|
||||
// Note: The PublicCertificate.metadata_encryption_key and
|
||||
// PublicCertificate.for_selected_contacts are not returned from the server
|
||||
// for remote devices.
|
||||
absl::Time not_before =
|
||||
FromJavaTime(public_certificate.start_time().seconds() * 1000);
|
||||
absl::Time not_after =
|
||||
FromJavaTime(public_certificate.end_time().seconds() * 1000);
|
||||
std::vector<uint8_t> public_key(public_certificate.public_key().begin(),
|
||||
public_certificate.public_key().end());
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key =
|
||||
crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
|
||||
public_certificate.secret_key());
|
||||
std::vector<uint8_t> id(public_certificate.secret_id().begin(),
|
||||
public_certificate.secret_id().end());
|
||||
std::vector<uint8_t> encrypted_metadata(
|
||||
public_certificate.encrypted_metadata_bytes().begin(),
|
||||
public_certificate.encrypted_metadata_bytes().end());
|
||||
std::vector<uint8_t> metadata_encryption_key_tag(
|
||||
public_certificate.metadata_encryption_key_tag().begin(),
|
||||
public_certificate.metadata_encryption_key_tag().end());
|
||||
|
||||
if (!IsDataValid(not_before, not_after, public_key, secret_key.get(), id,
|
||||
encrypted_metadata, metadata_encryption_key_tag)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Note: Failure to decrypt the metadata key or failure to confirm that the
|
||||
// decrypted metadata key agrees with the key commitment tag should not log an
|
||||
// error. When another device advertises their encrypted metadata key, we do
|
||||
// not know what public certificate that corresponds to. So, we will
|
||||
// potentially be calling DecryptPublicCertificate() on all of our public
|
||||
// certificates with the same encrypted metadata key until we find the correct
|
||||
// one.
|
||||
auto decrypted_metadata_key =
|
||||
DecryptMetadataKey(encrypted_metadata_key, secret_key.get());
|
||||
if (!decrypted_metadata_key ||
|
||||
!VerifyMetadataEncryptionKeyTag(*decrypted_metadata_key,
|
||||
metadata_encryption_key_tag)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// If the key was able to be decrypted, we expect the metadata to be able to
|
||||
// be decrypted.
|
||||
auto decrypted_metadata_bytes = DecryptMetadataPayload(
|
||||
encrypted_metadata, *decrypted_metadata_key, secret_key.get());
|
||||
if (!decrypted_metadata_bytes) {
|
||||
NL_LOG(ERROR) << "Metadata decryption failed: Failed to decrypt metadata"
|
||||
<< "payload.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata;
|
||||
if (!unencrypted_metadata.ParseFromArray(decrypted_metadata_bytes->data(),
|
||||
decrypted_metadata_bytes->size())) {
|
||||
NL_LOG(ERROR) << "Metadata decryption failed: Failed to parse decrypted "
|
||||
<< "metadata payload.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return NearbyShareDecryptedPublicCertificate(
|
||||
not_before, not_after, std::move(secret_key), std::move(public_key),
|
||||
std::move(id), std::move(unencrypted_metadata),
|
||||
public_certificate.for_self_share());
|
||||
}
|
||||
|
||||
NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
|
||||
absl::Time not_before, absl::Time not_after,
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key,
|
||||
std::vector<uint8_t> public_key, std::vector<uint8_t> id,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
|
||||
bool for_self_share)
|
||||
: not_before_(not_before),
|
||||
not_after_(not_after),
|
||||
secret_key_(std::move(secret_key)),
|
||||
public_key_(std::move(public_key)),
|
||||
id_(std::move(id)),
|
||||
unencrypted_metadata_(std::move(unencrypted_metadata)),
|
||||
for_self_share_(for_self_share) {}
|
||||
|
||||
NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
|
||||
const NearbyShareDecryptedPublicCertificate& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
NearbyShareDecryptedPublicCertificate&
|
||||
NearbyShareDecryptedPublicCertificate::operator=(
|
||||
const NearbyShareDecryptedPublicCertificate& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
not_before_ = other.not_before_;
|
||||
not_after_ = other.not_after_;
|
||||
secret_key_ = crypto::SymmetricKey::Import(
|
||||
crypto::SymmetricKey::Algorithm::AES, other.secret_key_->key());
|
||||
public_key_ = other.public_key_;
|
||||
id_ = other.id_;
|
||||
unencrypted_metadata_ = other.unencrypted_metadata_;
|
||||
for_self_share_ = other.for_self_share_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
|
||||
NearbyShareDecryptedPublicCertificate&&) = default;
|
||||
|
||||
NearbyShareDecryptedPublicCertificate&
|
||||
NearbyShareDecryptedPublicCertificate::operator=(
|
||||
NearbyShareDecryptedPublicCertificate&&) = default;
|
||||
|
||||
NearbyShareDecryptedPublicCertificate::
|
||||
~NearbyShareDecryptedPublicCertificate() = default;
|
||||
|
||||
bool NearbyShareDecryptedPublicCertificate::VerifySignature(
|
||||
absl::Span<const uint8_t> payload,
|
||||
absl::Span<const uint8_t> signature) const {
|
||||
crypto::SignatureVerifier verifier;
|
||||
if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
|
||||
public_key_)) {
|
||||
NL_LOG(ERROR) << "Verification failed: Initialization unsuccessful.";
|
||||
return false;
|
||||
}
|
||||
|
||||
verifier.VerifyUpdate(payload);
|
||||
return verifier.VerifyFinal();
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
NearbyShareDecryptedPublicCertificate::HashAuthenticationToken(
|
||||
absl::Span<const uint8_t> authentication_token) const {
|
||||
return ComputeAuthenticationTokenHash(
|
||||
authentication_token, as_bytes(absl::MakeSpan(secret_key_->key())));
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,119 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Stores decrypted metadata and crypto keys for the remote device that uploaded
|
||||
// this certificate to the Nearby Share server. Use DecryptPublicCertificate()
|
||||
// to generate an instance. This class provides a method for verifying a signed
|
||||
// payload during the authentication flow.
|
||||
class NearbyShareDecryptedPublicCertificate {
|
||||
public:
|
||||
// Attempts to decrypt the encrypted metadata of the PublicCertificate proto
|
||||
// by first decrypting the |encrypted_metadata_key| using the secret key
|
||||
// then using the decrypted key to decrypt the metadata. Returns absl::nullopt
|
||||
// if the metadata was not successfully decrypted or if the proto data is
|
||||
// invalid.
|
||||
static std::optional<NearbyShareDecryptedPublicCertificate>
|
||||
DecryptPublicCertificate(
|
||||
const nearby::sharing::proto::PublicCertificate& public_certificate,
|
||||
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key);
|
||||
|
||||
NearbyShareDecryptedPublicCertificate(
|
||||
const NearbyShareDecryptedPublicCertificate& other);
|
||||
NearbyShareDecryptedPublicCertificate& operator=(
|
||||
const NearbyShareDecryptedPublicCertificate& other);
|
||||
NearbyShareDecryptedPublicCertificate(
|
||||
NearbyShareDecryptedPublicCertificate&&);
|
||||
NearbyShareDecryptedPublicCertificate& operator=(
|
||||
NearbyShareDecryptedPublicCertificate&&);
|
||||
|
||||
virtual ~NearbyShareDecryptedPublicCertificate();
|
||||
|
||||
const std::vector<uint8_t>& id() const { return id_; }
|
||||
absl::Time not_before() const { return not_before_; }
|
||||
absl::Time not_after() const { return not_after_; }
|
||||
const nearby::sharing::proto::EncryptedMetadata& unencrypted_metadata()
|
||||
const {
|
||||
return unencrypted_metadata_;
|
||||
}
|
||||
|
||||
bool for_self_share() const { return for_self_share_; }
|
||||
|
||||
// Verifies the |signature| of the signed |payload| using |public_key_|.
|
||||
// Returns true if verification was successful.
|
||||
bool VerifySignature(absl::Span<const uint8_t> payload,
|
||||
absl::Span<const uint8_t> signature) const;
|
||||
|
||||
// Creates a hash of the |authentication_token|, using |secret_key_|. The use
|
||||
// of HKDF and the output vector size is part of the Nearby Share protocol and
|
||||
// conforms with the GmsCore implementation.
|
||||
std::vector<uint8_t> HashAuthenticationToken(
|
||||
absl::Span<const uint8_t> authentication_token) const;
|
||||
|
||||
private:
|
||||
NearbyShareDecryptedPublicCertificate(
|
||||
absl::Time not_before, absl::Time not_after,
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key,
|
||||
std::vector<uint8_t> public_key, std::vector<uint8_t> id,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
|
||||
bool for_self_share);
|
||||
|
||||
// The start and end times of the certificate's validity period. To avoid
|
||||
// issues with clock skew, these times may be offset compared to the
|
||||
// corresponding private certificate.
|
||||
absl::Time not_before_;
|
||||
absl::Time not_after_;
|
||||
|
||||
// A 32-byte AES key that was used for metadata key and metadata decryption.
|
||||
// Also, used to generate an authentication token hash.
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key_;
|
||||
|
||||
// A P-256 public key used for verification. The bytes comprise a DER-encoded
|
||||
// ASN.1 SubjectPublicKeyInfo from the X.509 specification (RFC 5280).
|
||||
std::vector<uint8_t> public_key_;
|
||||
|
||||
// An ID for the certificate, most likely generated from the secret key.
|
||||
std::vector<uint8_t> id_;
|
||||
|
||||
// Unencrypted device metadata. The proto name is misleading; it holds data
|
||||
// that was previously serialized and encrypted.
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata_;
|
||||
|
||||
// Indicates if this public certificate is from another device owned by the
|
||||
// same user.
|
||||
bool for_self_share_ = false;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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 "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
using ::nearby::sharing::proto::PublicCertificate;
|
||||
|
||||
// The for_selected_contacts field of a public certificate proto is irrelevant
|
||||
// for remote device certificates. Even if it is set, it is meaningless. It only
|
||||
// has meaning for private certificates converted to public certificates and
|
||||
// uploaded to the Nearby server.
|
||||
const DeviceVisibility kTestPublicCertificateVisibility =
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE;
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt) {
|
||||
PublicCertificate proto_cert =
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility);
|
||||
proto_cert.set_for_self_share(true);
|
||||
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> cert =
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
proto_cert, GetNearbyShareTestEncryptedMetadataKey());
|
||||
EXPECT_TRUE(cert);
|
||||
EXPECT_EQ(FromJavaTime(proto_cert.start_time().seconds() * 1000),
|
||||
cert->not_before());
|
||||
EXPECT_EQ(FromJavaTime(proto_cert.end_time().seconds() * 1000),
|
||||
cert->not_after());
|
||||
EXPECT_EQ(std::vector<uint8_t>(proto_cert.secret_id().begin(),
|
||||
proto_cert.secret_id().end()),
|
||||
cert->id());
|
||||
EXPECT_EQ(GetNearbyShareTestMetadata().SerializeAsString(),
|
||||
cert->unencrypted_metadata().SerializeAsString());
|
||||
EXPECT_EQ(proto_cert.for_self_share(), cert->for_self_share());
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt_IncorrectKeyFailure) {
|
||||
// Input incorrect metadata encryption key.
|
||||
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility),
|
||||
NearbyShareEncryptedMetadataKey(
|
||||
std::vector<uint8_t>(kNearbyShareNumBytesMetadataEncryptionKeySalt,
|
||||
0x00),
|
||||
std::vector<uint8_t>(kNearbyShareNumBytesMetadataEncryptionKey,
|
||||
0x00))));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest,
|
||||
Decrypt_MetadataDecryptionFailure) {
|
||||
// Use metadata that cannot be decrypted with the given key.
|
||||
PublicCertificate proto_cert =
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility);
|
||||
proto_cert.set_encrypted_metadata_bytes("invalid metadata");
|
||||
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
proto_cert, GetNearbyShareTestEncryptedMetadataKey()));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt_InvalidDataFailure) {
|
||||
// Do not accept the input PublicCertificate because the validity period does
|
||||
// not make sense.
|
||||
PublicCertificate proto_cert =
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility);
|
||||
proto_cert.mutable_end_time()->set_seconds(proto_cert.start_time().seconds() -
|
||||
1);
|
||||
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
proto_cert, GetNearbyShareTestEncryptedMetadataKey()));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Verify) {
|
||||
std::optional<NearbyShareDecryptedPublicCertificate> cert =
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility),
|
||||
GetNearbyShareTestEncryptedMetadataKey());
|
||||
EXPECT_TRUE(cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
|
||||
GetNearbyShareTestSampleSignature()));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Verify_InitFailure) {
|
||||
// Public key has invalid SubjectPublicKeyInfo format.
|
||||
PublicCertificate proto_cert =
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility);
|
||||
proto_cert.set_public_key("invalid public key");
|
||||
|
||||
auto cert = NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
proto_cert, GetNearbyShareTestEncryptedMetadataKey());
|
||||
ASSERT_TRUE(cert);
|
||||
EXPECT_FALSE(cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
|
||||
GetNearbyShareTestSampleSignature()));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, Verify_WrongSignature) {
|
||||
auto cert = NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility),
|
||||
GetNearbyShareTestEncryptedMetadataKey());
|
||||
EXPECT_FALSE(
|
||||
cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
|
||||
/*signature=*/absl::Span<const uint8_t>()));
|
||||
}
|
||||
|
||||
TEST(NearbyShareDecryptedPublicCertificateTest, HashAuthenticationToken) {
|
||||
auto cert = NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(kTestPublicCertificateVisibility),
|
||||
GetNearbyShareTestEncryptedMetadataKey());
|
||||
EXPECT_EQ(GetNearbyShareTestPayloadHashUsingSecretKey(),
|
||||
cert->HashAuthenticationToken(GetNearbyShareTestPayloadToSign()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
NearbyShareEncryptedMetadataKey::NearbyShareEncryptedMetadataKey(
|
||||
std::vector<uint8_t> salt, std::vector<uint8_t> encrypted_key)
|
||||
: salt_(std::move(salt)), encrypted_key_(std::move(encrypted_key)) {
|
||||
NL_DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt, salt_.size());
|
||||
NL_DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKey,
|
||||
encrypted_key_.size());
|
||||
}
|
||||
|
||||
NearbyShareEncryptedMetadataKey::NearbyShareEncryptedMetadataKey(
|
||||
const NearbyShareEncryptedMetadataKey&) = default;
|
||||
|
||||
NearbyShareEncryptedMetadataKey& NearbyShareEncryptedMetadataKey::operator=(
|
||||
const NearbyShareEncryptedMetadataKey&) = default;
|
||||
|
||||
NearbyShareEncryptedMetadataKey::NearbyShareEncryptedMetadataKey(
|
||||
NearbyShareEncryptedMetadataKey&&) = default;
|
||||
|
||||
NearbyShareEncryptedMetadataKey& NearbyShareEncryptedMetadataKey::operator=(
|
||||
NearbyShareEncryptedMetadataKey&&) = default;
|
||||
|
||||
NearbyShareEncryptedMetadataKey::~NearbyShareEncryptedMetadataKey() = default;
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Holds the encrypted symmetric key--the key used to encrypt user/device
|
||||
// metadata--as well as the salt used to encrypt the key.
|
||||
class NearbyShareEncryptedMetadataKey {
|
||||
public:
|
||||
NearbyShareEncryptedMetadataKey(std::vector<uint8_t> salt,
|
||||
std::vector<uint8_t> encrypted_key);
|
||||
NearbyShareEncryptedMetadataKey(const NearbyShareEncryptedMetadataKey&);
|
||||
NearbyShareEncryptedMetadataKey& operator=(
|
||||
const NearbyShareEncryptedMetadataKey&);
|
||||
NearbyShareEncryptedMetadataKey(NearbyShareEncryptedMetadataKey&&);
|
||||
NearbyShareEncryptedMetadataKey& operator=(NearbyShareEncryptedMetadataKey&&);
|
||||
~NearbyShareEncryptedMetadataKey();
|
||||
|
||||
const std::vector<uint8_t>& salt() const { return salt_; }
|
||||
const std::vector<uint8_t>& encrypted_key() const { return encrypted_key_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> salt_;
|
||||
std::vector<uint8_t> encrypted_key_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
|
||||
@@ -0,0 +1,420 @@
|
||||
// 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 "sharing/certificates/nearby_share_private_certificate.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/random/random.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/aead.h"
|
||||
#include "internal/crypto_cros/ec_private_key.h"
|
||||
#include "internal/crypto_cros/ec_signature_creator.h"
|
||||
#include "internal/crypto_cros/encryptor.h"
|
||||
#include "internal/crypto_cros/hmac.h"
|
||||
#include "internal/crypto_cros/sha2.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/internal/api/private_certificate_data.h"
|
||||
#include "sharing/internal/base/encode.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::api::PrivateCertificateData;
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
// Generates a random validity bound offset in the interval
|
||||
// [0, kNearbyShareMaxPrivateCertificateValidityBoundOffset).
|
||||
absl::Duration GenerateRandomOffset() {
|
||||
absl::BitGen bitgen;
|
||||
return absl::Microseconds(
|
||||
absl::Uniform(bitgen, 0,
|
||||
kNearbyShareMaxPrivateCertificateValidityBoundOffset /
|
||||
absl::Microseconds(1)));
|
||||
}
|
||||
|
||||
// Generates a certificate identifier by hashing the input secret |key|.
|
||||
std::vector<uint8_t> CreateCertificateIdFromSecretKey(
|
||||
const crypto::SymmetricKey& key) {
|
||||
NL_DCHECK_EQ(crypto::kSHA256Length, kNearbyShareNumBytesCertificateId);
|
||||
std::vector<uint8_t> id(kNearbyShareNumBytesCertificateId);
|
||||
crypto::SHA256HashString(key.key(), id.data(), id.size());
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// Creates an HMAC from |metadata_encryption_key| to be used as a key commitment
|
||||
// in certificates.
|
||||
std::optional<std::vector<uint8_t>> CreateMetadataEncryptionKeyTag(
|
||||
absl::Span<const uint8_t> metadata_encryption_key) {
|
||||
// This array of 0x00 is used to conform with the GmsCore implementation.
|
||||
std::vector<uint8_t> key(kNearbyShareNumBytesMetadataEncryptionKeyTag, 0x00);
|
||||
|
||||
std::vector<uint8_t> result(kNearbyShareNumBytesMetadataEncryptionKeyTag);
|
||||
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
|
||||
if (!hmac.Init(key) ||
|
||||
!hmac.Sign(metadata_encryption_key,
|
||||
absl::MakeSpan(result.data(), result.size())))
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string EncodeString(absl::string_view unencoded_string) {
|
||||
std::string result;
|
||||
absl::WebSafeBase64Escape(unencoded_string, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> DecodeString(const std::string* encoded_string) {
|
||||
std::string result;
|
||||
if (!encoded_string) return std::nullopt;
|
||||
|
||||
if (absl::WebSafeBase64Unescape(*encoded_string, &result)) {
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string BytesToEncodedString(const std::vector<uint8_t>& bytes) {
|
||||
return EncodeString(std::string(bytes.begin(), bytes.end()));
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> EncodedStringToBytes(
|
||||
const std::string* str) {
|
||||
std::optional<std::string> decoded_str = DecodeString(str);
|
||||
return decoded_str ? std::make_optional<std::vector<uint8_t>>(
|
||||
decoded_str->begin(), decoded_str->end())
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
std::string SaltsToString(const std::set<std::vector<uint8_t>>& salts) {
|
||||
std::string str;
|
||||
str.reserve(salts.size() * 2 * kNearbyShareNumBytesMetadataEncryptionKeySalt);
|
||||
for (const std::vector<uint8_t>& salt : salts) {
|
||||
str += nearby::utils::HexEncode(salt);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::set<std::vector<uint8_t>> StringToSalts(absl::string_view str) {
|
||||
const size_t chars_per_salt =
|
||||
2 * kNearbyShareNumBytesMetadataEncryptionKeySalt;
|
||||
NL_DCHECK_EQ(str.size() % chars_per_salt, 0);
|
||||
std::set<std::vector<uint8_t>> salts;
|
||||
for (size_t i = 0; i < str.size(); i += chars_per_salt) {
|
||||
std::string bytes =
|
||||
absl::HexStringToBytes(absl::string_view(&str[i], chars_per_salt));
|
||||
std::vector<uint8_t> salt(bytes.begin(), bytes.end());
|
||||
salts.insert(std::move(salt));
|
||||
}
|
||||
return salts;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
|
||||
DeviceVisibility visibility, absl::Time not_before,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata)
|
||||
: visibility_(visibility),
|
||||
not_before_(not_before),
|
||||
not_after_(not_before_ + kNearbyShareCertificateValidityPeriod),
|
||||
key_pair_(crypto::ECPrivateKey::Create()),
|
||||
secret_key_(crypto::SymmetricKey::GenerateRandomKey(
|
||||
crypto::SymmetricKey::Algorithm::AES,
|
||||
/*key_size_in_bits=*/8 * kNearbyShareNumBytesSecretKey)),
|
||||
metadata_encryption_key_(
|
||||
GenerateRandomBytes(kNearbyShareNumBytesMetadataEncryptionKey)),
|
||||
id_(CreateCertificateIdFromSecretKey(*secret_key_)),
|
||||
unencrypted_metadata_(std::move(unencrypted_metadata)) {
|
||||
NL_DCHECK_NE(
|
||||
static_cast<int>(visibility),
|
||||
static_cast<int>(DeviceVisibility::DEVICE_VISIBILITY_UNSPECIFIED));
|
||||
}
|
||||
|
||||
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
|
||||
DeviceVisibility visibility, absl::Time not_before, absl::Time not_after,
|
||||
std::unique_ptr<crypto::ECPrivateKey> key_pair,
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key,
|
||||
std::vector<uint8_t> metadata_encryption_key, std::vector<uint8_t> id,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
|
||||
std::set<std::vector<uint8_t>> consumed_salts)
|
||||
: visibility_(visibility),
|
||||
not_before_(not_before),
|
||||
not_after_(not_after),
|
||||
key_pair_(std::move(key_pair)),
|
||||
secret_key_(std::move(secret_key)),
|
||||
metadata_encryption_key_(std::move(metadata_encryption_key)),
|
||||
id_(std::move(id)),
|
||||
unencrypted_metadata_(std::move(unencrypted_metadata)),
|
||||
consumed_salts_(std::move(consumed_salts)) {
|
||||
NL_DCHECK_NE(
|
||||
static_cast<int>(visibility),
|
||||
static_cast<int>(DeviceVisibility::DEVICE_VISIBILITY_UNSPECIFIED));
|
||||
}
|
||||
|
||||
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
|
||||
const NearbySharePrivateCertificate& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
NearbySharePrivateCertificate& NearbySharePrivateCertificate::operator=(
|
||||
const NearbySharePrivateCertificate& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
visibility_ = other.visibility_;
|
||||
not_before_ = other.not_before_;
|
||||
not_after_ = other.not_after_;
|
||||
key_pair_ = other.key_pair_->Copy();
|
||||
secret_key_ = crypto::SymmetricKey::Import(
|
||||
crypto::SymmetricKey::Algorithm::AES, other.secret_key_->key());
|
||||
metadata_encryption_key_ = other.metadata_encryption_key_;
|
||||
id_ = other.id_;
|
||||
unencrypted_metadata_ = other.unencrypted_metadata_;
|
||||
consumed_salts_ = other.consumed_salts_;
|
||||
next_salts_for_testing_ = other.next_salts_for_testing_;
|
||||
offset_for_testing_ = other.offset_for_testing_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
|
||||
NearbySharePrivateCertificate&& other) = default;
|
||||
|
||||
NearbySharePrivateCertificate& NearbySharePrivateCertificate::operator=(
|
||||
NearbySharePrivateCertificate&& other) = default;
|
||||
|
||||
NearbySharePrivateCertificate::~NearbySharePrivateCertificate() = default;
|
||||
|
||||
std::optional<NearbyShareEncryptedMetadataKey>
|
||||
NearbySharePrivateCertificate::EncryptMetadataKey() {
|
||||
std::optional<std::vector<uint8_t>> salt = GenerateUnusedSalt();
|
||||
if (!salt) {
|
||||
NL_LOG(ERROR) << "Encryption failed: Salt generation unsuccessful.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::unique_ptr<crypto::Encryptor> encryptor =
|
||||
CreateNearbyShareCtrEncryptor(secret_key_.get(), *salt);
|
||||
if (!encryptor) {
|
||||
NL_LOG(ERROR) << "Encryption failed: Could not create CTR encryptor.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
NL_DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKey,
|
||||
metadata_encryption_key_.size());
|
||||
std::vector<uint8_t> encrypted_metadata_key;
|
||||
if (!encryptor->Encrypt(metadata_encryption_key_, &encrypted_metadata_key)) {
|
||||
NL_LOG(ERROR) << "Encryption failed: Could not encrypt metadata key.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return NearbyShareEncryptedMetadataKey(*salt, encrypted_metadata_key);
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> NearbySharePrivateCertificate::Sign(
|
||||
absl::Span<const uint8_t> payload) const {
|
||||
std::unique_ptr<crypto::ECSignatureCreator> signer(
|
||||
crypto::ECSignatureCreator::Create(key_pair_.get()));
|
||||
|
||||
std::vector<uint8_t> signature;
|
||||
if (!signer->Sign(payload, &signature)) {
|
||||
NL_LOG(ERROR) << "Signing failed.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> NearbySharePrivateCertificate::HashAuthenticationToken(
|
||||
absl::Span<const uint8_t> authentication_token) const {
|
||||
return ComputeAuthenticationTokenHash(
|
||||
authentication_token, as_bytes(absl::MakeSpan(secret_key_->key())));
|
||||
}
|
||||
|
||||
std::optional<nearby::sharing::proto::PublicCertificate>
|
||||
NearbySharePrivateCertificate::ToPublicCertificate() const {
|
||||
std::vector<uint8_t> public_key;
|
||||
if (!key_pair_->ExportPublicKey(&public_key)) {
|
||||
NL_LOG(ERROR) << "Failed to export public key.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> encrypted_metadata_bytes =
|
||||
EncryptMetadata();
|
||||
if (!encrypted_metadata_bytes) {
|
||||
NL_LOG(ERROR) << "Failed to encrypt metadata.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> metadata_encryption_key_tag =
|
||||
CreateMetadataEncryptionKeyTag(metadata_encryption_key_);
|
||||
if (!metadata_encryption_key_tag) {
|
||||
NL_LOG(ERROR) << "Failed to compute metadata encryption key tag.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
absl::Duration not_before_offset =
|
||||
offset_for_testing_.value_or(GenerateRandomOffset());
|
||||
absl::Duration not_after_offset =
|
||||
offset_for_testing_.value_or(GenerateRandomOffset());
|
||||
|
||||
nearby::sharing::proto::PublicCertificate public_certificate;
|
||||
public_certificate.set_secret_id(std::string(id_.begin(), id_.end()));
|
||||
public_certificate.set_secret_key(secret_key_->key());
|
||||
public_certificate.set_public_key(
|
||||
std::string(public_key.begin(), public_key.end()));
|
||||
public_certificate.mutable_start_time()->set_seconds(
|
||||
ToJavaTime(not_before_ - not_before_offset) / 1000);
|
||||
public_certificate.mutable_end_time()->set_seconds(
|
||||
ToJavaTime(not_after_ + not_after_offset) / 1000);
|
||||
public_certificate.set_for_selected_contacts(
|
||||
visibility_ == DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
public_certificate.set_metadata_encryption_key(std::string(
|
||||
metadata_encryption_key_.begin(), metadata_encryption_key_.end()));
|
||||
public_certificate.set_encrypted_metadata_bytes(std::string(
|
||||
encrypted_metadata_bytes->begin(), encrypted_metadata_bytes->end()));
|
||||
public_certificate.set_metadata_encryption_key_tag(
|
||||
std::string(metadata_encryption_key_tag->begin(),
|
||||
metadata_encryption_key_tag->end()));
|
||||
public_certificate.set_for_self_share(
|
||||
visibility_ == DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE);
|
||||
|
||||
return public_certificate;
|
||||
}
|
||||
|
||||
PrivateCertificateData NearbySharePrivateCertificate::ToCertificateData()
|
||||
const {
|
||||
std::vector<uint8_t> key_pair;
|
||||
key_pair_->ExportPrivateKey(&key_pair);
|
||||
return PrivateCertificateData{
|
||||
.visibility = static_cast<int>(visibility_),
|
||||
.not_before = absl::ToUnixNanos(not_before_),
|
||||
.not_after = absl::ToUnixNanos(not_after_),
|
||||
.key_pair = BytesToEncodedString(key_pair),
|
||||
.secret_key = EncodeString(secret_key_->key()),
|
||||
.metadata_encryption_key = BytesToEncodedString(metadata_encryption_key_),
|
||||
.id = BytesToEncodedString(id_),
|
||||
.unencrypted_metadata_proto =
|
||||
EncodeString(unencrypted_metadata_.SerializeAsString()),
|
||||
.consumed_salts = SaltsToString(consumed_salts_),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<NearbySharePrivateCertificate>
|
||||
NearbySharePrivateCertificate::FromCertificateData(
|
||||
const PrivateCertificateData& cert_data) {
|
||||
auto bytes_opt = EncodedStringToBytes(&cert_data.key_pair);
|
||||
if (!bytes_opt) return std::nullopt;
|
||||
std::unique_ptr<crypto::ECPrivateKey> key_pair =
|
||||
crypto::ECPrivateKey::CreateFromPrivateKeyInfo(*bytes_opt);
|
||||
auto str_opt = DecodeString(&cert_data.secret_key);
|
||||
if (!str_opt) return std::nullopt;
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key =
|
||||
crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
|
||||
*str_opt);
|
||||
bytes_opt = EncodedStringToBytes(&cert_data.metadata_encryption_key);
|
||||
if (!bytes_opt) return std::nullopt;
|
||||
std::vector<uint8_t> metadata_encryption_key = *bytes_opt;
|
||||
bytes_opt = EncodedStringToBytes(&cert_data.id);
|
||||
if (!bytes_opt) return std::nullopt;
|
||||
std::vector<uint8_t> id = *bytes_opt;
|
||||
str_opt = DecodeString(&cert_data.unencrypted_metadata_proto);
|
||||
if (!str_opt) return std::nullopt;
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata;
|
||||
if (!unencrypted_metadata.ParseFromString(*str_opt)) return std::nullopt;
|
||||
|
||||
return NearbySharePrivateCertificate(
|
||||
static_cast<DeviceVisibility>(cert_data.visibility),
|
||||
absl::FromUnixNanos(cert_data.not_before),
|
||||
absl::FromUnixNanos(cert_data.not_after), std::move(key_pair),
|
||||
std::move(secret_key), std::move(metadata_encryption_key), std::move(id),
|
||||
std::move(unencrypted_metadata),
|
||||
StringToSalts(cert_data.consumed_salts));
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>>
|
||||
NearbySharePrivateCertificate::GenerateUnusedSalt() {
|
||||
if (consumed_salts_.size() >= kNearbyShareMaxNumMetadataEncryptionKeySalts) {
|
||||
NL_LOG(ERROR) << "All salts exhausted for certificate.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (size_t attempt = 0;
|
||||
attempt < kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries;
|
||||
++attempt) {
|
||||
std::vector<uint8_t> salt;
|
||||
if (next_salts_for_testing_.empty()) {
|
||||
salt = GenerateRandomBytes(2u);
|
||||
} else {
|
||||
salt = next_salts_for_testing_.front();
|
||||
next_salts_for_testing_.pop();
|
||||
}
|
||||
NL_DCHECK_EQ(2u, salt.size());
|
||||
|
||||
if (consumed_salts_.find(salt) == consumed_salts_.end()) {
|
||||
consumed_salts_.insert(salt);
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
|
||||
NL_LOG(ERROR) << "Salt generation exceeded max number of retries. This is "
|
||||
"highly improbable.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>>
|
||||
NearbySharePrivateCertificate::EncryptMetadata() const {
|
||||
// Init() keeps a reference to the input key, so that reference must outlive
|
||||
// the lifetime of |aead|.
|
||||
std::vector<uint8_t> derived_key = DeriveNearbyShareKey(
|
||||
metadata_encryption_key_, kNearbyShareNumBytesAesGcmKey);
|
||||
|
||||
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
|
||||
aead.Init(derived_key);
|
||||
|
||||
std::vector<uint8_t> metadata_array(unencrypted_metadata_.ByteSizeLong());
|
||||
unencrypted_metadata_.SerializeToArray(metadata_array.data(),
|
||||
metadata_array.size());
|
||||
|
||||
return aead.Seal(
|
||||
metadata_array,
|
||||
/*nonce=*/
|
||||
DeriveNearbyShareKey(as_bytes(absl::MakeSpan(secret_key_->key())),
|
||||
kNearbyShareNumBytesAesGcmIv),
|
||||
/*additional_data=*/absl::Span<const uint8_t>());
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,189 @@
|
||||
// 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_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/crypto_cros/ec_private_key.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/internal/api/private_certificate_data.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
#ifndef FRIEND_TEST_ALL_PREFIXES
|
||||
#define FRIEND_TEST(test_case_name, test_name) \
|
||||
friend class test_case_name##_##test_name##_Test
|
||||
|
||||
#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \
|
||||
FRIEND_TEST(test_case_name, test_name); \
|
||||
FRIEND_TEST(test_case_name, DISABLED_##test_name); \
|
||||
FRIEND_TEST(test_case_name, FLAKY_##test_name)
|
||||
#endif
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Stores metadata and crypto keys for the local device. This certificate
|
||||
// can be converted to a public certificate and sent to select contacts, who
|
||||
// will then use the certificate for authenticating the local device before
|
||||
// transferring data. Provides method for signing a payload during
|
||||
// authentication with a remote device. Provides method for encrypting the
|
||||
// metadata encryption key, which can then be advertised.
|
||||
class NearbySharePrivateCertificate {
|
||||
public:
|
||||
// Inverse operation of ToCertificateData(). Returns absl::nullopt if the
|
||||
// conversion is not successful
|
||||
static std::optional<NearbySharePrivateCertificate> FromCertificateData(
|
||||
const nearby::sharing::api::PrivateCertificateData& cert_data);
|
||||
|
||||
// Generates a random EC key pair, secret key, and metadata encryption
|
||||
// key. Derives the certificate ID from the secret key. Derives the
|
||||
// not-after time from |not_before| and the certificate validity period fixed
|
||||
// by the Nearby Share protocol. Visibility cannot be "no one".
|
||||
NearbySharePrivateCertificate(
|
||||
proto::DeviceVisibility visibility, absl::Time not_before,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata);
|
||||
|
||||
NearbySharePrivateCertificate(
|
||||
proto::DeviceVisibility visibility, absl::Time not_before,
|
||||
absl::Time not_after, std::unique_ptr<crypto::ECPrivateKey> key_pair,
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key,
|
||||
std::vector<uint8_t> metadata_encryption_key, std::vector<uint8_t> id,
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
|
||||
std::set<std::vector<uint8_t>> consumed_salts);
|
||||
|
||||
NearbySharePrivateCertificate(const NearbySharePrivateCertificate& other);
|
||||
NearbySharePrivateCertificate& operator=(
|
||||
const NearbySharePrivateCertificate& other);
|
||||
NearbySharePrivateCertificate(NearbySharePrivateCertificate&& other);
|
||||
NearbySharePrivateCertificate& operator=(
|
||||
NearbySharePrivateCertificate&& other);
|
||||
|
||||
virtual ~NearbySharePrivateCertificate();
|
||||
|
||||
const std::vector<uint8_t>& id() const { return id_; }
|
||||
proto::DeviceVisibility visibility() const { return visibility_; }
|
||||
absl::Time not_before() const { return not_before_; }
|
||||
absl::Time not_after() const { return not_after_; }
|
||||
const nearby::sharing::proto::EncryptedMetadata& unencrypted_metadata()
|
||||
const {
|
||||
return unencrypted_metadata_;
|
||||
}
|
||||
|
||||
// Encrypts |metadata_encryption_key_| with the |secret_key_|, using a
|
||||
// randomly generated 2-byte salt that has not already been consumed. Returns
|
||||
// std::nullopt if the encryption fails or if there are no remaining salts.
|
||||
// Note: Due to the generation and storage of an unconsumed salt, this method
|
||||
// is not thread safe.
|
||||
std::optional<NearbyShareEncryptedMetadataKey> EncryptMetadataKey();
|
||||
|
||||
// Signs the input |payload| with the private key from |key_pair_|. Returns
|
||||
// std::nullopt if the signing was unsuccessful.
|
||||
std::optional<std::vector<uint8_t>> Sign(
|
||||
absl::Span<const uint8_t> payload) const;
|
||||
|
||||
// Creates a hash of the |authentication_token|, using |secret_key_|. The use
|
||||
// of HKDF and the output vector size is part of the Nearby Share protocol and
|
||||
// conforms with the GmsCore implementation.
|
||||
std::vector<uint8_t> HashAuthenticationToken(
|
||||
absl::Span<const uint8_t> authentication_token) const;
|
||||
|
||||
// Converts this private certificate to a public certificate proto that can be
|
||||
// shared with select contacts. Returns std::nullopt if the conversion was
|
||||
// unsuccessful.
|
||||
std::optional<nearby::sharing::proto::PublicCertificate> ToPublicCertificate()
|
||||
const;
|
||||
|
||||
// Converts this private certificate to PrivateCertificateData for storage
|
||||
// in Prefs.
|
||||
nearby::sharing::api::PrivateCertificateData ToCertificateData() const;
|
||||
|
||||
// For testing only.
|
||||
std::queue<std::vector<uint8_t>>& next_salts_for_testing() {
|
||||
return next_salts_for_testing_;
|
||||
}
|
||||
std::optional<absl::Duration>& offset_for_testing() {
|
||||
return offset_for_testing_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Generates a random 2-byte salt used for encrypting the metadata encryption
|
||||
// key. Adds returned salt to |consumed_salts_|. Returns absl::nullopt if the
|
||||
// maximum number of salts have been exhausted or if an unconsumed salt cannot
|
||||
// be found in a fixed number of attempts, though this is highly improbably.
|
||||
// Note: This function is not thread safe.
|
||||
std::optional<std::vector<uint8_t>> GenerateUnusedSalt();
|
||||
|
||||
// Encrypts |unencrypted_metadata_| with the |metadata_encryption_key_|, using
|
||||
// the |secret_key_| as salt.
|
||||
std::optional<std::vector<uint8_t>> EncryptMetadata() const;
|
||||
|
||||
// Specifies which contacts can receive the public certificate corresponding
|
||||
// to this private certificate.
|
||||
proto::DeviceVisibility visibility_;
|
||||
|
||||
// The start and end times of the certificate's validity period. Note: An
|
||||
// offset is not yet applied to these values. To avoid issues with clock skew,
|
||||
// offsets should be applied during conversion to a public certificate.
|
||||
absl::Time not_before_;
|
||||
absl::Time not_after_;
|
||||
|
||||
// The public/private P-256 key pair used for verification/signing to ensure
|
||||
// secret of public certificates. The public key is included in the
|
||||
// public certificate, but the private key will never leave the device.
|
||||
std::unique_ptr<crypto::ECPrivateKey> key_pair_;
|
||||
|
||||
// A 32-byte AES key used, along with a salt, to encrypt the
|
||||
// |metadata_encryption_key_|, after which it can be safely advertised. Also,
|
||||
// used to generate an authentication token hash. Included in the public
|
||||
// certificate.
|
||||
std::unique_ptr<crypto::SymmetricKey> secret_key_;
|
||||
|
||||
// A 14-byte symmetric key used to encrypt |unencrypted_metadata_|. Not
|
||||
// included in the public certificate.
|
||||
std::vector<uint8_t> metadata_encryption_key_;
|
||||
|
||||
// An ID for the certificate, generated from the secret key.
|
||||
std::vector<uint8_t> id_;
|
||||
|
||||
// Unencrypted device metadata. The proto name is misleading; it holds data
|
||||
// that will eventually be serialized and encrypted.
|
||||
nearby::sharing::proto::EncryptedMetadata unencrypted_metadata_;
|
||||
|
||||
// The set of 2-byte salts already used to encrypt the metadata key.
|
||||
std::set<std::vector<uint8_t>> consumed_salts_;
|
||||
|
||||
// For testing only.
|
||||
std::queue<std::vector<uint8_t>> next_salts_for_testing_;
|
||||
std::optional<absl::Duration> offset_for_testing_;
|
||||
|
||||
FRIEND_TEST_ALL_PREFIXES(NearbySharePrivateCertificateTest, ToFromDictionary);
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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 "sharing/certificates/nearby_share_private_certificate.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/test_util.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/enums.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, Construction) {
|
||||
NearbySharePrivateCertificate private_certificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
GetNearbyShareTestNotBefore(), GetNearbyShareTestMetadata());
|
||||
EXPECT_EQ(kNearbyShareNumBytesCertificateId, private_certificate.id().size());
|
||||
EXPECT_EQ(DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
private_certificate.visibility());
|
||||
EXPECT_EQ(GetNearbyShareTestNotBefore(), private_certificate.not_before());
|
||||
EXPECT_EQ(
|
||||
GetNearbyShareTestNotBefore() + kNearbyShareCertificateValidityPeriod,
|
||||
private_certificate.not_after());
|
||||
EXPECT_EQ(GetNearbyShareTestMetadata().SerializeAsString(),
|
||||
private_certificate.unencrypted_metadata().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, ToFromDictionary) {
|
||||
NearbySharePrivateCertificate before(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
GetNearbyShareTestNotBefore(), GetNearbyShareTestMetadata());
|
||||
// Generate a few consumed salts.
|
||||
for (size_t i = 0; i < 5; ++i) ASSERT_TRUE(before.EncryptMetadataKey());
|
||||
|
||||
NearbySharePrivateCertificate after(
|
||||
*NearbySharePrivateCertificate::FromCertificateData(
|
||||
before.ToCertificateData()));
|
||||
|
||||
EXPECT_EQ(before.id(), after.id());
|
||||
EXPECT_EQ(before.visibility(), after.visibility());
|
||||
EXPECT_EQ(before.not_before(), after.not_before());
|
||||
EXPECT_EQ(before.not_after(), after.not_after());
|
||||
EXPECT_EQ(before.unencrypted_metadata().SerializeAsString(),
|
||||
after.unencrypted_metadata().SerializeAsString());
|
||||
EXPECT_EQ(before.secret_key_->key(), after.secret_key_->key());
|
||||
EXPECT_EQ(before.metadata_encryption_key_, after.metadata_encryption_key_);
|
||||
EXPECT_EQ(before.consumed_salts_, after.consumed_salts_);
|
||||
|
||||
std::vector<uint8_t> before_private_key, after_private_key;
|
||||
before.key_pair_->ExportPrivateKey(&before_private_key);
|
||||
after.key_pair_->ExportPrivateKey(&after_private_key);
|
||||
EXPECT_EQ(before_private_key, after_private_key);
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, EncryptMetadataKey) {
|
||||
NearbySharePrivateCertificate private_certificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
|
||||
GetNearbyShareTestNotBefore(), GetNearbyShareTestMetadata());
|
||||
std::optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
|
||||
private_certificate.EncryptMetadataKey();
|
||||
ASSERT_TRUE(encrypted_metadata_key);
|
||||
EXPECT_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt,
|
||||
encrypted_metadata_key->salt().size());
|
||||
EXPECT_EQ(kNearbyShareNumBytesMetadataEncryptionKey,
|
||||
encrypted_metadata_key->encrypted_key().size());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, EncryptMetadataKey_FixedData) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
std::optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
|
||||
private_certificate.EncryptMetadataKey();
|
||||
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().encrypted_key(),
|
||||
encrypted_metadata_key->encrypted_key());
|
||||
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().salt(),
|
||||
encrypted_metadata_key->salt());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest,
|
||||
EncryptMetadataKey_SaltsExhaustedFailure) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
for (size_t i = 0; i < kNearbyShareMaxNumMetadataEncryptionKeySalts; ++i) {
|
||||
EXPECT_TRUE(private_certificate.EncryptMetadataKey());
|
||||
}
|
||||
EXPECT_FALSE(private_certificate.EncryptMetadataKey());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest,
|
||||
EncryptMetadataKey_TooManySaltGenerationRetriesFailure) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
EXPECT_TRUE(private_certificate.EncryptMetadataKey());
|
||||
while (private_certificate.next_salts_for_testing().size() <
|
||||
kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries) {
|
||||
private_certificate.next_salts_for_testing().push(GetNearbyShareTestSalt());
|
||||
}
|
||||
EXPECT_FALSE(private_certificate.EncryptMetadataKey());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, PublicCertificateConversion) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
private_certificate.offset_for_testing() = GetNearbyShareTestValidityOffset();
|
||||
std::optional<nearby::sharing::proto::PublicCertificate> public_certificate =
|
||||
private_certificate.ToPublicCertificate();
|
||||
ASSERT_TRUE(public_certificate);
|
||||
EXPECT_EQ(GetNearbyShareTestPublicCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS)
|
||||
.SerializeAsString(),
|
||||
public_certificate->SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, EncryptDecryptRoundtrip) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
|
||||
std::optional<NearbyShareDecryptedPublicCertificate>
|
||||
decrypted_public_certificate =
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
*private_certificate.ToPublicCertificate(),
|
||||
*private_certificate.EncryptMetadataKey());
|
||||
ASSERT_TRUE(decrypted_public_certificate);
|
||||
EXPECT_EQ(
|
||||
private_certificate.unencrypted_metadata().SerializeAsString(),
|
||||
decrypted_public_certificate->unencrypted_metadata().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, SignVerifyRoundtrip) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
std::optional<std::vector<uint8_t>> signature =
|
||||
private_certificate.Sign(GetNearbyShareTestPayloadToSign());
|
||||
ASSERT_TRUE(signature);
|
||||
|
||||
std::optional<NearbyShareDecryptedPublicCertificate>
|
||||
decrypted_public_certificate =
|
||||
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
*private_certificate.ToPublicCertificate(),
|
||||
*private_certificate.EncryptMetadataKey());
|
||||
EXPECT_TRUE(decrypted_public_certificate->VerifySignature(
|
||||
GetNearbyShareTestPayloadToSign(), *signature));
|
||||
}
|
||||
|
||||
TEST(NearbySharePrivateCertificateTest, HashAuthenticationToken) {
|
||||
NearbySharePrivateCertificate private_certificate =
|
||||
GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS);
|
||||
EXPECT_EQ(GetNearbyShareTestPayloadHashUsingSecretKey(),
|
||||
private_certificate.HashAuthenticationToken(
|
||||
GetNearbyShareTestPayloadToSign()));
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,343 @@
|
||||
// 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 "sharing/certificates/test_util.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "internal/base/bluetooth_address.h"
|
||||
#include "sharing/certificates/common.h"
|
||||
#include "sharing/certificates/constants.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/proto/timestamp.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::proto::DeviceVisibility;
|
||||
|
||||
// Sample P-256 public and private keys from RFC 6979 A.2.5 in their respective
|
||||
// ASN.1 formats: SubjectPublicKeyInfo (RFC 5280) and PKCS #8 PrivateKeyInfo
|
||||
// (RFC 5208).
|
||||
constexpr uint8_t kTestPublicKeyBytes[] = {
|
||||
0x30, 0x59,
|
||||
// Begin AlgorithmIdentifier: ecPublicKey, prime256v1
|
||||
0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
|
||||
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
|
||||
// End AlgorithmIdentifier
|
||||
0x03, 0x42, 0x00, 0x04,
|
||||
// Public key bytes (Ux):
|
||||
0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31, 0xc9, 0x61, 0xeb, 0x74,
|
||||
0xc6, 0x35, 0x6d, 0x68, 0xc0, 0x49, 0xb8, 0x92, 0x3b, 0x61, 0xfa, 0x6c,
|
||||
0xe6, 0x69, 0x62, 0x2e, 0x60, 0xf2, 0x9f, 0xb6,
|
||||
// Public key bytes (Uy):
|
||||
0x79, 0x03, 0xfe, 0x10, 0x08, 0xb8, 0xbc, 0x99, 0xa4, 0x1a, 0xe9, 0xe9,
|
||||
0x56, 0x28, 0xbc, 0x64, 0xf2, 0xf1, 0xb2, 0x0c, 0x2d, 0x7e, 0x9f, 0x51,
|
||||
0x77, 0xa3, 0xc2, 0x94, 0xd4, 0x46, 0x22, 0x99};
|
||||
constexpr uint8_t kTestPrivateKeyBytes[] = {
|
||||
0x30, 0x81, 0x87, 0x02, 0x01, 0x00,
|
||||
// Begin AlgorithmIdentifier: ecPublicKey, prime256v1
|
||||
0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
|
||||
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
|
||||
// End AlgorithmIdentifier
|
||||
0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20,
|
||||
// Begin private key bytes
|
||||
0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, 0x6b, 0x5c, 0x21, 0x57,
|
||||
0x67, 0xb1, 0xd6, 0x93, 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12,
|
||||
0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21,
|
||||
// End private key bytes
|
||||
0xa1, 0x44, 0x03, 0x42, 0x00, 0x04,
|
||||
// Public key:
|
||||
0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31, 0xc9, 0x61, 0xeb, 0x74,
|
||||
0xc6, 0x35, 0x6d, 0x68, 0xc0, 0x49, 0xb8, 0x92, 0x3b, 0x61, 0xfa, 0x6c,
|
||||
0xe6, 0x69, 0x62, 0x2e, 0x60, 0xf2, 0x9f, 0xb6, 0x79, 0x03, 0xfe, 0x10,
|
||||
0x08, 0xb8, 0xbc, 0x99, 0xa4, 0x1a, 0xe9, 0xe9, 0x56, 0x28, 0xbc, 0x64,
|
||||
0xf2, 0xf1, 0xb2, 0x0c, 0x2d, 0x7e, 0x9f, 0x51, 0x77, 0xa3, 0xc2, 0x94,
|
||||
0xd4, 0x46, 0x22, 0x99};
|
||||
|
||||
constexpr uint8_t kTestSecretKey[] = {
|
||||
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae,
|
||||
0xf0, 0x85, 0x7d, 0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61,
|
||||
0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4};
|
||||
|
||||
constexpr uint8_t kTestCertificateId[] = {
|
||||
0xb9, 0x3c, 0x72, 0xb7, 0x4b, 0xc8, 0x48, 0x7d, 0x29, 0x82, 0x70,
|
||||
0x05, 0xf8, 0x0d, 0x63, 0x59, 0x18, 0xf9, 0x1b, 0xc2, 0x2b, 0x14,
|
||||
0xd7, 0xed, 0x05, 0x71, 0x4d, 0x58, 0xf9, 0x67, 0x02, 0xdd};
|
||||
|
||||
constexpr uint8_t kTestMetadataEncryptionKey[] = {0x60, 0x1e, 0xc3, 0x13, 0x77,
|
||||
0x57, 0x89, 0xa5, 0xb7, 0xa7,
|
||||
0xf5, 0x04, 0xbb, 0xf3};
|
||||
|
||||
constexpr uint8_t kTestMetadataEncryptionKeyTag[] = {
|
||||
0x51, 0x9b, 0x16, 0xd8, 0x91, 0xb4, 0x0d, 0x81, 0x11, 0x21, 0xe3,
|
||||
0x70, 0x42, 0x80, 0x8f, 0x87, 0x23, 0x6a, 0x84, 0x9b, 0xcd, 0xac,
|
||||
0xbc, 0xe3, 0x54, 0xd7, 0xff, 0x53, 0xdf, 0x5d, 0x8a, 0xda};
|
||||
|
||||
constexpr uint8_t kTestSalt[] = {0xf0, 0xf1};
|
||||
|
||||
constexpr uint8_t kTestEncryptedMetadataKey[] = {0x52, 0x0e, 0x7e, 0x6b, 0x8e,
|
||||
0xb5, 0x40, 0xe8, 0xe2, 0xbd,
|
||||
0xa0, 0xee, 0x9d, 0x7b};
|
||||
|
||||
constexpr uint8_t kTestEncryptedMetadata[] = {
|
||||
0x4d, 0x59, 0x5d, 0xb6, 0xac, 0x70, 0x00, 0x8f, 0x32, 0x9d, 0x0d, 0xcf,
|
||||
0xc3, 0x8b, 0x01, 0x19, 0x1d, 0xad, 0x2e, 0xb4, 0x62, 0xec, 0xf3, 0xa5,
|
||||
0xe4, 0x89, 0x51, 0x37, 0x0d, 0x78, 0xad, 0x9d, 0x2e, 0xe5, 0x99, 0xd5,
|
||||
0xf7, 0x1d, 0x71, 0x47, 0xef, 0x33, 0xae, 0x4b, 0xe2, 0xda, 0x57, 0xfb,
|
||||
0x3c, 0xa9, 0x1b, 0xbb, 0x00, 0x67, 0x99, 0xf3, 0xa4, 0x03, 0xab, 0x73,
|
||||
0xe5, 0x1a, 0xf6, 0x5c, 0x5f, 0x15, 0xa0, 0x00, 0xa5, 0x41, 0xf9};
|
||||
|
||||
// Plaintext "sample" (from RFC 6979 A.2.5).
|
||||
constexpr uint8_t kTestPayloadToSign[] = {0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65};
|
||||
|
||||
// One possible signature (from RFC 6979 A.2.5).
|
||||
constexpr uint8_t kTestSampleSignature[] = {
|
||||
0x30,
|
||||
0x46, // length of remaining data
|
||||
0x02,
|
||||
0x21, // length of r
|
||||
// begin r (note 0x00 padding since leading bit of 0xEF is 1)
|
||||
0x00, 0xEF, 0xD4, 0x8B, 0x2A, 0xAC, 0xB6, 0xA8, 0xFD, 0x11, 0x40, 0xDD,
|
||||
0x9C, 0xD4, 0x5E, 0x81, 0xD6, 0x9D, 0x2C, 0x87, 0x7B, 0x56, 0xAA, 0xF9,
|
||||
0x91, 0xC3, 0x4D, 0x0E, 0xA8, 0x4E, 0xAF, 0x37, 0x16,
|
||||
// end r
|
||||
0x02,
|
||||
0x21, // length of s
|
||||
// begin s (note 0x00 padding since leading bit of 0xF7 is 1)
|
||||
0x00, 0xF7, 0xCB, 0x1C, 0x94, 0x2D, 0x65, 0x7C, 0x41, 0xD4, 0x36, 0xC7,
|
||||
0xA1, 0xB6, 0xE2, 0x9F, 0x65, 0xF3, 0xE9, 0x00, 0xDB, 0xB9, 0xAF, 0xF4,
|
||||
0x06, 0x4D, 0xC4, 0xAB, 0x2F, 0x84, 0x3A, 0xCD, 0xA8
|
||||
// end s
|
||||
};
|
||||
|
||||
// The result of HKDF of kTestPayloadToSign, using kTestSecretKey as salt. A
|
||||
// trivial info parameter is used, and the output length is fixed to be
|
||||
// kNearbyShareNumBytesAuthenticationTokenHash.
|
||||
constexpr uint8_t kTestPayloadHashUsingSecretKey[] = {0xE2, 0xCB, 0x90,
|
||||
0x58, 0xDE, 0x3A};
|
||||
|
||||
constexpr int64_t kTestNotBeforeMillis = 1881702000000;
|
||||
|
||||
constexpr int64_t kTestValidityOffsetMillis = 1800000; // 30 minutes
|
||||
|
||||
} // namespace
|
||||
|
||||
// Do not change. Values align with kTestEncryptedMetadata.
|
||||
constexpr char kTestDeviceName[] = "device_name";
|
||||
constexpr char kTestMetadataFullName[] = "full_name";
|
||||
constexpr char kTestMetadataIconUrl[] = "icon_url";
|
||||
constexpr char kTestMetadataAccountName[] = "foo@bar.org";
|
||||
constexpr char kTestUnparsedBluetoothMacAddress[] = "4E:65:61:72:62:79";
|
||||
|
||||
std::unique_ptr<crypto::ECPrivateKey> GetNearbyShareTestP256KeyPair() {
|
||||
return crypto::ECPrivateKey::CreateFromPrivateKeyInfo(kTestPrivateKeyBytes);
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestP256PublicKey() {
|
||||
static std::vector<uint8_t>* public_key = new std::vector<uint8_t>(
|
||||
std::begin(kTestPublicKeyBytes), std::end(kTestPublicKeyBytes));
|
||||
return *public_key;
|
||||
}
|
||||
|
||||
std::unique_ptr<crypto::SymmetricKey> GetNearbyShareTestSecretKey() {
|
||||
return crypto::SymmetricKey::Import(
|
||||
crypto::SymmetricKey::Algorithm::AES,
|
||||
std::string(reinterpret_cast<const char*>(kTestSecretKey),
|
||||
kNearbyShareNumBytesSecretKey));
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestCertificateId() {
|
||||
static std::vector<uint8_t>* id = new std::vector<uint8_t>(
|
||||
std::begin(kTestCertificateId), std::end(kTestCertificateId));
|
||||
return *id;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKey() {
|
||||
static const std::vector<uint8_t>* metadata_encryption_key =
|
||||
new std::vector<uint8_t>(kTestMetadataEncryptionKey,
|
||||
kTestMetadataEncryptionKey +
|
||||
kNearbyShareNumBytesMetadataEncryptionKey);
|
||||
return *metadata_encryption_key;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKeyTag() {
|
||||
static const std::vector<uint8_t>* tag =
|
||||
new std::vector<uint8_t>(std::begin(kTestMetadataEncryptionKeyTag),
|
||||
std::end(kTestMetadataEncryptionKeyTag));
|
||||
return *tag;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestSalt() {
|
||||
static const std::vector<uint8_t>* salt =
|
||||
new std::vector<uint8_t>(std::begin(kTestSalt), std::end(kTestSalt));
|
||||
return *salt;
|
||||
}
|
||||
|
||||
const NearbyShareEncryptedMetadataKey&
|
||||
GetNearbyShareTestEncryptedMetadataKey() {
|
||||
static const NearbyShareEncryptedMetadataKey* encrypted_metadata_key =
|
||||
new NearbyShareEncryptedMetadataKey(
|
||||
GetNearbyShareTestSalt(),
|
||||
std::vector<uint8_t>(std::begin(kTestEncryptedMetadataKey),
|
||||
std::end(kTestEncryptedMetadataKey)));
|
||||
return *encrypted_metadata_key;
|
||||
}
|
||||
|
||||
absl::Time GetNearbyShareTestNotBefore() {
|
||||
static const absl::Time not_before = FromJavaTime(kTestNotBeforeMillis);
|
||||
return not_before;
|
||||
}
|
||||
|
||||
absl::Duration GetNearbyShareTestValidityOffset() {
|
||||
static const absl::Duration offset =
|
||||
absl::Milliseconds(kTestValidityOffsetMillis);
|
||||
return offset;
|
||||
}
|
||||
|
||||
const nearby::sharing::proto::EncryptedMetadata& GetNearbyShareTestMetadata() {
|
||||
static const nearby::sharing::proto::EncryptedMetadata* metadata =
|
||||
new nearby::sharing::proto::EncryptedMetadata([] {
|
||||
std::array<uint8_t, 6> bytes;
|
||||
nearby::device::ParseBluetoothAddress(kTestUnparsedBluetoothMacAddress,
|
||||
absl::MakeSpan(bytes.data(), 6));
|
||||
|
||||
nearby::sharing::proto::EncryptedMetadata metadata;
|
||||
metadata.set_device_name(kTestDeviceName);
|
||||
metadata.set_full_name(kTestMetadataFullName);
|
||||
metadata.set_icon_url(kTestMetadataIconUrl);
|
||||
metadata.set_account_name(kTestMetadataAccountName);
|
||||
|
||||
metadata.set_bluetooth_mac_address(bytes.data(), 6u);
|
||||
return metadata;
|
||||
}());
|
||||
return *metadata;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestEncryptedMetadata() {
|
||||
static const std::vector<uint8_t>* bytes = new std::vector<uint8_t>(
|
||||
std::begin(kTestEncryptedMetadata), std::end(kTestEncryptedMetadata));
|
||||
return *bytes;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestPayloadToSign() {
|
||||
static const std::vector<uint8_t>* payload = new std::vector<uint8_t>(
|
||||
std::begin(kTestPayloadToSign), std::end(kTestPayloadToSign));
|
||||
return *payload;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestSampleSignature() {
|
||||
static const std::vector<uint8_t>* signature = new std::vector<uint8_t>(
|
||||
std::begin(kTestSampleSignature), std::end(kTestSampleSignature));
|
||||
return *signature;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestPayloadHashUsingSecretKey() {
|
||||
static const std::vector<uint8_t>* hash =
|
||||
new std::vector<uint8_t>(std::begin(kTestPayloadHashUsingSecretKey),
|
||||
std::end(kTestPayloadHashUsingSecretKey));
|
||||
return *hash;
|
||||
}
|
||||
|
||||
NearbySharePrivateCertificate GetNearbyShareTestPrivateCertificate(
|
||||
DeviceVisibility visibility, absl::Time not_before) {
|
||||
NearbySharePrivateCertificate cert(
|
||||
visibility, not_before,
|
||||
not_before + kNearbyShareCertificateValidityPeriod,
|
||||
GetNearbyShareTestP256KeyPair(), GetNearbyShareTestSecretKey(),
|
||||
GetNearbyShareTestMetadataEncryptionKey(),
|
||||
GetNearbyShareTestCertificateId(), GetNearbyShareTestMetadata(),
|
||||
/*consumed_salts=*/std::set<std::vector<uint8_t>>());
|
||||
cert.next_salts_for_testing().push(GetNearbyShareTestSalt());
|
||||
return cert;
|
||||
}
|
||||
|
||||
nearby::sharing::proto::PublicCertificate GetNearbyShareTestPublicCertificate(
|
||||
DeviceVisibility visibility, absl::Time not_before) {
|
||||
nearby::sharing::proto::PublicCertificate cert;
|
||||
cert.set_secret_id(std::string(GetNearbyShareTestCertificateId().begin(),
|
||||
GetNearbyShareTestCertificateId().end()));
|
||||
cert.set_secret_key(GetNearbyShareTestSecretKey()->key());
|
||||
cert.set_public_key(std::string(GetNearbyShareTestP256PublicKey().begin(),
|
||||
GetNearbyShareTestP256PublicKey().end()));
|
||||
cert.mutable_start_time()->set_seconds(
|
||||
ToJavaTime(not_before - GetNearbyShareTestValidityOffset()) / 1000);
|
||||
cert.mutable_end_time()->set_seconds(
|
||||
ToJavaTime(not_before + kNearbyShareCertificateValidityPeriod +
|
||||
GetNearbyShareTestValidityOffset()) /
|
||||
1000);
|
||||
cert.set_for_selected_contacts(
|
||||
visibility == DeviceVisibility::DEVICE_VISIBILITY_SELECTED_CONTACTS);
|
||||
cert.set_metadata_encryption_key(
|
||||
std::string(GetNearbyShareTestMetadataEncryptionKey().begin(),
|
||||
GetNearbyShareTestMetadataEncryptionKey().end()));
|
||||
cert.set_encrypted_metadata_bytes(
|
||||
std::string(GetNearbyShareTestEncryptedMetadata().begin(),
|
||||
GetNearbyShareTestEncryptedMetadata().end()));
|
||||
cert.set_metadata_encryption_key_tag(
|
||||
std::string(GetNearbyShareTestMetadataEncryptionKeyTag().begin(),
|
||||
GetNearbyShareTestMetadataEncryptionKeyTag().end()));
|
||||
return cert;
|
||||
}
|
||||
|
||||
std::vector<NearbySharePrivateCertificate>
|
||||
GetNearbyShareTestPrivateCertificateList(DeviceVisibility visibility) {
|
||||
std::vector<NearbySharePrivateCertificate> list;
|
||||
for (size_t i = 0; i < kNearbyShareNumPrivateCertificates; ++i) {
|
||||
list.push_back(GetNearbyShareTestPrivateCertificate(
|
||||
visibility, GetNearbyShareTestNotBefore() +
|
||||
i * kNearbyShareCertificateValidityPeriod));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
GetNearbyShareTestPublicCertificateList(DeviceVisibility visibility) {
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> list;
|
||||
for (size_t i = 0; i < kNearbyShareNumPrivateCertificates; ++i) {
|
||||
list.push_back(GetNearbyShareTestPublicCertificate(
|
||||
visibility, GetNearbyShareTestNotBefore() +
|
||||
i * kNearbyShareCertificateValidityPeriod));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
const NearbyShareDecryptedPublicCertificate&
|
||||
GetNearbyShareTestDecryptedPublicCertificate() {
|
||||
static const NearbyShareDecryptedPublicCertificate* cert =
|
||||
new NearbyShareDecryptedPublicCertificate(
|
||||
*NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
|
||||
GetNearbyShareTestPublicCertificate(
|
||||
DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS),
|
||||
GetNearbyShareTestEncryptedMetadataKey()));
|
||||
return *cert;
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 LOCATION_NEARBY_CPP_SHARING_IMPLEMENTATION_CERTIFICATES_TEST_UTIL_H_
|
||||
#define LOCATION_NEARBY_CPP_SHARING_IMPLEMENTATION_CERTIFICATES_TEST_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/crypto_cros/ec_private_key.h"
|
||||
#include "internal/crypto_cros/symmetric_key.h"
|
||||
#include "sharing/certificates/nearby_share_decrypted_public_certificate.h"
|
||||
#include "sharing/certificates/nearby_share_encrypted_metadata_key.h"
|
||||
#include "sharing/certificates/nearby_share_private_certificate.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/encrypted_metadata.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
extern const char kTestMetadataFullName[];
|
||||
extern const char kTestMetadataIconUrl[];
|
||||
extern const char kTestMetadataAccountName[];
|
||||
|
||||
// Test Bluetooth MAC address in the format "XX:XX:XX:XX:XX:XX".
|
||||
extern const char kTestUnparsedBluetoothMacAddress[];
|
||||
|
||||
std::unique_ptr<crypto::ECPrivateKey> GetNearbyShareTestP256KeyPair();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestP256PublicKey();
|
||||
|
||||
std::unique_ptr<crypto::SymmetricKey> GetNearbyShareTestSecretKey();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestCertificateId();
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKey();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKeyTag();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestSalt();
|
||||
const NearbyShareEncryptedMetadataKey& GetNearbyShareTestEncryptedMetadataKey();
|
||||
|
||||
absl::Time GetNearbyShareTestNotBefore();
|
||||
absl::Duration GetNearbyShareTestValidityOffset();
|
||||
|
||||
const nearby::sharing::proto::EncryptedMetadata& GetNearbyShareTestMetadata();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestEncryptedMetadata();
|
||||
|
||||
const std::vector<uint8_t>& GetNearbyShareTestPayloadToSign();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestSampleSignature();
|
||||
const std::vector<uint8_t>& GetNearbyShareTestPayloadHashUsingSecretKey();
|
||||
|
||||
NearbySharePrivateCertificate GetNearbyShareTestPrivateCertificate(
|
||||
proto::DeviceVisibility visibility,
|
||||
absl::Time not_before = GetNearbyShareTestNotBefore());
|
||||
nearby::sharing::proto::PublicCertificate GetNearbyShareTestPublicCertificate(
|
||||
proto::DeviceVisibility visibility,
|
||||
absl::Time not_before = GetNearbyShareTestNotBefore());
|
||||
|
||||
// Returns a list of |kNearbyShareNumPrivateCertificates| private/public
|
||||
// certificates, spanning contiguous validity periods.
|
||||
std::vector<NearbySharePrivateCertificate>
|
||||
GetNearbyShareTestPrivateCertificateList(proto::DeviceVisibility visibility);
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
GetNearbyShareTestPublicCertificateList(proto::DeviceVisibility visibility);
|
||||
|
||||
const NearbyShareDecryptedPublicCertificate&
|
||||
GetNearbyShareTestDecryptedPublicCertificate();
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // LOCATION_NEARBY_CPP_SHARING_IMPLEMENTATION_CERTIFICATES_TEST_UTIL_H_
|
||||
@@ -0,0 +1,87 @@
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "contacts",
|
||||
srcs = [
|
||||
"nearby_share_contact_manager.cc",
|
||||
"nearby_share_contact_manager_impl.cc",
|
||||
"nearby_share_contacts_sorter.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"nearby_share_contact_manager.h",
|
||||
"nearby_share_contact_manager_impl.h",
|
||||
"nearby_share_contacts_sorter.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/base",
|
||||
"//internal/crypto_cros",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation:types",
|
||||
"//sharing/common",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/base",
|
||||
"//sharing/internal/public:logging",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/local_device_data",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/functional:bind_front",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_protobuf//:protobuf_lite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "test_support",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"fake_nearby_share_contact_manager.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"fake_nearby_share_contact_manager.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":contacts",
|
||||
"//internal/platform/implementation:types",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/local_device_data",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "contacts_test",
|
||||
srcs = [
|
||||
"nearby_share_contact_manager_impl_test.cc",
|
||||
"nearby_share_contacts_sorter_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":contacts",
|
||||
"//internal/platform/implementation:types",
|
||||
"//internal/platform/implementation/g3", # fixdeps: keep
|
||||
"//internal/test",
|
||||
"//sharing/common",
|
||||
"//sharing/internal/api:mock_sharing_platform",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/test:nearby_test",
|
||||
"//sharing/local_device_data:test_support",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"//sharing/scheduling:test_support",
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021-2023 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 "sharing/contacts/fake_nearby_share_contact_manager.h"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
FakeNearbyShareContactManager::Factory::Factory() = default;
|
||||
|
||||
FakeNearbyShareContactManager::Factory::~Factory() = default;
|
||||
|
||||
std::unique_ptr<NearbyShareContactManager>
|
||||
FakeNearbyShareContactManager::Factory::CreateInstance(
|
||||
Context* context, AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager) {
|
||||
latest_nearby_client_factory_ = nearby_client_factory;
|
||||
latest_local_device_data_manager_ = local_device_data_manager;
|
||||
latest_account_manager_ = &account_manager;
|
||||
|
||||
auto instance = std::make_unique<FakeNearbyShareContactManager>();
|
||||
instances_.push_back(instance.get());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
FakeNearbyShareContactManager::FakeNearbyShareContactManager() = default;
|
||||
|
||||
FakeNearbyShareContactManager::~FakeNearbyShareContactManager() = default;
|
||||
|
||||
void FakeNearbyShareContactManager::DownloadContacts() {
|
||||
++num_download_contacts_calls_;
|
||||
}
|
||||
|
||||
void FakeNearbyShareContactManager::SetAllowedContacts(
|
||||
const std::set<std::string>& allowed_contact_ids) {
|
||||
set_allowed_contacts_calls_.push_back(allowed_contact_ids);
|
||||
}
|
||||
|
||||
void FakeNearbyShareContactManager::OnStart() {}
|
||||
|
||||
void FakeNearbyShareContactManager::OnStop() {}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021-2023 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_SHARING_CONTACTS_FAKE_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CONTACTS_FAKE_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager_impl.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// A fake implementation of NearbyShareContactManager, along with a fake
|
||||
// factory, to be used in tests. Stores parameters input into
|
||||
// NearbyShareContactManager method calls. Use the notification methods from the
|
||||
// base class--NotifyContactsDownloaded() and NotifyContactsUploaded()--to alert
|
||||
// observers of changes; these methods are made public in this fake class.
|
||||
class FakeNearbyShareContactManager : public NearbyShareContactManager {
|
||||
public:
|
||||
// Factory that creates FakeNearbyShareContactManager instances. Use in
|
||||
// NearbyShareContactManagerImpl::Factor::SetFactoryForTesting() in unit
|
||||
// tests.
|
||||
class Factory : public NearbyShareContactManagerImpl::Factory {
|
||||
public:
|
||||
Factory();
|
||||
~Factory() override;
|
||||
|
||||
// Returns all FakeNearbyShareContactManager instances created by
|
||||
// CreateInstance().
|
||||
std::vector<FakeNearbyShareContactManager*>& instances() {
|
||||
return instances_;
|
||||
}
|
||||
|
||||
nearby::sharing::api::SharingRpcClientFactory* latest_http_client_factory()
|
||||
const {
|
||||
return latest_nearby_client_factory_;
|
||||
}
|
||||
|
||||
NearbyShareLocalDeviceDataManager* latest_local_device_data_manager()
|
||||
const {
|
||||
return latest_local_device_data_manager_;
|
||||
}
|
||||
|
||||
AccountManager* latest_account_manager() const {
|
||||
return latest_account_manager_;
|
||||
}
|
||||
|
||||
private:
|
||||
// NearbyShareContactManagerImpl::Factory:
|
||||
std::unique_ptr<NearbyShareContactManager> CreateInstance(
|
||||
Context* context, AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager) override;
|
||||
|
||||
std::vector<FakeNearbyShareContactManager*> instances_;
|
||||
nearby::sharing::api::SharingRpcClientFactory*
|
||||
latest_nearby_client_factory_ = nullptr;
|
||||
NearbyShareLocalDeviceDataManager* latest_local_device_data_manager_ =
|
||||
nullptr;
|
||||
AccountManager* latest_account_manager_ = nullptr;
|
||||
};
|
||||
|
||||
FakeNearbyShareContactManager();
|
||||
~FakeNearbyShareContactManager() override;
|
||||
|
||||
size_t num_download_contacts_calls() const {
|
||||
return num_download_contacts_calls_;
|
||||
}
|
||||
|
||||
// Returns inputs of all SetAllowedContacts() calls.
|
||||
const std::vector<std::set<std::string>>& set_allowed_contacts_calls() const {
|
||||
return set_allowed_contacts_calls_;
|
||||
}
|
||||
|
||||
// Make protected methods from base class public in this fake class.
|
||||
using NearbyShareContactManager::NotifyContactsDownloaded;
|
||||
using NearbyShareContactManager::NotifyContactsUploaded;
|
||||
|
||||
private:
|
||||
// NearbyShareContactsManager:
|
||||
void DownloadContacts() override;
|
||||
void SetAllowedContacts(
|
||||
const std::set<std::string>& allowed_contact_ids) override;
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
|
||||
size_t num_download_contacts_calls_ = 0;
|
||||
std::vector<std::set<std::string>> set_allowed_contacts_calls_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CONTACTS_FAKE_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021-2023 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 "sharing/contacts/nearby_share_contact_manager.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
NearbyShareContactManager::NearbyShareContactManager() = default;
|
||||
|
||||
NearbyShareContactManager::~NearbyShareContactManager() = default;
|
||||
|
||||
void NearbyShareContactManager::AddObserver(Observer* observer) {
|
||||
observers_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareContactManager::RemoveObserver(Observer* observer) {
|
||||
observers_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareContactManager::Start() {
|
||||
if (is_running_) return;
|
||||
|
||||
is_running_ = true;
|
||||
OnStart();
|
||||
}
|
||||
|
||||
void NearbyShareContactManager::Stop() {
|
||||
if (!is_running_) return;
|
||||
|
||||
is_running_ = false;
|
||||
OnStop();
|
||||
}
|
||||
|
||||
void NearbyShareContactManager::NotifyContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) {
|
||||
for (Observer* observer : observers_.GetObservers()) {
|
||||
observer->OnContactsDownloaded(allowed_contact_ids, contacts,
|
||||
num_unreachable_contacts_filtered_out);
|
||||
}
|
||||
}
|
||||
|
||||
void NearbyShareContactManager::NotifyContactsUploaded(
|
||||
bool did_contacts_change_since_last_upload) {
|
||||
for (Observer* observer : observers_.GetObservers()) {
|
||||
observer->OnContactsUploaded(did_contacts_change_since_last_upload);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2021-2023 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_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/base/observer_list.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// The Nearby Share contacts manager interfaces with the Nearby server in the
|
||||
// following ways:
|
||||
// 1) The user's contacts are downloaded from People API, using the Nearby
|
||||
// server as a proxy.
|
||||
// 2) All the user's contacts are uploaded to Nearby server, along with an
|
||||
// indication of what contacts are allowed for selected-contacts visibility
|
||||
// mode. The Nearby server will distribute all-contacts and selected-contacts
|
||||
// visibility certificates accordingly. For privacy reasons, the Nearby server
|
||||
// needs to explicitly receive the list of contacts from the device instead of
|
||||
// pulling them directly from People API.
|
||||
//
|
||||
// All contact data and update notifications are conveyed via observer methods;
|
||||
// the manager does not return data directly from function calls.
|
||||
class NearbyShareContactManager {
|
||||
public:
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void OnContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) = 0;
|
||||
virtual void OnContactsUploaded(
|
||||
bool did_contacts_change_since_last_upload) = 0;
|
||||
};
|
||||
|
||||
NearbyShareContactManager();
|
||||
virtual ~NearbyShareContactManager();
|
||||
|
||||
void AddObserver(Observer* observer);
|
||||
void RemoveObserver(Observer* observer);
|
||||
|
||||
// Starts/Stops contact task scheduling.
|
||||
void Start();
|
||||
void Stop();
|
||||
bool is_running() { return is_running_; }
|
||||
|
||||
// nearby_share::mojom::ContactManager:
|
||||
// Downloads the user's contact list from the server. The locally persisted
|
||||
// list of allowed contacts is reconciled with the newly downloaded contacts.
|
||||
// If the user's contact list or the allowlist has changed since the last
|
||||
// successful contacts upload to the Nearby Share server, via the UpdateDevice
|
||||
// RPC, an upload is requested. Contact downloads (and uploads if necessary)
|
||||
// are also scheduled periodically. The results are sent to observers via
|
||||
// OnContactsDownloaded(), and if an upload occurs, observers are notified via
|
||||
// OnContactsUploaded().
|
||||
virtual void DownloadContacts() = 0;
|
||||
|
||||
// Assigns the set of contacts that the local device allows sharing with when
|
||||
// in selected-contacts visibility mode. (Note: This set is irrelevant for
|
||||
// all-contact visibility mode.) The allowed contact list determines what
|
||||
// contacts receive the local device's "selected-contacts" visibility public
|
||||
// certificates. Changes to the allowlist will trigger RPC calls to upload the
|
||||
// new allowlist to the Nearby Share server.
|
||||
virtual void SetAllowedContacts(
|
||||
const std::set<std::string>& allowed_contact_ids) = 0;
|
||||
|
||||
protected:
|
||||
virtual void OnStart() = 0;
|
||||
virtual void OnStop() = 0;
|
||||
|
||||
void NotifyContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out);
|
||||
void NotifyContactsUploaded(bool did_contacts_change_since_last_upload);
|
||||
|
||||
private:
|
||||
bool is_running_ = false;
|
||||
ObserverList<Observer> observers_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_H_
|
||||
@@ -0,0 +1,442 @@
|
||||
// Copyright 2023 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 "sharing/contacts/nearby_share_contact_manager_impl.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/functional/bind_front.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/crypto_cros/secure_hash.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/contacts/nearby_share_contacts_sorter.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/base/encode.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager_impl.h"
|
||||
#include "sharing/proto/contact_rpc.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::api::PreferenceManager;
|
||||
using ::nearby::sharing::proto::Contact;
|
||||
using ::nearby::sharing::proto::ContactRecord;
|
||||
using ::nearby::sharing::proto::ListContactPeopleRequest;
|
||||
using ::nearby::sharing::proto::ListContactPeopleResponse;
|
||||
|
||||
constexpr absl::Duration kContactUploadPeriod = absl::Hours(24);
|
||||
constexpr absl::Duration kContactDownloadPeriod = absl::Hours(12);
|
||||
|
||||
// Removes contact IDs from the allowlist if they are not in |contacts|.
|
||||
std::set<std::string> RemoveNonexistentContactsFromAllowlist(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& contacts) {
|
||||
std::set<std::string> new_allowed_contact_ids;
|
||||
for (const ContactRecord& contact : contacts) {
|
||||
if (allowed_contact_ids.find(contact.id()) != allowed_contact_ids.end())
|
||||
new_allowed_contact_ids.insert(contact.id());
|
||||
}
|
||||
return new_allowed_contact_ids;
|
||||
}
|
||||
|
||||
// Converts a list of ContactRecord protos, along with the allowlist, into a
|
||||
// list of Contact protos.
|
||||
std::vector<Contact> ContactRecordsToContacts(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& contact_records) {
|
||||
std::vector<Contact> contacts;
|
||||
for (const ContactRecord& contact_record : contact_records) {
|
||||
bool is_selected = allowed_contact_ids.find(contact_record.id()) !=
|
||||
allowed_contact_ids.end();
|
||||
for (const proto::Contact_Identifier& identifier :
|
||||
contact_record.identifiers()) {
|
||||
Contact contact;
|
||||
*contact.mutable_identifier() = identifier;
|
||||
contact.set_is_selected(is_selected);
|
||||
contacts.push_back(contact);
|
||||
}
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
Contact CreateLocalContact(absl::string_view profile_user_name) {
|
||||
Contact contact;
|
||||
contact.mutable_identifier()->set_account_name(
|
||||
std::string(profile_user_name));
|
||||
// Always consider your own account a selected contact.
|
||||
contact.set_is_selected(true);
|
||||
contact.set_is_self(true);
|
||||
return contact;
|
||||
}
|
||||
|
||||
// Creates a hex-encoded hash of the contact data, implicitly including the
|
||||
// allowlist, to be sent to the Nearby Share server. This hash is persisted and
|
||||
// used to detect any changes to the user's contact list or allowlist since the
|
||||
// last successful upload to the server. The hash is invariant under the
|
||||
// ordering of |contacts|.
|
||||
std::string ComputeHash(const std::vector<Contact>& contacts) {
|
||||
// To ensure that the hash is invariant under ordering of input |contacts|,
|
||||
// add all serialized protos to an ordered set. Then, incrementally calculate
|
||||
// the hash as we iterate through the set.
|
||||
std::set<std::string> serialized_contacts_set;
|
||||
for (const Contact& contact : contacts) {
|
||||
serialized_contacts_set.insert(contact.SerializeAsString());
|
||||
}
|
||||
|
||||
std::unique_ptr<crypto::SecureHash> hasher =
|
||||
crypto::SecureHash::Create(crypto::SecureHash::Algorithm::SHA256);
|
||||
for (const std::string& serialized_contact : serialized_contacts_set) {
|
||||
hasher->Update(serialized_contact.data(), serialized_contact.size());
|
||||
}
|
||||
std::vector<uint8_t> hash(hasher->GetHashLength());
|
||||
hasher->Finish(hash.data(), hash.size());
|
||||
|
||||
return nearby::utils::HexEncode(hash);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
NearbyShareContactManagerImpl::Factory*
|
||||
NearbyShareContactManagerImpl::Factory::test_factory_ = nullptr;
|
||||
|
||||
// static
|
||||
std::unique_ptr<NearbyShareContactManager>
|
||||
NearbyShareContactManagerImpl::Factory::Create(
|
||||
Context* context, PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager) {
|
||||
if (test_factory_) {
|
||||
return test_factory_->CreateInstance(context, account_manager,
|
||||
nearby_client_factory,
|
||||
local_device_data_manager);
|
||||
}
|
||||
|
||||
return absl::WrapUnique(new NearbyShareContactManagerImpl(
|
||||
context, preference_manager, account_manager, nearby_client_factory,
|
||||
local_device_data_manager));
|
||||
}
|
||||
|
||||
// static
|
||||
void NearbyShareContactManagerImpl::Factory::SetFactoryForTesting(
|
||||
Factory* test_factory) {
|
||||
test_factory_ = test_factory;
|
||||
}
|
||||
|
||||
NearbyShareContactManagerImpl::Factory::~Factory() = default;
|
||||
|
||||
NearbyShareContactManagerImpl::NearbyShareContactManagerImpl(
|
||||
Context* context, PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager)
|
||||
: preference_manager_(preference_manager),
|
||||
account_manager_(account_manager),
|
||||
nearby_client_factory_(nearby_client_factory),
|
||||
nearby_share_client_(nearby_client_factory_->CreateInstance()),
|
||||
local_device_data_manager_(local_device_data_manager),
|
||||
periodic_contact_upload_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreatePeriodicScheduler(
|
||||
context, preference_manager_, kContactUploadPeriod,
|
||||
/*retry_failures=*/false,
|
||||
/*require_connectivity=*/true,
|
||||
prefs::kNearbySharingSchedulerPeriodicContactUploadName,
|
||||
[&] { OnPeriodicContactsUploadRequested(); })),
|
||||
contact_download_and_upload_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreatePeriodicScheduler(
|
||||
context, preference_manager_, kContactDownloadPeriod,
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/true,
|
||||
prefs::kNearbySharingSchedulerContactDownloadAndUploadName,
|
||||
[&] { DownloadContacts(); })),
|
||||
executor_(context->CreateSequencedTaskRunner()) {}
|
||||
|
||||
NearbyShareContactManagerImpl::~NearbyShareContactManagerImpl() = default;
|
||||
|
||||
void NearbyShareContactManagerImpl::OnContactsDownloadCompleted(
|
||||
std::vector<ContactRecord> contacts) {
|
||||
NL_LOG(INFO) << __func__ << ": Completed to download contacts from backend";
|
||||
size_t initial_num_contacts = contacts.size();
|
||||
contacts.erase(
|
||||
std::remove_if(contacts.begin(), contacts.end(),
|
||||
[](const nearby::sharing::proto::ContactRecord& contact) {
|
||||
return !contact.is_reachable();
|
||||
}),
|
||||
contacts.end());
|
||||
uint32_t num_unreachable_contacts_filtered_out =
|
||||
initial_num_contacts - contacts.size();
|
||||
NL_VLOG(1) << __func__ << ": Removed "
|
||||
<< num_unreachable_contacts_filtered_out
|
||||
<< " unreachable contacts.";
|
||||
OnContactsDownloadSuccess(std::move(contacts),
|
||||
num_unreachable_contacts_filtered_out);
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::ContactDownloadContext::FetchNextPage() {
|
||||
NL_LOG(INFO) << __func__ << ": Downloading page=" << page_number_++;
|
||||
ListContactPeopleRequest request;
|
||||
if (next_page_token_.has_value()) {
|
||||
request.set_page_token(*next_page_token_);
|
||||
}
|
||||
nearby_share_client_->ListContactPeople(
|
||||
request,
|
||||
[this](
|
||||
const absl::StatusOr<ListContactPeopleResponse>& response) mutable {
|
||||
if (!response.ok()) {
|
||||
NL_LOG(ERROR) << __func__ << ": Failed to download contacts.";
|
||||
std::move(download_failure_callback_)();
|
||||
return;
|
||||
}
|
||||
|
||||
contacts_.insert(contacts_.end(), response->contact_records().begin(),
|
||||
response->contact_records().end());
|
||||
|
||||
if (response->next_page_token().empty()) {
|
||||
std::move(download_success_callback_)(std::move(contacts_));
|
||||
return;
|
||||
}
|
||||
// Continue with next page.
|
||||
next_page_token_ = response->next_page_token();
|
||||
FetchNextPage();
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::DownloadContacts() {
|
||||
executor_->PostTask([this]() {
|
||||
NL_LOG(INFO) << __func__ << ": Start to download contacts";
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": Ignore to download contacts due to manager is not "
|
||||
"running.";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ContactRecord> contacts;
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING)
|
||||
<< __func__
|
||||
<< ": Ignore to download certificates due to no login account.";
|
||||
OnContactsDownloadSuccess(contacts, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently Contacts download is synchronous. It completes after
|
||||
// FetchNextPage() returns.
|
||||
auto context = std::make_unique<ContactDownloadContext>(
|
||||
nearby_share_client_.get(),
|
||||
absl::bind_front(
|
||||
&NearbyShareContactManagerImpl::OnContactsDownloadFailure, this),
|
||||
absl::bind_front(
|
||||
&NearbyShareContactManagerImpl::OnContactsDownloadCompleted, this));
|
||||
context->FetchNextPage();
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::SetAllowedContacts(
|
||||
const std::set<std::string>& allowed_contact_ids) {
|
||||
// If the allowlist changed, re-upload contacts to Nearby server.
|
||||
if (SetAllowlist(allowed_contact_ids))
|
||||
contact_download_and_upload_scheduler_->MakeImmediateRequest();
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnStart() {
|
||||
periodic_contact_upload_scheduler_->Start();
|
||||
contact_download_and_upload_scheduler_->Start();
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnStop() {
|
||||
periodic_contact_upload_scheduler_->Stop();
|
||||
contact_download_and_upload_scheduler_->Stop();
|
||||
}
|
||||
|
||||
std::set<std::string> NearbyShareContactManagerImpl::GetAllowedContacts()
|
||||
const {
|
||||
std::set<std::string> allowlist;
|
||||
for (const std::string& id : preference_manager_.GetStringArray(
|
||||
prefs::kNearbySharingAllowedContactsName, {})) {
|
||||
allowlist.insert(id);
|
||||
}
|
||||
return allowlist;
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnPeriodicContactsUploadRequested() {
|
||||
NL_VLOG(1) << __func__
|
||||
<< ": Periodic Nearby Share contacts upload requested. "
|
||||
<< "Upload will occur after next contacts download.";
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnContactsDownloadSuccess(
|
||||
std::vector<ContactRecord> contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) {
|
||||
NL_LOG(INFO) << __func__ << ": Nearby Share download of " << contacts.size()
|
||||
<< " contacts succeeded.";
|
||||
|
||||
// Remove contacts from the allowlist that are not in the contact list.
|
||||
SetAllowlist(
|
||||
RemoveNonexistentContactsFromAllowlist(GetAllowedContacts(), contacts));
|
||||
|
||||
// Notify observers that the contact list was downloaded.
|
||||
std::set<std::string> allowed_contact_ids = GetAllowedContacts();
|
||||
NotifyAllObserversContactsDownloaded(allowed_contact_ids, contacts,
|
||||
num_unreachable_contacts_filtered_out);
|
||||
|
||||
std::vector<Contact> contacts_to_upload =
|
||||
ContactRecordsToContacts(GetAllowedContacts(), contacts);
|
||||
|
||||
// Enable cross-device self-share by adding your account to the list of
|
||||
// contacts. It is also marked as a selected contact.
|
||||
std::optional<AccountManager::Account> account =
|
||||
account_manager_.GetCurrentAccount();
|
||||
|
||||
if (!account.has_value()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": Profile user name is not valid; could not "
|
||||
<< "add self to list of contacts to upload.";
|
||||
} else {
|
||||
contacts_to_upload.push_back(CreateLocalContact(account->email));
|
||||
}
|
||||
|
||||
std::string last_contact_upload_hash = preference_manager_.GetString(
|
||||
prefs::kNearbySharingContactUploadHashName, "");
|
||||
std::string contact_upload_hash = ComputeHash(contacts_to_upload);
|
||||
bool did_contacts_change_since_last_upload =
|
||||
contact_upload_hash != last_contact_upload_hash;
|
||||
|
||||
if (did_contacts_change_since_last_upload) {
|
||||
NL_VLOG(1) << __func__ << ": Contact list or allowlist changed since last "
|
||||
<< "successful upload to the Nearby Share server.";
|
||||
}
|
||||
|
||||
// Request a contacts upload if the contact list or allowlist has changed
|
||||
// since the last successful upload. Also request an upload periodically.
|
||||
if (did_contacts_change_since_last_upload ||
|
||||
periodic_contact_upload_scheduler_->IsWaitingForResult()) {
|
||||
absl::Notification notification;
|
||||
bool upload_success = false;
|
||||
local_device_data_manager_->UploadContacts(std::move(contacts_to_upload),
|
||||
[&](bool success) {
|
||||
upload_success = success;
|
||||
notification.Notify();
|
||||
});
|
||||
|
||||
notification.WaitForNotification();
|
||||
NL_LOG(INFO) << __func__ << ": Completed to upload contacts with result:"
|
||||
<< upload_success;
|
||||
|
||||
OnContactsUploadFinished(did_contacts_change_since_last_upload,
|
||||
contact_upload_hash, upload_success);
|
||||
return;
|
||||
}
|
||||
|
||||
// No upload is needed.
|
||||
contact_download_and_upload_scheduler_->HandleResult(/*success=*/true);
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnContactsDownloadFailure() {
|
||||
NL_LOG(WARNING) << __func__ << ": Nearby Share contacts download failed.";
|
||||
|
||||
contact_download_and_upload_scheduler_->HandleResult(/*success=*/false);
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::OnContactsUploadFinished(
|
||||
bool did_contacts_change_since_last_upload,
|
||||
absl::string_view contact_upload_hash, bool success) {
|
||||
NL_LOG(INFO) << __func__ << ": Upload of contacts to Nearby Share server "
|
||||
<< (success ? "succeeded." : "failed.")
|
||||
<< " Contact upload hash: " << contact_upload_hash;
|
||||
if (success) {
|
||||
// Only resolve the periodic upload request on success; let the
|
||||
// download-and-upload scheduler handle any failure retries. The periodic
|
||||
// upload scheduler will remember that it has an outstanding request even
|
||||
// after reboot.
|
||||
if (periodic_contact_upload_scheduler_->IsWaitingForResult()) {
|
||||
periodic_contact_upload_scheduler_->HandleResult(success);
|
||||
}
|
||||
|
||||
std::string last_contact_upload_hash = preference_manager_.GetString(
|
||||
prefs::kNearbySharingContactUploadHashName, "");
|
||||
|
||||
preference_manager_.SetString(prefs::kNearbySharingContactUploadHashName,
|
||||
contact_upload_hash);
|
||||
|
||||
if (last_contact_upload_hash.empty()) {
|
||||
// If no contacts are uploaded before, set the flag to false in order to
|
||||
// prevent the certificate manager from regenerating certificates.
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": Mark contacts change flag to false due to no "
|
||||
"contacts upload before.";
|
||||
did_contacts_change_since_last_upload = false;
|
||||
}
|
||||
NotifyContactsUploaded(did_contacts_change_since_last_upload);
|
||||
}
|
||||
|
||||
contact_download_and_upload_scheduler_->HandleResult(success);
|
||||
}
|
||||
|
||||
bool NearbyShareContactManagerImpl::SetAllowlist(
|
||||
const std::set<std::string>& new_allowlist) {
|
||||
if (new_allowlist == GetAllowedContacts()) return false;
|
||||
|
||||
std::vector<std::string> allowlist_value;
|
||||
allowlist_value.reserve(new_allowlist.size());
|
||||
for (const std::string& id : new_allowlist) {
|
||||
allowlist_value.push_back(id);
|
||||
}
|
||||
|
||||
preference_manager_.SetStringArray(prefs::kNearbySharingAllowedContactsName,
|
||||
allowlist_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void NearbyShareContactManagerImpl::NotifyAllObserversContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) {
|
||||
// Sort the contacts before sending the list to observers.
|
||||
std::vector<ContactRecord> sorted_contacts = contacts;
|
||||
SortNearbyShareContactRecords(&sorted_contacts);
|
||||
|
||||
// First, notify NearbyShareContactManager::Observers.
|
||||
// Note: These are direct observers of the NearbyShareContactManager base
|
||||
// class, distinct from the mojo remote observers that we notify below.
|
||||
NotifyContactsDownloaded(allowed_contact_ids, sorted_contacts,
|
||||
num_unreachable_contacts_filtered_out);
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2023 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_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_IMPL_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/platform/task_runner.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Implementation of NearbyShareContactManager that persists the set of allowed
|
||||
// contact IDs--for selected-contacts visibility mode--in prefs. Other
|
||||
// contact data is downloaded from People API, via the NearbyShare server, as
|
||||
// needed.
|
||||
//
|
||||
// The Nearby Share server must be explicitly informed of all contacts this
|
||||
// device is aware of--needed for all-contacts visibility mode--as well as what
|
||||
// contacts are allowed for selected-contacts visibility mode. These uploaded
|
||||
// contact lists are used by the server to distribute the device's public
|
||||
// certificates accordingly. This implementation persists a hash of the last
|
||||
// uploaded contact data, and after every contacts download, a subsequent upload
|
||||
// request is made if we detect that the contact list or allowlist has changed
|
||||
// since the last successful upload. We also schedule periodic contact uploads
|
||||
// just in case the server removed the record.
|
||||
//
|
||||
// In addition to supporting on-demand contact downloads, this implementation
|
||||
// periodically checks in with the Nearby Share server to see if the user's
|
||||
// contact list has changed since the last upload.
|
||||
class NearbyShareContactManagerImpl : public NearbyShareContactManager {
|
||||
public:
|
||||
class Factory {
|
||||
public:
|
||||
static std::unique_ptr<NearbyShareContactManager> Create(
|
||||
Context* context,
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager);
|
||||
static void SetFactoryForTesting(Factory* test_factory);
|
||||
|
||||
protected:
|
||||
virtual ~Factory();
|
||||
virtual std::unique_ptr<NearbyShareContactManager> CreateInstance(
|
||||
Context* context, AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager) = 0;
|
||||
|
||||
private:
|
||||
static Factory* test_factory_;
|
||||
};
|
||||
|
||||
~NearbyShareContactManagerImpl() override;
|
||||
|
||||
private:
|
||||
// Class for maintaining a single instance of contacts download request. It
|
||||
// is responsible for downloading all available pages and making the results
|
||||
// or error available.
|
||||
class ContactDownloadContext {
|
||||
public:
|
||||
ContactDownloadContext(
|
||||
nearby::sharing::api::SharingRpcClient* nearby_share_client,
|
||||
absl::AnyInvocable<void() &&> download_failure_callback,
|
||||
absl::AnyInvocable<
|
||||
void(
|
||||
std::vector<nearby::sharing::proto::ContactRecord> contacts) &&>
|
||||
download_success_callback)
|
||||
: nearby_share_client_(nearby_share_client),
|
||||
download_failure_callback_(std::move(download_failure_callback)),
|
||||
download_success_callback_(std::move(download_success_callback)) {}
|
||||
|
||||
// Fetches the next page of contacts.
|
||||
// If |next_page_token_| is empty, it fetches the first page.
|
||||
// On successful download, if page token in the response is empty, the
|
||||
// |download_success_callback_| is invoked with all downloaded contacts.
|
||||
void FetchNextPage();
|
||||
|
||||
private:
|
||||
nearby::sharing::api::SharingRpcClient* const nearby_share_client_;
|
||||
std::optional<std::string> next_page_token_;
|
||||
int page_number_ = 1;
|
||||
std::vector<nearby::sharing::proto::ContactRecord> contacts_;
|
||||
absl::AnyInvocable<void() &&> download_failure_callback_;
|
||||
absl::AnyInvocable<void(
|
||||
std::vector<nearby::sharing::proto::ContactRecord> contacts) &&>
|
||||
download_success_callback_;
|
||||
};
|
||||
|
||||
NearbyShareContactManagerImpl(
|
||||
Context* context,
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager,
|
||||
nearby::sharing::api::SharingRpcClientFactory* nearby_client_factory,
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager);
|
||||
|
||||
// NearbyShareContactsManager:
|
||||
void DownloadContacts() override;
|
||||
void SetAllowedContacts(
|
||||
const std::set<std::string>& allowed_contact_ids) override;
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
|
||||
std::set<std::string> GetAllowedContacts() const;
|
||||
bool SetAllowlist(const std::set<std::string>& new_allowlist);
|
||||
void OnContactsDownloadCompleted(
|
||||
std::vector<nearby::sharing::proto::ContactRecord> contacts);
|
||||
void OnContactsDownloadSuccess(
|
||||
std::vector<::nearby::sharing::proto::ContactRecord> contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out);
|
||||
void OnContactsDownloadFailure();
|
||||
void OnPeriodicContactsUploadRequested();
|
||||
void OnContactsUploadFinished(bool did_contacts_change_since_last_upload,
|
||||
absl::string_view contact_upload_hash,
|
||||
bool success);
|
||||
|
||||
// Notify the base-class and mojo observers that contacts were downloaded.
|
||||
void NotifyAllObserversContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out);
|
||||
|
||||
nearby::sharing::api::PreferenceManager& preference_manager_;
|
||||
AccountManager& account_manager_;
|
||||
nearby::sharing::api::SharingRpcClientFactory* const nearby_client_factory_;
|
||||
std::unique_ptr<nearby::sharing::api::SharingRpcClient> nearby_share_client_;
|
||||
NearbyShareLocalDeviceDataManager* local_device_data_manager_ = nullptr;
|
||||
std::unique_ptr<NearbyShareScheduler> periodic_contact_upload_scheduler_;
|
||||
std::unique_ptr<NearbyShareScheduler> contact_download_and_upload_scheduler_;
|
||||
|
||||
std::unique_ptr<TaskRunner> executor_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACT_MANAGER_IMPL_H_
|
||||
@@ -0,0 +1,672 @@
|
||||
// Copyright 2023 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 "sharing/contacts/nearby_share_contact_manager_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/test/fake_account_manager.h"
|
||||
#include "internal/test/fake_task_runner.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/contacts/nearby_share_contact_manager.h"
|
||||
#include "sharing/contacts/nearby_share_contacts_sorter.h"
|
||||
#include "sharing/internal/api/fake_nearby_share_client.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/test/fake_context.h"
|
||||
#include "sharing/internal/test/fake_preference_manager.h"
|
||||
#include "sharing/local_device_data/fake_nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/contact_rpc.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler_factory.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::api::PreferenceManager;
|
||||
using ::nearby::sharing::proto::Contact;
|
||||
using ::nearby::sharing::proto::ContactRecord;
|
||||
|
||||
constexpr char kTestContactIdPrefix[] = "id_";
|
||||
constexpr char kTestContactEmailPrefix[] = "email_";
|
||||
constexpr char kTestContactPhonePrefix[] = "phone_";
|
||||
constexpr char kTestDefaultDeviceName[] = "Josh's Chromebook";
|
||||
constexpr char kTestProfileUserName[] = "test@google.com";
|
||||
constexpr char kTestAccountId[] = "test_account_id";
|
||||
constexpr char kTestDefaultContactsHash[] = "last_hash";
|
||||
const char* kTestPersonNames[] = {"BBB BBB", "CCC CCC", "AAA AAA"};
|
||||
|
||||
// From nearby_share_contact_manager_impl.cc.
|
||||
constexpr absl::Duration kContactUploadPeriod = absl::Hours(24);
|
||||
constexpr absl::Duration kContactDownloadPeriod = absl::Hours(12);
|
||||
|
||||
std::string GetTestContactId(size_t index) {
|
||||
return absl::StrCat(kTestContactIdPrefix, index);
|
||||
}
|
||||
std::string GetTestContactEmail(size_t index) {
|
||||
return absl::StrCat(kTestContactEmailPrefix, index);
|
||||
}
|
||||
std::string GetTestContactPhone(size_t index) {
|
||||
return absl::StrCat(kTestContactPhonePrefix, index);
|
||||
}
|
||||
|
||||
std::set<std::string> TestContactIds(size_t num_contacts) {
|
||||
std::set<std::string> ids;
|
||||
for (size_t i = 0; i < num_contacts; ++i) {
|
||||
ids.insert(GetTestContactId(i));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::vector<ContactRecord> TestContactRecordList(size_t num_contacts) {
|
||||
std::vector<ContactRecord> contact_list;
|
||||
for (size_t i = 0; i < num_contacts; ++i) {
|
||||
ContactRecord contact;
|
||||
contact.set_id(GetTestContactId(i));
|
||||
contact.set_image_url("https://www.google.com/");
|
||||
contact.set_person_name(kTestPersonNames[i % 3]);
|
||||
contact.set_is_reachable(true);
|
||||
// only one of these fields should be set...
|
||||
switch ((i % 3)) {
|
||||
case 0:
|
||||
contact.add_identifiers()->set_account_name(GetTestContactEmail(i));
|
||||
break;
|
||||
case 1:
|
||||
contact.add_identifiers()->set_phone_number(GetTestContactPhone(i));
|
||||
break;
|
||||
case 2:
|
||||
contact.add_identifiers()->set_obfuscated_gaia("4938tyah");
|
||||
break;
|
||||
}
|
||||
contact_list.push_back(contact);
|
||||
}
|
||||
return contact_list;
|
||||
}
|
||||
|
||||
// Converts a list of ContactRecord protos, along with the allowlist, into a
|
||||
// list of Contact protos. To enable self-sharing across devices, we expect the
|
||||
// local device to include itself in the contact list as an allowed contact.
|
||||
// Partially from nearby_share_contact_manager_impl.cc.
|
||||
std::vector<Contact> BuildContactListToUpload(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& contact_records) {
|
||||
std::vector<Contact> contacts;
|
||||
for (const auto& contact_record : contact_records) {
|
||||
bool is_selected = allowed_contact_ids.find(contact_record.id()) !=
|
||||
allowed_contact_ids.end();
|
||||
for (const auto& identifier : contact_record.identifiers()) {
|
||||
Contact contact;
|
||||
*contact.mutable_identifier() = identifier;
|
||||
contact.set_is_selected(is_selected);
|
||||
contacts.push_back(contact);
|
||||
}
|
||||
}
|
||||
|
||||
// Add self to list of contacts.
|
||||
Contact contact;
|
||||
contact.mutable_identifier()->set_account_name(kTestProfileUserName);
|
||||
contact.set_is_selected(true);
|
||||
contacts.push_back(contact);
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
void VerifyDownloadNotificationContacts(
|
||||
const std::set<std::string>& expected_allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& expected_unordered_contacts,
|
||||
const std::set<std::string>& notification_allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& notification_contacts) {
|
||||
EXPECT_EQ(notification_allowed_contact_ids, expected_allowed_contact_ids);
|
||||
EXPECT_EQ(notification_contacts.size(), expected_unordered_contacts.size());
|
||||
|
||||
// Verify that observers receive contacts in sorted order.
|
||||
std::vector<ContactRecord> expected_ordered_contacts =
|
||||
expected_unordered_contacts;
|
||||
SortNearbyShareContactRecords(&expected_ordered_contacts);
|
||||
for (size_t i = 0; i < expected_ordered_contacts.size(); ++i) {
|
||||
EXPECT_EQ(notification_contacts[i].SerializeAsString(),
|
||||
expected_ordered_contacts[i].SerializeAsString());
|
||||
}
|
||||
}
|
||||
|
||||
class NearbyShareContactManagerImplTest
|
||||
: public ::testing::Test,
|
||||
public NearbyShareContactManager::Observer {
|
||||
protected:
|
||||
struct AllowlistChangedNotification {
|
||||
bool were_contacts_added_to_allowlist;
|
||||
bool were_contacts_removed_from_allowlist;
|
||||
};
|
||||
struct ContactsDownloadedNotification {
|
||||
std::set<std::string> allowed_contact_ids;
|
||||
std::vector<ContactRecord> contacts;
|
||||
uint32_t num_unreachable_contacts_filtered_out;
|
||||
};
|
||||
struct ContactsUploadedNotification {
|
||||
bool did_contacts_change_since_last_upload;
|
||||
};
|
||||
|
||||
NearbyShareContactManagerImplTest()
|
||||
: local_device_data_manager_(kTestDefaultDeviceName) {
|
||||
local_device_data_manager_.set_is_sync_mode(true);
|
||||
}
|
||||
|
||||
~NearbyShareContactManagerImplTest() override = default;
|
||||
|
||||
void SetUp() override {
|
||||
prefs::RegisterNearbySharingPrefs(preference_manager_);
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(&scheduler_factory_);
|
||||
AccountManager::Account account;
|
||||
account.id = kTestAccountId;
|
||||
account.email = kTestProfileUserName;
|
||||
fake_account_manager_.SetAccount(account);
|
||||
|
||||
manager_ = NearbyShareContactManagerImpl::Factory::Create(
|
||||
&fake_context_, preference_manager_,
|
||||
fake_account_manager_, &nearby_client_factory_,
|
||||
&local_device_data_manager_);
|
||||
|
||||
VerifySchedulerInitialization();
|
||||
manager_->AddObserver(this);
|
||||
|
||||
preference_manager().SetString(prefs::kNearbySharingContactUploadHashName,
|
||||
kTestDefaultContactsHash);
|
||||
manager_->Start();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
manager_->RemoveObserver(this);
|
||||
manager_.reset();
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(nullptr);
|
||||
preference_manager().SetString(prefs::kNearbySharingContactUploadHashName,
|
||||
"");
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
EXPECT_TRUE(FakeTaskRunner::WaitForRunningTasksWithTimeout(
|
||||
absl::Milliseconds(1000)));
|
||||
}
|
||||
|
||||
void SetUploadResult(bool success) {
|
||||
local_device_data_manager_.SetUploadContactsResult(success);
|
||||
}
|
||||
|
||||
void SetDownloadSuccessResult(const std::vector<ContactRecord>& contacts) {
|
||||
proto::ListContactPeopleResponse response;
|
||||
response.set_next_page_token(nullptr);
|
||||
response.mutable_contact_records()->Add(contacts.begin(), contacts.end());
|
||||
std::vector<absl::StatusOr<proto::ListContactPeopleResponse>> responses;
|
||||
responses.push_back(response);
|
||||
client()->SetListContactPeopleResponses(responses);
|
||||
}
|
||||
|
||||
void SetDownloadFailureResult() {
|
||||
std::vector<absl::StatusOr<proto::ListContactPeopleResponse>> responses;
|
||||
responses.push_back(absl::InternalError(""));
|
||||
client()->SetListContactPeopleResponses(responses);
|
||||
}
|
||||
|
||||
void DownloadContacts(
|
||||
bool download_success, bool expect_upload, bool upload_success,
|
||||
std::optional<std::set<std::string>> allowed_contact_ids,
|
||||
std::optional<std::vector<ContactRecord>> contacts) {
|
||||
// Track for download contacts.
|
||||
size_t num_handled_results =
|
||||
download_and_upload_scheduler()->handled_results().size();
|
||||
size_t num_download_notifications =
|
||||
contacts_downloaded_notifications_.size();
|
||||
size_t num_upload_contacts_calls =
|
||||
local_device_data_manager_.upload_contacts_calls().size();
|
||||
// Invoke upload callback from local device data manager.
|
||||
size_t num_upload_notifications = contacts_uploaded_notifications_.size();
|
||||
size_t num_download_and_upload_handled_results =
|
||||
download_and_upload_scheduler()->handled_results().size();
|
||||
size_t num_periodic_upload_handled_results =
|
||||
periodic_upload_scheduler()->handled_results().size();
|
||||
|
||||
manager_->DownloadContacts();
|
||||
Sync();
|
||||
if (download_success) {
|
||||
VerifyDownloadNotificationSent(
|
||||
/*initial_num_notifications=*/num_download_notifications,
|
||||
*allowed_contact_ids, *contacts);
|
||||
|
||||
// Verify that contacts start uploading if needed.
|
||||
EXPECT_EQ(local_device_data_manager_.upload_contacts_calls().size(),
|
||||
num_upload_contacts_calls + (expect_upload ? 1 : 0));
|
||||
|
||||
// Verify that the success result is sent to the download/upload scheduler
|
||||
// if a subsequent upload isn't required.
|
||||
EXPECT_EQ(download_and_upload_scheduler()->handled_results().size(),
|
||||
num_handled_results + 1);
|
||||
if (!expect_upload) {
|
||||
EXPECT_TRUE(download_and_upload_scheduler()->handled_results().back());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check on upload.
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadContactsCall& call =
|
||||
local_device_data_manager_.upload_contacts_calls().back();
|
||||
|
||||
std::vector<Contact> expected_upload_contacts =
|
||||
BuildContactListToUpload(*allowed_contact_ids, *contacts);
|
||||
// Ordering doesn't matter. Otherwise, because of internal sorting,
|
||||
// comparison would be difficult.
|
||||
ASSERT_EQ(expected_upload_contacts.size(), call.contacts.size());
|
||||
absl::flat_hash_set<std::string> expected_contacts_set;
|
||||
absl::flat_hash_set<std::string> call_contacts_set;
|
||||
for (size_t i = 0; i < expected_upload_contacts.size(); ++i) {
|
||||
expected_contacts_set.insert(
|
||||
expected_upload_contacts[i].SerializeAsString());
|
||||
call_contacts_set.insert(call.contacts[i].SerializeAsString());
|
||||
}
|
||||
|
||||
// Verify upload notification was sent on success.
|
||||
EXPECT_EQ(contacts_uploaded_notifications_.size(),
|
||||
num_upload_notifications + (upload_success ? 1 : 0));
|
||||
if (upload_success) {
|
||||
// We only expect uploads to occur if contacts have changed since the
|
||||
// last
|
||||
// upload or if a periodic upload was requested.
|
||||
EXPECT_TRUE(contacts_uploaded_notifications_.back()
|
||||
.did_contacts_change_since_last_upload ||
|
||||
periodic_upload_scheduler()->IsWaitingForResult());
|
||||
|
||||
if (periodic_upload_scheduler()->IsWaitingForResult()) {
|
||||
EXPECT_EQ(periodic_upload_scheduler()->handled_results().size(),
|
||||
num_periodic_upload_handled_results + 1);
|
||||
EXPECT_TRUE(periodic_upload_scheduler()->handled_results().back());
|
||||
periodic_upload_scheduler()->SetIsWaitingForResult(false);
|
||||
} else {
|
||||
EXPECT_EQ(periodic_upload_scheduler()->handled_results().size(),
|
||||
num_periodic_upload_handled_results);
|
||||
}
|
||||
}
|
||||
// Verify that the result is sent to download/upload scheduler.
|
||||
EXPECT_EQ(download_and_upload_scheduler()->handled_results().size(),
|
||||
num_download_and_upload_handled_results + 1);
|
||||
EXPECT_EQ(download_and_upload_scheduler()->handled_results().back(),
|
||||
upload_success);
|
||||
} else {
|
||||
EXPECT_EQ(download_and_upload_scheduler()->handled_results().size(),
|
||||
num_handled_results + 1);
|
||||
EXPECT_FALSE(download_and_upload_scheduler()->handled_results().back());
|
||||
}
|
||||
}
|
||||
|
||||
void MakePeriodicUploadRequest() {
|
||||
periodic_upload_scheduler()->InvokeRequestCallback();
|
||||
periodic_upload_scheduler()->SetIsWaitingForResult(true);
|
||||
Sync();
|
||||
}
|
||||
|
||||
void SetAllowedContacts(const std::set<std::string>& allowed_contact_ids,
|
||||
bool expect_allowlist_changed) {
|
||||
size_t num_download_and_upload_requests =
|
||||
download_and_upload_scheduler()->num_immediate_requests();
|
||||
|
||||
manager_->SetAllowedContacts(allowed_contact_ids);
|
||||
|
||||
// Verify that download/upload is requested if the allowlist changed.
|
||||
EXPECT_EQ(
|
||||
download_and_upload_scheduler()->num_immediate_requests(),
|
||||
num_download_and_upload_requests + (expect_allowlist_changed ? 1 : 0));
|
||||
}
|
||||
|
||||
PreferenceManager& preference_manager() { return preference_manager_; }
|
||||
|
||||
private:
|
||||
// NearbyShareContactManager::Observer:
|
||||
void OnContactsDownloaded(
|
||||
const std::set<std::string>& allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& contacts,
|
||||
uint32_t num_unreachable_contacts_filtered_out) override {
|
||||
ContactsDownloadedNotification notification;
|
||||
notification.allowed_contact_ids = allowed_contact_ids;
|
||||
notification.contacts = contacts;
|
||||
contacts_downloaded_notifications_.push_back(notification);
|
||||
}
|
||||
void OnContactsUploaded(bool did_contacts_change_since_last_upload) override {
|
||||
ContactsUploadedNotification notification;
|
||||
notification.did_contacts_change_since_last_upload =
|
||||
did_contacts_change_since_last_upload;
|
||||
contacts_uploaded_notifications_.push_back(notification);
|
||||
}
|
||||
|
||||
FakeNearbyShareClient* client() {
|
||||
return nearby_client_factory_.instances().back();
|
||||
}
|
||||
|
||||
FakeNearbyShareScheduler* periodic_upload_scheduler() {
|
||||
return scheduler_factory_.pref_name_to_periodic_instance()
|
||||
.at(prefs::kNearbySharingSchedulerPeriodicContactUploadName)
|
||||
.fake_scheduler;
|
||||
}
|
||||
|
||||
FakeNearbyShareScheduler* download_and_upload_scheduler() {
|
||||
return scheduler_factory_.pref_name_to_periodic_instance()
|
||||
.at(prefs::kNearbySharingSchedulerContactDownloadAndUploadName)
|
||||
.fake_scheduler;
|
||||
}
|
||||
|
||||
// Verify scheduler input parameters.
|
||||
void VerifySchedulerInitialization() {
|
||||
FakeNearbyShareSchedulerFactory::PeriodicInstance
|
||||
download_and_upload_scheduler_instance =
|
||||
scheduler_factory_.pref_name_to_periodic_instance().at(
|
||||
prefs::kNearbySharingSchedulerContactDownloadAndUploadName);
|
||||
EXPECT_TRUE(download_and_upload_scheduler_instance.fake_scheduler);
|
||||
EXPECT_EQ(download_and_upload_scheduler_instance.request_period,
|
||||
kContactDownloadPeriod);
|
||||
EXPECT_TRUE(download_and_upload_scheduler_instance.retry_failures);
|
||||
EXPECT_TRUE(download_and_upload_scheduler_instance.require_connectivity);
|
||||
|
||||
FakeNearbyShareSchedulerFactory::PeriodicInstance
|
||||
periodic_upload_scheduler_instance =
|
||||
scheduler_factory_.pref_name_to_periodic_instance().at(
|
||||
prefs::kNearbySharingSchedulerPeriodicContactUploadName);
|
||||
EXPECT_TRUE(periodic_upload_scheduler_instance.fake_scheduler);
|
||||
EXPECT_EQ(periodic_upload_scheduler_instance.request_period,
|
||||
kContactUploadPeriod);
|
||||
EXPECT_FALSE(periodic_upload_scheduler_instance.retry_failures);
|
||||
EXPECT_TRUE(periodic_upload_scheduler_instance.require_connectivity);
|
||||
}
|
||||
|
||||
void TriggerDownloadScheduler() {
|
||||
// Fire scheduler and verify downloader creation.
|
||||
download_and_upload_scheduler()->InvokeRequestCallback();
|
||||
}
|
||||
|
||||
void VerifyDownloadNotificationSent(
|
||||
size_t initial_num_notifications,
|
||||
const std::set<std::string>& expected_allowed_contact_ids,
|
||||
const std::vector<ContactRecord>& expected_unordered_contacts) {
|
||||
EXPECT_EQ(contacts_downloaded_notifications_.size(),
|
||||
initial_num_notifications + 1);
|
||||
|
||||
// Verify notification sent to regular (not mojo) observers.
|
||||
VerifyDownloadNotificationContacts(
|
||||
expected_allowed_contact_ids, expected_unordered_contacts,
|
||||
contacts_downloaded_notifications_.back().allowed_contact_ids,
|
||||
contacts_downloaded_notifications_.back().contacts);
|
||||
}
|
||||
|
||||
nearby::FakePreferenceManager preference_manager_;
|
||||
FakeAccountManager fake_account_manager_;
|
||||
FakeContext fake_context_;
|
||||
std::vector<AllowlistChangedNotification> allowlist_changed_notifications_;
|
||||
std::vector<ContactsDownloadedNotification>
|
||||
contacts_downloaded_notifications_;
|
||||
std::vector<ContactsUploadedNotification> contacts_uploaded_notifications_;
|
||||
FakeNearbyShareClientFactory nearby_client_factory_;
|
||||
FakeNearbyShareLocalDeviceDataManager local_device_data_manager_;
|
||||
std::unique_ptr<FakeAccountManager> account_manager_;
|
||||
FakeNearbyShareSchedulerFactory scheduler_factory_;
|
||||
std::unique_ptr<NearbyShareContactManager> manager_;
|
||||
};
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest, SetAllowlist) {
|
||||
// Add initial allowed contacts.
|
||||
SetAllowedContacts(TestContactIds(/*num_contacts=*/3u),
|
||||
/*expect_allowlist_changed=*/true);
|
||||
// Remove last allowed contact.
|
||||
SetAllowedContacts(TestContactIds(/*num_contacts=*/2u),
|
||||
/*expect_allowlist_changed=*/true);
|
||||
// Add back last allowed contact.
|
||||
SetAllowedContacts(TestContactIds(/*num_contacts=*/3u),
|
||||
/*expect_allowlist_changed=*/true);
|
||||
// Set list without any changes.
|
||||
SetAllowedContacts(TestContactIds(/*num_contacts=*/3u),
|
||||
/*expect_allowlist_changed=*/false);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_WithFirstUpload) {
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/4u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
// Because contacts have never been uploaded, a subsequent upload should be
|
||||
// requested, which succeeds.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
// When contacts are downloaded again, we detect that contacts have not
|
||||
// changed, so no upload should be made
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/false,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest,
|
||||
DownloadContacts_DetectContactListChanged) {
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/3u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
// Because contacts have never been uploaded, a subsequent upload is
|
||||
// requested, which succeeds.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// When contacts are downloaded again, we detect that contacts have changed
|
||||
// since the last upload.
|
||||
contact_records = TestContactRecordList(/*num_contacts=*/4u);
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest,
|
||||
DownloadContacts_DetectAllowlistChanged) {
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/3u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
|
||||
// Because contacts have never been uploaded, a subsequent upload is
|
||||
// requested, which succeeds.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// When contacts are downloaded again, we detect that the allowlist has
|
||||
// changed since the last upload.
|
||||
allowlist = TestContactIds(/*num_contacts=*/1u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest,
|
||||
DownloadContacts_PeriodicUploadRequest) {
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/3u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
|
||||
// Because contacts have never been uploaded, a subsequent upload is
|
||||
// requested, which succeeds.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// Because device records on the server will be removed after a few days if
|
||||
// the device does not contact the server, we ensure that contacts are
|
||||
// uploaded periodically. Make that request now. Contacts will be uploaded
|
||||
// after the next contact download. It will not force a download now,
|
||||
// however.
|
||||
MakePeriodicUploadRequest();
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
// When contacts are downloaded again, we detect that contacts have not
|
||||
// changed. However, we expect an upload because a periodic request was
|
||||
// made.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_FailDownload) {
|
||||
SetDownloadFailureResult();
|
||||
DownloadContacts(/*download_success=*/false, /*expect_upload=*/false,
|
||||
/*upload_success=*/false,
|
||||
/*allowed_contact_ids=*/std::nullopt,
|
||||
/*contacts=*/std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_RetryFailedUpload) {
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/3u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
|
||||
// Because contacts have never been uploaded, a subsequent upload is
|
||||
// requested, which succeeds.
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// When contacts are downloaded again, we detect that contacts have changed
|
||||
// since the last upload. Fail this upload.
|
||||
contact_records = TestContactRecordList(/*num_contacts=*/4u);
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(false);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/false,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// When contacts are downloaded again, we should continue to indicate that
|
||||
// contacts have changed since the last upload, and attempt another upload.
|
||||
// (In other words, this tests that the contact-upload hash isn't updated
|
||||
// prematurely.)
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareContactManagerImplTest, ContactUploadHash) {
|
||||
EXPECT_EQ(preference_manager().GetString(
|
||||
prefs::kNearbySharingContactUploadHashName, std::string()),
|
||||
std::string(kTestDefaultContactsHash));
|
||||
|
||||
std::vector<ContactRecord> contact_records =
|
||||
TestContactRecordList(/*num_contacts=*/10u);
|
||||
std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
|
||||
SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
|
||||
|
||||
SetDownloadSuccessResult(contact_records);
|
||||
SetUploadResult(true);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/true,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/contact_records);
|
||||
|
||||
// Hardcode expected contact upload hash to ensure that hashed value is
|
||||
// consistent across process starts. If this test starts to fail, check one
|
||||
// of the following:
|
||||
// 1. Did the test data change? No worries; just update this hash value.
|
||||
// 2. Did the hashing function change? As long as the function is stable
|
||||
// across (most) process starts, then everything is okay; just update
|
||||
// this hash value. A changed hash value will result in an extra
|
||||
// server call, so as long as the value is stable for the most part,
|
||||
// it's okay.
|
||||
const char kExpectedHash[] =
|
||||
"A6DE36F14A9752DF247D92C4ECBEBC708691C33596C4D0C7D02F09F4BA65A37B";
|
||||
EXPECT_EQ(kExpectedHash,
|
||||
preference_manager().GetString(
|
||||
prefs::kNearbySharingContactUploadHashName, std::string()));
|
||||
|
||||
// Try a few different permutations of contacts to ensure that the hash is
|
||||
// invariant under ordering.
|
||||
std::default_random_engine rng;
|
||||
for (size_t i = 0; i < 10u; ++i) {
|
||||
// We do not expect an upload because the contacts did not change in any
|
||||
// way other than ordering.
|
||||
std::vector<ContactRecord> shuffled_contacts = contact_records;
|
||||
std::shuffle(shuffled_contacts.begin(), shuffled_contacts.end(), rng);
|
||||
|
||||
SetDownloadSuccessResult(shuffled_contacts);
|
||||
SetUploadResult(true);
|
||||
DownloadContacts(/*download_success=*/true, /*expect_upload=*/false,
|
||||
/*upload_success=*/true,
|
||||
/*allowed_contact_ids=*/allowlist,
|
||||
/*contacts=*/shuffled_contacts);
|
||||
|
||||
EXPECT_EQ(preference_manager().GetString(
|
||||
prefs::kNearbySharingContactUploadHashName, std::string()),
|
||||
kExpectedHash);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2021 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 "sharing/contacts/nearby_share_contacts_sorter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
struct ContactSortingFields {
|
||||
// Primary sorting key: person name if not empty; otherwise, email.
|
||||
std::optional<std::string> person_name_or_email;
|
||||
// Secondary sorting key. Note: It is okay if email is also used as the
|
||||
// primary sorting key.
|
||||
std::optional<std::string> email;
|
||||
// Tertiary sorting key.
|
||||
std::optional<std::string> phone_number;
|
||||
// Last resort sorting key. The contact ID should be unique for each
|
||||
// contact record, guaranteeing uniquely defined ordering.
|
||||
std::string id;
|
||||
};
|
||||
|
||||
ContactSortingFields GetContactSortingFields(
|
||||
const nearby::sharing::proto::ContactRecord& contact) {
|
||||
ContactSortingFields fields;
|
||||
fields.id = contact.id();
|
||||
for (const proto::Contact_Identifier& identifier : contact.identifiers()) {
|
||||
switch (identifier.identifier_case()) {
|
||||
case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
|
||||
kAccountName:
|
||||
if (!fields.email) {
|
||||
fields.email = identifier.account_name();
|
||||
}
|
||||
break;
|
||||
case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
|
||||
kPhoneNumber:
|
||||
if (!fields.phone_number) {
|
||||
fields.phone_number = identifier.phone_number();
|
||||
}
|
||||
break;
|
||||
case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
|
||||
kObfuscatedGaia:
|
||||
break;
|
||||
case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
|
||||
IDENTIFIER_NOT_SET:
|
||||
break;
|
||||
}
|
||||
}
|
||||
fields.person_name_or_email =
|
||||
contact.person_name().empty()
|
||||
? fields.email
|
||||
: std::make_optional<std::string>(contact.person_name());
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
class ContactRecordComparator {
|
||||
public:
|
||||
explicit ContactRecordComparator(std::locale locale) : locale_(locale) {}
|
||||
|
||||
bool operator()(const nearby::sharing::proto::ContactRecord& c1,
|
||||
const nearby::sharing::proto::ContactRecord& c2) const {
|
||||
ContactSortingFields f1 = GetContactSortingFields(c1);
|
||||
ContactSortingFields f2 = GetContactSortingFields(c2);
|
||||
|
||||
switch (CollatorCompare(f1.person_name_or_email, f2.person_name_or_email)) {
|
||||
case 0:
|
||||
// Do nothing. Compare with the next field.
|
||||
break;
|
||||
case -1:
|
||||
return true;
|
||||
case 1:
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (CollatorCompare(f1.email, f2.email)) {
|
||||
case 0:
|
||||
// Do nothing. Compare with the next field.
|
||||
break;
|
||||
case -1:
|
||||
return true;
|
||||
case 1:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f1.phone_number != f2.phone_number) {
|
||||
if (!f1.phone_number) return false;
|
||||
if (!f2.phone_number) return true;
|
||||
return *f1.phone_number < *f2.phone_number;
|
||||
}
|
||||
|
||||
return f1.id < f2.id;
|
||||
}
|
||||
|
||||
private:
|
||||
int CollatorCompare(const std::optional<std::string>& a,
|
||||
const std::optional<std::string>& b) const {
|
||||
// Sort populated strings before absl::nullopt.
|
||||
if (!a && !b) return 0;
|
||||
if (!b) return -1;
|
||||
if (!a) return 1;
|
||||
|
||||
// Sort using a locale-based collator if available.
|
||||
if (std::has_facet<std::ctype<char>>(locale_)) {
|
||||
auto& facet = std::use_facet<std::collate<char>>(locale_);
|
||||
std::string s1 = *a;
|
||||
std::string s2 = *b;
|
||||
|
||||
return facet.compare(&s1[0], &s1[0] + s1.size(), &s2[0],
|
||||
&s2[0] + s2.size());
|
||||
}
|
||||
|
||||
// Fall back on standard string comparison, though we hope and expect
|
||||
// that locale-based sorting will succeed.
|
||||
if (*a == *b) {
|
||||
return 0;
|
||||
}
|
||||
return *a < *b ? -1 : 1;
|
||||
}
|
||||
|
||||
std::locale locale_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void SortNearbyShareContactRecords(
|
||||
std::vector<nearby::sharing::proto::ContactRecord>* contacts,
|
||||
absl::string_view locale_string) {
|
||||
// initialized to default program environment locale.
|
||||
std::locale loc = std::locale("");
|
||||
|
||||
if (!locale_string.empty()) {
|
||||
loc = std::locale(locale_string.data());
|
||||
}
|
||||
|
||||
ContactRecordComparator comparator(loc);
|
||||
std::sort(contacts->begin(), contacts->end(), comparator);
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2021 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_SHARING_CONTACTS_NEARBY_SHARE_CONTACTS_SORTER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACTS_SORTER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// Sort |contacts| by the following fields:
|
||||
// - person name or email address if name is empty (primary),
|
||||
// - email, even if this is also used as the primary (secondary),
|
||||
// - phone number (tertiary),
|
||||
// - contact record id (last resort; should always be unique).
|
||||
//
|
||||
// This sorted order is unique for a given |locale|, presuming every element of
|
||||
// |contacts| has a unique ContactRecord::id(). The ordering between fields is
|
||||
// locale-dependent. For example, 'Å' will be sorted with these 'A's for
|
||||
// US-based sorting, whereas 'Å' will be sorted after 'Z' for Sweden-based
|
||||
// sorting, because 'Å' comes after 'Z' in the Swedish alphabet. By default,
|
||||
// |locale| is inferred from system settings.
|
||||
void SortNearbyShareContactRecords(
|
||||
std::vector<nearby::sharing::proto::ContactRecord>* contacts,
|
||||
absl::string_view locale_string = "");
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_CONTACTS_NEARBY_SHARE_CONTACTS_SORTER_H_
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2021 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 "sharing/contacts/nearby_share_contacts_sorter.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::proto::ContactRecord;
|
||||
using ::protobuf_matchers::EqualsProto;
|
||||
using ::testing::Pointwise;
|
||||
|
||||
const std::vector<ContactRecord>& contacts() {
|
||||
static const std::vector<ContactRecord>* contacts =
|
||||
new std::vector<ContactRecord>([] {
|
||||
ContactRecord contact0;
|
||||
contact0.set_person_name("Claire");
|
||||
contact0.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact1;
|
||||
contact1.set_person_name("Alice");
|
||||
contact1.add_identifiers()->set_account_name("y@gmail.com");
|
||||
contact1.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact2;
|
||||
contact2.set_person_name("Alice");
|
||||
contact2.add_identifiers()->set_account_name("x@gmail.com");
|
||||
contact2.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact3;
|
||||
contact3.add_identifiers()->set_account_name("bob@gmail.com");
|
||||
contact3.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact4;
|
||||
contact4.add_identifiers()->set_phone_number("222-222-2222");
|
||||
contact4.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact5;
|
||||
contact5.add_identifiers()->set_phone_number("111-111-1111");
|
||||
contact5.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact6;
|
||||
contact6.set_person_name("David");
|
||||
contact6.add_identifiers()->set_account_name("z@gmail.com");
|
||||
contact6.add_identifiers()->set_phone_number("222-222-2222");
|
||||
contact6.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact7;
|
||||
contact7.set_person_name("David");
|
||||
contact7.add_identifiers()->set_account_name("z@gmail.com");
|
||||
contact7.add_identifiers()->set_phone_number("111-111-1111");
|
||||
contact7.set_id("2");
|
||||
contact7.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact8;
|
||||
contact8.set_person_name("David");
|
||||
contact8.add_identifiers()->set_account_name("z@gmail.com");
|
||||
contact8.add_identifiers()->set_phone_number("111-111-1111");
|
||||
contact8.set_id("1");
|
||||
contact8.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact9;
|
||||
contact9.set_person_name("中村光");
|
||||
contact9.set_is_reachable(true);
|
||||
auto a = contact9.person_name();
|
||||
|
||||
ContactRecord contact10;
|
||||
contact10.set_person_name("王皓");
|
||||
contact10.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact11;
|
||||
contact11.set_person_name("中村俊輔");
|
||||
contact11.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact12;
|
||||
contact12.set_person_name("丁立人");
|
||||
contact12.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact13;
|
||||
contact13.set_person_name("Á");
|
||||
contact13.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact14;
|
||||
contact14.set_person_name("Ñ");
|
||||
contact14.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact15;
|
||||
contact15.set_person_name("å");
|
||||
contact15.set_id("5");
|
||||
contact15.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact16;
|
||||
contact16.set_person_name("Å");
|
||||
contact16.set_id("3");
|
||||
contact16.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact17;
|
||||
contact17.set_person_name("åz");
|
||||
contact17.set_id("4");
|
||||
contact17.set_is_reachable(true);
|
||||
|
||||
ContactRecord contact18;
|
||||
contact18.set_person_name("Opus");
|
||||
contact18.set_is_reachable(true);
|
||||
|
||||
return std::vector<ContactRecord>{
|
||||
contact0, contact1, contact2, contact3, contact4,
|
||||
contact5, contact6, contact7, contact8, contact9,
|
||||
contact10, contact11, contact12, contact13, contact14,
|
||||
contact15, contact16, contact17, contact18};
|
||||
}());
|
||||
|
||||
return *contacts;
|
||||
}
|
||||
|
||||
void VerifySort(const std::vector<ContactRecord>& expected_contacts,
|
||||
const std::vector<ContactRecord>& unsorted_contacts,
|
||||
absl::string_view locale_string) {
|
||||
// Try a few different permutations of |unsorted_contacts|, which should all
|
||||
// be sorted to |expected_contacts|.
|
||||
std::default_random_engine rng;
|
||||
for (size_t i = 0; i < 10u; ++i) {
|
||||
std::vector<ContactRecord> sorted_contacts = contacts();
|
||||
std::shuffle(sorted_contacts.begin(), sorted_contacts.end(), rng);
|
||||
SortNearbyShareContactRecords(&sorted_contacts, locale_string);
|
||||
ASSERT_EQ(expected_contacts.size(), sorted_contacts.size());
|
||||
EXPECT_THAT(sorted_contacts, Pointwise(EqualsProto(), sorted_contacts));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NearbyShareContactsSorter, US) {
|
||||
// Expected ordering:
|
||||
// Á | | |
|
||||
// å | | | ID: 5
|
||||
// Å | | | ID: 3
|
||||
// Alice | x@gmail.com | |
|
||||
// Alice | y@gmail.com | |
|
||||
// åz | | | ID: 4
|
||||
// | bob@gmail.com | |
|
||||
// Claire | | |
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 1
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 2
|
||||
// David | z@gmail.com | 222-222-2222 |
|
||||
// Ñ | | |
|
||||
// Opus | | |
|
||||
// 丁立人 | | |
|
||||
// 中村俊輔 | | |
|
||||
// 中村光 | | |
|
||||
// 王皓 | | |
|
||||
// | | 111-111-1111 |
|
||||
// | | 222-222-2222 |
|
||||
std::vector<ContactRecord> expected_contacts{
|
||||
contacts()[13], contacts()[15], contacts()[16], contacts()[2],
|
||||
contacts()[1], contacts()[17], contacts()[3], contacts()[0],
|
||||
contacts()[8], contacts()[7], contacts()[6], contacts()[14],
|
||||
contacts()[18], contacts()[12], contacts()[11], contacts()[9],
|
||||
contacts()[10], contacts()[5], contacts()[4]};
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
VerifySort(expected_contacts, contacts(), "en_US.UTF-8"));
|
||||
}
|
||||
|
||||
TEST(NearbyShareContactsSorter, DISABLED_Sweden) {
|
||||
// Expected ordering:
|
||||
// Á | | |
|
||||
// Alice | x@gmail.com | |
|
||||
// Alice | y@gmail.com | |
|
||||
// | bob@gmail.com | |
|
||||
// Claire | | |
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 1
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 2
|
||||
// David | z@gmail.com | 222-222-2222 |
|
||||
// Ñ | | |
|
||||
// Opus | | |
|
||||
// å | | | ID: 5
|
||||
// Å | | | ID: 3
|
||||
// åz | | | ID: 4
|
||||
// 丁立人 | | |
|
||||
// 中村俊輔 | | |
|
||||
// 中村光 | | |
|
||||
// 王皓 | | |
|
||||
// | | 111-111-1111 |
|
||||
// | | 222-222-2222 |
|
||||
std::vector<ContactRecord> expected_contacts{
|
||||
contacts()[13], contacts()[2], contacts()[1], contacts()[3],
|
||||
contacts()[0], contacts()[8], contacts()[7], contacts()[6],
|
||||
contacts()[14], contacts()[18], contacts()[15], contacts()[16],
|
||||
contacts()[17], contacts()[12], contacts()[11], contacts()[9],
|
||||
contacts()[10], contacts()[5], contacts()[4]};
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
VerifySort(expected_contacts, contacts(), "sv-SE.UTF-8"));
|
||||
}
|
||||
|
||||
TEST(NearbyShareContactsSorter, DISABLED_China) {
|
||||
// Expected ordering:
|
||||
// Á | | |
|
||||
// å | | | ID: 5
|
||||
// Å | | | ID: 3
|
||||
// Alice | x@gmail.com | |
|
||||
// Alice | y@gmail.com | |
|
||||
// åz | | | ID: 4
|
||||
// | bob@gmail.com | |
|
||||
// Claire | | |
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 1
|
||||
// David | z@gmail.com | 111-111-1111 | ID: 2
|
||||
// David | z@gmail.com | 222-222-2222 |
|
||||
// Ñ | | |
|
||||
// Opus | | |
|
||||
// 丁立人 | | |
|
||||
// 王皓 | | |
|
||||
// 中村光 | | |
|
||||
// 中村俊輔 | | |
|
||||
// | | 111-111-1111 |
|
||||
// | | 222-222-2222 |
|
||||
std::vector<ContactRecord> expected_contacts{
|
||||
contacts()[13], contacts()[15], contacts()[16], contacts()[2],
|
||||
contacts()[1], contacts()[17], contacts()[3], contacts()[0],
|
||||
contacts()[8], contacts()[7], contacts()[6], contacts()[14],
|
||||
contacts()[18], contacts()[12], contacts()[10], contacts()[9],
|
||||
contacts()[11], contacts()[5], contacts()[4]};
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
VerifySort(expected_contacts, contacts(), "zh_CN.UTF-8"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -16,18 +16,10 @@ licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "utf_utils",
|
||||
srcs = [
|
||||
"utf_string_conversions.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"utf_string_configuration.h",
|
||||
"utf_string_conversions.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//sharing/internal/public:logging",
|
||||
"//third_party/icu_utf:icu_utf_assistant",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
@@ -54,12 +46,10 @@ cc_test(
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"encode_test.cc",
|
||||
"utf_string_conversions_test.cc",
|
||||
],
|
||||
shard_count = 8,
|
||||
deps = [
|
||||
":base",
|
||||
":utf_utils",
|
||||
"//internal/platform/implementation/g3", # fixdeps: keep
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright 2021 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_SHARING_INTERNAL_BASE_UTF_STRING_CONFIGURATION_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_INTERNAL_BASE_UTF_STRING_CONFIGURATION_H_
|
||||
|
||||
// A set of macros to use for platform detection.
|
||||
#if defined(__native_client__)
|
||||
// __native_client__ must be first, so that other OS_ defines are not set.
|
||||
#define OS_NACL 1
|
||||
#define OS_NACL_SFI
|
||||
#elif defined(ANDROID)
|
||||
#define OS_ANDROID 1
|
||||
#elif defined(__APPLE__)
|
||||
// Only include TargetConditionals after testing ANDROID as some Android builds
|
||||
// on the Mac have this header available and it's not needed unless the target
|
||||
// is really an Apple platform.
|
||||
#include <TargetConditionals.h>
|
||||
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
#define OS_IOS 1
|
||||
#else
|
||||
#define OS_MAC 1
|
||||
#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
#elif defined(__linux__)
|
||||
#if !defined(OS_CHROMEOS)
|
||||
// Do not define OS_LINUX on Chrome OS build.
|
||||
// The OS_CHROMEOS macro is defined in GN.
|
||||
#define OS_LINUX 1
|
||||
#endif // !defined(OS_CHROMEOS)
|
||||
#if defined(__GLIBC__) && !defined(__UCLIBC__)
|
||||
// We really are using glibc, not uClibc pretending to be glibc.
|
||||
#define LIBC_GLIBC 1
|
||||
#endif
|
||||
#elif defined(_WIN32)
|
||||
#define OS_WIN 1
|
||||
#elif defined(__Fuchsia__)
|
||||
#define OS_FUCHSIA 1
|
||||
#elif defined(__FreeBSD__)
|
||||
#define OS_FREEBSD 1
|
||||
#elif defined(__NetBSD__)
|
||||
#define OS_NETBSD 1
|
||||
#elif defined(__OpenBSD__)
|
||||
#define OS_OPENBSD 1
|
||||
#elif defined(__sun)
|
||||
#define OS_SOLARIS 1
|
||||
#elif defined(__QNXNTO__)
|
||||
#define OS_QNX 1
|
||||
#elif defined(_AIX)
|
||||
#define OS_AIX 1
|
||||
#elif defined(__asmjs__) || defined(__wasm__)
|
||||
#define OS_ASMJS 1
|
||||
#elif defined(__MVS__)
|
||||
#define OS_ZOS 1
|
||||
#else
|
||||
#error Please add support for your platform in build/build_config.h
|
||||
#endif
|
||||
// NOTE: Adding a new port? Please follow
|
||||
// https://chromium.googlesource.com/chromium/src/+/main/docs/new_port_policy.md
|
||||
|
||||
#if defined(OS_MAC) || defined(OS_IOS)
|
||||
#define OS_APPLE 1
|
||||
#endif
|
||||
|
||||
// For access to standard BSD features, use OS_BSD instead of a
|
||||
// more specific macro.
|
||||
#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD)
|
||||
#define OS_BSD 1
|
||||
#endif
|
||||
|
||||
// For access to standard POSIX features, use OS_POSIX instead of a
|
||||
// more specific macro.
|
||||
#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) || \
|
||||
defined(OS_FREEBSD) || defined(OS_IOS) || defined(OS_LINUX) || \
|
||||
defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NACL) || \
|
||||
defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_QNX) || \
|
||||
defined(OS_SOLARIS) || defined(OS_ZOS)
|
||||
#define OS_POSIX 1
|
||||
#endif
|
||||
|
||||
// Compiler detection. Note: clang masquerades as GCC on POSIX and as MSVC on
|
||||
// Windows.
|
||||
#if defined(__GNUC__)
|
||||
#define COMPILER_GCC 1
|
||||
#elif defined(_MSC_VER)
|
||||
#define COMPILER_MSVC 1
|
||||
#else
|
||||
#error Please add support for your compiler in build/build_config.h
|
||||
#endif
|
||||
|
||||
// Type detection for wchar_t.
|
||||
#if defined(OS_WIN)
|
||||
#define WCHAR_T_IS_UTF16
|
||||
#elif defined(OS_FUCHSIA)
|
||||
#define WCHAR_T_IS_UTF32
|
||||
#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
|
||||
(__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
|
||||
#define WCHAR_T_IS_UTF32
|
||||
#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
|
||||
(__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
|
||||
// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to
|
||||
// compile in this mode (in particular, Chrome doesn't). This is intended for
|
||||
// other projects using base who manage their own dependencies and make sure
|
||||
// short wchar works for them.
|
||||
#define WCHAR_T_IS_UTF16
|
||||
#else
|
||||
#error Please add support for your compiler in build/build_config.h
|
||||
#endif
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_BASE_UTF_STRING_CONFIGURATION_H_
|
||||
@@ -1,510 +0,0 @@
|
||||
// Copyright 2021 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 "sharing/internal/base/utf_string_conversions.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "third_party/icu_utf/icu_utf.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace utils {
|
||||
namespace {
|
||||
|
||||
using MachineWord = uintptr_t;
|
||||
|
||||
constexpr int32_t kErrorCodePoint = 0xFFFD;
|
||||
|
||||
inline bool IsMachineWordAligned(const void* pointer) {
|
||||
return !(reinterpret_cast<MachineWord>(pointer) & (sizeof(MachineWord) - 1));
|
||||
}
|
||||
|
||||
template <class Char>
|
||||
bool DoIsStringAscii(const Char* characters, size_t length) {
|
||||
// Bitmasks to detect non-ASCII characters for character sizes of 8, 16 and 32
|
||||
// bits.
|
||||
constexpr MachineWord NonASCIIMasks[] = {
|
||||
0, MachineWord(0x8080808080808080ULL), MachineWord(0xFF80FF80FF80FF80ULL),
|
||||
0, MachineWord(0xFFFFFF80FFFFFF80ULL),
|
||||
};
|
||||
|
||||
if (!length) return true;
|
||||
constexpr MachineWord non_ascii_bit_mask = NonASCIIMasks[sizeof(Char)];
|
||||
static_assert(non_ascii_bit_mask, "Error: Invalid Mask");
|
||||
MachineWord all_char_bits = 0;
|
||||
const Char* end = characters + length;
|
||||
|
||||
// Prologue: align the input.
|
||||
while (!IsMachineWordAligned(characters) && characters < end)
|
||||
all_char_bits |= *characters++;
|
||||
if (all_char_bits & non_ascii_bit_mask) return false;
|
||||
|
||||
// Compare the values of CPU word size.
|
||||
constexpr size_t chars_per_word = sizeof(MachineWord) / sizeof(Char);
|
||||
constexpr int batch_count = 16;
|
||||
while (characters <= end - batch_count * chars_per_word) {
|
||||
all_char_bits = 0;
|
||||
for (int i = 0; i < batch_count; ++i) {
|
||||
all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
|
||||
characters += chars_per_word;
|
||||
}
|
||||
if (all_char_bits & non_ascii_bit_mask) return false;
|
||||
}
|
||||
|
||||
// Process the remaining words.
|
||||
all_char_bits = 0;
|
||||
while (characters <= end - chars_per_word) {
|
||||
all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
|
||||
characters += chars_per_word;
|
||||
}
|
||||
|
||||
// Process the remaining bytes.
|
||||
while (characters < end) all_char_bits |= *characters++;
|
||||
|
||||
return !(all_char_bits & non_ascii_bit_mask);
|
||||
}
|
||||
|
||||
inline bool IsValidCharacter(uint32_t code_point) {
|
||||
// Excludes non-characters (U+FDD0..U+FDEF, and all code points
|
||||
// ending in 0xFFFE or 0xFFFF) from the set of valid code points.
|
||||
// https://unicode.org/faq/private_use.html#nonchar1
|
||||
return code_point < 0xD800u ||
|
||||
(code_point >= 0xE000u && code_point < 0xFDD0u) ||
|
||||
(code_point > 0xFDEFu && code_point <= 0x10FFFFu &&
|
||||
(code_point & 0xFFFEu) != 0xFFFEu);
|
||||
}
|
||||
|
||||
template <bool (*Validator)(uint32_t)>
|
||||
inline bool DoIsStringUtf8(std::string_view str) {
|
||||
const char* src = str.data();
|
||||
int32_t src_len = static_cast<int32_t>(str.length());
|
||||
int32_t char_index = 0;
|
||||
|
||||
while (char_index < src_len) {
|
||||
int32_t code_point;
|
||||
CBU8_NEXT(src, char_index, src_len, code_point);
|
||||
if (!Validator(code_point)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Size coefficient ----------------------------------------------------------
|
||||
// The maximum number of codeunits in the destination encoding corresponding to
|
||||
// one codeunit in the source encoding.
|
||||
|
||||
template <typename SrcChar, typename DestChar>
|
||||
struct SizeCoefficient {
|
||||
static_assert(sizeof(SrcChar) < sizeof(DestChar),
|
||||
"Default case: from a smaller encoding to the bigger one");
|
||||
|
||||
// ASCII symbols are encoded by one codeunit in all encodings.
|
||||
static constexpr int value = 1;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SizeCoefficient<char16_t, char> {
|
||||
// One UTF-16 code unit corresponds to at most 3 code units in UTF-8.
|
||||
static constexpr int value = 3;
|
||||
};
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF32)
|
||||
template <>
|
||||
struct SizeCoefficient<wchar_t, char> {
|
||||
// UTF-8 uses at most 4 code units per character.
|
||||
static constexpr int value = 4;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SizeCoefficient<wchar_t, char16_t> {
|
||||
// UTF-16 uses at most 2 code units per character.
|
||||
static constexpr int value = 2;
|
||||
};
|
||||
#endif // defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
template <typename SrcChar, typename DestChar>
|
||||
constexpr int size_coefficient_v =
|
||||
SizeCoefficient<std::decay_t<SrcChar>, std::decay_t<DestChar>>::value;
|
||||
|
||||
// UnicodeAppendUnsafe --------------------------------------------------------
|
||||
// Function overloads that write code_point to the output string. Output string
|
||||
// has to have enough space for the codepoint.
|
||||
|
||||
// Convenience typedef that checks whether the passed in type is integral (i.e.
|
||||
// bool, char, int or their extended versions) and is of the correct size.
|
||||
template <typename Char, size_t N>
|
||||
using EnableIfBitsAre = std::enable_if_t<
|
||||
std::is_integral<Char>::value && CHAR_BIT * sizeof(Char) == N, bool>;
|
||||
|
||||
template <typename Char, EnableIfBitsAre<Char, 8> = true>
|
||||
void UnicodeAppendUnsafe(Char* out, int32_t* size, uint32_t code_point) {
|
||||
CBU8_APPEND_UNSAFE(out, *size, code_point);
|
||||
}
|
||||
|
||||
template <typename Char, EnableIfBitsAre<Char, 16> = true>
|
||||
void UnicodeAppendUnsafe(Char* out, int32_t* size, uint32_t code_point) {
|
||||
CBU16_APPEND_UNSAFE(out, *size, code_point);
|
||||
}
|
||||
|
||||
template <typename Char, EnableIfBitsAre<Char, 32> = true>
|
||||
void UnicodeAppendUnsafe(Char* out, int32_t* size, uint32_t code_point) {
|
||||
out[(*size)++] = code_point;
|
||||
}
|
||||
|
||||
// DoUtfConversion ------------------------------------------------------------
|
||||
// Main driver of UtfConversion specialized for different Src encodings.
|
||||
// dest has to have enough room for the converted text.
|
||||
|
||||
template <typename DestChar>
|
||||
bool DoUtfConversion(const char* src, int32_t src_len, DestChar* dest,
|
||||
int32_t* dest_len) {
|
||||
bool success = true;
|
||||
|
||||
for (int32_t i = 0; i < src_len;) {
|
||||
int32_t code_point;
|
||||
CBU8_NEXT(src, i, src_len, code_point);
|
||||
|
||||
if (!IsValidCodepoint(code_point)) {
|
||||
success = false;
|
||||
code_point = kErrorCodePoint;
|
||||
}
|
||||
|
||||
UnicodeAppendUnsafe(dest, dest_len, code_point);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
template <typename DestChar>
|
||||
bool DoUtfConversion(const char16_t* src, int32_t src_len, DestChar* dest,
|
||||
int32_t* dest_len) {
|
||||
bool success = true;
|
||||
|
||||
auto ConvertSingleChar = [&success](char16_t in) -> int32_t {
|
||||
if (!CBU16_IS_SINGLE(in) || !IsValidCodepoint(in)) {
|
||||
success = false;
|
||||
return kErrorCodePoint;
|
||||
}
|
||||
return in;
|
||||
};
|
||||
|
||||
int32_t i = 0;
|
||||
|
||||
// Always have another symbol in order to avoid checking boundaries in the
|
||||
// middle of the surrogate pair.
|
||||
while (i < src_len - 1) {
|
||||
int32_t code_point;
|
||||
|
||||
if (CBU16_IS_LEAD(src[i]) && CBU16_IS_TRAIL(src[i + 1])) {
|
||||
code_point = CBU16_GET_SUPPLEMENTARY(src[i], src[i + 1]);
|
||||
if (!IsValidCodepoint(code_point)) {
|
||||
code_point = kErrorCodePoint;
|
||||
success = false;
|
||||
}
|
||||
i += 2;
|
||||
} else {
|
||||
code_point = ConvertSingleChar(src[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
UnicodeAppendUnsafe(dest, dest_len, code_point);
|
||||
}
|
||||
|
||||
if (i < src_len)
|
||||
UnicodeAppendUnsafe(dest, dest_len, ConvertSingleChar(src[i]));
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
template <typename DestChar>
|
||||
bool DoUtfConversion(const wchar_t* src, int32_t src_len, DestChar* dest,
|
||||
int32_t* dest_len) {
|
||||
bool success = true;
|
||||
|
||||
for (int32_t i = 0; i < src_len; ++i) {
|
||||
int32_t code_point = src[i];
|
||||
|
||||
if (!IsValidCodepoint(code_point)) {
|
||||
success = false;
|
||||
code_point = kErrorCodePoint;
|
||||
}
|
||||
|
||||
UnicodeAppendUnsafe(dest, dest_len, code_point);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#endif // defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
// UtfConversion --------------------------------------------------------------
|
||||
// Function template for generating all UTF conversions.
|
||||
|
||||
template <typename InputString, typename DestString>
|
||||
bool UtfConversion(const InputString& src_str, DestString* dest_str) {
|
||||
if (IsStringAscii(src_str)) {
|
||||
dest_str->assign(src_str.begin(), src_str.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
dest_str->resize(src_str.length() *
|
||||
size_coefficient_v<typename InputString::value_type,
|
||||
typename DestString::value_type>);
|
||||
|
||||
// Empty string is ASCII => it OK to call operator[].
|
||||
auto* dest = &(*dest_str)[0];
|
||||
|
||||
// ICU requires 32 bit numbers.
|
||||
int32_t src_len32 = static_cast<int32_t>(src_str.length());
|
||||
int32_t dest_len32 = 0;
|
||||
|
||||
bool res = DoUtfConversion(src_str.data(), src_len32, dest, &dest_len32);
|
||||
|
||||
dest_str->resize(dest_len32);
|
||||
dest_str->shrink_to_fit();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
inline const char16_t* as_u16cstr(const wchar_t* str) {
|
||||
return reinterpret_cast<const char16_t*>(str);
|
||||
}
|
||||
|
||||
inline const char16_t* as_u16cstr(std::wstring_view str) {
|
||||
return reinterpret_cast<const char16_t*>(str.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
// UTF16 <-> UTF8 --------------------------------------------------------------
|
||||
|
||||
bool Utf8ToUtf16(const char* src, size_t src_len, std::u16string* output) {
|
||||
return UtfConversion(std::string_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::u16string Utf8ToUtf16(std::string_view utf8) {
|
||||
std::u16string ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
Utf8ToUtf16(utf8.data(), utf8.size(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Utf16ToUtf8(const char16_t* src, size_t src_len, std::string* output) {
|
||||
return UtfConversion(std::u16string_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::string Utf16ToUtf8(std::u16string_view utf16) {
|
||||
std::string ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
Utf16ToUtf8(utf16.data(), utf16.length(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// UTF-16 <-> Wide -------------------------------------------------------------
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
// When wide == UTF-16 the conversions are a NOP.
|
||||
|
||||
bool WideToUtf16(const wchar_t* src, size_t src_len, std::u16string* output) {
|
||||
output->assign(src, src + src_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::u16string WideToUtf16(std::wstring_view wide) {
|
||||
return std::u16string(wide.begin(), wide.end());
|
||||
}
|
||||
|
||||
bool Utf16ToWide(const char16_t* src, size_t src_len, std::wstring* output) {
|
||||
output->assign(src, src + src_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring Utf16ToWide(std::u16string_view utf16) {
|
||||
return std::wstring(utf16.begin(), utf16.end());
|
||||
}
|
||||
|
||||
#elif defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
bool WideToUtf16(const wchar_t* src, size_t src_len, std::u16string* output) {
|
||||
return UtfConversion(std::wstring_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::u16string WideToUtf16(std::wstring_view wide) {
|
||||
std::u16string ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
WideToUtf16(wide.data(), wide.length(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Utf16ToWide(const char16_t* src, size_t src_len, std::wstring* output) {
|
||||
return UtfConversion(std::u16string_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::wstring Utf16ToWide(std::u16string_view utf16) {
|
||||
std::wstring ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
Utf16ToWide(utf16.data(), utf16.length(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
// UTF-8 <-> Wide --------------------------------------------------------------
|
||||
|
||||
// UTF8ToWide is the same code, regardless of whether wide is 16 or 32 bits
|
||||
|
||||
bool Utf8ToWide(const char* src, size_t src_len, std::wstring* output) {
|
||||
return UtfConversion(std::string_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::wstring Utf8ToWide(std::string_view utf8) {
|
||||
std::wstring ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
Utf8ToWide(utf8.data(), utf8.length(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
// Easy case since we can use the "utf" versions we already wrote above.
|
||||
|
||||
bool WideToUtf8(const wchar_t* src, size_t src_len, std::string* output) {
|
||||
return Utf16ToUtf8(as_u16cstr(src), src_len, output);
|
||||
}
|
||||
|
||||
std::string WideToUtf8(std::wstring_view wide) {
|
||||
return Utf16ToUtf8(std::u16string_view(as_u16cstr(wide), wide.size()));
|
||||
}
|
||||
|
||||
#elif defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
bool WideToUtf8(const wchar_t* src, size_t src_len, std::string* output) {
|
||||
return UtfConversion(std::wstring_view(src, src_len), output);
|
||||
}
|
||||
|
||||
std::string WideToUtf8(std::wstring_view wide) {
|
||||
std::string ret;
|
||||
// Ignore the success flag of this call, it will do the best it can for
|
||||
// invalid input, which is what we want here.
|
||||
WideToUtf8(wide.data(), wide.length(), &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
std::u16string AsciiToUtf16(std::string_view ascii) {
|
||||
NL_DCHECK(IsStringAscii(ascii));
|
||||
return std::u16string(ascii.begin(), ascii.end());
|
||||
}
|
||||
|
||||
std::string Utf16ToAscii(std::u16string_view utf16) {
|
||||
NL_DCHECK(IsStringAscii(utf16));
|
||||
return std::string(utf16.begin(), utf16.end());
|
||||
}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
std::wstring AsciiToWide(std::string_view ascii) {
|
||||
NL_DCHECK(IsStringAscii(ascii));
|
||||
return std::wstring(ascii.begin(), ascii.end());
|
||||
}
|
||||
|
||||
std::string WideToAscii(std::string_view wide) {
|
||||
NL_DCHECK(IsStringAscii(wide));
|
||||
return std::string(wide.begin(), wide.end());
|
||||
}
|
||||
#endif // defined(WCHAR_T_IS_UTF16)
|
||||
|
||||
bool IsStringAscii(std::string_view str) {
|
||||
return DoIsStringAscii(str.data(), str.length());
|
||||
}
|
||||
|
||||
bool IsStringAscii(std::u16string_view str) {
|
||||
return DoIsStringAscii(str.data(), str.length());
|
||||
}
|
||||
|
||||
bool IsStringUtf8(std::string_view str) {
|
||||
return DoIsStringUtf8<IsValidCharacter>(str);
|
||||
}
|
||||
|
||||
bool IsStringAscii(std::wstring_view str) {
|
||||
return DoIsStringAscii(str.data(), str.length());
|
||||
}
|
||||
|
||||
bool IsValidCodepoint(uint32_t code_point) {
|
||||
// Excludes code points that are not Unicode scalar values, i.e.
|
||||
// surrogate code points ([0xD800, 0xDFFF]). Additionally, excludes
|
||||
// code points larger than 0x10FFFF (the highest codepoint allowed).
|
||||
// Non-characters and unassigned code points are allowed.
|
||||
// https://unicode.org/glossary/#unicode_scalar_value
|
||||
return code_point < 0xD800u ||
|
||||
(code_point >= 0xE000u && code_point <= 0x10FFFFu);
|
||||
}
|
||||
|
||||
void TruncateUtf8ToByteSize(const std::string& input, size_t byte_size,
|
||||
std::string* output) {
|
||||
NL_DCHECK(output);
|
||||
if (byte_size > input.length()) {
|
||||
*output = input;
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: This cast is necessary because CBU8_NEXT uses int32_ts.
|
||||
int32_t truncation_length = static_cast<int32_t>(byte_size);
|
||||
int32_t char_index = truncation_length - 1;
|
||||
const char* data = input.data();
|
||||
|
||||
// Using CBU8, we will move backwards from the truncation point
|
||||
// to the beginning of the string looking for a valid UTF8
|
||||
// character. Once a full UTF8 character is found, we will
|
||||
// truncate the string to the end of that character.
|
||||
while (char_index >= 0) {
|
||||
int32_t prev = char_index;
|
||||
int32_t code_point = 0;
|
||||
CBU8_NEXT(data, char_index, truncation_length, code_point);
|
||||
if (!IsValidCharacter(code_point) || !IsValidCodepoint(code_point)) {
|
||||
char_index = prev - 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (char_index >= 0)
|
||||
*output = input.substr(0, char_index);
|
||||
else
|
||||
output->clear();
|
||||
}
|
||||
|
||||
std::string ToString(const char* str) {
|
||||
if (str == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return std::string(str);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace nearby
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Google LLC
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,103 +15,24 @@
|
||||
#ifndef THIRD_PARTY_NEARBY_SHARING_INTERNAL_BASE_UTF_STRING_CONVERSIONS_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_INTERNAL_BASE_UTF_STRING_CONVERSIONS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#if defined(GITHUB_BUILD)
|
||||
// Stub out string conversion functions for github builds.
|
||||
namespace nearby::utils {
|
||||
std::string WideToUtf8(std::wstring_view wide) { return std::string(); }
|
||||
std::wstring Utf8ToWide(std::string_view utf8) { return std::wstring(); }
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "sharing/internal/base/utf_string_configuration.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace utils {
|
||||
|
||||
// These convert between UTF-8, -16, and -32 strings. They are potentially slow,
|
||||
// so avoid unnecessary conversions. The low-level versions return a boolean
|
||||
// indicating whether the conversion was 100% valid. In this case, it will still
|
||||
// do the best it can and put the result in the output buffer. The versions that
|
||||
// return strings ignore this error and just return the best conversion
|
||||
// possible.
|
||||
bool WideToUtf8(const wchar_t* src, size_t src_len, std::string* output);
|
||||
std::string WideToUtf8(std::wstring_view wide);
|
||||
bool Utf8ToWide(const char* src, size_t src_len, std::wstring* output);
|
||||
std::wstring Utf8ToWide(std::string_view utf8);
|
||||
|
||||
bool WideToUtf16(const wchar_t* src, size_t src_len, std::u16string* output);
|
||||
std::u16string WideToUtf16(std::wstring_view wide);
|
||||
bool Utf16ToWide(const char16_t* src, size_t src_len, std::wstring* output);
|
||||
std::wstring Utf16ToWide(std::u16string_view utf16);
|
||||
|
||||
bool Utf8ToUtf16(const char* src, size_t src_len, std::u16string* output);
|
||||
std::u16string Utf8ToUtf16(std::string_view utf8);
|
||||
bool Utf16ToUtf8(const char16_t* src, size_t src_len, std::string* output);
|
||||
std::string Utf16ToUtf8(std::u16string_view utf16);
|
||||
|
||||
// This converts an ASCII string, typically a hard coded constant, to a UTF16
|
||||
// string.
|
||||
std::u16string AsciiToUtf16(std::string_view ascii);
|
||||
|
||||
// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
|
||||
// beforehand.
|
||||
std::string Utf16ToAscii(std::u16string_view utf16);
|
||||
|
||||
bool IsStringAscii(std::string_view str);
|
||||
bool IsStringAscii(std::u16string_view str);
|
||||
bool IsStringAscii(std::wstring_view str);
|
||||
bool IsStringUtf8(std::string_view str);
|
||||
|
||||
inline bool IsValidCodepoint(uint32_t code_point);
|
||||
bool IsStringUtf8(std::string_view str) { return true; }
|
||||
|
||||
void TruncateUtf8ToByteSize(const std::string& input, size_t byte_size,
|
||||
std::string* output);
|
||||
std::string* output) {}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
// This converts an ASCII string, typically a hard coded constant, to a wide
|
||||
// string.
|
||||
std::wstring AsciiToWide(std::string_view ascii);
|
||||
} // namespace nearby::utils
|
||||
|
||||
// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
|
||||
// beforehand.
|
||||
std::string WideToAscii(std::wstring_view wide);
|
||||
#endif // defined(WCHAR_T_IS_UTF16)
|
||||
#elif defined(NEARBY_CHROMIUM)
|
||||
// Forward to chromium implementations.
|
||||
|
||||
// The conversion functions in this file should not be used to convert string
|
||||
// literals. Instead, the corresponding prefixes (e.g. u"" for UTF16 or L"" for
|
||||
// Wide) should be used. Deleting the overloads here catches these cases at
|
||||
// compile time.
|
||||
template <size_t N>
|
||||
std::u16string WideToUtf16(const wchar_t (&str)[N]) {
|
||||
static_assert(N == 0, "Error: Use the u\"...\" prefix instead.");
|
||||
return std::u16string();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::u16string Utf8ToUtf16(const char (&str)[N]) {
|
||||
static_assert(N == 0, "Error: Use the u\"...\" prefix instead.");
|
||||
return std::u16string();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::u16string AsciiToUtf16(const char (&str)[N]) {
|
||||
static_assert(N == 0, "Error: Use the u\"...\" prefix instead.");
|
||||
return std::u16string();
|
||||
}
|
||||
|
||||
// Mutable character arrays are usually only populated during runtime. Continue
|
||||
// to allow this conversion.
|
||||
template <size_t N>
|
||||
std::u16string AsciiToUtf16(char (&str)[N]) {
|
||||
return AsciiToUtf16(std::string_view(str));
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t size(const T (&array)[N]) noexcept {
|
||||
return N;
|
||||
}
|
||||
|
||||
std::string ToString(const char* str);
|
||||
|
||||
} // namespace utils
|
||||
} // namespace nearby
|
||||
#else // defined(GITHUB_BUILD)
|
||||
#include "sharing/internal/base/strings/utf_string_conversions.h" // IWYU pragma: export
|
||||
#endif // defined(GITHUB_BUILD)
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_INTERNAL_BASE_UTF_STRING_CONVERSIONS_H_
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
// Copyright 2021 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 "sharing/internal/base/utf_string_conversions.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace utils {
|
||||
namespace {
|
||||
|
||||
const wchar_t* const kConvertRoundtripCases[] = {
|
||||
L"Google Video",
|
||||
// "网页 图片 资讯更多 »"
|
||||
L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
|
||||
// "Παγκόσμιος Ιστός"
|
||||
L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
|
||||
L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
|
||||
// "Поиск страниц на русском"
|
||||
L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
|
||||
L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
|
||||
L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
|
||||
// "전체서비스"
|
||||
L"\xc804\xccb4\xc11c\xbe44\xc2a4",
|
||||
|
||||
// Test characters that take more than 16 bits. This will depend on whether
|
||||
// wchar_t is 16 or 32 bits.
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
L"\xd800\xdf00",
|
||||
// ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
|
||||
// A,B,C,D,E)
|
||||
L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
|
||||
#elif defined(WCHAR_T_IS_UTF32)
|
||||
L"\x10300",
|
||||
// ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
|
||||
// A,B,C,D,E)
|
||||
L"\x11d40\x11d41\x11d42\x11d43\x11d44",
|
||||
#endif
|
||||
};
|
||||
|
||||
// Helper used to test TruncateUtf8ToByteSize.
|
||||
bool Truncated(const std::string& input, const size_t byte_size,
|
||||
std::string* output) {
|
||||
size_t prev = input.length();
|
||||
TruncateUtf8ToByteSize(input, byte_size, output);
|
||||
return prev != output->length();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UtfStringConversionsTest, ConvertUtf8AndWide) {
|
||||
// we round-trip all the wide strings through UTF-8 to make sure everything
|
||||
// agrees on the conversion. This uses the stream operators to test them
|
||||
// simultaneously.
|
||||
for (auto* i : kConvertRoundtripCases) {
|
||||
std::ostringstream utf8;
|
||||
utf8 << WideToUtf8(i);
|
||||
std::wostringstream wide;
|
||||
wide << Utf8ToWide(utf8.str());
|
||||
|
||||
EXPECT_EQ(i, wide.str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UtfStringConversionsTest, ConvertUtf8AndWideEmptyString) {
|
||||
// An empty std::wstring should be converted to an empty std::string,
|
||||
// and vice versa.
|
||||
std::wstring wempty;
|
||||
std::string empty;
|
||||
EXPECT_EQ(empty, WideToUtf8(wempty));
|
||||
EXPECT_EQ(wempty, Utf8ToWide(empty));
|
||||
}
|
||||
|
||||
TEST(UtfStringConversionsTest, ConvertUtf8ToWide) {
|
||||
struct Utf8ToWideCase {
|
||||
const char* utf8;
|
||||
const wchar_t* wide;
|
||||
bool success;
|
||||
} convert_cases[] = {
|
||||
// Regular UTF-8 input.
|
||||
{"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true},
|
||||
// Non-character is passed through.
|
||||
{"\xef\xbf\xbfHello", L"\xffffHello", true},
|
||||
// Truncated UTF-8 sequence.
|
||||
{"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false},
|
||||
// Truncated off the end.
|
||||
{"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false},
|
||||
// Non-shortest-form UTF-8.
|
||||
{"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\xfffd\xfffd\xfffd\x597d", false},
|
||||
// This UTF-8 character is decoded to a UTF-16 surrogate, which is illegal.
|
||||
{"\xed\xb0\x80", L"\xfffd\xfffd\xfffd", false},
|
||||
// Non-BMP characters. The second is a non-character regarded as valid.
|
||||
// The result will either be in UTF-16 or UTF-32.
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
{"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true},
|
||||
{"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true},
|
||||
#elif defined(WCHAR_T_IS_UTF32)
|
||||
{"A\xF0\x90\x8C\x80z", L"A\x10300z", true},
|
||||
{"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true},
|
||||
#endif
|
||||
};
|
||||
|
||||
for (const auto& i : convert_cases) {
|
||||
std::wstring converted;
|
||||
EXPECT_EQ(i.success, Utf8ToWide(i.utf8, strlen(i.utf8), &converted));
|
||||
std::wstring expected(i.wide);
|
||||
EXPECT_EQ(expected, converted);
|
||||
}
|
||||
|
||||
// Manually test an embedded NULL.
|
||||
std::wstring converted;
|
||||
EXPECT_TRUE(Utf8ToWide("\00Z\t", 3, &converted));
|
||||
ASSERT_EQ(3U, converted.length());
|
||||
EXPECT_EQ(static_cast<wchar_t>(0), converted[0]);
|
||||
EXPECT_EQ('Z', converted[1]);
|
||||
EXPECT_EQ('\t', converted[2]);
|
||||
|
||||
// Make sure that conversion replaces, not appends.
|
||||
EXPECT_TRUE(Utf8ToWide("B", 1, &converted));
|
||||
ASSERT_EQ(1U, converted.length());
|
||||
EXPECT_EQ('B', converted[0]);
|
||||
}
|
||||
|
||||
#if defined(WCHAR_T_IS_UTF16)
|
||||
// This test is only valid when wchar_t == UTF-16.
|
||||
TEST(UtfStringConversionsTest, ConvertUtf16ToUtf8) {
|
||||
struct WideToUtf8Case {
|
||||
const wchar_t* utf16;
|
||||
const char* utf8;
|
||||
bool success;
|
||||
} convert_cases[] = {
|
||||
// Regular UTF-16 input.
|
||||
{L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
|
||||
// Test a non-BMP character.
|
||||
{L"\xd800\xdf00", "\xF0\x90\x8C\x80", true},
|
||||
// Non-characters are passed through.
|
||||
{L"\xffffHello", "\xEF\xBF\xBFHello", true},
|
||||
{L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true},
|
||||
// The first character is a truncated UTF-16 character.
|
||||
{L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
|
||||
// Truncated at the end.
|
||||
{L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false},
|
||||
};
|
||||
|
||||
for (const auto& test : convert_cases) {
|
||||
std::string converted;
|
||||
EXPECT_EQ(test.success,
|
||||
WideToUtf8(test.utf16, wcslen(test.utf16), &converted));
|
||||
std::string expected(test.utf8);
|
||||
EXPECT_EQ(expected, converted);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(WCHAR_T_IS_UTF32)
|
||||
// This test is only valid when wchar_t == UTF-32.
|
||||
TEST(UtfStringConversionsTest, ConvertUtf32ToUtf8) {
|
||||
struct WideToUtf8Case {
|
||||
const wchar_t* utf32;
|
||||
const char* utf8;
|
||||
bool success;
|
||||
} convert_cases[] = {
|
||||
// Regular 16-bit input.
|
||||
{L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
|
||||
// Test a non-BMP character.
|
||||
{L"A\x10300z", "A\xF0\x90\x8C\x80z", true},
|
||||
// Non-characters are passed through.
|
||||
{L"\xffffHello", "\xEF\xBF\xBFHello", true},
|
||||
{L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true},
|
||||
// Invalid Unicode code points.
|
||||
{L"\xfffffffHello", "\xEF\xBF\xBDHello", false},
|
||||
// The first character is a truncated UTF-16 character.
|
||||
{L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
|
||||
{L"\xdc01Hello", "\xef\xbf\xbdHello", false},
|
||||
};
|
||||
|
||||
for (const auto& test : convert_cases) {
|
||||
std::string converted;
|
||||
EXPECT_EQ(test.success,
|
||||
WideToUtf8(test.utf32, wcslen(test.utf32), &converted));
|
||||
std::string expected(test.utf8);
|
||||
EXPECT_EQ(expected, converted);
|
||||
}
|
||||
}
|
||||
#endif // defined(WCHAR_T_IS_UTF32)
|
||||
|
||||
TEST(UtfStringConversionsTest, TruncateUtf8ToByteSize) {
|
||||
std::string output;
|
||||
|
||||
// Empty strings and invalid byte_size arguments
|
||||
EXPECT_FALSE(Truncated(std::string(), 0, &output));
|
||||
EXPECT_EQ(output, "");
|
||||
EXPECT_TRUE(Truncated("\xe1\x80\xbf", 0, &output));
|
||||
EXPECT_EQ(output, "");
|
||||
EXPECT_FALSE(Truncated("\xe1\x80\xbf", static_cast<size_t>(-1), &output));
|
||||
EXPECT_FALSE(Truncated("\xe1\x80\xbf", 4, &output));
|
||||
|
||||
// Testing the truncation of valid UTF8 correctly
|
||||
EXPECT_TRUE(Truncated("abc", 2, &output));
|
||||
EXPECT_EQ(output, "ab");
|
||||
EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 2, &output));
|
||||
EXPECT_EQ(output.compare("\xc2\x81"), 0);
|
||||
EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 3, &output));
|
||||
EXPECT_EQ(output.compare("\xc2\x81"), 0);
|
||||
EXPECT_FALSE(Truncated("\xc2\x81\xc2\x81", 4, &output));
|
||||
EXPECT_EQ(output.compare("\xc2\x81\xc2\x81"), 0);
|
||||
|
||||
{
|
||||
const char array[] = "\x00\x00\xc2\x81\xc2\x81";
|
||||
const std::string array_string(array, size(array));
|
||||
EXPECT_TRUE(Truncated(array_string, 4, &output));
|
||||
EXPECT_EQ(output.compare(std::string("\x00\x00\xc2\x81", 4)), 0);
|
||||
}
|
||||
|
||||
{
|
||||
const char array[] = "\x00\xc2\x81\xc2\x81";
|
||||
const std::string array_string(array, size(array));
|
||||
EXPECT_TRUE(Truncated(array_string, 4, &output));
|
||||
EXPECT_EQ(output.compare(std::string("\x00\xc2\x81", 3)), 0);
|
||||
}
|
||||
|
||||
// Testing invalid UTF8
|
||||
EXPECT_TRUE(Truncated("\xed\xa0\x80\xed\xbf\xbf", 6, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xed\xa0\x8f", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xed\xbf\xbf", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
// Testing invalid UTF8 mixed with valid UTF8
|
||||
EXPECT_FALSE(Truncated("\xe1\x80\xbf", 3, &output));
|
||||
EXPECT_EQ(output.compare("\xe1\x80\xbf"), 0);
|
||||
EXPECT_FALSE(Truncated("\xf1\x80\xa0\xbf", 4, &output));
|
||||
EXPECT_EQ(output.compare("\xf1\x80\xa0\xbf"), 0);
|
||||
EXPECT_FALSE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf", 10, &output));
|
||||
EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf"), 0);
|
||||
EXPECT_TRUE(
|
||||
Truncated("a\xc2\x81\xe1\x80\xbf\xf1"
|
||||
"a"
|
||||
"\x80\xa0",
|
||||
10, &output));
|
||||
EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1"
|
||||
"a"),
|
||||
0);
|
||||
EXPECT_FALSE(
|
||||
Truncated("\xef\xbb\xbf"
|
||||
"abc",
|
||||
6, &output));
|
||||
EXPECT_EQ(output.compare("\xef\xbb\xbf"
|
||||
"abc"),
|
||||
0);
|
||||
|
||||
// Overlong sequences
|
||||
EXPECT_TRUE(Truncated("\xc0\x80", 2, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xc1\x80\xc1\x81", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xe0\x80\x80", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xe0\x82\x80", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xe0\x9f\xbf", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf0\x80\x80\x8D", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf0\x80\x82\x91", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf0\x80\xa0\x80", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf0\x8f\xbb\xbf", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf8\x80\x80\x80\xbf", 5, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xfc\x80\x80\x80\xa0\xa5", 6, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
// Beyond U+10FFFF (the upper limit of Unicode codespace)
|
||||
EXPECT_TRUE(Truncated("\xf4\x90\x80\x80", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf8\xa0\xbf\x80\xbf", 5, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xfc\x9c\xbf\x80\xbf\x80", 6, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
// BOMs in UTF-16(BE|LE) and UTF-32(BE|LE)
|
||||
EXPECT_TRUE(Truncated("\xfe\xff", 2, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xff\xfe", 2, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
{
|
||||
const char array[] = "\x00\x00\xfe\xff";
|
||||
const std::string array_string(array, size(array));
|
||||
EXPECT_TRUE(Truncated(array_string, 4, &output));
|
||||
EXPECT_EQ(output.compare(std::string("\x00\x00", 2)), 0);
|
||||
}
|
||||
|
||||
// Variants on the previous test
|
||||
{
|
||||
const char array[] = "\xff\xfe\x00\x00";
|
||||
const std::string array_string(array, 4);
|
||||
EXPECT_FALSE(Truncated(array_string, 4, &output));
|
||||
EXPECT_EQ(output.compare(std::string("\xff\xfe\x00\x00", 4)), 0);
|
||||
}
|
||||
{
|
||||
const char array[] = "\xff\x00\x00\xfe";
|
||||
const std::string array_string(array, size(array));
|
||||
EXPECT_TRUE(Truncated(array_string, 4, &output));
|
||||
EXPECT_EQ(output.compare(std::string("\xff\x00\x00", 3)), 0);
|
||||
}
|
||||
|
||||
// Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and <FDD0,FDEF>
|
||||
EXPECT_TRUE(Truncated("\xef\xbf\xbe", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf0\x8f\xbf\xbe", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xf3\xbf\xbf\xbf", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xef\xb7\x90", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_TRUE(Truncated("\xef\xb7\xaf", 3, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
// Strings in legacy encodings that are valid in UTF-8, but
|
||||
// are invalid as UTF-8 in real data.
|
||||
EXPECT_TRUE(Truncated("caf\xe9", 4, &output));
|
||||
EXPECT_EQ(output.compare("caf"), 0);
|
||||
EXPECT_TRUE(Truncated("\xb0\xa1\xb0\xa2", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
EXPECT_FALSE(Truncated("\xa7\x41\xa6\x6e", 4, &output));
|
||||
EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
|
||||
EXPECT_TRUE(Truncated("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7, &output));
|
||||
EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
|
||||
|
||||
// Testing using the same string as input and output.
|
||||
EXPECT_FALSE(Truncated(output, 4, &output));
|
||||
EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
|
||||
EXPECT_TRUE(Truncated(output, 3, &output));
|
||||
EXPECT_EQ(output.compare("\xa7\x41"), 0);
|
||||
|
||||
// "abc" with U+201[CD] in windows-125[0-8]
|
||||
EXPECT_TRUE(
|
||||
Truncated("\x93"
|
||||
"abc\x94",
|
||||
5, &output));
|
||||
EXPECT_EQ(output.compare("\x93"
|
||||
"abc"),
|
||||
0);
|
||||
|
||||
// U+0639 U+064E U+0644 U+064E in ISO-8859-6
|
||||
EXPECT_TRUE(Truncated("\xd9\xee\xe4\xee", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
|
||||
// U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7
|
||||
EXPECT_TRUE(Truncated("\xe3\xe5\xe9\xdC", 4, &output));
|
||||
EXPECT_EQ(output.compare(""), 0);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,97 @@
|
||||
# Copyright 2024 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.
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "local_device_data",
|
||||
srcs = [
|
||||
"nearby_share_local_device_data_manager.cc",
|
||||
"nearby_share_local_device_data_manager_impl.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"nearby_share_local_device_data_manager.h",
|
||||
"nearby_share_local_device_data_manager_impl.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/base",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation:types",
|
||||
"//sharing/common",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/base:utf_utils",
|
||||
"//sharing/internal/public:logging",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_protobuf//:protobuf_lite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "test_support",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"fake_nearby_share_local_device_data_manager.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"fake_nearby_share_local_device_data_manager.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":local_device_data",
|
||||
"//sharing/common",
|
||||
"//sharing/internal/api:platform",
|
||||
"//sharing/internal/public:types",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "local_device_data_test",
|
||||
srcs = [
|
||||
"nearby_share_local_device_data_manager_impl_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":local_device_data",
|
||||
"//internal/platform/implementation:types",
|
||||
"//internal/platform/implementation/g3", # fixdeps: keep
|
||||
"//internal/test",
|
||||
"//sharing/common",
|
||||
"//sharing/common:test_support",
|
||||
"//sharing/internal/api:mock_sharing_platform",
|
||||
"//sharing/internal/test:nearby_test",
|
||||
"//sharing/proto:share_cc_proto",
|
||||
"//sharing/scheduling",
|
||||
"//sharing/scheduling:test_support",
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2021-2023 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 "sharing/local_device_data/fake_nearby_share_local_device_data_manager.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
class NearbyShareClientFactory;
|
||||
class NearbyShareProfileInfoProvider;
|
||||
|
||||
namespace {
|
||||
|
||||
using ::nearby::sharing::api::SharingRpcClientFactory;
|
||||
|
||||
constexpr absl::string_view kDefaultId = "123456789A";
|
||||
constexpr absl::string_view kDefaultDeviceName = "Barack's Chromebook";
|
||||
|
||||
} // namespace
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::Factory::Factory() = default;
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::Factory::~Factory() = default;
|
||||
|
||||
std::unique_ptr<NearbyShareLocalDeviceDataManager>
|
||||
FakeNearbyShareLocalDeviceDataManager::Factory::CreateInstance(
|
||||
nearby::Context* context, SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider) {
|
||||
latest_rpc_client_factory_ = rpc_client_factory;
|
||||
latest_profile_info_provider_ = profile_info_provider;
|
||||
|
||||
auto instance = std::make_unique<FakeNearbyShareLocalDeviceDataManager>(
|
||||
kDefaultDeviceName);
|
||||
instances_.push_back(instance.get());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadContactsCall::UploadContactsCall(
|
||||
std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback)
|
||||
: contacts(std::move(contacts)), callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadContactsCall::UploadContactsCall(
|
||||
UploadContactsCall&&) = default;
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadContactsCall::
|
||||
~UploadContactsCall() = default;
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadCertificatesCall::
|
||||
UploadCertificatesCall(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback)
|
||||
: certificates(std::move(certificates)), callback(std::move(callback)) {}
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadCertificatesCall::
|
||||
UploadCertificatesCall(UploadCertificatesCall&&) = default;
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::UploadCertificatesCall::
|
||||
~UploadCertificatesCall() = default;
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::FakeNearbyShareLocalDeviceDataManager(
|
||||
absl::string_view default_device_name)
|
||||
: id_(kDefaultId), device_name_(default_device_name) {}
|
||||
|
||||
FakeNearbyShareLocalDeviceDataManager::
|
||||
~FakeNearbyShareLocalDeviceDataManager() = default;
|
||||
|
||||
std::string FakeNearbyShareLocalDeviceDataManager::GetId() { return id_; }
|
||||
|
||||
std::string FakeNearbyShareLocalDeviceDataManager::GetDeviceName() const {
|
||||
return device_name_;
|
||||
}
|
||||
|
||||
std::optional<std::string> FakeNearbyShareLocalDeviceDataManager::GetFullName()
|
||||
const {
|
||||
return full_name_;
|
||||
}
|
||||
|
||||
std::optional<std::string> FakeNearbyShareLocalDeviceDataManager::GetIconUrl()
|
||||
const {
|
||||
return icon_url_;
|
||||
}
|
||||
|
||||
DeviceNameValidationResult
|
||||
FakeNearbyShareLocalDeviceDataManager::ValidateDeviceName(
|
||||
absl::string_view name) {
|
||||
return next_validation_result_;
|
||||
}
|
||||
|
||||
DeviceNameValidationResult FakeNearbyShareLocalDeviceDataManager::SetDeviceName(
|
||||
absl::string_view name) {
|
||||
if (next_validation_result_ != DeviceNameValidationResult::kValid)
|
||||
return next_validation_result_;
|
||||
|
||||
if (device_name_ != name) {
|
||||
device_name_ = std::string(name);
|
||||
NotifyLocalDeviceDataChanged(
|
||||
/*did_device_name_change=*/true,
|
||||
/*did_full_name_change=*/false,
|
||||
/*did_icon_change=*/false);
|
||||
}
|
||||
|
||||
return DeviceNameValidationResult::kValid;
|
||||
}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::DownloadDeviceData() {
|
||||
++num_download_device_data_calls_;
|
||||
}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::UploadContacts(
|
||||
std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback) {
|
||||
upload_contacts_calls_.emplace_back(std::move(contacts), callback);
|
||||
if (is_sync_mode_) {
|
||||
callback(upload_contact_result_);
|
||||
}
|
||||
}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::UploadCertificates(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback) {
|
||||
upload_certificates_calls_.emplace_back(std::move(certificates), callback);
|
||||
if (is_sync_mode_) {
|
||||
callback(upload_certificate_result_);
|
||||
}
|
||||
}
|
||||
void FakeNearbyShareLocalDeviceDataManager::SetFullName(
|
||||
const absl::optional<std::string>& full_name) {
|
||||
if (full_name_ == full_name) return;
|
||||
|
||||
full_name_ = full_name;
|
||||
NotifyLocalDeviceDataChanged(
|
||||
/*did_device_name_change=*/false,
|
||||
/*did_full_name_change=*/true,
|
||||
/*did_icon_change=*/false);
|
||||
}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::SetIconUrl(
|
||||
const absl::optional<std::string>& icon_url) {
|
||||
if (icon_url_ == icon_url) return;
|
||||
|
||||
icon_url_ = icon_url;
|
||||
NotifyLocalDeviceDataChanged(
|
||||
/*did_device_name_change=*/false,
|
||||
/*did_full_name_change=*/false,
|
||||
/*did_icon_change=*/true);
|
||||
}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::OnStart() {}
|
||||
|
||||
void FakeNearbyShareLocalDeviceDataManager::OnStop() {}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2021-2023 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_SHARING_LOCAL_DEVICE_DATA_FAKE_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_FAKE_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager_impl.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// A fake implementation of NearbyShareLocalDeviceDataManager, along with a fake
|
||||
// factory, to be used in tests.
|
||||
class FakeNearbyShareLocalDeviceDataManager
|
||||
: public NearbyShareLocalDeviceDataManager {
|
||||
public:
|
||||
// Factory that creates FakeNearbyShareLocalDeviceDataManager instances. Use
|
||||
// in NearbyShareLocalDeviceDataManagerImpl::Factory::SetFactoryForTesting()
|
||||
// in unit tests.
|
||||
class Factory : public NearbyShareLocalDeviceDataManagerImpl::Factory {
|
||||
public:
|
||||
Factory();
|
||||
~Factory() override;
|
||||
|
||||
// Returns all FakeNearbyShareLocalDeviceDataManager instances created by
|
||||
// CreateInstance().
|
||||
std::vector<FakeNearbyShareLocalDeviceDataManager*>& instances() {
|
||||
return instances_;
|
||||
}
|
||||
|
||||
nearby::sharing::api::SharingRpcClientFactory* latest_rpc_client_factory()
|
||||
const {
|
||||
return latest_rpc_client_factory_;
|
||||
}
|
||||
|
||||
NearbyShareProfileInfoProvider* latest_profile_info_provider() const {
|
||||
return latest_profile_info_provider_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<NearbyShareLocalDeviceDataManager> CreateInstance(
|
||||
nearby::Context* context,
|
||||
nearby::sharing::api::SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider) override;
|
||||
|
||||
private:
|
||||
std::vector<FakeNearbyShareLocalDeviceDataManager*> instances_;
|
||||
nearby::sharing::api::SharingRpcClientFactory* latest_rpc_client_factory_ =
|
||||
nullptr;
|
||||
NearbyShareProfileInfoProvider* latest_profile_info_provider_ = nullptr;
|
||||
};
|
||||
|
||||
struct UploadContactsCall {
|
||||
UploadContactsCall(std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback);
|
||||
UploadContactsCall(UploadContactsCall&&);
|
||||
~UploadContactsCall();
|
||||
|
||||
std::vector<nearby::sharing::proto::Contact> contacts;
|
||||
UploadCompleteCallback callback;
|
||||
};
|
||||
|
||||
struct UploadCertificatesCall {
|
||||
UploadCertificatesCall(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback);
|
||||
UploadCertificatesCall(UploadCertificatesCall&&);
|
||||
~UploadCertificatesCall();
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates;
|
||||
UploadCompleteCallback callback;
|
||||
};
|
||||
|
||||
explicit FakeNearbyShareLocalDeviceDataManager(
|
||||
absl::string_view default_device_name);
|
||||
~FakeNearbyShareLocalDeviceDataManager() override;
|
||||
|
||||
// NearbyShareLocalDeviceDataManager:
|
||||
std::string GetId() override;
|
||||
std::string GetDeviceName() const override;
|
||||
std::optional<std::string> GetFullName() const override;
|
||||
std::optional<std::string> GetIconUrl() const override;
|
||||
DeviceNameValidationResult ValidateDeviceName(
|
||||
absl::string_view name) override;
|
||||
DeviceNameValidationResult SetDeviceName(absl::string_view name) override;
|
||||
void DownloadDeviceData() override;
|
||||
void UploadContacts(std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback) override;
|
||||
void UploadCertificates(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback) override;
|
||||
|
||||
// Make protected observer-notification methods from the base class public in
|
||||
// this fake class.
|
||||
using NearbyShareLocalDeviceDataManager::NotifyLocalDeviceDataChanged;
|
||||
|
||||
void SetId(absl::string_view id) { id_ = std::string(id); }
|
||||
void SetFullName(const std::optional<std::string>& full_name);
|
||||
void SetIconUrl(const std::optional<std::string>& icon_url);
|
||||
|
||||
size_t num_download_device_data_calls() const {
|
||||
return num_download_device_data_calls_;
|
||||
}
|
||||
|
||||
std::vector<UploadContactsCall>& upload_contacts_calls() {
|
||||
return upload_contacts_calls_;
|
||||
}
|
||||
|
||||
std::vector<UploadCertificatesCall>& upload_certificates_calls() {
|
||||
return upload_certificates_calls_;
|
||||
}
|
||||
|
||||
void set_next_validation_result(DeviceNameValidationResult result) {
|
||||
next_validation_result_ = result;
|
||||
}
|
||||
|
||||
// methods for synchronization test.
|
||||
void set_is_sync_mode(bool is_sync_mode) { is_sync_mode_ = is_sync_mode; }
|
||||
|
||||
void SetUploadContactsResult(bool upload_contact_result) {
|
||||
upload_contact_result_ = upload_contact_result;
|
||||
}
|
||||
|
||||
void SetUploadCertificatesResult(bool upload_certificate_result) {
|
||||
upload_certificate_result_ = upload_certificate_result;
|
||||
}
|
||||
|
||||
private:
|
||||
// NearbyShareLocalDeviceDataManager:
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
|
||||
std::string id_;
|
||||
std::string device_name_;
|
||||
std::optional<std::string> full_name_;
|
||||
std::optional<std::string> icon_url_;
|
||||
size_t num_download_device_data_calls_ = 0;
|
||||
std::vector<UploadContactsCall> upload_contacts_calls_;
|
||||
std::vector<UploadCertificatesCall> upload_certificates_calls_;
|
||||
DeviceNameValidationResult next_validation_result_ =
|
||||
DeviceNameValidationResult::kValid;
|
||||
|
||||
// Used to indicate whether the class is running in synchronization mode.
|
||||
bool is_sync_mode_ = false;
|
||||
bool upload_contact_result_ = false;
|
||||
bool upload_certificate_result_ = false;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_FAKE_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2021-2023 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 "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sharing/internal/public/logging.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
const size_t kNearbyShareDeviceNameMaxLength = 32;
|
||||
|
||||
NearbyShareLocalDeviceDataManager::NearbyShareLocalDeviceDataManager() =
|
||||
default;
|
||||
|
||||
NearbyShareLocalDeviceDataManager::~NearbyShareLocalDeviceDataManager() =
|
||||
default;
|
||||
|
||||
void NearbyShareLocalDeviceDataManager::AddObserver(Observer* observer) {
|
||||
observers_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManager::RemoveObserver(Observer* observer) {
|
||||
observers_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManager::Start() {
|
||||
if (is_running_) return;
|
||||
|
||||
is_running_ = true;
|
||||
OnStart();
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManager::Stop() {
|
||||
if (!is_running_) return;
|
||||
|
||||
is_running_ = false;
|
||||
OnStop();
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManager::NotifyLocalDeviceDataChanged(
|
||||
bool did_device_name_change, bool did_full_name_change,
|
||||
bool did_icon_change) {
|
||||
NL_LOG(INFO) << __func__ << ": did_device_name_change="
|
||||
<< (did_device_name_change ? "true" : "false")
|
||||
<< ", did_full_name_change="
|
||||
<< (did_full_name_change ? "true" : "false")
|
||||
<< ", did_icon_change=" << (did_icon_change ? "true" : "false");
|
||||
for (auto& observer : observers_.GetObservers()) {
|
||||
observer->OnLocalDeviceDataChanged(did_device_name_change,
|
||||
did_full_name_change, did_icon_change);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2021-2023 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_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/base/observer_list.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
// The maximum length in bytes allowed for a device name, as encoded in UTF-8 in
|
||||
// a std::string, which will not contain a null terminator.
|
||||
extern const size_t kNearbyShareDeviceNameMaxLength;
|
||||
|
||||
// Manages local device data related to the UpdateDevice RPC such as the device
|
||||
// ID, name, and icon URL; provides the user's full name and icon URL returned
|
||||
// from the Nearby server; and handles uploading contacts and certificates to
|
||||
// the Nearby server. The uploading of contacts and certificates might seem out
|
||||
// of place, but this class is the entry point for all UpdateDevice RPC calls.
|
||||
class NearbyShareLocalDeviceDataManager {
|
||||
public:
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
virtual void OnLocalDeviceDataChanged(bool did_device_name_change,
|
||||
bool did_full_name_change,
|
||||
bool did_icon_change) = 0;
|
||||
};
|
||||
|
||||
using UploadCompleteCallback = std::function<void(bool success)>;
|
||||
|
||||
NearbyShareLocalDeviceDataManager();
|
||||
virtual ~NearbyShareLocalDeviceDataManager();
|
||||
|
||||
void AddObserver(Observer* observer);
|
||||
void RemoveObserver(Observer* observer);
|
||||
|
||||
// Starts/Stops local-device-data task scheduling.
|
||||
void Start();
|
||||
void Stop();
|
||||
bool is_running() { return is_running_; }
|
||||
|
||||
// Returns the immutable ID generated for the local device, used to
|
||||
// differentiate a user's devices when communicating with the Nearby server.
|
||||
virtual std::string GetId() = 0;
|
||||
|
||||
// Returns the name of the local device, for example, "Josh's Chromebook."
|
||||
// This can be modified by SetDeviceName().
|
||||
virtual std::string GetDeviceName() const = 0;
|
||||
|
||||
// Returns the user's full name, for example, "Barack Obama". Returns
|
||||
// absl::nullopt if the name has not yet been set from an UpdateDevice RPC
|
||||
// response.
|
||||
virtual std::optional<std::string> GetFullName() const = 0;
|
||||
|
||||
// Returns the URL of the user's image. Returns absl::nullopt if the URL has
|
||||
// not yet been set from an UpdateDevice RPC response.
|
||||
virtual std::optional<std::string> GetIconUrl() const = 0;
|
||||
|
||||
// Validates the provided device name and returns an error if validation
|
||||
// fails. This is just a check and the device name is not persisted.
|
||||
virtual DeviceNameValidationResult ValidateDeviceName(
|
||||
absl::string_view name) = 0;
|
||||
|
||||
// Sets and persists the device name in prefs. The device name is first
|
||||
// validated and if validation fails and error is returned and the device name
|
||||
// is not persisted. The device name is *not* uploaded to the Nearby Share
|
||||
// server; the UpdateDevice proto device_name field in an artifact. Observers
|
||||
// are notified via OnLocalDeviceDataChanged() if the device name changes.
|
||||
virtual DeviceNameValidationResult SetDeviceName(absl::string_view name) = 0;
|
||||
|
||||
// Makes an UpdateDevice RPC call to the Nearby Share server to retrieve all
|
||||
// available device data, which includes the full name and icon URL for now.
|
||||
// This action is also scheduled periodically. Observers are notified via
|
||||
// OnLocalDeviceDataChanged() if any device data changes.
|
||||
virtual void DownloadDeviceData() = 0;
|
||||
|
||||
// Uses the UpdateDevice RPC to send the local device's contact list to the
|
||||
// Nearby Share server, including which contacts are allowed for
|
||||
// selected-contacts visibility mode. This should only be invoked by the
|
||||
// contact manager, and the contact manager should handle scheduling, failure
|
||||
// retry, etc.
|
||||
virtual void UploadContacts(
|
||||
std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback) = 0;
|
||||
|
||||
// Uses the UpdateDevice RPC to send the local device's public certificates to
|
||||
// the Nearby Share server. This should only be invoked by the certificate
|
||||
// manager, and the certificate manager should handle scheduling, failure
|
||||
// retry, etc.
|
||||
virtual void UploadCertificates(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback) = 0;
|
||||
|
||||
protected:
|
||||
virtual void OnStart() = 0;
|
||||
virtual void OnStop() = 0;
|
||||
|
||||
void NotifyLocalDeviceDataChanged(bool did_device_name_change,
|
||||
bool did_full_name_change,
|
||||
bool did_icon_change);
|
||||
|
||||
private:
|
||||
bool is_running_ = false;
|
||||
nearby::ObserverList<Observer> observers_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_H_
|
||||
@@ -0,0 +1,414 @@
|
||||
// Copyright 2023 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 "sharing/local_device_data/nearby_share_local_device_data_manager_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/random/random.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/device_info.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/common/nearby_share_profile_info_provider.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/base/utf_string_conversions.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/internal/public/logging.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/device_rpc.pb.h"
|
||||
#include "sharing/proto/field_mask.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
using ::nearby::sharing::api::PreferenceManager;
|
||||
using ::nearby::sharing::api::SharingRpcClientFactory;
|
||||
using ::nearby::sharing::proto::UpdateDeviceRequest;
|
||||
using ::nearby::sharing::proto::UpdateDeviceResponse;
|
||||
|
||||
// Using the alphanumeric characters below, this provides 36^10 unique device
|
||||
// IDs. Note that the uniqueness requirement is not global; the IDs are only
|
||||
// used to differentiate between devices associated with a single GAIA account.
|
||||
// This ID length agrees with the GmsCore implementation.
|
||||
constexpr size_t kDeviceIdLength = 10;
|
||||
|
||||
// Possible characters used in a randomly generated device ID. This agrees with
|
||||
// the GmsCore implementation.
|
||||
constexpr std::array<char, 36> kAlphaNumericChars = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
|
||||
constexpr absl::string_view kDeviceIdPrefix = "users/me/devices/";
|
||||
constexpr absl::string_view kContactsFieldMaskPath = "contacts";
|
||||
constexpr absl::string_view kCertificatesFieldMaskPath = "public_certificates";
|
||||
|
||||
constexpr absl::Duration kDeviceDataDownloadPeriod = absl::Hours(12);
|
||||
|
||||
constexpr absl::string_view kDefaultDeviceName = "$0\'s $1";
|
||||
|
||||
// Returns a truncated version of |name| that is |overflow_length| characters
|
||||
// too long. For example, name="Reallylongname" with overflow_length=5 will
|
||||
// return "Really...".
|
||||
std::string GetTruncatedName(std::string name, size_t overflow_length) {
|
||||
std::string ellipsis("...");
|
||||
size_t max_name_length = name.length() - overflow_length - ellipsis.length();
|
||||
// DCHECK_GT(max_name_length, 0u);
|
||||
|
||||
std::string truncated;
|
||||
nearby::utils::TruncateUtf8ToByteSize(name, max_name_length, &truncated);
|
||||
truncated.append(ellipsis);
|
||||
return truncated;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
NearbyShareLocalDeviceDataManagerImpl::Factory*
|
||||
NearbyShareLocalDeviceDataManagerImpl::Factory::test_factory_ = nullptr;
|
||||
|
||||
// static
|
||||
std::unique_ptr<NearbyShareLocalDeviceDataManager>
|
||||
NearbyShareLocalDeviceDataManagerImpl::Factory::Create(
|
||||
Context* context, PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager, nearby::DeviceInfo& device_info,
|
||||
SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider) {
|
||||
if (test_factory_) {
|
||||
return test_factory_->CreateInstance(context, rpc_client_factory,
|
||||
profile_info_provider);
|
||||
}
|
||||
|
||||
return absl::WrapUnique(new NearbyShareLocalDeviceDataManagerImpl(
|
||||
context, preference_manager, account_manager, device_info,
|
||||
rpc_client_factory, profile_info_provider));
|
||||
}
|
||||
|
||||
// static
|
||||
void NearbyShareLocalDeviceDataManagerImpl::Factory::SetFactoryForTesting(
|
||||
Factory* test_factory) {
|
||||
test_factory_ = test_factory;
|
||||
}
|
||||
|
||||
NearbyShareLocalDeviceDataManagerImpl::Factory::~Factory() = default;
|
||||
|
||||
NearbyShareLocalDeviceDataManagerImpl::NearbyShareLocalDeviceDataManagerImpl(
|
||||
Context* context, PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager, nearby::DeviceInfo& device_info,
|
||||
SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider)
|
||||
: preference_manager_(preference_manager),
|
||||
account_manager_(account_manager),
|
||||
device_info_(device_info),
|
||||
profile_info_provider_(profile_info_provider),
|
||||
nearby_share_client_(rpc_client_factory->CreateInstance()),
|
||||
device_id_(GetId()),
|
||||
download_device_data_scheduler_(
|
||||
NearbyShareSchedulerFactory::CreatePeriodicScheduler(
|
||||
context, preference_manager_, kDeviceDataDownloadPeriod,
|
||||
/*retry_failures=*/true,
|
||||
/*require_connectivity=*/true,
|
||||
prefs::kNearbySharingSchedulerDownloadDeviceDataName,
|
||||
[&]() { DownloadDeviceData(); })),
|
||||
executor_(context->CreateSequencedTaskRunner()) {}
|
||||
|
||||
NearbyShareLocalDeviceDataManagerImpl::
|
||||
~NearbyShareLocalDeviceDataManagerImpl() = default;
|
||||
|
||||
std::string NearbyShareLocalDeviceDataManagerImpl::GetId() {
|
||||
std::string id =
|
||||
preference_manager_.GetString(prefs::kNearbySharingDeviceIdName, "");
|
||||
if (!id.empty()) return id;
|
||||
|
||||
absl::BitGen bitgen;
|
||||
for (size_t i = 0; i < kDeviceIdLength; ++i)
|
||||
id += kAlphaNumericChars[absl::Uniform(
|
||||
bitgen, 0, static_cast<int>(kAlphaNumericChars.size()))];
|
||||
|
||||
preference_manager_.SetString(prefs::kNearbySharingDeviceIdName, id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
std::string NearbyShareLocalDeviceDataManagerImpl::GetDeviceName() const {
|
||||
std::string device_name = preference_manager_.GetString(
|
||||
prefs::kNearbySharingDeviceNameName, std::string());
|
||||
return device_name.empty() ? GetDefaultDeviceName() : device_name;
|
||||
}
|
||||
|
||||
std::optional<std::string> NearbyShareLocalDeviceDataManagerImpl::GetFullName()
|
||||
const {
|
||||
return preference_manager_.GetString(prefs::kNearbySharingFullNameName,
|
||||
std::string());
|
||||
}
|
||||
|
||||
std::optional<std::string> NearbyShareLocalDeviceDataManagerImpl::GetIconUrl()
|
||||
const {
|
||||
return preference_manager_.GetString(prefs::kNearbySharingIconUrlName,
|
||||
std::string());
|
||||
}
|
||||
|
||||
std::optional<std::string> NearbyShareLocalDeviceDataManagerImpl::GetIconToken()
|
||||
const {
|
||||
return preference_manager_.GetString(prefs::kNearbySharingIconTokenName,
|
||||
std::string());
|
||||
}
|
||||
|
||||
DeviceNameValidationResult
|
||||
NearbyShareLocalDeviceDataManagerImpl::ValidateDeviceName(
|
||||
absl::string_view name) {
|
||||
if (name.empty()) return DeviceNameValidationResult::kErrorEmpty;
|
||||
|
||||
if (!nearby::utils::IsStringUtf8(std::string_view(name.data(), name.size())))
|
||||
return DeviceNameValidationResult::kErrorNotValidUtf8;
|
||||
|
||||
if (name.length() > kNearbyShareDeviceNameMaxLength)
|
||||
return DeviceNameValidationResult::kErrorTooLong;
|
||||
|
||||
return DeviceNameValidationResult::kValid;
|
||||
}
|
||||
|
||||
DeviceNameValidationResult NearbyShareLocalDeviceDataManagerImpl::SetDeviceName(
|
||||
absl::string_view name) {
|
||||
if (name == GetDeviceName()) return DeviceNameValidationResult::kValid;
|
||||
|
||||
auto error = ValidateDeviceName(name);
|
||||
if (error != DeviceNameValidationResult::kValid) return error;
|
||||
|
||||
preference_manager_.SetString(prefs::kNearbySharingDeviceNameName, name);
|
||||
|
||||
NotifyLocalDeviceDataChanged(/*did_device_name_change=*/true,
|
||||
/*did_full_name_change=*/false,
|
||||
/*did_icon_change=*/false);
|
||||
|
||||
return DeviceNameValidationResult::kValid;
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::DownloadDeviceData() {
|
||||
executor_->PostTask([&]() {
|
||||
NL_LOG(INFO) << __func__ << ": started";
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING) << "DownloadDeviceData: skip to download device data due "
|
||||
"to manager is stopped.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": skip to download device data due "
|
||||
"to no login account.";
|
||||
download_device_data_scheduler_->HandleResult(/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateDeviceRequest request;
|
||||
request.mutable_device()->set_name(
|
||||
absl::StrCat(kDeviceIdPrefix, device_id_));
|
||||
nearby_share_client_->UpdateDevice(
|
||||
request, [this](const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
// check whether the manager is running again
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING)
|
||||
<< "DownloadDeviceData: skip to download device data due "
|
||||
"to manager is stopped.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.ok()) {
|
||||
NL_LOG(WARNING) << "DownloadDeviceData: Got response from backend.";
|
||||
HandleUpdateDeviceResponse(*response);
|
||||
} else {
|
||||
NL_LOG(WARNING)
|
||||
<< "DownloadDeviceData: Failed to get response from backend.";
|
||||
}
|
||||
|
||||
download_device_data_scheduler_->HandleResult(
|
||||
/*success=*/response.ok());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::UploadContacts(
|
||||
std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback) {
|
||||
executor_->PostTask(
|
||||
[&, contacts = std::move(contacts), callback = std::move(callback)]() {
|
||||
NL_LOG(INFO) << __func__ << ": size=" << contacts.size();
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING) << "UploadContacts: skip to upload contacts due "
|
||||
"to manager is stopped.";
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": skip to upload contacts due "
|
||||
"to no login account.";
|
||||
callback(/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateDeviceRequest request;
|
||||
request.mutable_device()->set_name(
|
||||
absl::StrCat(kDeviceIdPrefix, device_id_));
|
||||
request.mutable_device()->mutable_contacts()->Add(contacts.begin(),
|
||||
contacts.end());
|
||||
request.mutable_update_mask()->add_paths(
|
||||
std::string(kContactsFieldMaskPath));
|
||||
nearby_share_client_->UpdateDevice(
|
||||
request, [callback = std::move(callback)](
|
||||
const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
callback(/*success=*/response.ok());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::UploadCertificates(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback) {
|
||||
executor_->PostTask([&, certificates = std::move(certificates),
|
||||
callback = std::move(callback)]() {
|
||||
NL_LOG(INFO) << __func__ << ": Upload " << certificates.size()
|
||||
<< " certificates.";
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING) << "UploadContacts: skip to upload certificates due "
|
||||
"to manager is stopped.";
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!account_manager_.GetCurrentAccount().has_value()) {
|
||||
NL_LOG(WARNING) << __func__
|
||||
<< ": skip to upload certificates due "
|
||||
"to no login account.";
|
||||
callback(/*success=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateDeviceRequest request;
|
||||
request.mutable_device()->set_name(
|
||||
absl::StrCat(kDeviceIdPrefix, device_id_));
|
||||
request.mutable_device()->mutable_public_certificates()->Add(
|
||||
certificates.begin(), certificates.end());
|
||||
request.mutable_update_mask()->add_paths(
|
||||
std::string(kCertificatesFieldMaskPath));
|
||||
nearby_share_client_->UpdateDevice(
|
||||
request, [this, callback = std::move(callback)](
|
||||
const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
// check whether the manager is running again
|
||||
if (!is_running()) {
|
||||
NL_LOG(WARNING)
|
||||
<< "DownloadDeviceData: skip to upload certificates due "
|
||||
"to manager is stopped.";
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(/*success=*/response.ok());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::OnStart() {
|
||||
// This schedules an immediate download of the full name and icon URL from the
|
||||
// server if that has never happened before.
|
||||
download_device_data_scheduler_->Start();
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::OnStop() {
|
||||
download_device_data_scheduler_->Stop();
|
||||
}
|
||||
|
||||
std::string NearbyShareLocalDeviceDataManagerImpl::GetDefaultDeviceName()
|
||||
const {
|
||||
std::string device_type_name = device_info_.GetDeviceTypeName();
|
||||
std::string device_name = device_info_.GetOsDeviceName();
|
||||
std::optional<std::string> given_name =
|
||||
profile_info_provider_->GetGivenName();
|
||||
if (!given_name.has_value()) return device_name;
|
||||
|
||||
std::string default_device_name =
|
||||
absl::Substitute(kDefaultDeviceName, *given_name, device_type_name);
|
||||
|
||||
if (default_device_name.length() <= kNearbyShareDeviceNameMaxLength)
|
||||
return default_device_name;
|
||||
|
||||
std::string truncated_name =
|
||||
GetTruncatedName(*given_name, default_device_name.length() -
|
||||
kNearbyShareDeviceNameMaxLength);
|
||||
|
||||
return absl::Substitute(kDefaultDeviceName, truncated_name, device_type_name);
|
||||
}
|
||||
|
||||
void NearbyShareLocalDeviceDataManagerImpl::HandleUpdateDeviceResponse(
|
||||
const std::optional<nearby::sharing::proto::UpdateDeviceResponse>&
|
||||
response) {
|
||||
if (!response) return;
|
||||
|
||||
bool did_full_name_change = response->person_name() != GetFullName();
|
||||
if (did_full_name_change) {
|
||||
preference_manager_.SetString(prefs::kNearbySharingFullNameName,
|
||||
response->person_name());
|
||||
}
|
||||
|
||||
// NOTE(http://crbug.com/1211189): An icon URL can change without the
|
||||
// underlying image changing. For example, icon URLs for some child accounts
|
||||
// can rotate on every UpdateDevice RPC call; a timestamp is included in the
|
||||
// URL. The icon token is used to detect changes in the underlying image. If a
|
||||
// new URL is sent and the token doesn't change, the old URL may still be
|
||||
// valid for a couple of weeks, for example. So, private certificates do not
|
||||
// necessarily need to update the icon URL whenever it changes. Also, we don't
|
||||
// expect the token to change without the URL changing; regardless, we don't
|
||||
// consider the icon changed unless the URL changes. That way, private
|
||||
// certificates will not be unnecessarily regenerated.
|
||||
bool did_icon_url_change = response->image_url() != GetIconUrl();
|
||||
bool did_icon_token_change = response->image_token() != GetIconToken();
|
||||
bool did_icon_change = did_icon_url_change && did_icon_token_change;
|
||||
if (did_icon_url_change) {
|
||||
preference_manager_.SetString(prefs::kNearbySharingIconUrlName,
|
||||
response->image_url());
|
||||
}
|
||||
if (did_icon_token_change) {
|
||||
preference_manager_.SetString(prefs::kNearbySharingIconTokenName,
|
||||
response->image_token());
|
||||
}
|
||||
|
||||
if (!did_full_name_change && !did_icon_change) return;
|
||||
|
||||
NotifyLocalDeviceDataChanged(/*did_device_name_change=*/false,
|
||||
did_full_name_change, did_icon_change);
|
||||
}
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 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_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_IMPL_H_
|
||||
#define THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/device_info.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/platform/task_runner.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/internal/api/preference_manager.h"
|
||||
#include "sharing/internal/api/sharing_rpc_client.h"
|
||||
#include "sharing/internal/public/context.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
|
||||
class NearbyShareProfileInfoProvider;
|
||||
class NearbyShareScheduler;
|
||||
|
||||
// Implementation of NearbyShareLocalDeviceDataManager that persists device data
|
||||
// in prefs. All RPC-related calls are guarded by a timeout, so callbacks are
|
||||
// guaranteed to be invoked. In addition to supporting on-demand device-data
|
||||
// downloads, this implementation schedules periodic downloads of device
|
||||
// data--full name and icon URL--from the server.
|
||||
class NearbyShareLocalDeviceDataManagerImpl
|
||||
: public NearbyShareLocalDeviceDataManager {
|
||||
public:
|
||||
class Factory {
|
||||
public:
|
||||
static std::unique_ptr<NearbyShareLocalDeviceDataManager> Create(
|
||||
Context* context,
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager, nearby::DeviceInfo& device_info,
|
||||
nearby::sharing::api::SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider);
|
||||
static void SetFactoryForTesting(Factory* test_factory);
|
||||
|
||||
protected:
|
||||
virtual ~Factory();
|
||||
virtual std::unique_ptr<NearbyShareLocalDeviceDataManager> CreateInstance(
|
||||
Context* context,
|
||||
nearby::sharing::api::SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider) = 0;
|
||||
|
||||
private:
|
||||
static Factory* test_factory_;
|
||||
};
|
||||
|
||||
~NearbyShareLocalDeviceDataManagerImpl() override;
|
||||
|
||||
private:
|
||||
NearbyShareLocalDeviceDataManagerImpl(
|
||||
Context* context,
|
||||
nearby::sharing::api::PreferenceManager& preference_manager,
|
||||
AccountManager& account_manager, nearby::DeviceInfo& device_info,
|
||||
nearby::sharing::api::SharingRpcClientFactory* rpc_client_factory,
|
||||
NearbyShareProfileInfoProvider* profile_info_provider);
|
||||
|
||||
// NearbyShareLocalDeviceDataManager:
|
||||
std::string GetId() override;
|
||||
std::string GetDeviceName() const override;
|
||||
std::optional<std::string> GetFullName() const override;
|
||||
std::optional<std::string> GetIconUrl() const override;
|
||||
DeviceNameValidationResult ValidateDeviceName(
|
||||
absl::string_view name) override;
|
||||
DeviceNameValidationResult SetDeviceName(absl::string_view name) override;
|
||||
void DownloadDeviceData() override;
|
||||
void UploadContacts(std::vector<nearby::sharing::proto::Contact> contacts,
|
||||
UploadCompleteCallback callback) override;
|
||||
void UploadCertificates(
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> certificates,
|
||||
UploadCompleteCallback callback) override;
|
||||
void OnStart() override;
|
||||
void OnStop() override;
|
||||
|
||||
std::optional<std::string> GetIconToken() const;
|
||||
|
||||
// Creates a default device name of the form "<given name>'s <device type>."
|
||||
// For example, "Josh's Chromebook." If a given name cannot be found, returns
|
||||
// just the device type. If the resulting name is too long the user's name
|
||||
// will be truncated, for example "Mi...'s Chromebook."
|
||||
std::string GetDefaultDeviceName() const;
|
||||
|
||||
void HandleUpdateDeviceResponse(
|
||||
const std::optional<nearby::sharing::proto::UpdateDeviceResponse>&
|
||||
response);
|
||||
|
||||
nearby::sharing::api::PreferenceManager& preference_manager_;
|
||||
AccountManager& account_manager_;
|
||||
nearby::DeviceInfo& device_info_;
|
||||
NearbyShareProfileInfoProvider* const profile_info_provider_;
|
||||
std::unique_ptr<nearby::sharing::api::SharingRpcClient> nearby_share_client_;
|
||||
const std::string device_id_;
|
||||
std::unique_ptr<NearbyShareScheduler> download_device_data_scheduler_;
|
||||
std::unique_ptr<TaskRunner> executor_;
|
||||
};
|
||||
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_SHARING_LOCAL_DEVICE_DATA_NEARBY_SHARE_LOCAL_DEVICE_DATA_MANAGER_IMPL_H_
|
||||
@@ -0,0 +1,530 @@
|
||||
// Copyright 2023 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 "sharing/local_device_data/nearby_share_local_device_data_manager_impl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "internal/platform/implementation/account_manager.h"
|
||||
#include "internal/test/fake_account_manager.h"
|
||||
#include "internal/test/fake_device_info.h"
|
||||
#include "internal/test/fake_task_runner.h"
|
||||
#include "sharing/common/fake_nearby_share_profile_info_provider.h"
|
||||
#include "sharing/common/nearby_share_enums.h"
|
||||
#include "sharing/common/nearby_share_prefs.h"
|
||||
#include "sharing/internal/api/fake_nearby_share_client.h"
|
||||
#include "sharing/internal/test/fake_context.h"
|
||||
#include "sharing/internal/test/fake_preference_manager.h"
|
||||
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
|
||||
#include "sharing/proto/device_rpc.pb.h"
|
||||
#include "sharing/proto/rpc_resources.pb.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler.h"
|
||||
#include "sharing/scheduling/fake_nearby_share_scheduler_factory.h"
|
||||
#include "sharing/scheduling/nearby_share_scheduler_factory.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace sharing {
|
||||
namespace {
|
||||
|
||||
using UpdateDeviceResponse = nearby::sharing::proto::UpdateDeviceResponse;
|
||||
using Contact = nearby::sharing::proto::Contact;
|
||||
|
||||
const char kDefaultDeviceName[] = "$0\'s $1";
|
||||
const char kFakeDeviceName[] = "My Cool Chromebook";
|
||||
const char kFakeEmptyDeviceName[] = "";
|
||||
const char kFakeFullName[] = "Barack Obama";
|
||||
const char kFakeGivenName[] = "Barack奥巴马";
|
||||
const char kFakeIconUrl[] = "https://www.google.com";
|
||||
const char kFakeIconUrl2[] = "https://www.google.com/2";
|
||||
const char kFakeIconToken[] = "token";
|
||||
const char kFakeIconToken2[] = "token2";
|
||||
const char kFakeInvalidDeviceName[] = "\xC0";
|
||||
const char kFakeTooLongDeviceName[] = "this string is 33 bytes in UTF-8!";
|
||||
const char kFakeTooLongGivenName[] = "this is a 33-byte string in utf-8";
|
||||
constexpr char kTestAccountId[] = "test_account_id";
|
||||
constexpr char kTestProfileUserName[] = "test@google.com";
|
||||
|
||||
absl::StatusOr<UpdateDeviceResponse> CreateResponse(
|
||||
const std::optional<std::string>& full_name,
|
||||
const std::optional<std::string>& icon_url,
|
||||
const std::optional<std::string>& icon_token) {
|
||||
absl::StatusOr<UpdateDeviceResponse> result;
|
||||
UpdateDeviceResponse response;
|
||||
if (full_name) response.set_person_name(*full_name);
|
||||
|
||||
if (icon_url) response.set_image_url(*icon_url);
|
||||
|
||||
if (icon_token) response.set_image_token(*icon_token);
|
||||
|
||||
result = response;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Contact> GetFakeContacts() {
|
||||
Contact contact1;
|
||||
Contact contact2;
|
||||
contact1.mutable_identifier()->set_account_name("account1");
|
||||
contact2.mutable_identifier()->set_account_name("account2");
|
||||
return {std::move(contact1), std::move(contact2)};
|
||||
}
|
||||
|
||||
std::vector<nearby::sharing::proto::PublicCertificate> GetFakeCertificates() {
|
||||
nearby::sharing::proto::PublicCertificate cert1;
|
||||
nearby::sharing::proto::PublicCertificate cert2;
|
||||
cert1.set_secret_id("id1");
|
||||
cert2.set_secret_id("id2");
|
||||
return {std::move(cert1), std::move(cert2)};
|
||||
}
|
||||
|
||||
class NearbyShareLocalDeviceDataManagerImplTest
|
||||
: public ::testing::Test,
|
||||
public NearbyShareLocalDeviceDataManager::Observer {
|
||||
protected:
|
||||
struct ObserverNotification {
|
||||
ObserverNotification(bool did_device_name_change, bool did_full_name_change,
|
||||
bool did_icon_change)
|
||||
: did_device_name_change(did_device_name_change),
|
||||
did_full_name_change(did_full_name_change),
|
||||
did_icon_change(did_icon_change) {}
|
||||
~ObserverNotification() = default;
|
||||
|
||||
bool operator==(const ObserverNotification& other) const {
|
||||
return did_device_name_change == other.did_device_name_change &&
|
||||
did_full_name_change == other.did_full_name_change &&
|
||||
did_icon_change == other.did_icon_change;
|
||||
}
|
||||
|
||||
bool did_device_name_change;
|
||||
bool did_full_name_change;
|
||||
bool did_icon_change;
|
||||
};
|
||||
|
||||
NearbyShareLocalDeviceDataManagerImplTest() = default;
|
||||
~NearbyShareLocalDeviceDataManagerImplTest() override = default;
|
||||
|
||||
void SetUp() override {
|
||||
prefs::RegisterNearbySharingPrefs(preference_manager_);
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(&scheduler_factory_);
|
||||
profile_info_provider()->set_given_name(kFakeGivenName);
|
||||
|
||||
AccountManager::Account account;
|
||||
account.id = kTestAccountId;
|
||||
account.email = kTestProfileUserName;
|
||||
fake_account_manager_.SetAccount(account);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
NearbyShareSchedulerFactory::SetFactoryForTesting(nullptr);
|
||||
}
|
||||
|
||||
// NearbyShareLocalDeviceDataManager::Observer:
|
||||
void OnLocalDeviceDataChanged(bool did_device_name_change,
|
||||
bool did_full_name_change,
|
||||
bool did_icon_change) override {
|
||||
notifications_.emplace_back(did_device_name_change, did_full_name_change,
|
||||
did_icon_change);
|
||||
}
|
||||
|
||||
void CreateManager() {
|
||||
manager_ = NearbyShareLocalDeviceDataManagerImpl::Factory::Create(
|
||||
&context_, preference_manager_, fake_account_manager_,
|
||||
fake_device_info_, &nearby_client_factory_, &profile_info_provider_);
|
||||
manager_->AddObserver(this);
|
||||
++num_manager_creations_;
|
||||
num_download_device_data_ = 0;
|
||||
VerifyInitialization();
|
||||
manager_->Start();
|
||||
}
|
||||
|
||||
void DestroyManager() {
|
||||
manager_->RemoveObserver(this);
|
||||
manager_.reset();
|
||||
}
|
||||
|
||||
void DownloadDeviceData(
|
||||
const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
// The scheduler requests a download of device data from the server.
|
||||
EXPECT_EQ(client()->update_device_requests().size(),
|
||||
num_download_device_data_);
|
||||
device_data_scheduler()->InvokeRequestCallback();
|
||||
Sync();
|
||||
EXPECT_EQ(client()->update_device_requests().size(),
|
||||
num_download_device_data_ + 1);
|
||||
num_download_device_data_++;
|
||||
EXPECT_TRUE(client()->list_contact_people_requests().empty());
|
||||
EXPECT_TRUE(client()->list_public_certificates_requests().empty());
|
||||
|
||||
size_t num_handled_results =
|
||||
device_data_scheduler()->handled_results().size();
|
||||
|
||||
client()->SetUpdateDeviceResponse(response);
|
||||
manager_->DownloadDeviceData();
|
||||
Sync();
|
||||
EXPECT_EQ(client()->update_device_requests().size(),
|
||||
num_download_device_data_ + 1);
|
||||
num_download_device_data_++;
|
||||
EXPECT_EQ(num_handled_results + 1,
|
||||
device_data_scheduler()->handled_results().size());
|
||||
EXPECT_EQ(response.ok(), device_data_scheduler()->handled_results().back());
|
||||
}
|
||||
|
||||
void UploadContacts(const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
std::optional<bool> returned_success;
|
||||
|
||||
client()->SetUpdateDeviceResponse(response);
|
||||
manager_->UploadContacts(
|
||||
GetFakeContacts(),
|
||||
[&returned_success](bool success) { returned_success = success; });
|
||||
Sync();
|
||||
EXPECT_TRUE(client()->list_public_certificates_requests().empty());
|
||||
auto device = client()->update_device_requests().back().device().contacts();
|
||||
std::vector<Contact> expected_fake_contacts = GetFakeContacts();
|
||||
for (size_t i = 0; i < expected_fake_contacts.size(); ++i) {
|
||||
EXPECT_EQ(expected_fake_contacts[i].SerializeAsString(),
|
||||
client()
|
||||
->update_device_requests()
|
||||
.back()
|
||||
.device()
|
||||
.contacts()
|
||||
.at(i)
|
||||
.SerializeAsString());
|
||||
}
|
||||
|
||||
EXPECT_EQ(response.ok(), returned_success);
|
||||
}
|
||||
|
||||
void UploadCertificates(
|
||||
const absl::StatusOr<UpdateDeviceResponse>& response) {
|
||||
std::optional<bool> returned_success;
|
||||
EXPECT_TRUE(client()->list_contact_people_requests().empty());
|
||||
|
||||
client()->SetUpdateDeviceResponse(response);
|
||||
manager_->UploadCertificates(
|
||||
GetFakeCertificates(),
|
||||
[&returned_success](bool success) { returned_success = success; });
|
||||
|
||||
Sync();
|
||||
std::vector<nearby::sharing::proto::PublicCertificate>
|
||||
expected_fake_certificates = GetFakeCertificates();
|
||||
for (size_t i = 0; i < expected_fake_certificates.size(); ++i) {
|
||||
EXPECT_EQ(expected_fake_certificates[i].SerializeAsString(),
|
||||
client()
|
||||
->update_device_requests()
|
||||
.back()
|
||||
.device()
|
||||
.public_certificates()
|
||||
.at(i)
|
||||
.SerializeAsString());
|
||||
}
|
||||
|
||||
EXPECT_EQ(response.ok(), returned_success);
|
||||
}
|
||||
|
||||
NearbyShareLocalDeviceDataManager* manager() { return manager_.get(); }
|
||||
|
||||
FakeNearbyShareProfileInfoProvider* profile_info_provider() {
|
||||
return &profile_info_provider_;
|
||||
}
|
||||
|
||||
const std::vector<ObserverNotification>& notifications() {
|
||||
return notifications_;
|
||||
}
|
||||
|
||||
FakeNearbyShareScheduler* device_data_scheduler() {
|
||||
return scheduler_factory_.pref_name_to_periodic_instance()
|
||||
.at(prefs::kNearbySharingSchedulerDownloadDeviceDataName)
|
||||
.fake_scheduler;
|
||||
}
|
||||
|
||||
std::string GetDeviceName() const {
|
||||
return fake_device_info_.GetOsDeviceName();
|
||||
}
|
||||
|
||||
std::string GetDeviceTypeName() const {
|
||||
return fake_device_info_.GetDeviceTypeName();
|
||||
}
|
||||
|
||||
FakeNearbyShareClient* client() {
|
||||
return nearby_client_factory_.instances().back();
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
EXPECT_TRUE(FakeTaskRunner::WaitForRunningTasksWithTimeout(
|
||||
absl::Milliseconds(1000)));
|
||||
}
|
||||
|
||||
private:
|
||||
void VerifyInitialization() {
|
||||
// Verify device data scheduler input parameters.
|
||||
const FakeNearbyShareSchedulerFactory::PeriodicInstance&
|
||||
device_data_scheduler_instance =
|
||||
scheduler_factory_.pref_name_to_periodic_instance().at(
|
||||
prefs::kNearbySharingSchedulerDownloadDeviceDataName);
|
||||
EXPECT_TRUE(device_data_scheduler_instance.fake_scheduler);
|
||||
EXPECT_EQ(absl::Hours(12), device_data_scheduler_instance.request_period);
|
||||
EXPECT_TRUE(device_data_scheduler_instance.retry_failures);
|
||||
EXPECT_TRUE(device_data_scheduler_instance.require_connectivity);
|
||||
}
|
||||
|
||||
nearby::FakePreferenceManager preference_manager_;
|
||||
nearby::FakeAccountManager fake_account_manager_;
|
||||
nearby::FakeDeviceInfo fake_device_info_;
|
||||
nearby::FakeContext context_;
|
||||
size_t num_manager_creations_ = 0;
|
||||
size_t num_download_device_data_ = 0;
|
||||
std::vector<ObserverNotification> notifications_;
|
||||
FakeNearbyShareClientFactory nearby_client_factory_;
|
||||
FakeNearbyShareProfileInfoProvider profile_info_provider_;
|
||||
FakeNearbyShareSchedulerFactory scheduler_factory_;
|
||||
std::unique_ptr<NearbyShareLocalDeviceDataManager> manager_;
|
||||
};
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, DeviceId) {
|
||||
CreateManager();
|
||||
|
||||
// A 10-character alphanumeric ID is automatically generated if one doesn't
|
||||
// already exist.
|
||||
std::string id = manager()->GetId();
|
||||
EXPECT_EQ(id.size(), 10u);
|
||||
for (const char c : id) EXPECT_TRUE(std::isalnum(c));
|
||||
|
||||
// The ID is persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->GetId(), id);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, DefaultDeviceName) {
|
||||
CreateManager();
|
||||
|
||||
// If given name is null, only return the device type.
|
||||
profile_info_provider()->set_given_name(std::nullopt);
|
||||
EXPECT_EQ(manager()->GetDeviceName(),
|
||||
GetDeviceName());
|
||||
|
||||
// Set given name and expect full default device name of the form
|
||||
// "<given name>'s <device type>."
|
||||
profile_info_provider()->set_given_name(kFakeGivenName);
|
||||
EXPECT_EQ(absl::Substitute(kDefaultDeviceName,
|
||||
kFakeGivenName,
|
||||
GetDeviceTypeName()),
|
||||
manager()->GetDeviceName());
|
||||
|
||||
// Make sure that when we use a given name that is very long we truncate
|
||||
// correctly.
|
||||
profile_info_provider()->set_given_name(kFakeTooLongGivenName);
|
||||
EXPECT_EQ(kNearbyShareDeviceNameMaxLength, manager()->GetDeviceName().size());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, ValidateDeviceName) {
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->ValidateDeviceName(kFakeDeviceName),
|
||||
DeviceNameValidationResult::kValid);
|
||||
EXPECT_EQ(manager()->ValidateDeviceName(kFakeEmptyDeviceName),
|
||||
DeviceNameValidationResult::kErrorEmpty);
|
||||
EXPECT_EQ(manager()->ValidateDeviceName(kFakeTooLongDeviceName),
|
||||
DeviceNameValidationResult::kErrorTooLong);
|
||||
EXPECT_EQ(manager()->ValidateDeviceName(kFakeInvalidDeviceName),
|
||||
DeviceNameValidationResult::kErrorNotValidUtf8);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, SetDeviceName) {
|
||||
CreateManager();
|
||||
|
||||
profile_info_provider()->set_given_name(kFakeGivenName);
|
||||
std::string expected_default_device_name =
|
||||
absl::Substitute(kDefaultDeviceName, kFakeGivenName, GetDeviceTypeName());
|
||||
EXPECT_EQ(manager()->GetDeviceName(), expected_default_device_name);
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
auto error = manager()->SetDeviceName(kFakeEmptyDeviceName);
|
||||
EXPECT_EQ(error, DeviceNameValidationResult::kErrorEmpty);
|
||||
EXPECT_EQ(manager()->GetDeviceName(), expected_default_device_name);
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
error = manager()->SetDeviceName(kFakeTooLongDeviceName);
|
||||
EXPECT_EQ(error, DeviceNameValidationResult::kErrorTooLong);
|
||||
EXPECT_EQ(manager()->GetDeviceName(), expected_default_device_name);
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
error = manager()->SetDeviceName(kFakeInvalidDeviceName);
|
||||
EXPECT_EQ(error, DeviceNameValidationResult::kErrorNotValidUtf8);
|
||||
EXPECT_EQ(manager()->GetDeviceName(), expected_default_device_name);
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
error = manager()->SetDeviceName(kFakeDeviceName);
|
||||
EXPECT_EQ(error, DeviceNameValidationResult::kValid);
|
||||
EXPECT_EQ(manager()->GetDeviceName(), kFakeDeviceName);
|
||||
EXPECT_EQ(notifications().size(), 1u);
|
||||
EXPECT_EQ(ObserverNotification(/*did_device_name_change=*/true,
|
||||
/*did_full_name_change=*/false,
|
||||
/*did_icon_change=*/false),
|
||||
notifications().back());
|
||||
|
||||
// Verify that the data is persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->GetDeviceName(), kFakeDeviceName);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, DownloadDeviceData_Success) {
|
||||
CreateManager();
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
DownloadDeviceData(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl, kFakeIconToken));
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl);
|
||||
EXPECT_EQ(notifications().size(), 1u);
|
||||
EXPECT_EQ(ObserverNotification(/*did_device_name_change=*/false,
|
||||
/*did_full_name_change=*/true,
|
||||
/*did_icon_change=*/true),
|
||||
notifications()[0]);
|
||||
|
||||
// Verify that the data is persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest,
|
||||
DownloadDeviceData_EmptyData) {
|
||||
CreateManager();
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
// The server returns empty strings for the full name and icon URL/token.
|
||||
// GetFullName() and GetIconUrl() should return non-nullopt values even though
|
||||
// they are trivial values.
|
||||
DownloadDeviceData(CreateResponse("", "", ""));
|
||||
EXPECT_EQ(manager()->GetFullName(), "");
|
||||
EXPECT_EQ(manager()->GetIconUrl(), "");
|
||||
EXPECT_EQ(notifications().size(), 0u);
|
||||
|
||||
// Return empty strings again. Ensure that the trivial full name and icon
|
||||
// URL/token values are not considered changed and no notification is sent.
|
||||
DownloadDeviceData(CreateResponse("", "", ""));
|
||||
EXPECT_EQ(manager()->GetFullName(), "");
|
||||
EXPECT_EQ(manager()->GetIconUrl(), "");
|
||||
EXPECT_EQ(notifications().size(), 0u);
|
||||
|
||||
// Verify that the data is persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->GetFullName(), "");
|
||||
EXPECT_EQ(manager()->GetIconUrl(), "");
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest,
|
||||
DownloadDeviceData_IconToken) {
|
||||
CreateManager();
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
|
||||
DownloadDeviceData(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl, kFakeIconToken));
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl);
|
||||
EXPECT_EQ(notifications().size(), 1u);
|
||||
EXPECT_EQ(ObserverNotification(/*did_device_name_change=*/false,
|
||||
/*did_full_name_change=*/true,
|
||||
/*did_icon_change=*/true),
|
||||
notifications()[0]);
|
||||
|
||||
// Destroy and recreate to ensure name, URL, and token are all persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
|
||||
// The icon URL changes but the token does not; no notification sent.
|
||||
DownloadDeviceData(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl2, kFakeIconToken));
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl2);
|
||||
EXPECT_EQ(notifications().size(), 1u);
|
||||
|
||||
// The icon token changes but the URL does not; no notification sent.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
DownloadDeviceData(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl2, kFakeIconToken2));
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl2);
|
||||
EXPECT_EQ(notifications().size(), 1u);
|
||||
|
||||
// The icon URL and token change; notification sent.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
DownloadDeviceData(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl, kFakeIconToken));
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl);
|
||||
EXPECT_EQ(notifications().size(), 2u);
|
||||
EXPECT_EQ(ObserverNotification(/*did_device_name_change=*/false,
|
||||
/*did_full_name_change=*/false,
|
||||
/*did_icon_change=*/true),
|
||||
notifications()[1]);
|
||||
|
||||
// Verify that the data is persisted.
|
||||
DestroyManager();
|
||||
CreateManager();
|
||||
EXPECT_EQ(manager()->GetFullName(), kFakeFullName);
|
||||
EXPECT_EQ(manager()->GetIconUrl(), kFakeIconUrl);
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, DownloadDeviceData_Failure) {
|
||||
CreateManager();
|
||||
DownloadDeviceData(/*response=*/absl::InternalError(""));
|
||||
|
||||
// No full name or icon URL set because the response was null.
|
||||
EXPECT_EQ(manager()->GetFullName(), std::string());
|
||||
EXPECT_EQ(manager()->GetIconUrl(), std::string());
|
||||
EXPECT_TRUE(notifications().empty());
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, UploadContacts_Success) {
|
||||
CreateManager();
|
||||
UploadContacts(CreateResponse(kFakeFullName, kFakeIconUrl, kFakeIconToken));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, UploadContacts_Failure) {
|
||||
CreateManager();
|
||||
UploadContacts(/*response=*/absl::InternalError(""));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, UploadCertificates_Success) {
|
||||
CreateManager();
|
||||
UploadCertificates(
|
||||
CreateResponse(kFakeFullName, kFakeIconUrl, kFakeIconToken));
|
||||
}
|
||||
|
||||
TEST_F(NearbyShareLocalDeviceDataManagerImplTest, UploadCertificates_Failure) {
|
||||
CreateManager();
|
||||
UploadCertificates(/*response=*/absl::InternalError(""));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharing
|
||||
} // namespace nearby
|
||||
Reference in New Issue
Block a user