Add more sharing code to github

PiperOrigin-RevId: 601277252
This commit is contained in:
Francis Tsui
2024-01-24 17:05:16 -08:00
committed by Copybara-Service
parent 809c36139f
commit e9ae655916
54 changed files with 10809 additions and 1111 deletions
+3 -3
View File
@@ -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
+124
View File
@@ -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",
],
)
+126
View File
@@ -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
+77
View File
@@ -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_
+184
View File
@@ -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
+117
View File
@@ -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 youve seen in the past is statistically likely
// to be the device youre 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
+343
View File
@@ -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
+84
View File
@@ -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_
+87
View File
@@ -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
-10
View File
@@ -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
+14 -93
View File
@@ -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
+97
View File
@@ -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