diff --git a/sharing/BUILD b/sharing/BUILD index 43f6618d..a3f8f0a1 100644 --- a/sharing/BUILD +++ b/sharing/BUILD @@ -74,10 +74,12 @@ cc_library( name = "types", srcs = [ "advertisement.cc", + "advertisement_capabilities.cc", "share_target.cc", ], hdrs = [ "advertisement.h", + "advertisement_capabilities.h", "constants.h", "nearby_connection.h", "nearby_connections_manager.h", @@ -1000,3 +1002,13 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "advertisement_capabilities_test", + srcs = ["advertisement_capabilities_test.cc"], + deps = [ + ":types", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sharing/advertisement.cc b/sharing/advertisement.cc index 16c7eaaf..322615e6 100644 --- a/sharing/advertisement.cc +++ b/sharing/advertisement.cc @@ -23,11 +23,11 @@ #include #include "absl/types/span.h" +#include "sharing/advertisement_capabilities.h" #include "sharing/common/nearby_share_enums.h" #include "sharing/internal/public/logging.h" -namespace nearby { -namespace sharing { +namespace nearby::sharing { namespace { // v1 advertisements: @@ -57,6 +57,7 @@ enum class TlvTypes : uint8_t { kUnknown = 0, kQrCode = 1, kVendorId = 2, + kCapabilities = 3, }; // The length in bytes of the vendor ID in the TLV advertisement. constexpr uint8_t kVendorIdLength = 1; @@ -126,7 +127,7 @@ bool ParseHasDeviceName(uint8_t b) { std::unique_ptr Advertisement::NewInstance( std::vector salt, std::vector encrypted_metadata_key, ShareTargetType device_type, std::optional device_name, - uint8_t vendor_id) { + uint8_t vendor_id, AdvertisementCapabilities capabilities) { if (salt.size() != Advertisement::kSaltSize) { LOG(ERROR) << "Failed to create advertisement because the salt did " "not match the expected length " @@ -153,17 +154,21 @@ std::unique_ptr Advertisement::NewInstance( // Using `new` to access a non-public constructor. return std::make_unique( /* version= */ 0, std::move(salt), std::move(encrypted_metadata_key), - device_type, std::move(device_name), vendor_id); + device_type, std::move(device_name), vendor_id, std::move(capabilities)); } std::vector Advertisement::ToEndpointInfo() const { + std::vector capabilities_data = capabilities_.ToBytes(); // We add 3 bytes for vendor ID because of type (1 byte), len (1 byte), and // the ID itself (1 byte). int size = kMinimumSize + (device_name_.has_value() ? 1 : 0) + (device_name_.has_value() ? device_name_->size() : 0) + (vendor_id_ != static_cast(BlockedVendorId::kNone) ? (kTlvMinimumLength + kVendorIdLength) - : 0); + : 0) + + (capabilities_.IsEmpty() || capabilities_data.empty() + ? 0 + : (kTlvMinimumLength + capabilities_data.size())); std::vector endpoint_info; endpoint_info.reserve(size); @@ -190,6 +195,14 @@ std::vector Advertisement::ToEndpointInfo() const { // The vendor ID itself. endpoint_info.push_back(vendor_id_); } + // Add capabilities TLV + if (!capabilities_.IsEmpty() && !capabilities_data.empty()) { + VLOG(1) << "Adding capabilities to advertisement"; + endpoint_info.push_back(static_cast(TlvTypes::kCapabilities)); + endpoint_info.push_back(static_cast(capabilities_data.size())); + endpoint_info.insert(endpoint_info.end(), capabilities_data.begin(), + capabilities_data.end()); + } return endpoint_info; } @@ -204,7 +217,7 @@ std::unique_ptr Advertisement::FromEndpointInfo( return nullptr; } - auto iter = endpoint_info.begin(); + auto iter = endpoint_info.cbegin(); uint8_t first_byte = *iter++; int version = ParseVersion(first_byte); @@ -242,6 +255,7 @@ std::unique_ptr Advertisement::FromEndpointInfo( } uint8_t vendor_id = static_cast(BlockedVendorId::kNone); + AdvertisementCapabilities capabilities{}; while (endpoint_info.end() - iter >= kTlvMinimumLength) { // We will parse a TLV element now. TlvTypes type = static_cast(*iter++); @@ -263,6 +277,11 @@ std::unique_ptr Advertisement::FromEndpointInfo( // TODO: b/341984671 - Implement handling for this TLV type. iter += value_len; break; + case TlvTypes::kCapabilities: + capabilities = AdvertisementCapabilities::Parse( + absl::MakeConstSpan(iter, value_len)); + iter += value_len; + break; default: LOG(ERROR) << "Unknown TLV type: " << static_cast(type); iter += value_len; @@ -272,7 +291,7 @@ std::unique_ptr Advertisement::FromEndpointInfo( return Advertisement::NewInstance( std::move(salt), std::move(encrypted_metadata_key), device_type, - std::move(optional_device_name), vendor_id); + std::move(optional_device_name), vendor_id, std::move(capabilities)); // LINT.ThenChange(//depot/google3/third_party/nearby/connections/implementation/mediums/advertisements/advertisement_util.cc) } @@ -280,7 +299,8 @@ bool Advertisement::operator==(const Advertisement& other) const { return version_ == other.version_ && salt_ == other.salt_ && encrypted_metadata_key_ == other.encrypted_metadata_key_ && device_type_ == other.device_type_ && - device_name_ == other.device_name_ && vendor_id_ == other.vendor_id_; + device_name_ == other.device_name_ && vendor_id_ == other.vendor_id_ && + capabilities_.ToBytes() == other.capabilities_.ToBytes(); } // private @@ -288,13 +308,14 @@ Advertisement::Advertisement(int version, std::vector salt, std::vector encrypted_metadata_key, ShareTargetType device_type, std::optional device_name, - uint8_t vendor_id) + uint8_t vendor_id, + AdvertisementCapabilities capabilities) : version_(version), salt_(std::move(salt)), encrypted_metadata_key_(std::move(encrypted_metadata_key)), device_type_(device_type), device_name_(std::move(device_name)), - vendor_id_(vendor_id) {} + vendor_id_(vendor_id), + capabilities_(std::move(capabilities)) {} -} // namespace sharing -} // namespace nearby +} // namespace nearby::sharing diff --git a/sharing/advertisement.h b/sharing/advertisement.h index 3ea3dcf6..83df79d5 100644 --- a/sharing/advertisement.h +++ b/sharing/advertisement.h @@ -23,10 +23,10 @@ #include #include "absl/types/span.h" +#include "sharing/advertisement_capabilities.h" #include "sharing/common/nearby_share_enums.h" -namespace nearby { -namespace sharing { +namespace nearby::sharing { // An advertisement in the form of // [VERSION|VISIBILITY][SALT][ACCOUNT_IDENTIFIER][LEN][DEVICE_NAME]. @@ -44,17 +44,17 @@ class Advertisement { }; // LINT.ThenChange(//depot/google3/java/com/google/android/gmscore/integ/client/nearby/src/com/google/android/gms/nearby/sharing/SharingOptions.java:VendorId) + static std::unique_ptr NewInstance( std::vector salt, std::vector encrypted_metadata_key, ShareTargetType device_type, std::optional device_name, - uint8_t vendor_id); + uint8_t vendor_id, AdvertisementCapabilities capabilities); - // TODO: b/341967036 - Remove uses of std::optional for device name. Empty - // string should be enough. Advertisement(int version, std::vector salt, std::vector encrypted_metadata_key, ShareTargetType device_type, - std::optional device_name, uint8_t vendor_id); + std::optional device_name, uint8_t vendor_id, + AdvertisementCapabilities capabilities); ~Advertisement() = default; Advertisement(const Advertisement&) = default; Advertisement& operator=(const Advertisement&) = default; @@ -96,14 +96,15 @@ class Advertisement { ShareTargetType device_type_ = ShareTargetType::kUnknown; // The human-readable name of the remote device. - std::optional device_name_ = std::nullopt; + const std::optional device_name_; // The vendor identifier of the remote device. Reference for vendor ID: // google3/java/com/google/android/gmscore/integ/client/nearby/src/com/google/android/gms/nearby/sharing/SharingOptions.java const uint8_t vendor_id_; + + const AdvertisementCapabilities capabilities_; }; -} // namespace sharing -} // namespace nearby +} // namespace nearby::sharing #endif // THIRD_PARTY_NEARBY_SHARING_ADVERTISEMENT_H_ diff --git a/sharing/advertisement_capabilities.cc b/sharing/advertisement_capabilities.cc new file mode 100644 index 00000000..8d664f91 --- /dev/null +++ b/sharing/advertisement_capabilities.cc @@ -0,0 +1,61 @@ +// Copyright 2025 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/advertisement_capabilities.h" + +#include +#include + +#include "absl/types/span.h" +#include "sharing/internal/public/logging.h" + +namespace nearby::sharing { + +constexpr uint8_t kFileSyncMask = 0b00000001; + +AdvertisementCapabilities AdvertisementCapabilities::Parse( + absl::Span data) { + AdvertisementCapabilities capabilities{}; + for (const uint8_t byte : data) { + switch (byte) { + case static_cast(Capability::kFileSync): + capabilities.Add(Capability::kFileSync); + break; + default: + continue; + } + } + return capabilities; +} + +std::vector AdvertisementCapabilities::ToBytes() const { + std::vector bytes; + for (const Capability capability : capabilities_) { + switch (capability) { + case Capability::kFileSync: + if (bytes.empty()) { + bytes.resize(1); + } + bytes[0] |= kFileSyncMask; + break; + default: + LOG(DFATAL) << "Unhandled capability: " + << static_cast(capability); + break; + } + } + return bytes; +} + +} // namespace nearby::sharing diff --git a/sharing/advertisement_capabilities.h b/sharing/advertisement_capabilities.h new file mode 100644 index 00000000..30b32326 --- /dev/null +++ b/sharing/advertisement_capabilities.h @@ -0,0 +1,55 @@ +// Copyright 2025 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_ADVERTISEMENT_CAPABILITIES_H_ +#define THIRD_PARTY_NEARBY_SHARING_ADVERTISEMENT_CAPABILITIES_H_ + +#include +#include +#include +#include "absl/types/span.h" + +namespace nearby::sharing { + +// A container class for storing capabilities to be added to QuickShare +// advertisements. +class AdvertisementCapabilities { + public: + enum class Capability { + kInvalid = 0, + kFileSync = 1, // File sync extension support. + }; + + // Parses serialized capabilities from an advertisement. + static AdvertisementCapabilities Parse(absl::Span data); + + AdvertisementCapabilities(std::initializer_list capabilities) + : capabilities_(capabilities) {} + + void Add(Capability capability) { capabilities_.push_back(capability); } + + // Returns true if there are no capabilities in this object. + bool IsEmpty() const { return capabilities_.empty(); } + + // Serializes the capabilities into a byte array for inclusion in an + // advertisement. + std::vector ToBytes() const; + + private: + std::vector capabilities_; +}; + +} // namespace nearby::sharing + +#endif // THIRD_PARTY_NEARBY_SHARING_ADVERTISEMENT_CAPABILITIES_H_ diff --git a/sharing/advertisement_capabilities_test.cc b/sharing/advertisement_capabilities_test.cc new file mode 100644 index 00000000..8bba3da6 --- /dev/null +++ b/sharing/advertisement_capabilities_test.cc @@ -0,0 +1,67 @@ +// Copyright 2025 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/advertisement_capabilities.h" +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" + +namespace nearby::sharing { +namespace { + +TEST(AdvertisementCapabilitiesTest, ParseEmpty) { + EXPECT_TRUE(AdvertisementCapabilities::Parse({}).IsEmpty()); +} + +TEST(AdvertisementCapabilitiesTest, ParseFileSync) { + uint8_t data[] = { + static_cast(AdvertisementCapabilities::Capability::kFileSync)}; + AdvertisementCapabilities capabilities = + AdvertisementCapabilities::Parse(data); + EXPECT_FALSE(capabilities.IsEmpty()); + EXPECT_THAT(capabilities.ToBytes(), testing::ElementsAre(0x01)); +} + +TEST(AdvertisementCapabilitiesTest, ToBytesEmpty) { + AdvertisementCapabilities capabilities({}); + EXPECT_TRUE(capabilities.ToBytes().empty()); +} + +TEST(AdvertisementCapabilitiesTest, ToBytesFileSync) { + AdvertisementCapabilities capabilities( + {AdvertisementCapabilities::Capability::kFileSync}); + EXPECT_THAT(capabilities.ToBytes(), testing::ElementsAre(0x01)); +} + +TEST(AdvertisementCapabilitiesTest, AddCapability) { + AdvertisementCapabilities capabilities({}); + EXPECT_TRUE(capabilities.IsEmpty()); + capabilities.Add(AdvertisementCapabilities::Capability::kFileSync); + EXPECT_FALSE(capabilities.IsEmpty()); + EXPECT_THAT(capabilities.ToBytes(), testing::ElementsAre(0x01)); +} + +TEST(AdvertisementCapabilitiesTest, MultipleAdds) { + // Currently only kFileSync is supported. + AdvertisementCapabilities capabilities({}); + capabilities.Add(AdvertisementCapabilities::Capability::kFileSync); + capabilities.Add(AdvertisementCapabilities::Capability::kFileSync); + // Duplicate adds should still result in bit 0 being set. + EXPECT_THAT(capabilities.ToBytes(), testing::ElementsAre(0x01)); +} + +} // namespace +} // namespace nearby::sharing diff --git a/sharing/advertisement_test.cc b/sharing/advertisement_test.cc index ff286518..6d9277e6 100644 --- a/sharing/advertisement_test.cc +++ b/sharing/advertisement_test.cc @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "absl/types/span.h" +#include "sharing/advertisement_capabilities.h" #include "sharing/common/nearby_share_enums.h" namespace nearby { @@ -43,6 +44,7 @@ struct TestParameters { ShareTargetType target_type; std::optional target_name; int vendor_id; + AdvertisementCapabilities capabilities; }; class AdvertisementTest : public testing::TestWithParam {}; @@ -51,17 +53,18 @@ TEST_P(AdvertisementTest, TestAdvertisementRoundTrip) { auto params = GetParam(); auto advertisement = Advertisement::NewInstance( params.salt, params.encrypted_metadata_key, params.target_type, - params.target_name, params.vendor_id); + params.target_name, params.vendor_id, params.capabilities); auto bytes = advertisement->ToEndpointInfo(); auto advertisement_from_bytes = Advertisement::FromEndpointInfo(bytes); EXPECT_EQ(*advertisement_from_bytes, *advertisement); } TEST(BadAdvertisementTest, TestTlvParsingOnAdvertisement) { + AdvertisementCapabilities capabilities{}; auto advertisement = Advertisement::NewInstance( std::vector(Advertisement::kSaltSize), std::vector(Advertisement::kMetadataEncryptionKeyHashByteSize), - ShareTargetType::kLaptop, std::nullopt, /*vendor_id=*/1); + ShareTargetType::kLaptop, std::nullopt, /*vendor_id=*/1, capabilities); auto bytes = advertisement->ToEndpointInfo(); // Add a TLV field for QR code. bytes.insert(bytes.end(), kQrCodeTlvBytes.begin(), kQrCodeTlvBytes.end()); @@ -149,6 +152,24 @@ INSTANTIATE_TEST_SUITE_P( .target_type = ShareTargetType::kLaptop, .target_name = std::nullopt, .vendor_id = 0})); +INSTANTIATE_TEST_SUITE_P( + Capabilities, AdvertisementTest, + testing::Values( + TestParameters{.salt = std::vector(Advertisement::kSaltSize), + .encrypted_metadata_key = std::vector( + Advertisement::kMetadataEncryptionKeyHashByteSize), + .target_type = ShareTargetType::kPhone, + .target_name = std::nullopt, + .vendor_id = 0, + .capabilities = AdvertisementCapabilities{}}, + TestParameters{.salt = std::vector(Advertisement::kSaltSize), + .encrypted_metadata_key = std::vector( + Advertisement::kMetadataEncryptionKeyHashByteSize), + .target_type = ShareTargetType::kPhone, + .target_name = std::nullopt, + .vendor_id = 0, + .capabilities = AdvertisementCapabilities{ + AdvertisementCapabilities::Capability::kFileSync}})); } // namespace } // namespace sharing diff --git a/sharing/nearby_sharing_service_impl.cc b/sharing/nearby_sharing_service_impl.cc index ecdc59b3..11de518d 100644 --- a/sharing/nearby_sharing_service_impl.cc +++ b/sharing/nearby_sharing_service_impl.cc @@ -51,6 +51,7 @@ #include "internal/platform/task_runner.h" #include "proto/sharing_enums.pb.h" #include "sharing/advertisement.h" +#include "sharing/advertisement_capabilities.h" #include "sharing/analytics/analytics_information.h" #include "sharing/analytics/analytics_recorder.h" #include "sharing/attachment_container.h" @@ -1503,11 +1504,16 @@ NearbySharingServiceImpl::CreateEndpointInfo( ShareTargetType device_type = static_cast(device_info_.GetDeviceType()); + AdvertisementCapabilities capabilities{}; + if (supports_file_sync_) { + capabilities.Add(AdvertisementCapabilities::Capability::kFileSync); + } std::unique_ptr advertisement = Advertisement::NewInstance( std::move(salt), std::move(encrypted_key), device_type, device_name, visibility == DeviceVisibility::DEVICE_VISIBILITY_EVERYONE ? static_cast(GetReceivingVendorId()) - : static_cast(BlockedVendorId::kNone)); + : static_cast(BlockedVendorId::kNone), + std::move(capabilities)); if (advertisement) { return advertisement->ToEndpointInfo(); } else { diff --git a/sharing/nearby_sharing_service_impl.h b/sharing/nearby_sharing_service_impl.h index ec045249..7f9a13ee 100644 --- a/sharing/nearby_sharing_service_impl.h +++ b/sharing/nearby_sharing_service_impl.h @@ -524,6 +524,7 @@ class NearbySharingServiceImpl // If true, a new endpoint id will be generated at the next advertisement. bool force_new_endpoint_id_ = false; OutgoingTargetsManager outgoing_targets_manager_; + bool supports_file_sync_ = false; }; } // namespace nearby::sharing diff --git a/sharing/nearby_sharing_service_impl_test.cc b/sharing/nearby_sharing_service_impl_test.cc index dd4bc07f..50ba2b95 100644 --- a/sharing/nearby_sharing_service_impl_test.cc +++ b/sharing/nearby_sharing_service_impl_test.cc @@ -30,7 +30,6 @@ #include #include -#include "base/casts.h" #include "gmock/gmock.h" #include "protobuf-matchers/protocol-buffer-matchers.h" #include "gtest/gtest.h" @@ -54,6 +53,7 @@ #include "internal/test/fake_task_runner.h" #include "internal/test/mock_account_observer.h" #include "sharing/advertisement.h" +#include "sharing/advertisement_capabilities.h" #include "sharing/analytics/analytics_recorder.h" #include "sharing/attachment_container.h" #include "sharing/certificates/fake_nearby_share_certificate_manager.h" @@ -739,7 +739,7 @@ class NearbySharingServiceImplTest : public testing::Test { std::unique_ptr advertisement = Advertisement::NewInstance( GetNearbyShareTestEncryptedMetadataKey().salt(), GetNearbyShareTestEncryptedMetadataKey().encrypted_key(), kDeviceType, - kDeviceName, vendor_id); + kDeviceName, vendor_id, AdvertisementCapabilities{}); return advertisement->ToEndpointInfo(); } @@ -1079,7 +1079,7 @@ class NearbySharingServiceImplTest : public testing::Test { std::unique_ptr advertisement = Advertisement::NewInstance( GetNearbyShareTestEncryptedMetadataKey().salt(), GetNearbyShareTestEncryptedMetadataKey().encrypted_key(), kDeviceType, - std::nullopt, kVendorId); + std::nullopt, kVendorId, AdvertisementCapabilities{}); return advertisement->ToEndpointInfo(); } @@ -4517,7 +4517,7 @@ TEST_F(NearbySharingServiceImplTest, CreateShareTarget) { std::unique_ptr advertisement = Advertisement::NewInstance( GetNearbyShareTestEncryptedMetadataKey().salt(), GetNearbyShareTestEncryptedMetadataKey().encrypted_key(), kDeviceType, - kDeviceName, kVendorId); + kDeviceName, kVendorId, AdvertisementCapabilities{}); // Flip |for_self_share| to true to ensure the resulting ShareTarget picks // this up.