diff --git a/Package.swift b/Package.swift index d96c9aa4..2dfe92d2 100644 --- a/Package.swift +++ b/Package.swift @@ -535,6 +535,7 @@ let package = Package( .headerSearchPath("third_party/ukey2/ukey2/"), .headerSearchPath("third_party/ukey2/compiled_proto/src/main/proto"), .define("NO_WEBRTC"), + .define("GITHUB_BUILD"), ] ), .target( @@ -553,6 +554,7 @@ let package = Package( .headerSearchPath("compiled_proto/"), .define("NO_WEBRTC"), .define("NC_OSS_BUILD"), + .define("GITHUB_BUILD"), ] ), .target( @@ -566,6 +568,7 @@ let package = Package( .headerSearchPath("./"), .headerSearchPath("compiled_proto/"), .define("NO_WEBRTC"), + .define("GITHUB_BUILD"), ] ), .target( diff --git a/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.cc b/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.cc index 2da3e87d..acf3497d 100644 --- a/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.cc +++ b/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.cc @@ -977,6 +977,9 @@ inline constexpr ConnectionResponseFrame::Impl_::Impl_( handshake_data_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), + wifi_direct_device_name_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), os_info_{nullptr}, location_hint_{nullptr}, status_{0}, @@ -4110,7 +4113,8 @@ PROTOBUF_NDEBUG_INLINE ConnectionResponseFrame::Impl_::Impl_( [[maybe_unused]] const ::location::nearby::connections::ConnectionResponseFrame& from_msg) : _has_bits_{from._has_bits_}, _cached_size_{0}, - handshake_data_(arena, from.handshake_data_) {} + handshake_data_(arena, from.handshake_data_), + wifi_direct_device_name_(arena, from.wifi_direct_device_name_) {} ConnectionResponseFrame::ConnectionResponseFrame( ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, @@ -4126,10 +4130,10 @@ ConnectionResponseFrame::ConnectionResponseFrame( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.os_info_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + _impl_.os_info_ = (CheckHasBit(cached_has_bits, 0x00000004U)) ? ::google::protobuf::MessageLite::CopyConstruct(arena, *from._impl_.os_info_) : nullptr; - _impl_.location_hint_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + _impl_.location_hint_ = (CheckHasBit(cached_has_bits, 0x00000008U)) ? ::google::protobuf::MessageLite::CopyConstruct(arena, *from._impl_.location_hint_) : nullptr; ::memcpy(reinterpret_cast(&_impl_) + @@ -4146,7 +4150,8 @@ PROTOBUF_NDEBUG_INLINE ConnectionResponseFrame::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) : _cached_size_{0}, - handshake_data_(arena) {} + handshake_data_(arena), + wifi_direct_device_name_(arena) {} inline void ConnectionResponseFrame::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); @@ -4169,6 +4174,7 @@ inline void ConnectionResponseFrame::SharedDtor(MessageLite& self) { this_._internal_metadata_.Delete<::std::string>(); ABSL_DCHECK(this_.GetArena() == nullptr); this_._impl_.handshake_data_.Destroy(); + this_._impl_.wifi_direct_device_name_.Destroy(); delete this_._impl_.os_info_; delete this_._impl_.location_hint_; this_._impl_.~Impl_(); @@ -4214,16 +4220,16 @@ ConnectionResponseFrame::GetClassData() const { return ConnectionResponseFrame_class_data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<4, 9, 3, 0, 2> +const ::_pbi::TcParseTable<4, 10, 3, 0, 2> ConnectionResponseFrame::_table_ = { { PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_._has_bits_), 0, // no _extensions_ - 9, 120, // max_field_number, fast_idx_mask + 10, 120, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), - 4294966784, // skipmap + 4294966272, // skipmap offsetof(decltype(_table_), field_entries), - 9, // num_field_entries + 10, // num_field_entries 3, // num_aux_entries offsetof(decltype(_table_), aux_entries), ConnectionResponseFrame_class_data_.base(), @@ -4236,7 +4242,7 @@ ConnectionResponseFrame::_table_ = { {::_pbi::TcParser::MiniParse, {}}, // optional int32 status = 1 [deprecated = true]; {::_pbi::TcParser::FastV32S1, - {8, 3, 0, + {8, 4, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.status_)}}, // optional bytes handshake_data = 2; {::_pbi::TcParser::FastBS1, @@ -4244,33 +4250,36 @@ ConnectionResponseFrame::_table_ = { PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.handshake_data_)}}, // optional .location.nearby.connections.ConnectionResponseFrame.ResponseStatus response = 3; {::_pbi::TcParser::FastEr0S1, - {24, 4, 2, + {24, 5, 2, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.response_)}}, // optional .location.nearby.connections.OsInfo os_info = 4; {::_pbi::TcParser::FastMtS1, - {34, 1, 0, + {34, 2, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.os_info_)}}, // optional int32 multiplex_socket_bitmask = 5; {::_pbi::TcParser::FastV32S1, - {40, 5, 0, + {40, 6, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.multiplex_socket_bitmask_)}}, // optional int32 nearby_connections_version = 6 [deprecated = true]; {::_pbi::TcParser::FastV32S1, - {48, 6, 0, + {48, 7, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.nearby_connections_version_)}}, // optional int32 safe_to_disconnect_version = 7; {::_pbi::TcParser::FastV32S1, - {56, 7, 0, + {56, 8, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.safe_to_disconnect_version_)}}, // optional .location.nearby.connections.LocationHint location_hint = 8; {::_pbi::TcParser::FastMtS1, - {66, 2, 1, + {66, 3, 1, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.location_hint_)}}, // optional int32 keep_alive_timeout_millis = 9; {::_pbi::TcParser::FastV32S1, - {72, 8, 0, + {72, 9, 0, PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.keep_alive_timeout_millis_)}}, - {::_pbi::TcParser::MiniParse, {}}, + // optional string wifi_direct_device_name = 10; + {::_pbi::TcParser::FastBS1, + {82, 1, 0, + PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.wifi_direct_device_name_)}}, {::_pbi::TcParser::MiniParse, {}}, {::_pbi::TcParser::MiniParse, {}}, {::_pbi::TcParser::MiniParse, {}}, @@ -4280,23 +4289,25 @@ ConnectionResponseFrame::_table_ = { 65535, 65535 }}, {{ // optional int32 status = 1 [deprecated = true]; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.status_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.status_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, // optional bytes handshake_data = 2; {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.handshake_data_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kBytes | ::_fl::kRepAString)}, // optional .location.nearby.connections.ConnectionResponseFrame.ResponseStatus response = 3; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.response_), _Internal::kHasBitsOffset + 4, 2, (0 | ::_fl::kFcOptional | ::_fl::kEnumRange)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.response_), _Internal::kHasBitsOffset + 5, 2, (0 | ::_fl::kFcOptional | ::_fl::kEnumRange)}, // optional .location.nearby.connections.OsInfo os_info = 4; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.os_info_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.os_info_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // optional int32 multiplex_socket_bitmask = 5; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.multiplex_socket_bitmask_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.multiplex_socket_bitmask_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, // optional int32 nearby_connections_version = 6 [deprecated = true]; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.nearby_connections_version_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.nearby_connections_version_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, // optional int32 safe_to_disconnect_version = 7; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.safe_to_disconnect_version_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.safe_to_disconnect_version_), _Internal::kHasBitsOffset + 8, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, // optional .location.nearby.connections.LocationHint location_hint = 8; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.location_hint_), _Internal::kHasBitsOffset + 2, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.location_hint_), _Internal::kHasBitsOffset + 3, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // optional int32 keep_alive_timeout_millis = 9; - {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.keep_alive_timeout_millis_), _Internal::kHasBitsOffset + 8, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.keep_alive_timeout_millis_), _Internal::kHasBitsOffset + 9, 0, (0 | ::_fl::kFcOptional | ::_fl::kInt32)}, + // optional string wifi_direct_device_name = 10; + {PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.wifi_direct_device_name_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kBytes | ::_fl::kRepAString)}, }}, {{ {::_pbi::TcParser::GetTable<::location::nearby::connections::OsInfo>()}, @@ -4314,25 +4325,32 @@ PROTOBUF_NOINLINE void ConnectionResponseFrame::Clear() { (void) cached_has_bits; cached_has_bits = _impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { _impl_.handshake_data_.ClearNonDefaultToEmpty(); } if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _impl_.wifi_direct_device_name_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(_impl_.os_info_ != nullptr); _impl_.os_info_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(_impl_.location_hint_ != nullptr); _impl_.location_hint_->Clear(); } } - if (BatchCheckHasBit(cached_has_bits, 0x000000f8U)) { + if (BatchCheckHasBit(cached_has_bits, 0x000000f0U)) { ::memset(&_impl_.status_, 0, static_cast<::size_t>( - reinterpret_cast(&_impl_.safe_to_disconnect_version_) - - reinterpret_cast(&_impl_.status_)) + sizeof(_impl_.safe_to_disconnect_version_)); + reinterpret_cast(&_impl_.nearby_connections_version_) - + reinterpret_cast(&_impl_.status_)) + sizeof(_impl_.nearby_connections_version_)); + } + if (BatchCheckHasBit(cached_has_bits, 0x00000300U)) { + ::memset(&_impl_.safe_to_disconnect_version_, 0, static_cast<::size_t>( + reinterpret_cast(&_impl_.keep_alive_timeout_millis_) - + reinterpret_cast(&_impl_.safe_to_disconnect_version_)) + sizeof(_impl_.keep_alive_timeout_millis_)); } - _impl_.keep_alive_timeout_millis_ = 0; _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::std::string>(); } @@ -4357,7 +4375,7 @@ PROTOBUF_NOINLINE void ConnectionResponseFrame::Clear() { cached_has_bits = this_._impl_._has_bits_[0]; // optional int32 status = 1 [deprecated = true]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { target = ::google::protobuf::internal::WireFormatLite::WriteInt32ToArrayWithField<1>( stream, this_._internal_status(), target); @@ -4370,54 +4388,60 @@ PROTOBUF_NOINLINE void ConnectionResponseFrame::Clear() { } // optional .location.nearby.connections.ConnectionResponseFrame.ResponseStatus response = 3; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( 3, this_._internal_response(), target); } // optional .location.nearby.connections.OsInfo os_info = 4; - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 4, *this_._impl_.os_info_, this_._impl_.os_info_->GetCachedSize(), target, stream); } // optional int32 multiplex_socket_bitmask = 5; - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { target = ::google::protobuf::internal::WireFormatLite::WriteInt32ToArrayWithField<5>( stream, this_._internal_multiplex_socket_bitmask(), target); } // optional int32 nearby_connections_version = 6 [deprecated = true]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { target = ::google::protobuf::internal::WireFormatLite::WriteInt32ToArrayWithField<6>( stream, this_._internal_nearby_connections_version(), target); } // optional int32 safe_to_disconnect_version = 7; - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { target = ::google::protobuf::internal::WireFormatLite::WriteInt32ToArrayWithField<7>( stream, this_._internal_safe_to_disconnect_version(), target); } // optional .location.nearby.connections.LocationHint location_hint = 8; - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 8, *this_._impl_.location_hint_, this_._impl_.location_hint_->GetCachedSize(), target, stream); } // optional int32 keep_alive_timeout_millis = 9; - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { target = ::google::protobuf::internal::WireFormatLite::WriteInt32ToArrayWithField<9>( stream, this_._internal_keep_alive_timeout_millis(), target); } + // optional string wifi_direct_device_name = 10; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + const ::std::string& _s = this_._internal_wifi_direct_device_name(); + target = stream->WriteStringMaybeAliased(10, _s, target); + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = stream->WriteRaw( this_._internal_metadata_.unknown_fields<::std::string>(::google::protobuf::internal::GetEmptyString).data(), @@ -4449,45 +4473,50 @@ PROTOBUF_NOINLINE void ConnectionResponseFrame::Clear() { total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize( this_._internal_handshake_data()); } - // optional .location.nearby.connections.OsInfo os_info = 4; + // optional string wifi_direct_device_name = 10; if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_wifi_direct_device_name()); + } + // optional .location.nearby.connections.OsInfo os_info = 4; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.os_info_); } // optional .location.nearby.connections.LocationHint location_hint = 8; - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.location_hint_); } // optional int32 status = 1 [deprecated = true]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( this_._internal_status()); } // optional .location.nearby.connections.ConnectionResponseFrame.ResponseStatus response = 3; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { total_size += 1 + ::_pbi::WireFormatLite::EnumSize(this_._internal_response()); } // optional int32 multiplex_socket_bitmask = 5; - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( this_._internal_multiplex_socket_bitmask()); } // optional int32 nearby_connections_version = 6 [deprecated = true]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( this_._internal_nearby_connections_version()); } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000300U)) { // optional int32 safe_to_disconnect_version = 7; - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( this_._internal_safe_to_disconnect_version()); } - } - { // optional int32 keep_alive_timeout_millis = 9; - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { total_size += ::_pbi::WireFormatLite::Int32SizePlusOne( this_._internal_keep_alive_timeout_millis()); } @@ -4519,6 +4548,9 @@ void ConnectionResponseFrame::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_internal_set_handshake_data(from._internal_handshake_data()); } if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _this->_internal_set_wifi_direct_device_name(from._internal_wifi_direct_device_name()); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(from._impl_.os_info_ != nullptr); if (_this->_impl_.os_info_ == nullptr) { _this->_impl_.os_info_ = ::google::protobuf::MessageLite::CopyConstruct(arena, *from._impl_.os_info_); @@ -4526,7 +4558,7 @@ void ConnectionResponseFrame::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.os_info_->MergeFrom(*from._impl_.os_info_); } } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(from._impl_.location_hint_ != nullptr); if (_this->_impl_.location_hint_ == nullptr) { _this->_impl_.location_hint_ = ::google::protobuf::MessageLite::CopyConstruct(arena, *from._impl_.location_hint_); @@ -4534,24 +4566,26 @@ void ConnectionResponseFrame::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.location_hint_->MergeFrom(*from._impl_.location_hint_); } } - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { _this->_impl_.status_ = from._impl_.status_; } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { _this->_impl_.response_ = from._impl_.response_; } - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { _this->_impl_.multiplex_socket_bitmask_ = from._impl_.multiplex_socket_bitmask_; } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { _this->_impl_.nearby_connections_version_ = from._impl_.nearby_connections_version_; } - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + } + if (BatchCheckHasBit(cached_has_bits, 0x00000300U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { _this->_impl_.safe_to_disconnect_version_ = from._impl_.safe_to_disconnect_version_; } - } - if (CheckHasBit(cached_has_bits, 0x00000100U)) { - _this->_impl_.keep_alive_timeout_millis_ = from._impl_.keep_alive_timeout_millis_; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + _this->_impl_.keep_alive_timeout_millis_ = from._impl_.keep_alive_timeout_millis_; + } } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::std::string>( @@ -4573,6 +4607,7 @@ void ConnectionResponseFrame::InternalSwap(ConnectionResponseFrame* PROTOBUF_RES _internal_metadata_.InternalSwap(&other->_internal_metadata_); swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.handshake_data_, &other->_impl_.handshake_data_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.wifi_direct_device_name_, &other->_impl_.wifi_direct_device_name_, arena); ::google::protobuf::internal::memswap< PROTOBUF_FIELD_OFFSET(ConnectionResponseFrame, _impl_.keep_alive_timeout_millis_) + sizeof(ConnectionResponseFrame::_impl_.keep_alive_timeout_millis_) diff --git a/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.h b/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.h index 4441f499..db39782d 100644 --- a/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.h +++ b/compiled_proto/connections/implementation/proto/offline_wire_formats.pb.h @@ -8114,6 +8114,7 @@ class ConnectionResponseFrame final : public ::google::protobuf::MessageLite // accessors ------------------------------------------------------- enum : int { kHandshakeDataFieldNumber = 2, + kWifiDirectDeviceNameFieldNumber = 10, kOsInfoFieldNumber = 4, kLocationHintFieldNumber = 8, kStatusFieldNumber = 1, @@ -8138,6 +8139,22 @@ class ConnectionResponseFrame final : public ::google::protobuf::MessageLite PROTOBUF_ALWAYS_INLINE void _internal_set_handshake_data(const ::std::string& value); ::std::string* PROTOBUF_NONNULL _internal_mutable_handshake_data(); + public: + // optional string wifi_direct_device_name = 10; + bool has_wifi_direct_device_name() const; + void clear_wifi_direct_device_name() ; + const ::std::string& wifi_direct_device_name() const; + template + void set_wifi_direct_device_name(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_wifi_direct_device_name(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_wifi_direct_device_name(); + void set_allocated_wifi_direct_device_name(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_wifi_direct_device_name() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_wifi_direct_device_name(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_wifi_direct_device_name(); + public: // optional .location.nearby.connections.OsInfo os_info = 4; bool has_os_info() const; @@ -8239,7 +8256,7 @@ class ConnectionResponseFrame final : public ::google::protobuf::MessageLite private: class _Internal; friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable<4, 9, + static const ::google::protobuf::internal::TcParseTable<4, 10, 3, 0, 2> _table_; @@ -8262,6 +8279,7 @@ class ConnectionResponseFrame final : public ::google::protobuf::MessageLite ::google::protobuf::internal::HasBits<1> _has_bits_; ::google::protobuf::internal::CachedSize _cached_size_; ::google::protobuf::internal::ArenaStringPtr handshake_data_; + ::google::protobuf::internal::ArenaStringPtr wifi_direct_device_name_; ::location::nearby::connections::OsInfo* PROTOBUF_NULLABLE os_info_; ::location::nearby::connections::LocationHint* PROTOBUF_NULLABLE location_hint_; ::int32_t status_; @@ -13226,14 +13244,14 @@ inline ConnectionRequestFrame::DeviceCase ConnectionRequestFrame::Device_case() // optional int32 status = 1 [deprecated = true]; inline bool ConnectionResponseFrame::has_status() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); return value; } inline void ConnectionResponseFrame::clear_status() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.status_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000008U); + 0x00000010U); } inline ::int32_t ConnectionResponseFrame::status() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.status) @@ -13241,7 +13259,7 @@ inline ::int32_t ConnectionResponseFrame::status() const { } inline void ConnectionResponseFrame::set_status(::int32_t value) { _internal_set_status(value); - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.status) } inline ::int32_t ConnectionResponseFrame::_internal_status() const { @@ -13324,14 +13342,14 @@ inline void ConnectionResponseFrame::set_allocated_handshake_data(::std::string* // optional .location.nearby.connections.ConnectionResponseFrame.ResponseStatus response = 3; inline bool ConnectionResponseFrame::has_response() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); return value; } inline void ConnectionResponseFrame::clear_response() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.response_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000010U); + 0x00000020U); } inline ::location::nearby::connections::ConnectionResponseFrame_ResponseStatus ConnectionResponseFrame::response() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.response) @@ -13339,7 +13357,7 @@ inline ::location::nearby::connections::ConnectionResponseFrame_ResponseStatus C } inline void ConnectionResponseFrame::set_response(::location::nearby::connections::ConnectionResponseFrame_ResponseStatus value) { _internal_set_response(value); - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.response) } inline ::location::nearby::connections::ConnectionResponseFrame_ResponseStatus ConnectionResponseFrame::_internal_response() const { @@ -13356,7 +13374,7 @@ inline void ConnectionResponseFrame::_internal_set_response(::location::nearby:: // optional .location.nearby.connections.OsInfo os_info = 4; inline bool ConnectionResponseFrame::has_os_info() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); PROTOBUF_ASSUME(!value || _impl_.os_info_ != nullptr); return value; } @@ -13364,7 +13382,7 @@ inline void ConnectionResponseFrame::clear_os_info() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.os_info_ != nullptr) _impl_.os_info_->Clear(); ClearHasBit(_impl_._has_bits_[0], - 0x00000002U); + 0x00000004U); } inline const ::location::nearby::connections::OsInfo& ConnectionResponseFrame::_internal_os_info() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -13383,16 +13401,16 @@ inline void ConnectionResponseFrame::unsafe_arena_set_allocated_os_info( } _impl_.os_info_ = reinterpret_cast<::location::nearby::connections::OsInfo*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:location.nearby.connections.ConnectionResponseFrame.os_info) } inline ::location::nearby::connections::OsInfo* PROTOBUF_NULLABLE ConnectionResponseFrame::release_os_info() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::location::nearby::connections::OsInfo* released = _impl_.os_info_; _impl_.os_info_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -13412,7 +13430,7 @@ inline ::location::nearby::connections::OsInfo* PROTOBUF_NULLABLE ConnectionResp ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:location.nearby.connections.ConnectionResponseFrame.os_info) - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::location::nearby::connections::OsInfo* temp = _impl_.os_info_; _impl_.os_info_ = nullptr; return temp; @@ -13427,7 +13445,7 @@ inline ::location::nearby::connections::OsInfo* PROTOBUF_NONNULL ConnectionRespo } inline ::location::nearby::connections::OsInfo* PROTOBUF_NONNULL ConnectionResponseFrame::mutable_os_info() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); ::location::nearby::connections::OsInfo* _msg = _internal_mutable_os_info(); // @@protoc_insertion_point(field_mutable:location.nearby.connections.ConnectionResponseFrame.os_info) return _msg; @@ -13444,9 +13462,9 @@ inline void ConnectionResponseFrame::set_allocated_os_info(::location::nearby::c if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } _impl_.os_info_ = reinterpret_cast<::location::nearby::connections::OsInfo*>(value); @@ -13455,14 +13473,14 @@ inline void ConnectionResponseFrame::set_allocated_os_info(::location::nearby::c // optional int32 multiplex_socket_bitmask = 5; inline bool ConnectionResponseFrame::has_multiplex_socket_bitmask() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); return value; } inline void ConnectionResponseFrame::clear_multiplex_socket_bitmask() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.multiplex_socket_bitmask_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000020U); + 0x00000040U); } inline ::int32_t ConnectionResponseFrame::multiplex_socket_bitmask() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.multiplex_socket_bitmask) @@ -13470,7 +13488,7 @@ inline ::int32_t ConnectionResponseFrame::multiplex_socket_bitmask() const { } inline void ConnectionResponseFrame::set_multiplex_socket_bitmask(::int32_t value) { _internal_set_multiplex_socket_bitmask(value); - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.multiplex_socket_bitmask) } inline ::int32_t ConnectionResponseFrame::_internal_multiplex_socket_bitmask() const { @@ -13484,14 +13502,14 @@ inline void ConnectionResponseFrame::_internal_set_multiplex_socket_bitmask(::in // optional int32 nearby_connections_version = 6 [deprecated = true]; inline bool ConnectionResponseFrame::has_nearby_connections_version() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); return value; } inline void ConnectionResponseFrame::clear_nearby_connections_version() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.nearby_connections_version_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000040U); + 0x00000080U); } inline ::int32_t ConnectionResponseFrame::nearby_connections_version() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.nearby_connections_version) @@ -13499,7 +13517,7 @@ inline ::int32_t ConnectionResponseFrame::nearby_connections_version() const { } inline void ConnectionResponseFrame::set_nearby_connections_version(::int32_t value) { _internal_set_nearby_connections_version(value); - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.nearby_connections_version) } inline ::int32_t ConnectionResponseFrame::_internal_nearby_connections_version() const { @@ -13513,14 +13531,14 @@ inline void ConnectionResponseFrame::_internal_set_nearby_connections_version(:: // optional int32 safe_to_disconnect_version = 7; inline bool ConnectionResponseFrame::has_safe_to_disconnect_version() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); return value; } inline void ConnectionResponseFrame::clear_safe_to_disconnect_version() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.safe_to_disconnect_version_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000080U); + 0x00000100U); } inline ::int32_t ConnectionResponseFrame::safe_to_disconnect_version() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.safe_to_disconnect_version) @@ -13528,7 +13546,7 @@ inline ::int32_t ConnectionResponseFrame::safe_to_disconnect_version() const { } inline void ConnectionResponseFrame::set_safe_to_disconnect_version(::int32_t value) { _internal_set_safe_to_disconnect_version(value); - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.safe_to_disconnect_version) } inline ::int32_t ConnectionResponseFrame::_internal_safe_to_disconnect_version() const { @@ -13542,7 +13560,7 @@ inline void ConnectionResponseFrame::_internal_set_safe_to_disconnect_version(:: // optional .location.nearby.connections.LocationHint location_hint = 8; inline bool ConnectionResponseFrame::has_location_hint() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); PROTOBUF_ASSUME(!value || _impl_.location_hint_ != nullptr); return value; } @@ -13550,7 +13568,7 @@ inline void ConnectionResponseFrame::clear_location_hint() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.location_hint_ != nullptr) _impl_.location_hint_->Clear(); ClearHasBit(_impl_._has_bits_[0], - 0x00000004U); + 0x00000008U); } inline const ::location::nearby::connections::LocationHint& ConnectionResponseFrame::_internal_location_hint() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -13569,16 +13587,16 @@ inline void ConnectionResponseFrame::unsafe_arena_set_allocated_location_hint( } _impl_.location_hint_ = reinterpret_cast<::location::nearby::connections::LocationHint*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:location.nearby.connections.ConnectionResponseFrame.location_hint) } inline ::location::nearby::connections::LocationHint* PROTOBUF_NULLABLE ConnectionResponseFrame::release_location_hint() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::location::nearby::connections::LocationHint* released = _impl_.location_hint_; _impl_.location_hint_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -13598,7 +13616,7 @@ inline ::location::nearby::connections::LocationHint* PROTOBUF_NULLABLE Connecti ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:location.nearby.connections.ConnectionResponseFrame.location_hint) - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::location::nearby::connections::LocationHint* temp = _impl_.location_hint_; _impl_.location_hint_ = nullptr; return temp; @@ -13613,7 +13631,7 @@ inline ::location::nearby::connections::LocationHint* PROTOBUF_NONNULL Connectio } inline ::location::nearby::connections::LocationHint* PROTOBUF_NONNULL ConnectionResponseFrame::mutable_location_hint() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); ::location::nearby::connections::LocationHint* _msg = _internal_mutable_location_hint(); // @@protoc_insertion_point(field_mutable:location.nearby.connections.ConnectionResponseFrame.location_hint) return _msg; @@ -13630,9 +13648,9 @@ inline void ConnectionResponseFrame::set_allocated_location_hint(::location::nea if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } _impl_.location_hint_ = reinterpret_cast<::location::nearby::connections::LocationHint*>(value); @@ -13641,14 +13659,14 @@ inline void ConnectionResponseFrame::set_allocated_location_hint(::location::nea // optional int32 keep_alive_timeout_millis = 9; inline bool ConnectionResponseFrame::has_keep_alive_timeout_millis() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000200U); return value; } inline void ConnectionResponseFrame::clear_keep_alive_timeout_millis() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.keep_alive_timeout_millis_ = 0; ClearHasBit(_impl_._has_bits_[0], - 0x00000100U); + 0x00000200U); } inline ::int32_t ConnectionResponseFrame::keep_alive_timeout_millis() const { // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.keep_alive_timeout_millis) @@ -13656,7 +13674,7 @@ inline ::int32_t ConnectionResponseFrame::keep_alive_timeout_millis() const { } inline void ConnectionResponseFrame::set_keep_alive_timeout_millis(::int32_t value) { _internal_set_keep_alive_timeout_millis(value); - SetHasBit(_impl_._has_bits_[0], 0x00000100U); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.keep_alive_timeout_millis) } inline ::int32_t ConnectionResponseFrame::_internal_keep_alive_timeout_millis() const { @@ -13668,6 +13686,75 @@ inline void ConnectionResponseFrame::_internal_set_keep_alive_timeout_millis(::i _impl_.keep_alive_timeout_millis_ = value; } +// optional string wifi_direct_device_name = 10; +inline bool ConnectionResponseFrame::has_wifi_direct_device_name() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + return value; +} +inline void ConnectionResponseFrame::clear_wifi_direct_device_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.wifi_direct_device_name_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], + 0x00000002U); +} +inline const ::std::string& ConnectionResponseFrame::wifi_direct_device_name() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:location.nearby.connections.ConnectionResponseFrame.wifi_direct_device_name) + return _internal_wifi_direct_device_name(); +} +template +PROTOBUF_ALWAYS_INLINE void ConnectionResponseFrame::set_wifi_direct_device_name(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.wifi_direct_device_name_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:location.nearby.connections.ConnectionResponseFrame.wifi_direct_device_name) +} +inline ::std::string* PROTOBUF_NONNULL ConnectionResponseFrame::mutable_wifi_direct_device_name() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_wifi_direct_device_name(); + // @@protoc_insertion_point(field_mutable:location.nearby.connections.ConnectionResponseFrame.wifi_direct_device_name) + return _s; +} +inline const ::std::string& ConnectionResponseFrame::_internal_wifi_direct_device_name() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.wifi_direct_device_name_.Get(); +} +inline void ConnectionResponseFrame::_internal_set_wifi_direct_device_name(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.wifi_direct_device_name_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ConnectionResponseFrame::_internal_mutable_wifi_direct_device_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.wifi_direct_device_name_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ConnectionResponseFrame::release_wifi_direct_device_name() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:location.nearby.connections.ConnectionResponseFrame.wifi_direct_device_name) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.wifi_direct_device_name_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.wifi_direct_device_name_.Set("", GetArena()); + } + return released; +} +inline void ConnectionResponseFrame::set_allocated_wifi_direct_device_name(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.wifi_direct_device_name_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.wifi_direct_device_name_.IsDefault()) { + _impl_.wifi_direct_device_name_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:location.nearby.connections.ConnectionResponseFrame.wifi_direct_device_name) +} + // ------------------------------------------------------------------- // PayloadTransferFrame_PayloadHeader diff --git a/connections/implementation/BUILD b/connections/implementation/BUILD index 334a3149..7c5344f2 100644 --- a/connections/implementation/BUILD +++ b/connections/implementation/BUILD @@ -99,6 +99,8 @@ cc_library( "//internal/platform:base", "//internal/platform:logging", "//internal/platform:mac_address", + "//sharing/internal/base:utf_utils", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", ], @@ -262,6 +264,7 @@ cc_library( "//internal/platform/implementation:wifi_utils", "//proto:connections_enums_cc_proto", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/cleanup", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", @@ -349,6 +352,7 @@ cc_test( "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", ], ) diff --git a/connections/implementation/base_endpoint_channel.cc b/connections/implementation/base_endpoint_channel.cc index 05e4a303..81a5f1da 100644 --- a/connections/implementation/base_endpoint_channel.cc +++ b/connections/implementation/base_endpoint_channel.cc @@ -422,6 +422,15 @@ int BaseEndpointChannel::GetFrequency() const { return frequency_; } // Returns the try count of this EndpointChannel. int BaseEndpointChannel::GetTryCount() const { return try_count_; } +void BaseEndpointChannel::SetLocalEndpointId( + const std::string& local_endpoint_id) { + local_endpoint_id_ = local_endpoint_id; +} + +std::string BaseEndpointChannel::GetLocalEndpointId() const { + return local_endpoint_id_; +} + int BaseEndpointChannel::GetMaxAllowedReadBytes() const { int64_t max_allowed_read_bytes = NearbyFlags::GetInstance().GetInt64Flag( config_package_nearby::nearby_connections_feature:: diff --git a/connections/implementation/base_endpoint_channel.h b/connections/implementation/base_endpoint_channel.h index 421a8a9f..86b03299 100644 --- a/connections/implementation/base_endpoint_channel.h +++ b/connections/implementation/base_endpoint_channel.h @@ -81,6 +81,8 @@ class BaseEndpointChannel : public EndpointChannel { uint32_t GetNextKeepAliveSeqNo() const override; void SetAnalyticsRecorder(analytics::AnalyticsRecorder* analytics_recorder, const std::string& endpoint_id) override; + void SetLocalEndpointId(const std::string& local_endpoint_id) override; + std::string GetLocalEndpointId() const override; // Reads a complete packet from the underlying medium. virtual ExceptionOr DispatchPacket() { @@ -166,6 +168,7 @@ class BaseEndpointChannel : public EndpointChannel { analytics::AnalyticsRecorder* analytics_recorder_ = nullptr; std::string endpoint_id_ = ""; + std::string local_endpoint_id_ = ""; }; } // namespace nearby::connections diff --git a/connections/implementation/base_pcp_handler.cc b/connections/implementation/base_pcp_handler.cc index ae833f73..d2f993be 100644 --- a/connections/implementation/base_pcp_handler.cc +++ b/connections/implementation/base_pcp_handler.cc @@ -25,6 +25,7 @@ #include "securegcm/ukey2_handshake.h" #include "absl/base/thread_annotations.h" +#include "absl/cleanup/cleanup.h" #include "absl/container/btree_map.h" #include "absl/container/flat_hash_set.h" #include "absl/strings/escaping.h" @@ -94,7 +95,6 @@ using ::location::nearby::connections::ConnectionResponseFrame; using ::location::nearby::connections::ConnectionsDevice; using ::location::nearby::connections::MediumMetadata; using ::location::nearby::connections::OfflineFrame; -using ::location::nearby::connections::OsInfo; using ::location::nearby::connections::PresenceDevice; using ::location::nearby::connections::V1Frame; using ::location::nearby::proto::connections::OperationResultCode; @@ -576,15 +576,16 @@ Status BasePcpHandler::WaitForResult(const std::string& method_name, return result.result(); } -void BasePcpHandler::RunOnPcpHandlerThread(const std::string& name, +bool BasePcpHandler::RunOnPcpHandlerThread(const std::string& name, Runnable runnable) { if (closed_.Get()) { LOG(WARNING) << "Skip to run PCP Handler task " << name << " due to PCP Handler is closed"; - return; + return false; } serial_executor_.Execute(name, std::move(runnable)); + return true; } EncryptionRunner::ResultListener BasePcpHandler::GetResultListener( @@ -894,13 +895,19 @@ ConnectionInfo BasePcpHandler::FillConnectionInfo( connection_info.ap_frequency = wifi_info.ap_frequency; if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: - kEnableDynamicRoleSwitch) && - client->GetLocalOsInfo().type() == OsInfo::APPLE) { - ::location::nearby::connections::MediumRole medium_role_info; - medium_role_info.set_support_awdl_publisher(true); - medium_role_info.set_support_awdl_subscriber(true); - medium_role_info.set_support_wifi_hotspot_client(true); - connection_info.medium_role.emplace(medium_role_info); + kEnableDynamicRoleSwitch)) { + LOG(INFO) << "kEnableDynamicRoleSwitch is enabled"; + ClientProxy::MediumsAvailability mediums_availability; + mediums_availability.is_wifi_direct_go_available = + mediums_->GetWifiDirect().IsGOAvailable(); + mediums_availability.is_wifi_direct_gc_available = + mediums_->GetWifiDirect().IsGCAvailable(); + mediums_availability.is_wifi_hotspot_ap_available = + mediums_->GetWifiHotspot().IsAPAvailable(); + mediums_availability.is_wifi_hotspot_client_available = + mediums_->GetWifiHotspot().IsClientAvailable(); + connection_info.medium_role.emplace( + client->GetLocalMediumRole(mediums_availability)); } LOG(INFO) << "Query for WIFI information: is_supports_5_ghz=" << connection_info.supports_5_ghz @@ -1585,7 +1592,8 @@ Status BasePcpHandler::AcceptConnection(ClientProxy* client, Exception write_exception = channel->Write(parser::ForConnectionResponse( - Status::kSuccess, client->GetLocalOsInfo())); + Status::kSuccess, client->GetLocalOsInfo(), + client->GetLocalDeviceName())); if (!write_exception.Ok()) { LOG(INFO) << "AcceptConnection: failed to send response: endpoint_id=" << endpoint_id; @@ -1646,7 +1654,8 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, Exception write_exception = channel->Write(parser::ForConnectionResponse( - Status::kConnectionRejected, client->GetLocalOsInfo())); + Status::kConnectionRejected, client->GetLocalOsInfo(), + client->GetLocalDeviceName())); if (!write_exception.Ok()) { LOG(INFO) << "RejectConnection: failed to send response: endpoint_id=" << endpoint_id; @@ -1673,9 +1682,10 @@ void BasePcpHandler::OnIncomingFrame( OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, location::nearby::proto::connections::Medium medium) { CountDownLatch latch(1); - RunOnPcpHandlerThread( + bool scheduled = RunOnPcpHandlerThread( "incoming-frame", [this, client, endpoint_id, frame, &latch]() RUN_ON_PCP_HANDLER_THREAD() { + absl::Cleanup release_caller = [&latch] { latch.CountDown(); }; LOG(INFO) << "OnConnectionResponse: endpoint_id=" << endpoint_id; if (client->HasRemoteEndpointResponded(endpoint_id)) { @@ -1729,9 +1739,14 @@ void BasePcpHandler::OnIncomingFrame( EvaluateConnectionResult(client, endpoint_id, /* can_close_immediately= */ true); - latch.CountDown(); + if (connection_response.has_wifi_direct_device_name()) { + client->SetRemoteDeviceName( + endpoint_id, connection_response.wifi_direct_device_name()); + } }); - WaitForLatch("OnIncomingFrame()", &latch); + if (scheduled) { + WaitForLatch("OnIncomingFrame()", &latch); + } } void BasePcpHandler::OnEndpointDisconnect(ClientProxy* client, diff --git a/connections/implementation/base_pcp_handler.h b/connections/implementation/base_pcp_handler.h index 49cf0b70..e5e6a1eb 100644 --- a/connections/implementation/base_pcp_handler.h +++ b/connections/implementation/base_pcp_handler.h @@ -278,7 +278,7 @@ class BasePcpHandler : public PcpHandler, }; void Shutdown(); - void RunOnPcpHandlerThread(const std::string& name, Runnable runnable); + bool RunOnPcpHandlerThread(const std::string& name, Runnable runnable); BluetoothDevice GetRemoteBluetoothDevice( MacAddress remote_bluetooth_mac_address); diff --git a/connections/implementation/base_pcp_handler_test.cc b/connections/implementation/base_pcp_handler_test.cc index 93f29a5e..59e809d9 100644 --- a/connections/implementation/base_pcp_handler_test.cc +++ b/connections/implementation/base_pcp_handler_test.cc @@ -455,8 +455,7 @@ class BasePcpHandlerTest void TearDown() override { env_.Stop(); } std::unique_ptr CreateAnalyticsRecorder() { - auto recorder = - std::make_unique(); + auto recorder = std::make_unique(); mock_analytics_recorder_ptr_ = recorder.get(); return recorder; } @@ -576,10 +575,9 @@ class BasePcpHandlerTest [channel = channel_a.get()]() { return channel->DoRead(); }); EXPECT_CALL(*channel_a, Write(_)) .WillOnce(Return(Exception{Exception::kSuccess})) - .WillRepeatedly( - [channel = channel_a.get()](absl::string_view data) { - return channel->DoWrite(data); - }); + .WillRepeatedly([channel = channel_a.get()](absl::string_view data) { + return channel->DoWrite(data); + }); EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_a, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); @@ -588,10 +586,9 @@ class BasePcpHandlerTest .WillRepeatedly( [channel = channel_b.get()]() { return channel->DoRead(); }); EXPECT_CALL(*channel_b, Write(_)) - .WillRepeatedly( - [channel = channel_b.get()](absl::string_view data) { - return channel->DoWrite(data); - }); + .WillRepeatedly([channel = channel_b.get()](absl::string_view data) { + return channel->DoWrite(data); + }); EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_b, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); @@ -628,10 +625,9 @@ class BasePcpHandlerTest .WillRepeatedly( [channel = channel_b.get()]() { return channel->DoRead(); }); EXPECT_CALL(*channel_b, Write(_)) - .WillRepeatedly( - [channel = channel_b.get()](absl::string_view data) { - return channel->DoWrite(data); - }); + .WillRepeatedly([channel = channel_b.get()](absl::string_view data) { + return channel->DoWrite(data); + }); EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_b, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); @@ -751,16 +747,15 @@ class BasePcpHandlerTest auto allowed_mediums = pcp_handler->GetDiscoveryMediums(client); EXPECT_CALL(*pcp_handler, ConnectImpl) - .WillRepeatedly( - [&channel_a, connect_medium]( - ClientProxy* client, - MockPcpHandler::DiscoveredEndpoint* endpoint) { - return MockPcpHandler::ConnectImplResult{ - .medium = connect_medium, - .status = {Status::kSuccess}, - .endpoint_channel = std::move(channel_a), - }; - }); + .WillRepeatedly([&channel_a, connect_medium]( + ClientProxy* client, + MockPcpHandler::DiscoveredEndpoint* endpoint) { + return MockPcpHandler::ConnectImplResult{ + .medium = connect_medium, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel_a), + }; + }); for (const auto& discovered_medium : allowed_mediums) { pcp_handler->OnEndpointFound( @@ -1610,7 +1605,7 @@ TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { LOG(INFO) << "Simulating remote accept: id=" << endpoint_id; OsInfo os_info; auto frame = parser::FromBytes( - parser::ForConnectionResponse(Status::kSuccess, os_info)); + parser::ForConnectionResponse(Status::kSuccess, os_info, "device_name")); EXPECT_CALL(mock_connection_listener_.bandwidth_changed_cb, Call).Times(1); pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, client_.get(), connect_medium); @@ -1621,6 +1616,48 @@ TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { env_.Stop(); } +TEST_P(BasePcpHandlerTest, OnIncomingFrameDuplicateFrameDoesNotDeadlock) { + env_.Start(); + std::string endpoint_id{"1234"}; + Mediums m; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); + StartDiscovery(client_.get(), &pcp_handler); + auto mediums = pcp_handler.GetDiscoveryMediums(client_.get()); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(connect_medium); + auto& channel_a = channel_pair.first; + std::shared_ptr channel_b = + std::move(channel_pair.second); + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b, client_.get(), + &pcp_handler, connect_medium); + LOG(INFO) << "Attempting to accept connection: id=" << endpoint_id; + EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); + EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) + .Times(AtLeast(0)); + EXPECT_EQ(pcp_handler.AcceptConnection(client_.get(), endpoint_id, {}), + Status{Status::kSuccess}); + LOG(INFO) << "Simulating remote accept: id=" << endpoint_id; + OsInfo os_info; + auto frame = parser::FromBytes( + parser::ForConnectionResponse(Status::kSuccess, os_info, "device_name")); + EXPECT_CALL(mock_connection_listener_.bandwidth_changed_cb, Call).Times(1); + pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, client_.get(), + connect_medium); + LOG(INFO) << "Simulating duplicate remote accept: id=" << endpoint_id; + pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, client_.get(), + connect_medium); + LOG(INFO) << "Closing connection: id=" << endpoint_id; + channel_b->Close(); + bwu.Shutdown(); + pcp_handler.DisconnectFromEndpointManager(); + env_.Stop(); +} + TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { env_.Start(); std::atomic_int destroyed_flag = 0; @@ -1798,8 +1835,8 @@ TEST_F(BasePcpHandlerTest, InjectEndpoint) { EXPECT_CALL(pcp_handler, InjectEndpointImpl(client_.get(), service_id, _)) .WillOnce([&pcp_handler, &endpoint_id]( - ClientProxy* client, const std::string& service_id, - const OutOfBandConnectionMetadata& metadata) { + ClientProxy* client, const std::string& service_id, + const OutOfBandConnectionMetadata& metadata) { pcp_handler.OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ @@ -1862,8 +1899,8 @@ TEST_F(BasePcpHandlerTest, ::testing::InSequence seq; EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call) .WillOnce([id = endpoint_id](const std::string& endpoint_id, - const ByteArray& endpoint_info, - const std::string& service_id) { + const ByteArray& endpoint_info, + const std::string& service_id) { EXPECT_EQ(endpoint_id, id); EXPECT_EQ(endpoint_info, ByteArray{"ABCD"}); }); @@ -1875,8 +1912,8 @@ TEST_F(BasePcpHandlerTest, EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call) .WillOnce([id = endpoint_id](const std::string& endpoint_id, - const ByteArray& endpoint_info, - const std::string& service_id) { + const ByteArray& endpoint_info, + const std::string& service_id) { EXPECT_EQ(endpoint_id, id); EXPECT_EQ(endpoint_info, ByteArray{"ABCDEF"}); }); @@ -1975,8 +2012,8 @@ TEST_F(BasePcpHandlerTest, TestStartStopEndpointLostAlarm) { EXPECT_CALL(pcp_handler, InjectEndpointImpl) .WillOnce([&pcp_handler, &endpoint_id]( - ClientProxy* client, const std::string& service_id, - const OutOfBandConnectionMetadata& metadata) { + ClientProxy* client, const std::string& service_id, + const OutOfBandConnectionMetadata& metadata) { pcp_handler.OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ @@ -2038,8 +2075,8 @@ TEST_F(BasePcpHandlerTest, TestStartEndpointLostByMediumAlarms) { EXPECT_CALL(pcp_handler, InjectEndpointImpl) .WillOnce([&pcp_handler, &endpoint_id]( - ClientProxy* client, const std::string& service_id, - const OutOfBandConnectionMetadata& metadata) { + ClientProxy* client, const std::string& service_id, + const OutOfBandConnectionMetadata& metadata) { pcp_handler.OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ @@ -2103,31 +2140,30 @@ TEST_F(BasePcpHandlerTest, TestEndpointFoundStopsAlarm) { bool first_call = true; EXPECT_CALL(pcp_handler, InjectEndpointImpl) .Times(2) - .WillRepeatedly( - [&pcp_handler, &endpoint_id, &first_call]( - ClientProxy* client, const std::string& service_id, - const OutOfBandConnectionMetadata& metadata) { - ByteArray endpoint_info; - if (first_call) { - endpoint_info = ByteArray("ABCD"); - } else { - endpoint_info = ByteArray("ABCDE"); - } - first_call = false; - pcp_handler.OnEndpointFound( - client, - std::make_shared(MockDiscoveredEndpoint{ - { - endpoint_id, - endpoint_info, - service_id, - Medium::BLUETOOTH, - WebRtcState::kUndefined, - }, - MockContext{nullptr}, - })); - return Status{Status::kSuccess}; - }); + .WillRepeatedly([&pcp_handler, &endpoint_id, &first_call]( + ClientProxy* client, const std::string& service_id, + const OutOfBandConnectionMetadata& metadata) { + ByteArray endpoint_info; + if (first_call) { + endpoint_info = ByteArray("ABCD"); + } else { + endpoint_info = ByteArray("ABCDE"); + } + first_call = false; + pcp_handler.OnEndpointFound( + client, + std::make_shared(MockDiscoveredEndpoint{ + { + endpoint_id, + endpoint_info, + service_id, + Medium::BLUETOOTH, + WebRtcState::kUndefined, + }, + MockContext{nullptr}, + })); + return Status{Status::kSuccess}; + }); pcp_handler.InjectEndpoint( client_.get(), service_id, OutOfBandConnectionMetadata{ @@ -2949,5 +2985,37 @@ TEST_F(BasePcpHandlerTest, TestForceUpdateEndpointIdAdvertisingOption) { env_.Stop(); } +TEST_P(BasePcpHandlerTest, + FillConnectionInfo_kEnableDynamicRoleSwitch_Enabled) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + + Mediums m; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); + + ConnectionRequestInfo request_info = { + .endpoint_info = ByteArray("EndpointInfo"), + }; + ConnectionOptions connection_options = {}; + + // Call FillConnectionInfo with dynamic role switch enabled + ConnectionInfo connection_info = pcp_handler.FillConnectionInfo( + client_.get(), request_info, connection_options); + + // Verify that medium_role is set (populated) in connection_info! + EXPECT_TRUE(connection_info.medium_role.has_value()); + + bwu.Shutdown(); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); +} + } // namespace } // namespace nearby::connections diff --git a/connections/implementation/bwu_manager.cc b/connections/implementation/bwu_manager.cc index 6c8b8a0c..7790bef6 100644 --- a/connections/implementation/bwu_manager.cc +++ b/connections/implementation/bwu_manager.cc @@ -36,6 +36,7 @@ #include "connections/implementation/mediums/mediums.h" #include "connections/implementation/offline_frames.h" #include "connections/implementation/service_id_constants.h" +#include "connections/strategy.h" #include "internal/flags/nearby_flags.h" #include "connections/medium_selector.h" #include "internal/platform/cancelable_alarm.h" @@ -311,11 +312,16 @@ void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, if (is_dynamic_role_switch_enabled_ && client->GetMediumRole(endpoint_id).has_value()) { MediumRole medium_role = client->GetMediumRole(endpoint_id).value(); - if (NeedToSwitchRole(client, endpoint_id, proposed_medium, medium_role)) { + auto remote_os_info = client->GetRemoteOsInfo(endpoint_id); + + if (NeedToSwitchRole(client, endpoint_id, proposed_medium, medium_role, + remote_os_info.value_or(OsInfo()))) { if (!channel ->Write(parser::ForBwuPathRequest( + proposed_medium, client->GetUpgradeMediums(endpoint_id).GetMediums(true), - medium_role)) + medium_role, + mediums_->GetWifi().GetCapability().supports_5_ghz)) .Ok()) { LOG(ERROR) << "BwuManager couldn't complete the upgrade for endpoint " << endpoint_id << " to medium " @@ -585,6 +591,12 @@ void BwuManager::OnBwuNegotiationFrame( /* record_analytic= */ true, OperationResultCode::NEARBY_GENERIC_REMOTE_UPGRADE_FAILURE); break; + case BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_REQUEST: + if (frame.upgrade_path_info().has_upgrade_path_request()) { + ProcessUpgradePathRequest(client, endpoint_id, + frame.upgrade_path_info()); + } + break; case BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL: if (!in_progress_upgrades_.contains(endpoint_id)) { LOG(ERROR) << "Received LAST_WRITE_TO_PRIOR_CHANNEL for endpoint " @@ -670,7 +682,19 @@ void BwuManager::OnIncomingConnection( "OfflineFrame on EndpointChannel " << channel->GetName(); - const std::string& endpoint_id = introduction.endpoint_id(); + std::string endpoint_id = introduction.endpoint_id(); + if (is_dynamic_role_switch_enabled_ && + !in_progress_upgrades_.contains(endpoint_id) && + introduction.has_last_endpoint_id() && + !introduction.last_endpoint_id().empty()) { + std::string last_endpoint_id = introduction.last_endpoint_id(); + if (in_progress_upgrades_.contains(last_endpoint_id)) { + LOG(INFO) << "BwuManager: aliasing endpoint ID " << endpoint_id + << " to " << last_endpoint_id; + endpoint_id = last_endpoint_id; + } + } + ClientProxy* mapped_client; const auto item = in_progress_upgrades_.find(endpoint_id); if (item == in_progress_upgrades_.end()) return; @@ -724,6 +748,7 @@ void BwuManager::RunOnBwuManagerThread(const std::string& name, void BwuManager::RunUpgradeProtocol( ClientProxy* client, const std::string& endpoint_id, std::unique_ptr new_channel, bool enable_encryption) { + new_channel->SetLocalEndpointId(client->GetLocalEndpointId()); LOG(INFO) << "RunUpgradeProtocol new channel @" << new_channel.get() << " name: " << new_channel->GetName() << ", medium: " << location::nearby::proto::connections::Medium_Name( @@ -823,9 +848,11 @@ void BwuManager::ProcessBwuPathAvailableEvent( abort_bwu = true; } else { auto medium_role = client->GetMediumRole(endpoint_id); + auto remote_os_info = client->GetRemoteOsInfo(endpoint_id); if (medium_role.has_value() && !NeedToSwitchRole(client, endpoint_id, upgrade_medium, - medium_role.value())) { + medium_role.value(), + remote_os_info.value_or(OsInfo()))) { abort_bwu = true; } } @@ -937,8 +964,13 @@ void BwuManager::ProcessBwuPathAvailableEvent( } in_progress_upgrades_.emplace(endpoint_id, client); + bool local_supports_disabling = + (client->GetAdvertisingOptions().strategy == Strategy::kP2pPointToPoint || + client->GetDiscoveryOptions().strategy == Strategy::kP2pPointToPoint); + bool enable_encryption = !upgrade_path_info.supports_disabling_encryption() || + !local_supports_disabling; RunUpgradeProtocol(client, endpoint_id, std::move(channel), - !upgrade_path_info.supports_disabling_encryption()); + enable_encryption); } ErrorOr> @@ -1032,10 +1064,25 @@ BwuManager::ProcessBwuPathAvailableEventInternal( // Write the requisite BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION as // the first OfflineFrame on this new EndpointChannel. + std::string last_local_endpoint_id = client->GetLastLocalEndpointId(); + std::shared_ptr previous_channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (previous_channel != nullptr) { + last_local_endpoint_id = previous_channel->GetLocalEndpointId(); + } + LOG(INFO) << "BwuManager get last_local_endpoint_id " + << last_local_endpoint_id << " from " + << (previous_channel != nullptr ? "endpoint channel" + : "client proxy"); + + bool local_supports_disabling = + (client->GetAdvertisingOptions().strategy == Strategy::kP2pPointToPoint || + client->GetDiscoveryOptions().strategy == Strategy::kP2pPointToPoint); if (!new_channel ->Write(parser::ForBwuIntroduction( - client->GetLocalEndpointId(), - upgrade_path_info.supports_disabling_encryption())) + client->GetLocalEndpointId(), last_local_endpoint_id, + local_supports_disabling && + upgrade_path_info.supports_disabling_encryption())) .Ok()) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. @@ -1634,7 +1681,13 @@ void BwuManager::AttemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( bool BwuManager::NeedToSwitchRole( ClientProxy* client, const std::string& endpoint_id, Medium medium, - const location::nearby::connections::MediumRole& medium_role) { + const location::nearby::connections::MediumRole& medium_role, + const location::nearby::connections::OsInfo& remote_os_info) { + if (!is_dynamic_role_switch_enabled_) { + return false; + } + // On called by receiver device, check if the sender device can host the + // upgrade medium or not if (GetLocalOsInfo(client).type() == OsInfo::APPLE) { switch (medium) { case Medium::WIFI_HOTSPOT: @@ -1643,6 +1696,105 @@ bool BwuManager::NeedToSwitchRole( break; } } + // For testing on Windows as a receiver device to request dynamic role switch. + // No need for final check in. + if (GetLocalOsInfo(client).type() == OsInfo::WINDOWS && + remote_os_info.type() == OsInfo::ANDROID) { + LOG(INFO) << "Local: Windows OS, Remote: Android device detected. " + "WifiDirect NeedToSwitchRole and let Android be GO. " + "medium_role.support_wifi_direct_group_owner(): " + << medium_role.support_wifi_direct_group_owner(); + switch (medium) { + case Medium::WIFI_DIRECT: + return medium_role.support_wifi_direct_group_owner(); + default: + break; + } + } + + return false; +} +// This feature currently is only used by Android as receiver device to request +// a dynamic role switch to Windows as Wi-Fi Direct GO. So WIFI_DIRECT is the +// preferred medium to upgrade to. +void BwuManager::ProcessUpgradePathRequest( + ClientProxy* client, const std::string& endpoint_id, + const location::nearby::connections::BandwidthUpgradeNegotiationFrame:: + UpgradePathInfo& upgrade_path_info) { + if (!is_dynamic_role_switch_enabled_) { + return; + } + LOG(INFO) << "BwuManager: processing incoming UPGRADE_PATH_REQUEST frame for " + "endpoint " + << endpoint_id; + + const auto& request = upgrade_path_info.upgrade_path_request(); + std::vector upgrade_mediums; + upgrade_mediums.reserve(request.mediums_size()); + bool has_wifi_direct = false; + for (auto m : request.mediums()) { + Medium medium = parser::UpgradePathInfoMediumToMedium( + static_cast( + m)); + LOG(INFO) << "BwuManager: UpgradePathRequest medium: " + << location::nearby::proto::connections::Medium_Name(medium); + upgrade_mediums.push_back(medium); + if (medium == Medium::WIFI_DIRECT) { + has_wifi_direct = true; + } + } + + const location::nearby::connections::MediumRole& medium_role = + request.medium_meta_data().medium_role(); + LOG(INFO) << "BwuManager: medium_role: " << medium_role.DebugString(); + + if (CanHost(client, medium_role)) { + Medium medium = ChooseBestUpgradeMedium(endpoint_id, upgrade_mediums); + if (has_wifi_direct) { + medium = Medium::WIFI_DIRECT; + } + LOG(INFO) << "BwuManager: Initiating BWU for endpoint " << endpoint_id + << " with medium " + << location::nearby::proto::connections::Medium_Name(medium); + InitiateBwuForEndpoint(client, endpoint_id, medium); + } else { + ProcessUpgradeFailureEvent( + client, endpoint_id, upgrade_path_info, + BandwidthUpgradeResult::REMOTE_CONNECTION_ERROR, + /* record_analytic= */ true, + OperationResultCode::NEARBY_GENERIC_REMOTE_UPGRADE_FAILURE); + } +} + +bool BwuManager::CanHost( + ClientProxy* client, + const location::nearby::connections::MediumRole& medium_role) { + if (!is_dynamic_role_switch_enabled_) { + return false; + } + ClientProxy::MediumsAvailability mediums_availability; + mediums_availability.is_wifi_direct_go_available = + mediums_->GetWifiDirect().IsGOAvailable(); + mediums_availability.is_wifi_direct_gc_available = + mediums_->GetWifiDirect().IsGCAvailable(); + mediums_availability.is_wifi_hotspot_ap_available = + mediums_->GetWifiHotspot().IsAPAvailable(); + mediums_availability.is_wifi_hotspot_client_available = + mediums_->GetWifiHotspot().IsClientAvailable(); + const location::nearby::connections::MediumRole& local_medium_role = + client->GetLocalMediumRole(mediums_availability); + if ((local_medium_role.support_wifi_direct_group_owner() && + medium_role.support_wifi_direct_group_client() && + mediums_->GetWifiDirect().IsGOAvailable()) || + (local_medium_role.support_wifi_hotspot_host() && + medium_role.support_wifi_hotspot_client() && + mediums_->GetWifiHotspot().IsAPAvailable()) || + (local_medium_role.support_wifi_aware_publisher() && + medium_role.support_wifi_aware_subscriber())) { + LOG(INFO) << "BwuManager: Can host the upgrade medium."; + return true; + } + LOG(INFO) << "BwuManager: Can't host the upgrade medium."; return false; } diff --git a/connections/implementation/bwu_manager.h b/connections/implementation/bwu_manager.h index d2f53913..5db69661 100644 --- a/connections/implementation/bwu_manager.h +++ b/connections/implementation/bwu_manager.h @@ -221,7 +221,16 @@ class BwuManager : public EndpointManager::FrameProcessor { bool NeedToSwitchRole( ClientProxy* client, const std::string& endpoint_id, Medium medium, - const location::nearby::connections::MediumRole& medium_role); + const location::nearby::connections::MediumRole& medium_role, + const location::nearby::connections::OsInfo& remote_os_info); + + void ProcessUpgradePathRequest( + ClientProxy* client, const std::string& endpoint_id, + const location::nearby::connections::BandwidthUpgradeNegotiationFrame:: + UpgradePathInfo& upgrade_path_info); + + bool CanHost(ClientProxy* client, + const location::nearby::connections::MediumRole& medium_role); virtual const location::nearby::connections::OsInfo& GetLocalOsInfo( ClientProxy* client) const; diff --git a/connections/implementation/bwu_manager_test.cc b/connections/implementation/bwu_manager_test.cc index 835d450a..8145db9c 100644 --- a/connections/implementation/bwu_manager_test.cc +++ b/connections/implementation/bwu_manager_test.cc @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "connections/connection_options.h" #include "connections/implementation/analytics/analytics_recorder.h" #include "connections/implementation/bwu_handler.h" @@ -81,12 +82,193 @@ CreateWifiHotspotCredentials() { return credentials; } -class BwuManagerTest : public ::testing::Test { +class BwuManagerBaseTest : public ::testing::Test { protected: - BwuManagerTest() { + void SetUp() override { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); NearbyFlags::GetInstance().OverrideBoolFlagValue( config_package_nearby::nearby_connections_feature::kEnableWifiDirect, true); + } + + void TearDown() override { + NearbyFlags::GetInstance().ResetOverridedValues(); + } +}; + +TEST_F(BwuManagerBaseTest, InitiateBwu_NeedToSwitchRole_Success) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + Mediums mediums; + BwuManager::Config config; + config.allow_upgrade_to.SetAll(false); + absl::flat_hash_map> handlers; + auto bwu_manager = std::make_unique(mediums, em, ecm, + std::move(handlers), config); + client.SetLocalOsType(OsInfo::APPLE); + auto channel1 = std::make_unique( + Medium::BLUETOOTH, std::string(kServiceIdA)); + MediumRole medium_role; + medium_role.set_support_wifi_hotspot_host(true); + client.OnConnectionInitiated( + std::string(kEndpointId1), + {.remote_endpoint_info = ByteArray("remote endpoint")}, + {.auto_upgrade_bandwidth = false, + .connection_info = + { + .medium_role = {medium_role}, + }}, + {}, ""); + client.OnConnectionAccepted(std::string(kEndpointId1)); + ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId1), + std::move(channel1)); + bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId1), + Medium::WIFI_HOTSPOT); + EXPECT_FALSE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId1))); + + ecm.UnregisterChannelForEndpoint(std::string(kEndpointId1), + DisconnectionReason::LOCAL_DISCONNECTION, + SafeDisconnectionResult::kSafeDisconnection); + bwu_manager->Shutdown(); +} + +TEST_F(BwuManagerBaseTest, + InitiateBwu_NeedToSwitchRole_WindowsAndroid_Success) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + Mediums mediums; + BwuManager::Config config; + config.allow_upgrade_to.SetAll(false); + absl::flat_hash_map> handlers; + auto bwu_manager = std::make_unique(mediums, em, ecm, + std::move(handlers), config); + + // Set up local as WINDOWS, remote as ANDROID + client.SetLocalOsType(OsInfo::WINDOWS); + OsInfo remote_os_info; + remote_os_info.set_type(OsInfo::ANDROID); + + auto channel1 = std::make_unique( + Medium::BLUETOOTH, std::string(kServiceIdA)); + auto* channel1_ptr = channel1.get(); + + MediumRole remote_medium_role; + remote_medium_role.set_support_wifi_direct_group_owner(true); + + client.OnConnectionInitiated( + std::string(kEndpointId1), + {.remote_endpoint_info = ByteArray("remote endpoint")}, + {.auto_upgrade_bandwidth = false, + .connection_info = + { + .medium_role = {remote_medium_role}, + }}, + {}, ""); + client.OnConnectionAccepted(std::string(kEndpointId1)); + client.SetRemoteOsInfo(kEndpointId1, remote_os_info); + + ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId1), + std::move(channel1)); + + // Verify that before upgrade, write_timestamp is infinite past + EXPECT_EQ(channel1_ptr->GetLastWriteTimestamp(), absl::InfinitePast()); + + // Initiate BWU for WiFi Direct on Windows, which forces + // role switch to Android + bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId1), + Medium::WIFI_DIRECT); + + // Since role is switched, upgrade is NOT initiated locally, but delegated + EXPECT_FALSE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId1))); + + // Verify that an UPGRADE_PATH_REQUEST frame was actually written to + // the channel + EXPECT_NE(channel1_ptr->GetLastWriteTimestamp(), absl::InfinitePast()); + + ecm.UnregisterChannelForEndpoint(std::string(kEndpointId1), + DisconnectionReason::LOCAL_DISCONNECTION, + SafeDisconnectionResult::kSafeDisconnection); + bwu_manager->Shutdown(); +} + +TEST_F(BwuManagerBaseTest, + InitiateBwu_NeedToSwitchRole_WindowsAndroid_NoSwitch_Success) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + Mediums mediums; + BwuManager::Config config; + config.allow_upgrade_to.SetAll(false); + config.allow_upgrade_to.wifi_direct = true; + absl::flat_hash_map> handlers; + auto fake_wifi_direct = std::make_unique(Medium::WIFI_DIRECT); + auto* fake_wifi_direct_ptr = fake_wifi_direct.get(); + handlers.emplace(Medium::WIFI_DIRECT, std::move(fake_wifi_direct)); + auto bwu_manager = std::make_unique(mediums, em, ecm, + std::move(handlers), config); + + // Set up local as WINDOWS, remote as ANDROID + client.SetLocalOsType(OsInfo::WINDOWS); + OsInfo remote_os_info; + remote_os_info.set_type(OsInfo::ANDROID); + + auto channel1 = std::make_unique( + Medium::BLUETOOTH, std::string(kServiceIdA)); + + MediumRole remote_medium_role; + remote_medium_role.set_support_wifi_direct_group_owner(false); + + client.OnConnectionInitiated( + std::string(kEndpointId1), + {.remote_endpoint_info = ByteArray("remote endpoint")}, + {.auto_upgrade_bandwidth = false, + .connection_info = + { + .medium_role = {remote_medium_role}, + }}, + {}, ""); + client.OnConnectionAccepted(std::string(kEndpointId1)); + client.SetRemoteOsInfo(kEndpointId1, remote_os_info); + + ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId1), + std::move(channel1)); + + // Verify that before upgrade, upgrade is not ongoing + EXPECT_FALSE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId1))); + + // Initiate BWU for WiFi Direct on Windows. Since remote doesn't support GO, + // we do not switch roles, so we host/upgrade locally. + bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId1), + Medium::WIFI_DIRECT); + + // Upgrade is ongoing locally + EXPECT_TRUE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId1))); + EXPECT_EQ(fake_wifi_direct_ptr->handle_initialize_calls().size(), 1u); + + ecm.UnregisterChannelForEndpoint(std::string(kEndpointId1), + DisconnectionReason::LOCAL_DISCONNECTION, + SafeDisconnectionResult::kSafeDisconnection); + bwu_manager->Shutdown(); +} + +class BwuManagerTest : public ::testing::Test { + protected: + static void SetUpTestSuite() { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature::kEnableWifiDirect, + true); + } + + static void TearDownTestSuite() { + NearbyFlags::GetInstance().ResetOverridedValues(); + } + + BwuManagerTest() { // Set up fake BWU handlers for WebRTC and WifiLAN. absl::flat_hash_map> handlers; auto fake_web_rtc = std::make_unique(Medium::WEB_RTC); @@ -210,65 +392,50 @@ class BwuManagerTest : public ::testing::Test { std::unique_ptr bwu_manager_; }; -TEST(BwuManagerBaseTest, AllowToUpgradeMedium) { - NearbyFlags::GetInstance().OverrideBoolFlagValue( - config_package_nearby::nearby_connections_feature::kEnableWifiDirect, - true); - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - Mediums mediums; - BwuManager::Config config; - config.allow_upgrade_to.SetAll(false); - absl::flat_hash_map> handlers; - auto bwu_manager = std::make_unique(mediums, em, ecm, - std::move(handlers), config); - +TEST_F(BwuManagerTest, AllowToUpgradeMedium) { auto channel1 = std::make_unique( Medium::BLUETOOTH, std::string(kServiceIdA)); - ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId1), + ecm_.RegisterChannelForEndpoint(&client_, std::string(kEndpointId1), std::move(channel1)); - bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId1), + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), Medium::WIFI_LAN); - EXPECT_TRUE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId1))); - ecm.UnregisterChannelForEndpoint(std::string(kEndpointId1), + EXPECT_TRUE(bwu_manager_->IsUpgradeOngoing(std::string(kEndpointId1))); + ecm_.UnregisterChannelForEndpoint(std::string(kEndpointId1), DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); auto channel2 = std::make_unique( Medium::BLUETOOTH, std::string(kServiceIdA)); - ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId2), + ecm_.RegisterChannelForEndpoint(&client_, std::string(kEndpointId2), std::move(channel2)); - bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId2), + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId2), Medium::WIFI_HOTSPOT); - EXPECT_TRUE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId2))); - ecm.UnregisterChannelForEndpoint(std::string(kEndpointId2), + EXPECT_TRUE(bwu_manager_->IsUpgradeOngoing(std::string(kEndpointId2))); + ecm_.UnregisterChannelForEndpoint(std::string(kEndpointId2), DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); auto channel3 = std::make_unique( Medium::BLUETOOTH, std::string(kServiceIdA)); - ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId3), + ecm_.RegisterChannelForEndpoint(&client_, std::string(kEndpointId3), std::move(channel3)); - bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId3), + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId3), Medium::WIFI_DIRECT); - EXPECT_TRUE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId3))); - ecm.UnregisterChannelForEndpoint(std::string(kEndpointId3), + EXPECT_TRUE(bwu_manager_->IsUpgradeOngoing(std::string(kEndpointId3))); + ecm_.UnregisterChannelForEndpoint(std::string(kEndpointId3), DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); auto channel4 = std::make_unique( Medium::WEB_RTC, std::string(kServiceIdA)); - ecm.RegisterChannelForEndpoint(&client, std::string(kEndpointId4), + ecm_.RegisterChannelForEndpoint(&client_, std::string(kEndpointId4), std::move(channel4)); - bwu_manager->InitiateBwuForEndpoint(&client, std::string(kEndpointId4), + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId4), Medium::BLUETOOTH); - EXPECT_FALSE(bwu_manager->IsUpgradeOngoing(std::string(kEndpointId4))); - ecm.UnregisterChannelForEndpoint(std::string(kEndpointId4), + EXPECT_FALSE(bwu_manager_->IsUpgradeOngoing(std::string(kEndpointId4))); + ecm_.UnregisterChannelForEndpoint(std::string(kEndpointId4), DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); - - bwu_manager->Shutdown(); } TEST(BwuManagerBaseTest, GcOnlyDoesNotInitializeWifiDirectAsGO) { @@ -428,167 +595,6 @@ TEST(BwuManagerBaseTest, InitiateBwu_NeedToSwitchRole_Success) { false); } -class BwuManagerTestParam : public BwuManagerTest, - public ::testing::WithParamInterface { - protected: - BwuManagerTestParam() { - SetSupportMultipleBwuMediums(GetParam()); - } -}; - -TEST_P(BwuManagerTestParam, InitiateBwu_Success) { - // Create the initial device-to-device Bluetooth connection. - FakeEndpointChannel* initial_channel = CreateInitialEndpoint( - &client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); - - // Initiate BWU, and send BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE - // to the Responder over the initial Bluetooth channel. - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WEB_RTC); - - // The appropriate upgrade medium handler is informed of the BWU initiation. - ASSERT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE( - fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_EQ(WrapInitiatorUpgradeServiceId(kServiceIdA), - fake_web_rtc_bwu_handler_->handle_initialize_calls()[0].service_id); - EXPECT_EQ( - kEndpointId1, - fake_web_rtc_bwu_handler_->handle_initialize_calls()[0].endpoint_id); - - // Establish the incoming connection on the new medium. Verify that the - // upgrade channel replaces the initial channel. - std::shared_ptr shared_initial_channel = - ecm_.GetChannelForEndpoint(std::string(kEndpointId1)); - EXPECT_EQ(initial_channel, shared_initial_channel.get()); - FakeEndpointChannel* upgraded_channel = - fake_web_rtc_bwu_handler_->NotifyBwuManagerOfIncomingConnection( - /*initialize_call_index=*/0u, bwu_manager_.get()); - EXPECT_EQ(upgraded_channel, - ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); - - // Confirm that upgrade channel is paused until initial channel is shut down. - EXPECT_TRUE(upgraded_channel->IsPaused()); - EXPECT_FALSE(initial_channel->is_closed()); - - // Receive BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL and then - // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the - // Responder device to trigger the shutdown of the initial Bluetooth channel. - ExceptionOr last_write_frame = - parser::FromBytes(parser::ForBwuLastWrite()); - bwu_manager_->OnIncomingFrame(last_write_frame.result(), - std::string(kEndpointId1), &client_, - Medium::BLUETOOTH); - ExceptionOr safe_to_close_frame = - parser::FromBytes(parser::ForBwuSafeToClose()); - bwu_manager_->OnIncomingFrame(safe_to_close_frame.result(), - std::string(kEndpointId1), &client_, - Medium::BLUETOOTH); - - // Confirm that upgrade channel is resumed after initial channel is shut down. - // Note: If we didn't grab the shared initial channel pointer above, this - // channel would have already been destroyed. - auto old_channel = - dynamic_cast(shared_initial_channel.get()); - EXPECT_FALSE(upgraded_channel->IsPaused()); - EXPECT_TRUE(old_channel->is_closed()); - EXPECT_EQ(location::nearby::proto::connections::DisconnectionReason::UPGRADED, - old_channel->disconnection_reason()); - UnRegisterChannelForEndpoint(kEndpointId1); -} - -TEST_P(BwuManagerTestParam, - InitiateBwu_Error_DontUpgradeIfAlreadyConenctedOverTheRequestedMedium) { - CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); - FullyUpgradeEndpoint(kEndpointId1, /*initial_medium=*/Medium::BLUETOOTH, - /*upgrade_medium=*/Medium::WEB_RTC); - EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - - // Ignore request to upgrade to WebRTC if we're already connected. - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WEB_RTC); - EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - UnRegisterChannelForEndpoint(kEndpointId1); -} - -TEST_P(BwuManagerTestParam, - InitiateBwu_Error_DontUpgradeFromWIFI_LANToWIFI_HOTSPOT) { - CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::WIFI_LAN); - - // Ignore request to upgrade to WebRTC if we're already connected. - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WIFI_HOTSPOT); - EXPECT_TRUE( - fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); - UnRegisterChannelForEndpoint(kEndpointId1); -} - -TEST_P(BwuManagerTestParam, InitiateBwu_Error_NoInitialMedium) { - // Try to upgrade to a Medium without an initial Medium. - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WIFI_HOTSPOT); - - // Make sure none of the other medium handlers are called. - EXPECT_TRUE(fake_web_rtc_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE( - fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); -} - -TEST_P(BwuManagerTestParam, InitiateBwu_Error_UpgradeAlreadyInProgress) { - CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); - - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WEB_RTC); - EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - - // Try to upgrade an endpoint that already has an ungrade in progress. Should - // just early return with no action. - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WIFI_LAN); - EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE( - fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); - EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); - UnRegisterChannelForEndpoint(kEndpointId1); -} - -TEST_P(BwuManagerTestParam, - InitiateBwu_Error_FailedToWriteUpgradePathAvailableFrame) { - // Create the initial device-to-device Bluetooth connection. - FakeEndpointChannel* initial_channel = CreateInitialEndpoint( - &client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); - - // Make the initial endpoint channel fail when writing the - // UPGRADE_PATH_AVAILABLE frame. - initial_channel->set_write_output(Exception{Exception::kIo}); - - bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), - Medium::WEB_RTC); - - // After we notify the WebRTC handler, we try to write the - // UPGRADE_PATH_AVAILABLE frame, but fail by just early returning. - EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); - - // However, we do not record an in-progress attempt. So, if we see an incoming - // connection over WebRTC, we ignore it. In other words, the initial BLUETOOTH - // channel is still used. - EXPECT_EQ(initial_channel, - ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); - FakeEndpointChannel* upgraded_channel = - fake_web_rtc_bwu_handler_->NotifyBwuManagerOfIncomingConnection( - /*initialize_call_index=*/0u, bwu_manager_.get()); - EXPECT_NE(upgraded_channel, - ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); - EXPECT_EQ(initial_channel, - ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); - UnRegisterChannelForEndpoint(kEndpointId1); -} - TEST_F(BwuManagerTest, InitiateBwu_Revert_OnDisconnect_MultipleEndpoints_FlagEnabled) { SetSupportMultipleBwuMediums(true); @@ -1286,6 +1292,374 @@ TEST_F(BwuManagerTest, ReceiveUnexpectedLastWriteBeforeUpgrade_NoWedge) { UnRegisterChannelForEndpoint(kEndpointId1); } +TEST_F(BwuManagerTest, ProcessUpgradePathRequest_CanHost_True) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + + // Shutdown original BwuManager to clean up registrations cleanly. + bwu_manager_->Shutdown(); + + // Set up fake BWU handlers for WifiDirect. + absl::flat_hash_map> handlers; + auto fake_wifi_direct = + std::make_unique(Medium::WIFI_DIRECT); + FakeBwuHandler* fake_wifi_direct_handler_ptr = fake_wifi_direct.get(); + handlers.emplace(Medium::WIFI_DIRECT, std::move(fake_wifi_direct)); + + BwuManager::Config config; + config.allow_upgrade_to = BooleanMediumSelector{.wifi_direct = true}; + + bwu_manager_ = std::make_unique( + mediums_, em_, ecm_, std::move(handlers), config); + bwu_manager_->MakeSingleThreadedForTesting(); + + // Create initial connection + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + // Build the UpgradePathRequest frame using the parser helper + location::nearby::connections::MediumRole remote_medium_role; + remote_medium_role.set_support_wifi_direct_group_client(true); + std::string bytes = parser::ForBwuPathRequest( + Medium::WIFI_DIRECT, {Medium::WIFI_DIRECT}, remote_medium_role, + /*supports_5_ghz=*/true); + OfflineFrame frame; + frame.ParseFromString(bytes); + + // Process the request + bwu_manager_->OnIncomingFrame(frame, std::string(kEndpointId1), &client_, + Medium::BLUETOOTH); + + // Verify that WiFi Direct BWU was initiated + EXPECT_EQ(fake_wifi_direct_handler_ptr->handle_initialize_calls().size(), 1u); + + UnRegisterChannelForEndpoint(kEndpointId1); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); +} + +TEST_F(BwuManagerTest, ProcessUpgradePathRequest_CanHost_False) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + + // Shutdown original BwuManager to clean up registrations cleanly. + bwu_manager_->Shutdown(); + + // Set up fake BWU handlers for WifiDirect. + absl::flat_hash_map> handlers; + auto fake_wifi_direct = + std::make_unique(Medium::WIFI_DIRECT); + FakeBwuHandler* fake_wifi_direct_handler_ptr = fake_wifi_direct.get(); + handlers.emplace(Medium::WIFI_DIRECT, std::move(fake_wifi_direct)); + + BwuManager::Config config; + config.allow_upgrade_to = BooleanMediumSelector{.wifi_direct = true}; + + bwu_manager_ = std::make_unique( + mediums_, em_, ecm_, std::move(handlers), config); + bwu_manager_->MakeSingleThreadedForTesting(); + + // Create initial connection + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + // Build the UpgradePathRequest frame where remote doesn't support GC + location::nearby::connections::MediumRole remote_medium_role; + std::string bytes = parser::ForBwuPathRequest( + Medium::WIFI_DIRECT, {Medium::WIFI_DIRECT}, remote_medium_role, + /*supports_5_ghz=*/true); + OfflineFrame frame; + frame.ParseFromString(bytes); + + // Process the request + bwu_manager_->OnIncomingFrame(frame, std::string(kEndpointId1), &client_, + Medium::BLUETOOTH); + + // Verify that WiFi Direct BWU was NOT initiated + EXPECT_EQ(fake_wifi_direct_handler_ptr->handle_initialize_calls().size(), 0u); + + UnRegisterChannelForEndpoint(kEndpointId1); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); +} + +TEST_F(BwuManagerTest, ProcessUpgradePathRequest_DynamicRoleSwitchDisabled) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); + + // Shutdown original BwuManager to clean up registrations cleanly. + bwu_manager_->Shutdown(); + + // Set up fake BWU handlers for WifiDirect. + absl::flat_hash_map> handlers; + auto fake_wifi_direct = + std::make_unique(Medium::WIFI_DIRECT); + FakeBwuHandler* fake_wifi_direct_handler_ptr = fake_wifi_direct.get(); + handlers.emplace(Medium::WIFI_DIRECT, std::move(fake_wifi_direct)); + + BwuManager::Config config; + config.allow_upgrade_to = BooleanMediumSelector{.wifi_direct = true}; + + bwu_manager_ = std::make_unique( + mediums_, em_, ecm_, std::move(handlers), config); + bwu_manager_->MakeSingleThreadedForTesting(); + + // Create initial connection + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + // Build the UpgradePathRequest frame where remote supports GC + location::nearby::connections::MediumRole remote_medium_role; + remote_medium_role.set_support_wifi_direct_group_client(true); + std::string bytes = parser::ForBwuPathRequest( + Medium::WIFI_DIRECT, {Medium::WIFI_DIRECT}, remote_medium_role, + /*supports_5_ghz=*/true); + OfflineFrame frame; + frame.ParseFromString(bytes); + + // Process the request + bwu_manager_->OnIncomingFrame(frame, std::string(kEndpointId1), &client_, + Medium::BLUETOOTH); + + // Verify that WiFi Direct BWU was NOT initiated + EXPECT_EQ(fake_wifi_direct_handler_ptr->handle_initialize_calls().size(), 0u); + + UnRegisterChannelForEndpoint(kEndpointId1); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); +} + +TEST_F(BwuManagerTest, OnIncomingConnection_EndpointAliasesToLastEndpointId) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + + // Shutdown original BwuManager to clean up registrations cleanly. + bwu_manager_->Shutdown(); + + // Create a new BwuManager with the overridden flag. We use WEB_RTC since + // it has a simple, standard BWU flow. + absl::flat_hash_map> handlers; + auto fake_web_rtc = std::make_unique(Medium::WEB_RTC); + handlers.emplace(Medium::WEB_RTC, std::move(fake_web_rtc)); + + BwuManager::Config config; + config.allow_upgrade_to = BooleanMediumSelector{.web_rtc = true}; + + bwu_manager_ = std::make_unique( + mediums_, em_, ecm_, std::move(handlers), config); + bwu_manager_->MakeSingleThreadedForTesting(); + + // Create initial connection with the old endpoint ID "OldEndpoint" + CreateInitialEndpoint(&client_, kServiceIdA, "OldEndpoint", + Medium::BLUETOOTH); + + // Initiate upgrade for "OldEndpoint" (inserts into in_progress_upgrades_) + bwu_manager_->InitiateBwuForEndpoint(&client_, "OldEndpoint", + Medium::WEB_RTC); + + // Now simulate incoming upgraded connection. Set introduction read output: + // - endpoint_id = "NewEndpoint" + // - last_endpoint_id = "OldEndpoint" + auto upgraded_channel = std::make_unique( + Medium::WEB_RTC, std::string(kServiceIdA)); + FakeEndpointChannel* upgraded_channel_raw = upgraded_channel.get(); + + std::string intro_frame = parser::ForBwuIntroduction( + "NewEndpoint", "OldEndpoint", /*supports_disabling_encryption=*/false); + upgraded_channel->set_read_output( + ExceptionOr(ByteArray(intro_frame))); + + auto connection = std::make_unique(); + connection->channel = std::move(upgraded_channel); + + // Invoke OnIncomingConnection + bwu_manager_->InvokeOnIncomingConnectionForTesting(&client_, + std::move(connection)); + + // Verify that an Ack frame was written to upgraded_channel_raw + EXPECT_NE(upgraded_channel_raw->GetLastWriteTimestamp(), + absl::InfinitePast()); + + // Clean up + UnRegisterChannelForEndpoint("OldEndpoint"); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); +} + +class BwuManagerTestParam : public BwuManagerTest, + public ::testing::WithParamInterface { + protected: + BwuManagerTestParam() { + SetSupportMultipleBwuMediums(GetParam()); + } +}; + +TEST_P(BwuManagerTestParam, InitiateBwu_Success) { + // Create the initial device-to-device Bluetooth connection. + FakeEndpointChannel* initial_channel = CreateInitialEndpoint( + &client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + // Initiate BWU, and send BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE + // to the Responder over the initial Bluetooth channel. + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WEB_RTC); + + // The appropriate upgrade medium handler is informed of the BWU initiation. + ASSERT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE( + fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_EQ(WrapInitiatorUpgradeServiceId(kServiceIdA), + fake_web_rtc_bwu_handler_->handle_initialize_calls()[0].service_id); + EXPECT_EQ( + kEndpointId1, + fake_web_rtc_bwu_handler_->handle_initialize_calls()[0].endpoint_id); + + // Establish the incoming connection on the new medium. Verify that the + // upgrade channel replaces the initial channel. + std::shared_ptr shared_initial_channel = + ecm_.GetChannelForEndpoint(std::string(kEndpointId1)); + EXPECT_EQ(initial_channel, shared_initial_channel.get()); + FakeEndpointChannel* upgraded_channel = + fake_web_rtc_bwu_handler_->NotifyBwuManagerOfIncomingConnection( + /*initialize_call_index=*/0u, bwu_manager_.get()); + EXPECT_EQ(upgraded_channel, + ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); + + // Confirm that upgrade channel is paused until initial channel is shut down. + EXPECT_TRUE(upgraded_channel->IsPaused()); + EXPECT_FALSE(initial_channel->is_closed()); + + // Receive BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL and then + // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the + // Responder device to trigger the shutdown of the initial Bluetooth channel. + ExceptionOr last_write_frame = + parser::FromBytes(parser::ForBwuLastWrite()); + bwu_manager_->OnIncomingFrame(last_write_frame.result(), + std::string(kEndpointId1), &client_, + Medium::BLUETOOTH); + ExceptionOr safe_to_close_frame = + parser::FromBytes(parser::ForBwuSafeToClose()); + bwu_manager_->OnIncomingFrame(safe_to_close_frame.result(), + std::string(kEndpointId1), &client_, + Medium::BLUETOOTH); + + // Confirm that upgrade channel is resumed after initial channel is shut down. + // Note: If we didn't grab the shared initial channel pointer above, this + // channel would have already been destroyed. + auto old_channel = + dynamic_cast(shared_initial_channel.get()); + EXPECT_FALSE(upgraded_channel->IsPaused()); + EXPECT_TRUE(old_channel->is_closed()); + EXPECT_EQ(location::nearby::proto::connections::DisconnectionReason::UPGRADED, + old_channel->disconnection_reason()); + UnRegisterChannelForEndpoint(kEndpointId1); +} + +TEST_P(BwuManagerTestParam, + InitiateBwu_Error_DontUpgradeIfAlreadyConenctedOverTheRequestedMedium) { + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + FullyUpgradeEndpoint(kEndpointId1, /*initial_medium=*/Medium::BLUETOOTH, + /*upgrade_medium=*/Medium::WEB_RTC); + EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + + // Ignore request to upgrade to WebRTC if we're already connected. + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WEB_RTC); + EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + UnRegisterChannelForEndpoint(kEndpointId1); +} + +TEST_P(BwuManagerTestParam, + InitiateBwu_Error_DontUpgradeFromWIFI_LANToWIFI_HOTSPOT) { + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::WIFI_LAN); + + // Ignore request to upgrade to WebRTC if we're already connected. + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WIFI_HOTSPOT); + EXPECT_TRUE( + fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); + UnRegisterChannelForEndpoint(kEndpointId1); +} + +TEST_P(BwuManagerTestParam, InitiateBwu_Error_NoInitialMedium) { + // Try to upgrade to a Medium without an initial Medium. + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WIFI_HOTSPOT); + + // Make sure none of the other medium handlers are called. + EXPECT_TRUE(fake_web_rtc_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE( + fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); +} + +TEST_P(BwuManagerTestParam, InitiateBwu_Error_UpgradeAlreadyInProgress) { + CreateInitialEndpoint(&client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WEB_RTC); + EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + + // Try to upgrade an endpoint that already has an ungrade in progress. Should + // just early return with no action. + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WIFI_LAN); + EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + EXPECT_TRUE(fake_wifi_lan_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE( + fake_wifi_hotspot_bwu_handler_->handle_initialize_calls().empty()); + EXPECT_TRUE(fake_wifi_direct_bwu_handler_->handle_initialize_calls().empty()); + UnRegisterChannelForEndpoint(kEndpointId1); +} + +TEST_P(BwuManagerTestParam, + InitiateBwu_Error_FailedToWriteUpgradePathAvailableFrame) { + // Create the initial device-to-device Bluetooth connection. + FakeEndpointChannel* initial_channel = CreateInitialEndpoint( + &client_, kServiceIdA, kEndpointId1, Medium::BLUETOOTH); + + // Make the initial endpoint channel fail when writing the + // UPGRADE_PATH_AVAILABLE frame. + initial_channel->set_write_output(Exception{Exception::kIo}); + + bwu_manager_->InitiateBwuForEndpoint(&client_, std::string(kEndpointId1), + Medium::WEB_RTC); + + // After we notify the WebRTC handler, we try to write the + // UPGRADE_PATH_AVAILABLE frame, but fail by just early returning. + EXPECT_EQ(1u, fake_web_rtc_bwu_handler_->handle_initialize_calls().size()); + + // However, we do not record an in-progress attempt. So, if we see an incoming + // connection over WebRTC, we ignore it. In other words, the initial BLUETOOTH + // channel is still used. + EXPECT_EQ(initial_channel, + ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); + FakeEndpointChannel* upgraded_channel = + fake_web_rtc_bwu_handler_->NotifyBwuManagerOfIncomingConnection( + /*initialize_call_index=*/0u, bwu_manager_.get()); + EXPECT_NE(upgraded_channel, + ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); + EXPECT_EQ(initial_channel, + ecm_.GetChannelForEndpoint(std::string(kEndpointId1)).get()); + UnRegisterChannelForEndpoint(kEndpointId1); +} + INSTANTIATE_TEST_SUITE_P(BwuManagerTestParam, BwuManagerTestParam, testing::Bool()); diff --git a/connections/implementation/client_proxy.cc b/connections/implementation/client_proxy.cc index aa6c4557..827d79b4 100644 --- a/connections/implementation/client_proxy.cc +++ b/connections/implementation/client_proxy.cc @@ -276,6 +276,12 @@ ClientProxy::ClientProxy(std::unique_ptr analytics_recorder) // Load advertising info from preferences. LoadClientInfoFromPreferences(); +#ifndef NEARBY_CHROMIUM + local_device_name_ = api::ImplementationPlatform::CreateDeviceInfo() + ->GetOsDeviceName() + .value_or(""); +#endif + if (preferences_manager_ != nullptr) { app_lifecycle_monitor_ = api::ImplementationPlatform::CreateAppLifecycleMonitor( @@ -376,9 +382,10 @@ void ClientProxy::SetBluetoothMacAddress(const std::string& endpoint_id, std::string ClientProxy::GenerateLocalEndpointId() { if (!cached_endpoint_id_.empty()) { - if (stable_endpoint_id_mode_) { + if (stable_endpoint_id_mode_ || HasOngoingConnection()) { LOG(INFO) << "ClientProxy [Local Endpoint Re-using cached " - "endpoint id due to in stable endpoint id mode]: " + "endpoint id due to in stable endpoint id mode or having " + "ongoing connection]: " "client=" << GetClientId() << "; cached_endpoint_id_=" << cached_endpoint_id_; @@ -875,6 +882,48 @@ bool ClientProxy::HasOngoingConnection() const { !GetConnectedEndpoints().empty(); } +bool ClientProxy::HasWifiDirectConnection() const { + MutexLock lock(&mutex_); + for (const auto& entry : connections_) { + if (entry.second.first.connected_medium == Medium::WIFI_DIRECT) { + LOG(INFO) << "ClientProxy [HasWifiDirectConnection]: true"; + return true; + } + } + LOG(INFO) << "ClientProxy [HasWifiDirectConnection]: false"; + return false; +} + +bool ClientProxy::HasWifiHotspotConnection() const { + MutexLock lock(&mutex_); + for (const auto& entry : connections_) { + if (entry.second.first.connected_medium == Medium::WIFI_HOTSPOT) { + return true; + } + } + return false; +} + +bool ClientProxy::HasWifiAwareConnection() const { + MutexLock lock(&mutex_); + for (const auto& entry : connections_) { + if (entry.second.first.connected_medium == Medium::WIFI_AWARE) { + return true; + } + } + return false; +} + +std::string ClientProxy::GetLastLocalEndpointId() const { + MutexLock lock(&mutex_); + return last_local_endpoint_id_; +} + +void ClientProxy::SetLastLocalEndpointId(absl::string_view endpoint_id) { + MutexLock lock(&mutex_); + last_local_endpoint_id_ = std::string(endpoint_id); +} + std::int32_t ClientProxy::GetNumOutgoingConnections() const { return GetMatchingEndpoints([](const Connection& connection) { return connection.status == Connection::kConnected && @@ -1133,6 +1182,26 @@ void ClientProxy::SetRemoteOsInfo(absl::string_view endpoint_id, } } +void ClientProxy::SetRemoteDeviceName(absl::string_view endpoint_id, + absl::string_view device_name) { + MutexLock lock(&mutex_); + ConnectionPair* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->first.device_name = std::string(device_name); + LOG(INFO) << "ClientProxy [SetRemoteDeviceName]: " << device_name; + } +} + +std::string ClientProxy::GetRemoteDeviceName( + absl::string_view endpoint_id) const { + MutexLock lock(&mutex_); + const ConnectionPair* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->first.device_name; + } + return ""; +} + std::optional ClientProxy::GetRemoteSafeToDisconnectVersion( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); @@ -1249,9 +1318,23 @@ void ClientProxy::RemoveAllEndpoints() { OnSessionComplete(); } +void ClientProxy::ResetLocalEndpointId() { + MutexLock lock(&mutex_); + if (HasOngoingConnection()) { + return; + } + if (!local_endpoint_id_.empty()) { + last_local_endpoint_id_ = local_endpoint_id_; + local_endpoint_id_.clear(); + } +} + void ClientProxy::OnSessionComplete() { MutexLock lock(&mutex_); if (connections_.empty() && !IsAdvertising()) { + if (!local_endpoint_id_.empty()) { + last_local_endpoint_id_ = local_endpoint_id_; + } local_endpoint_id_.clear(); analytics_recorder_->LogSession(); @@ -1298,6 +1381,9 @@ void ClientProxy::EnterStableEndpointIdMode() { << GetClientId(); stable_endpoint_id_mode_ = true; + if (!IsAdvertising() && !IsDiscovering() && !HasOngoingConnection()) { + ResetLocalEndpointId(); + } } void ClientProxy::ExitStableEndpointIdMode() { @@ -1305,6 +1391,7 @@ void ClientProxy::ExitStableEndpointIdMode() { VLOG(1) << "ClientProxy [ExitStableEndpointIdMode]: client=" << GetClientId(); stable_endpoint_id_mode_ = false; + ResetLocalEndpointId(); ScheduleClearCachedEndpointIdAlarm(); } @@ -1318,7 +1405,7 @@ void ClientProxy::ScheduleClearCachedEndpointIdAlarm() { return; } - if (HasOngoingConnection()) { + if (IsAdvertising() || IsDiscovering() || HasOngoingConnection()) { VLOG(1) << "ClientProxy [Handle clearing cached endpoint ID " "during disconnection]: client=" << GetClientId(); @@ -1431,6 +1518,41 @@ std::optional ClientProxy::GetMediumRole( return std::nullopt; } +location::nearby::connections::MediumRole ClientProxy::GetLocalMediumRole( + const ClientProxy::MediumsAvailability& mediums_availability) const { + location::nearby::connections::MediumRole medium_role; + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch)) { + return medium_role; + } + + if (GetLocalOsInfo().type() == OsInfo::APPLE) { + medium_role.set_support_awdl_publisher(true); + medium_role.set_support_awdl_subscriber(true); + // Apple always supports wifi hotspot client role since they can always + // join a hotspot. + medium_role.set_support_wifi_hotspot_client(true); + return medium_role; + } + + medium_role.set_support_wifi_direct_group_owner( + mediums_availability.is_wifi_direct_go_available && !IsUsingP2pMedium()); + medium_role.set_support_wifi_direct_group_client( + mediums_availability.is_wifi_direct_gc_available); + medium_role.set_support_wifi_hotspot_host( + mediums_availability.is_wifi_hotspot_ap_available && !IsUsingP2pMedium()); + medium_role.set_support_wifi_hotspot_client( + mediums_availability.is_wifi_hotspot_client_available); + LOG(INFO) << "medium_role: " << medium_role.DebugString(); + return medium_role; +} + +bool ClientProxy::IsUsingP2pMedium() const { + return HasWifiDirectConnection() || HasWifiHotspotConnection() || + HasWifiAwareConnection(); +} + std::optional ClientProxy::GetEndpointIdForDct() const { MutexLock lock(&mutex_); if (dct_endpoint_id_.empty()) { @@ -1559,6 +1681,7 @@ std::string ClientProxy::Dump() { ? location::nearby::connections::OsInfo::OsType_Name( it->second.first.os_info->type()) : "unknown") + << ", (remote device name) " << it->second.first.device_name << std::endl; } diff --git a/connections/implementation/client_proxy.h b/connections/implementation/client_proxy.h index e40ba901..1f0a1acb 100644 --- a/connections/implementation/client_proxy.h +++ b/connections/implementation/client_proxy.h @@ -75,6 +75,9 @@ class ClientProxy final { std::string GetLocalEndpointId(); std::string GetLocalEndpointInfo() { return local_endpoint_info_; } + std::string GetLocalDeviceName() { + return local_device_name_; + } // Override the base for received file attachments from a specific endpoint. // Returns true if the endpoint is found and the path is overridden. @@ -103,6 +106,9 @@ class ClientProxy final { // Clears all the runtime state of this client. void Reset(); + // Resets the local endpoint ID and sets the last local endpoint ID. + void ResetLocalEndpointId(); + // Marks this client as advertising with the given callbacks. void StartedAdvertising( const std::string& service_id, Strategy strategy, @@ -145,7 +151,6 @@ class ClientProxy final { MutexLock lock(&mutex_); local_endpoint_info_ = std::string(endpoint_info); } - void UpdateAdvertisingOptions(const AdvertisingOptions& advertising_options) { MutexLock lock(&mutex_); advertising_options_ = advertising_options; @@ -210,6 +215,14 @@ class ClientProxy final { // Returns true if there is at least one connected connection or one pending // connection. bool HasOngoingConnection() const; + // Returns true if there is at least one active WiFi Direct connection. + bool HasWifiDirectConnection() const; + // Returns true if there is at least one active WiFi Hotspot connection. + bool HasWifiHotspotConnection() const; + // Returns true if there is at least one active WiFi Aware connection. + bool HasWifiAwareConnection() const; + std::string GetLastLocalEndpointId() const; + void SetLastLocalEndpointId(absl::string_view endpoint_id); // Returns the number of endpoints that are connected and outgoing. std::int32_t GetNumOutgoingConnections() const; // Returns the number of endpoints that are connected and incoming. @@ -287,6 +300,9 @@ class ClientProxy final { void SetRemoteOsInfo( absl::string_view endpoint_id, const location::nearby::connections::OsInfo& remote_os_info); + void SetRemoteDeviceName(absl::string_view endpoint_id, + absl::string_view device_name); + std::string GetRemoteDeviceName(absl::string_view endpoint_id) const; void RegisterDeviceProvider(NearbyDeviceProvider* provider) { external_device_provider_ = provider; @@ -333,6 +349,18 @@ class ClientProxy final { std::optional GetMediumRole( absl::string_view endpoint_id) const; + struct MediumsAvailability { + bool is_wifi_direct_go_available = false; + bool is_wifi_direct_gc_available = false; + bool is_wifi_hotspot_ap_available = false; + bool is_wifi_hotspot_client_available = false; + }; + + location::nearby::connections::MediumRole GetLocalMediumRole( + const MediumsAvailability& mediums_availability) const; + + bool IsUsingP2pMedium() const; + // Forces client to regenerate a new local endpoint id. void ClearCachedLocalEndpointId(); @@ -372,6 +400,7 @@ class ClientProxy final { std::int32_t safe_to_disconnect_version; std::int32_t remote_multiplex_socket_bitmask; std::string save_path; + std::string device_name; }; using ConnectionPair = std::pair; @@ -441,6 +470,8 @@ class ClientProxy final { std::int64_t client_id_; std::string local_endpoint_id_; std::string local_endpoint_info_; + std::string last_local_endpoint_id_; + std::string local_device_name_; // If advertising is in stable endpoint ID mode, the endpoint ID is stable // for 30s after advertising or disconnection. When stable_endpoint_id_mode_ diff --git a/connections/implementation/client_proxy_test.cc b/connections/implementation/client_proxy_test.cc index 2e3a5ac6..d6357f06 100644 --- a/connections/implementation/client_proxy_test.cc +++ b/connections/implementation/client_proxy_test.cc @@ -1345,6 +1345,7 @@ TEST_F(ClientProxyTest, GetRemoteInfoNullWithoutConnections) { EXPECT_FALSE(client1() ->GetRemoteSafeToDisconnectVersion(advertising_endpoint.id) .has_value()); + EXPECT_EQ(client1()->GetRemoteDeviceName(advertising_endpoint.id), ""); } TEST_F(ClientProxyTest, SetRemoteInfoCorrect) { @@ -1365,6 +1366,10 @@ TEST_F(ClientProxyTest, SetRemoteInfoCorrect) { EXPECT_EQ( client1()->GetRemoteSafeToDisconnectVersion(advertising_endpoint.id), nearby_connections_version); + std::string device_name = "device_name"; + client1()->SetRemoteDeviceName(advertising_endpoint.id, device_name); + EXPECT_EQ(client1()->GetRemoteDeviceName(advertising_endpoint.id), + device_name); } // Test ClientProxy::AddCancellationFlag, where if a flag is already in the map, @@ -1563,6 +1568,257 @@ TEST_F(ClientProxyTest, GetSavePathDefaultsToEmpty) { EXPECT_THAT(client1()->GetSavePath(advertising_endpoint.id), IsEmpty()); } +TEST_F(ClientProxyTest, GetLocalMediumRoleFlagDisabled) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + false); + ClientProxy::MediumsAvailability availability; + availability.is_wifi_direct_go_available = true; + availability.is_wifi_direct_gc_available = true; + availability.is_wifi_hotspot_ap_available = true; + availability.is_wifi_hotspot_client_available = true; + + location::nearby::connections::MediumRole role = + client1()->GetLocalMediumRole(availability); + EXPECT_FALSE(role.support_awdl_publisher()); + EXPECT_FALSE(role.support_awdl_subscriber()); + EXPECT_FALSE(role.support_wifi_direct_group_owner()); + EXPECT_FALSE(role.support_wifi_direct_group_client()); + EXPECT_FALSE(role.support_wifi_hotspot_host()); + EXPECT_FALSE(role.support_wifi_hotspot_client()); +} + +TEST_F(ClientProxyTest, GetLocalMediumRoleAppleOs) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + client1()->SetLocalOsType(location::nearby::connections::OsInfo::APPLE); + ClientProxy::MediumsAvailability availability; + + location::nearby::connections::MediumRole role = + client1()->GetLocalMediumRole(availability); + EXPECT_TRUE(role.support_awdl_publisher()); + EXPECT_TRUE(role.support_awdl_subscriber()); + EXPECT_TRUE(role.support_wifi_hotspot_client()); + EXPECT_FALSE(role.support_wifi_direct_group_owner()); + EXPECT_FALSE(role.support_wifi_direct_group_client()); + EXPECT_FALSE(role.support_wifi_hotspot_host()); +} + +TEST_F(ClientProxyTest, GetLocalMediumRoleNonAppleOsNoP2pConnection) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + client1()->SetLocalOsType(location::nearby::connections::OsInfo::ANDROID); + ClientProxy::MediumsAvailability availability; + availability.is_wifi_direct_go_available = true; + availability.is_wifi_direct_gc_available = true; + availability.is_wifi_hotspot_ap_available = true; + availability.is_wifi_hotspot_client_available = true; + + location::nearby::connections::MediumRole role = + client1()->GetLocalMediumRole(availability); + EXPECT_TRUE(role.support_wifi_direct_group_owner()); + EXPECT_TRUE(role.support_wifi_direct_group_client()); + EXPECT_TRUE(role.support_wifi_hotspot_host()); + EXPECT_TRUE(role.support_wifi_hotspot_client()); +} + +TEST_F(ClientProxyTest, GetLocalMediumRoleNonAppleOsWithP2pConnection) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableDynamicRoleSwitch, + true); + client1()->SetLocalOsType(location::nearby::connections::OsInfo::ANDROID); + + // Setup an active P2P connection to make IsUsingP2pMedium() true + Endpoint advertising_endpoint = + StartAdvertising(client1(), advertising_connection_listener_); + OnAdvertisingConnectionInitiated(client1(), advertising_endpoint); + client1()->OnBandwidthChanged(advertising_endpoint.id, Medium::WIFI_DIRECT); + EXPECT_TRUE(client1()->IsUsingP2pMedium()); + + ClientProxy::MediumsAvailability availability; + availability.is_wifi_direct_go_available = true; + availability.is_wifi_direct_gc_available = true; + availability.is_wifi_hotspot_ap_available = true; + availability.is_wifi_hotspot_client_available = true; + + location::nearby::connections::MediumRole role = + client1()->GetLocalMediumRole(availability); + EXPECT_FALSE(role.support_wifi_direct_group_owner()); + EXPECT_TRUE(role.support_wifi_direct_group_client()); + EXPECT_FALSE(role.support_wifi_hotspot_host()); + EXPECT_TRUE(role.support_wifi_hotspot_client()); +} + +TEST_F(ClientProxyTest, GetNumIncomingAndOutgoingConnections) { + // Initially no connections + EXPECT_EQ(client1()->GetNumIncomingConnections(), 0); + EXPECT_EQ(client1()->GetNumOutgoingConnections(), 0); + + // Set expectation for acceptance callback on step 1 + // (which is outgoing based on discovery_connection_info_) + EXPECT_CALL(mock_advertising_connection_.accepted_cb, Call).Times(1); + + // Define a complete listener for advertising + ConnectionListener advertising_listener = { + .initiated_cb = mock_advertising_connection_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_advertising_connection_.accepted_cb.AsStdFunction(), + }; + + // 1. Establish connection 1 + Endpoint advertising_endpoint = + StartAdvertising(client1(), advertising_listener); + EXPECT_CALL(mock_advertising_connection_.initiated_cb, Call).Times(1); + client1()->OnConnectionInitiated( + advertising_endpoint.id, discovery_connection_info_, connection_options_, + advertising_listener, "connection_token1"); + + // Accept local, accept remote, and then OnConnectionAccepted + client1()->LocalEndpointAcceptedConnection( + advertising_endpoint.id, + { + .payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(), + .payload_progress_cb = + mock_discovery_payload_.payload_progress_cb.AsStdFunction(), + }); + client1()->RemoteEndpointAcceptedConnection(advertising_endpoint.id); + client1()->OnConnectionAccepted(advertising_endpoint.id); + + // Verify client1 has 0 incoming connections and 1 outgoing connection + EXPECT_EQ(client1()->GetNumIncomingConnections(), 0); + EXPECT_EQ(client1()->GetNumOutgoingConnections(), 1); + + // Set expectation for acceptance callback on step 2 + // (which is incoming based on advertising_connection_info_) + EXPECT_CALL(mock_discovery_connection_.accepted_cb, Call).Times(1); + + // 2. Establish connection 2 + StartDiscovery(client1(), GetDiscoveryListener()); + Endpoint remote_endpoint = { + .info = ByteArray{"remote endpoint name"}, + .id = "rem_ep_id", + }; + OnDiscoveryEndpointFound(client1(), remote_endpoint); + + EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1); + client1()->OnConnectionInitiated( + remote_endpoint.id, advertising_connection_info_, connection_options_, + discovery_connection_listener_, "connection_token2"); + + // Accept local, accept remote, and then OnConnectionAccepted + client1()->LocalEndpointAcceptedConnection( + remote_endpoint.id, + { + .payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(), + .payload_progress_cb = + mock_discovery_payload_.payload_progress_cb.AsStdFunction(), + }); + client1()->RemoteEndpointAcceptedConnection(remote_endpoint.id); + client1()->OnConnectionAccepted(remote_endpoint.id); + + // Verify client1 has 1 incoming connection and 1 outgoing connection + EXPECT_EQ(client1()->GetNumIncomingConnections(), 1); + EXPECT_EQ(client1()->GetNumOutgoingConnections(), 1); +} + +TEST_F(ClientProxyTest, IsUsingP2pMediumTests) { + // With no connections, IsUsingP2pMedium should be false + EXPECT_FALSE(client1()->IsUsingP2pMedium()); + + // 1. Connection with Non-P2P medium (e.g. WIFI_LAN) + Endpoint endpoint_lan = + StartAdvertising(client1(), advertising_connection_listener_); + OnAdvertisingConnectionInitiated(client1(), endpoint_lan); + client1()->OnBandwidthChanged(endpoint_lan.id, Medium::WIFI_LAN); + EXPECT_FALSE(client1()->IsUsingP2pMedium()); + + // Clean-up connection + client1()->OnDisconnected(endpoint_lan.id, /*notify=*/false); + EXPECT_FALSE(client1()->IsUsingP2pMedium()); + + // 2. Connection with WIFI_DIRECT + Endpoint endpoint_direct = + StartAdvertising(client1(), advertising_connection_listener_); + OnAdvertisingConnectionInitiated(client1(), endpoint_direct); + client1()->OnBandwidthChanged(endpoint_direct.id, Medium::WIFI_DIRECT); + EXPECT_TRUE(client1()->IsUsingP2pMedium()); + client1()->OnDisconnected(endpoint_direct.id, /*notify=*/false); + + // 3. Connection with WIFI_HOTSPOT + Endpoint endpoint_hotspot = + StartAdvertising(client1(), advertising_connection_listener_); + OnAdvertisingConnectionInitiated(client1(), endpoint_hotspot); + client1()->OnBandwidthChanged(endpoint_hotspot.id, Medium::WIFI_HOTSPOT); + EXPECT_TRUE(client1()->IsUsingP2pMedium()); + client1()->OnDisconnected(endpoint_hotspot.id, /*notify=*/false); + + // 4. Connection with WIFI_AWARE + Endpoint endpoint_aware = + StartAdvertising(client1(), advertising_connection_listener_); + OnAdvertisingConnectionInitiated(client1(), endpoint_aware); + client1()->OnBandwidthChanged(endpoint_aware.id, Medium::WIFI_AWARE); + EXPECT_TRUE(client1()->IsUsingP2pMedium()); + client1()->OnDisconnected(endpoint_aware.id, /*notify=*/false); + EXPECT_FALSE(client1()->IsUsingP2pMedium()); +} + +TEST_F(ClientProxyTest, GetAndSetLastLocalEndpointId) { + EXPECT_TRUE(client1()->GetLastLocalEndpointId().empty()); + client1()->SetLastLocalEndpointId("TestEndpointID"); + EXPECT_EQ(client1()->GetLastLocalEndpointId(), "TestEndpointID"); +} + +TEST_F(ClientProxyTest, ResetLocalEndpointId_OngoingConnectionReturnsEarly) { + std::string old_id = client1()->GetLocalEndpointId(); + ASSERT_FALSE(old_id.empty()); + + // Set up an ongoing connection + OnAdvertisingConnectionInitiated(client1(), + {ByteArray("EndpointInfo"), "EndA"}); + EXPECT_TRUE(client1()->HasOngoingConnection()); + + // ResetLocalEndpointId should NOT clear local_endpoint_id + client1()->ResetLocalEndpointId(); + EXPECT_EQ(client1()->GetLocalEndpointId(), old_id); + + // Terminate connection + client1()->OnDisconnected("EndA", /*notify=*/false); + EXPECT_FALSE(client1()->HasOngoingConnection()); + + // ResetLocalEndpointId should now successfully clear local_endpoint_id + client1()->ResetLocalEndpointId(); + EXPECT_NE(client1()->GetLocalEndpointId(), old_id); +} + +TEST_F(ClientProxyTest, ResetLocalEndpointId_SavesToLastLocalEndpointId) { + std::string old_id = client1()->GetLocalEndpointId(); + ASSERT_FALSE(old_id.empty()); + + client1()->ResetLocalEndpointId(); + EXPECT_EQ(client1()->GetLastLocalEndpointId(), old_id); +} + +TEST_F(ClientProxyTest, OnSessionComplete_SavesToLastLocalEndpointId) { + std::string old_id = client1()->GetLocalEndpointId(); + ASSERT_FALSE(old_id.empty()); + + // Put client into advertising mode first + client1()->StartedAdvertising(service_id_, strategy_, {}, {}, {}); + EXPECT_TRUE(client1()->IsAdvertising()); + + // Stopping advertising triggers OnSessionComplete. + // Since connections_ is empty, it completes the session and should save last + // endpoint ID. + client1()->StoppedAdvertising(); + EXPECT_FALSE(client1()->IsAdvertising()); + EXPECT_EQ(client1()->GetLastLocalEndpointId(), old_id); +} + } // namespace } // namespace connections } // namespace nearby diff --git a/connections/implementation/encryption_runner_test.cc b/connections/implementation/encryption_runner_test.cc index 743c1658..4868d59c 100644 --- a/connections/implementation/encryption_runner_test.cc +++ b/connections/implementation/encryption_runner_test.cc @@ -103,6 +103,10 @@ class FakeEndpointChannel : public EndpointChannel { } void SetAnalyticsRecorder(analytics::AnalyticsRecorder* analytics_recorder, const std::string& endpoint_id) override {} + void SetLocalEndpointId(const std::string& local_endpoint_id) override { + local_endpoint_id_ = local_endpoint_id; + } + std::string GetLocalEndpointId() const override { return local_endpoint_id_; } private: InputStream* in_ = nullptr; @@ -110,6 +114,7 @@ class FakeEndpointChannel : public EndpointChannel { absl::Time read_timestamp_ = absl::InfinitePast(); absl::Time write_timestamp_ = absl::InfinitePast(); mutable uint32_t next_keep_alive_seq_no_ = 0; + std::string local_endpoint_id_; }; struct User { diff --git a/connections/implementation/endpoint_channel.h b/connections/implementation/endpoint_channel.h index 1b286475..d3218498 100644 --- a/connections/implementation/endpoint_channel.h +++ b/connections/implementation/endpoint_channel.h @@ -128,6 +128,9 @@ class EndpointChannel { // Enables the multiplex socket on the EndpointChannel. virtual bool EnableMultiplexSocket() { return false; } + + virtual void SetLocalEndpointId(const std::string& local_endpoint_id) = 0; + virtual std::string GetLocalEndpointId() const = 0; }; inline bool operator==(const EndpointChannel& lhs, const EndpointChannel& rhs) { diff --git a/connections/implementation/endpoint_channel_manager.cc b/connections/implementation/endpoint_channel_manager.cc index ab2af35d..099aab27 100644 --- a/connections/implementation/endpoint_channel_manager.cc +++ b/connections/implementation/endpoint_channel_manager.cc @@ -17,7 +17,9 @@ #include #include #include +#include +#include "absl/strings/string_view.h" #include "absl/time/time.h" #include "connections/implementation/client_proxy.h" #include "connections/implementation/endpoint_channel.h" @@ -48,7 +50,9 @@ void EndpointChannelManager::RegisterChannelForEndpoint( LOG(INFO) << "EndpointChannelManager registered channel of type " << channel->GetType() << " to endpoint " << endpoint_id; - SetActiveEndpointChannel(client, endpoint_id, std::move(channel), + std::shared_ptr endpoint = + channel_state_.RegisterEndpoint(endpoint_id); + SetActiveEndpointChannel(client, endpoint_id, endpoint, std::move(channel), true /* enable_encryption */); LOG(INFO) << "Registered channel: id=" << endpoint_id; @@ -58,8 +62,17 @@ void EndpointChannelManager::ReplaceChannelForEndpoint( ClientProxy* client, const std::string& endpoint_id, std::shared_ptr channel, bool enable_encryption) { MutexLock lock(&mutex_); + + std::shared_ptr endpoint = + channel_state_.GetEndpointData(endpoint_id); + if (endpoint == nullptr) { + LOG(WARNING) << "EndpointChannelManager failed to replace channel because " + "endpoint " + << endpoint_id << " is not registered."; + return; + } if (client->IsSafeToDisconnectEnabled(endpoint_id) && - channel_state_.IsWaitingForSafeToDisconnectTimeout(endpoint_id)) { + endpoint->IsWaitingForSafeToDisconnectTimeout()) { LOG(WARNING) << "EndpointChannelManager failed to replace endpoint " << endpoint_id << "'s channel with type " << channel->GetType() @@ -67,13 +80,7 @@ void EndpointChannelManager::ReplaceChannelForEndpoint( return; } - auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); - if (endpoint != nullptr && endpoint->channel == nullptr) { - LOG(INFO) << "EndpointChannelManager is missing channel while " - "trying to update: endpoint " - << endpoint_id; - } - SetActiveEndpointChannel(client, endpoint_id, std::move(channel), + SetActiveEndpointChannel(client, endpoint_id, endpoint, std::move(channel), enable_encryption); } @@ -82,37 +89,47 @@ bool EndpointChannelManager::EncryptChannelForEndpoint( std::unique_ptr context) { MutexLock lock(&mutex_); - channel_state_.UpdateEncryptionContextForEndpoint(endpoint_id, - std::move(context)); - auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); - return channel_state_.EncryptChannel(endpoint); + std::shared_ptr endpoint = + channel_state_.GetEndpointData(endpoint_id); + if (endpoint == nullptr) { + LOG(WARNING) << "EncryptChannelForEndpoint failed " + << "because endpoint is not registered: " << endpoint_id; + return false; + } + endpoint->set_context(std::move(context)); + return endpoint->EncryptChannel(); } std::shared_ptr EndpointChannelManager::GetChannelForEndpoint( - const std::string& endpoint_id) { + absl::string_view endpoint_id) { MutexLock lock(&mutex_); - auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + std::shared_ptr endpoint = + channel_state_.GetEndpointData(endpoint_id); if (endpoint == nullptr) { LOG(INFO) << "No channel info for endpoint " << endpoint_id; return {}; } - return endpoint->channel; + return endpoint->channel(); } void EndpointChannelManager::SetActiveEndpointChannel( ClientProxy* client, const std::string& endpoint_id, + std::shared_ptr endpoint, std::shared_ptr channel, bool enable_encryption) { // Update the channel first, then encrypt this new channel, if // crypto context is present. channel->SetAnalyticsRecorder(&client->GetAnalyticsRecorder(), endpoint_id); - channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel)); - channel_state_.UpdateSafeToDisconnectForEndpoint( - endpoint_id, client->IsSafeToDisconnectEnabled(endpoint_id)); - auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); - if (endpoint->IsEncrypted() && enable_encryption) - channel_state_.EncryptChannel(endpoint); + channel->SetLocalEndpointId(client->GetLocalEndpointId()); + + endpoint->set_channel(std::move(channel)); + endpoint->set_safe_to_disconnect_enabled( + client->IsSafeToDisconnectEnabled(endpoint_id)); + + if (endpoint->IsEncrypted() && enable_encryption) { + endpoint->EncryptChannel(); + } } int EndpointChannelManager::GetConnectedEndpointsCount() const { @@ -133,7 +150,7 @@ void EndpointChannelManager::UpdateSafeToDisconnectForEndpoint( } void EndpointChannelManager::MarkEndpointStopWaitToDisconnect( - const std::string& endpoint_id, bool is_safe_to_disconnect, + absl::string_view endpoint_id, bool is_safe_to_disconnect, bool notify_stop_waiting) { MutexLock lock(&mutex_); channel_state_.MarkEndpointStopWaitToDisconnect( @@ -141,61 +158,128 @@ void EndpointChannelManager::MarkEndpointStopWaitToDisconnect( } bool EndpointChannelManager::CreateNewTimeoutDisconnectedState( - const std::string& endpoint_id, absl::Duration timeout_millis) { - return channel_state_.CreateNewTimeoutDisconnectedState(endpoint_id, - timeout_millis); + absl::string_view endpoint_id, absl::Duration timeout_millis) { + std::shared_ptr endpoint_data; + { + MutexLock lock(&mutex_); + endpoint_data = channel_state_.GetEndpointData(endpoint_id); + } + if (!endpoint_data) return false; + + LOG(INFO) << "[safe-to-disconnect] " + "Create TimeoutDisconnectedState for endpoint: " + << endpoint_id; + endpoint_data->CreateNewTimeoutDisconnectedState(timeout_millis); + return true; } -bool EndpointChannelManager::IsSafeToDisconnect( - const std::string& endpoint_id) { +bool EndpointChannelManager::IsSafeToDisconnect(absl::string_view endpoint_id) { + MutexLock lock(&mutex_); return channel_state_.IsSafeToDisconnect(endpoint_id); } + +bool EndpointChannelManager::IsWaitingForSafeToDisconnectTimeoutForTesting( + absl::string_view endpoint_id) { + MutexLock lock(&mutex_); + return channel_state_.IsWaitingForSafeToDisconnectTimeoutForTesting( + endpoint_id); +} + void EndpointChannelManager::RemoveTimeoutDisconnectedState( - const std::string& endpoint_id) { + absl::string_view endpoint_id) { MutexLock lock(&mutex_); channel_state_.RemoveTimeoutDisconnectedState(endpoint_id); } ///////////////////////////////// ChannelState ///////////////////////////////// -// endpoint - channel endpoint to encrypt -bool EndpointChannelManager::ChannelState::EncryptChannel( - EndpointChannelManager::ChannelState::EndpointData* endpoint) { - if (endpoint != nullptr && endpoint->channel != nullptr && - endpoint->context != nullptr) { - endpoint->channel->EnableEncryption(endpoint->context); +void EndpointChannelManager::ChannelState::EndpointData:: + CreateNewTimeoutDisconnectedState(absl::Duration timeout_millis) { + MutexLock lock(&timeout_to_disconnected_mutex_); + timeout_to_disconnected_enabled_ = true; + timeout_to_disconnected_notified_ = false; + timeout_to_disconnected_.Wait(timeout_millis); + LOG(INFO) << "[safe-to-disconnect] Wait is done with " + << (timeout_to_disconnected_notified_ ? "notification" : "timeout"); + if (!timeout_to_disconnected_notified_) { + is_safe_to_disconnect_ = true; + } + timeout_to_disconnected_notified_ = false; + timeout_to_disconnected_enabled_ = false; +} + +void EndpointChannelManager::ChannelState::EndpointData:: + MarkEndpointStopWaitToDisconnect(bool is_safe_to_disconnect, + bool notify_stop_waiting) { + MutexLock lock(&timeout_to_disconnected_mutex_); + this->is_safe_to_disconnect_ = is_safe_to_disconnect; + if (!timeout_to_disconnected_enabled_) return; + if (notify_stop_waiting) { + LOG(INFO) << "[safe-to-disconnect] Notify stop waiting before timeout."; + timeout_to_disconnected_.Notify(); + timeout_to_disconnected_notified_ = true; + } +} + +bool EndpointChannelManager::ChannelState::EndpointData:: + IsWaitingForSafeToDisconnectTimeout() const { + MutexLock lock(&timeout_to_disconnected_mutex_); + return timeout_to_disconnected_enabled_; +} + +bool EndpointChannelManager::ChannelState::EndpointData::IsSafeToDisconnect() + const { + MutexLock lock(&timeout_to_disconnected_mutex_); + return is_safe_to_disconnect_; +} + +void EndpointChannelManager::ChannelState::EndpointData:: + RemoveTimeoutDisconnectedState() { + MutexLock lock(&timeout_to_disconnected_mutex_); + timeout_to_disconnected_notified_ = false; + timeout_to_disconnected_enabled_ = false; +} + +bool EndpointChannelManager::ChannelState::EndpointData::EncryptChannel() { + if (context_ != nullptr) { + channel_->EnableEncryption(context_); return true; } return false; } -EndpointChannelManager::ChannelState::EndpointData* -EndpointChannelManager::ChannelState::LookupEndpointData( - const std::string& endpoint_id) { - auto item = endpoints_.find(endpoint_id); - return item != endpoints_.end() ? &item->second : nullptr; +std::shared_ptr +EndpointChannelManager::ChannelState::GetEndpointData( + absl::string_view endpoint_id) { + auto it = endpoints_.find(endpoint_id); + return it != endpoints_.end() ? it->second : nullptr; +} + +std::shared_ptr +EndpointChannelManager::ChannelState::RegisterEndpoint( + absl::string_view endpoint_id) { + std::shared_ptr& endpoint = endpoints_[endpoint_id]; + if (endpoint == nullptr) { + endpoint = std::make_shared(); + } else { + LOG(DFATAL) << "Endpoint " << endpoint_id + << " is already registered. It might not have been cleaned up " + "properly."; + } + return endpoint; } void EndpointChannelManager::ChannelState::DestroyAll() { - for (auto& item : endpoints_) { - RemoveEndpoint(item.first, DisconnectionReason::SHUTDOWN, - /* safe_to_disconnect_enabled */ false, - SafeDisconnectionResult::kSafeDisconnection); + // Collect all endpoint IDs to avoid iterator invalidation. + std::vector endpoint_ids; + endpoint_ids.reserve(endpoints_.size()); + for (const auto& [endpoint_id, endpoint_data] : endpoints_) { + endpoint_ids.push_back(endpoint_id); } - endpoints_.clear(); -} -void EndpointChannelManager::ChannelState::UpdateChannelForEndpoint( - const std::string& endpoint_id, std::shared_ptr channel) { - // Create EndpointData instance, if necessary, and populate channel. - endpoints_[endpoint_id].channel = std::move(channel); -} - -void EndpointChannelManager::ChannelState::UpdateEncryptionContextForEndpoint( - const std::string& endpoint_id, - std::unique_ptr context) { - // Create EndpointData instance, if necessary, and populate crypto context. - endpoints_[endpoint_id].context = std::move(context); + for (const auto& endpoint_id : endpoint_ids) { + RemoveEndpoint(endpoint_id, DisconnectionReason::SHUTDOWN); + } } void EndpointChannelManager::ChannelState::UpdateSafeToDisconnectForEndpoint( @@ -204,32 +288,28 @@ void EndpointChannelManager::ChannelState::UpdateSafeToDisconnectForEndpoint( "UpdateSafeToDisconnectForEndpoint for: " << endpoint_id << " " << safe_to_disconnect_enabled; - endpoints_[endpoint_id].safe_to_disconnect_enabled = - safe_to_disconnect_enabled; -} - -bool EndpointChannelManager::ChannelState::GetSafeToDisconnectForEndpoint( - const std::string& endpoint_id) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return false; - LOG(INFO) << "[safe-to-disconnect] GetSafeToDisconnectForEndpoint: " - << item->second.safe_to_disconnect_enabled; - return item->second.safe_to_disconnect_enabled; + std::shared_ptr endpoint = GetEndpointData(endpoint_id); + if (endpoint == nullptr) { + LOG(WARNING) << "UpdateSafeToDisconnectForEndpoint failed because endpoint " + << endpoint_id << " is not registered."; + return; + } + endpoint->set_safe_to_disconnect_enabled(safe_to_disconnect_enabled); } bool EndpointChannelManager::ChannelState::RemoveEndpoint( - const std::string& endpoint_id, DisconnectionReason reason, - bool safe_to_disconnect_enabled, SafeDisconnectionResult result) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return false; + absl::string_view endpoint_id, DisconnectionReason reason) { + auto it = endpoints_.find(endpoint_id); + if (it == endpoints_.end()) return false; MarkEndpointStopWaitToDisconnect(endpoint_id, /* is_safe_to_disconnect */ true, /* notify_stop_waiting */ true); - item->second.disconnect_reason = reason; - auto channel = item->second.channel; + it->second->set_disconnect_reason(reason); + std::shared_ptr channel = it->second->channel(); + bool safe_to_disconnect_enabled = it->second->safe_to_disconnect_enabled(); - if (channel && !channel->IsClosed() && !safe_to_disconnect_enabled) { + if (!channel->IsClosed() && !safe_to_disconnect_enabled) { // If the channel was paused (i.e. during a bandwidth upgrade negotiation) // we resume to ensure the thread won't hang when trying to write to it. channel->Resume(); @@ -246,18 +326,16 @@ bool EndpointChannelManager::ChannelState::RemoveEndpoint( } LOG(INFO) << "Remove Endpoint: " << endpoint_id; - endpoints_.erase(item); + endpoints_.erase(it); return true; } bool EndpointChannelManager::ChannelState::isWifiLanConnected() const { - for (auto& endpoint : endpoints_) { - auto channel = endpoint.second.channel; - if (channel) { - if (channel->GetMedium() == Medium::WIFI_LAN) { - LOG(INFO) << "Found WIFI_LAN Medium for endpoint:" << endpoint.first; - return true; - } + for (const auto& [endpoint_id, endpoint_data] : endpoints_) { + std::shared_ptr channel = endpoint_data->channel(); + if (channel->GetMedium() == Medium::WIFI_LAN) { + LOG(INFO) << "Found WIFI_LAN Medium for endpoint:" << endpoint_id; + return true; } } @@ -265,96 +343,53 @@ bool EndpointChannelManager::ChannelState::isWifiLanConnected() const { } void EndpointChannelManager::ChannelState::MarkEndpointStopWaitToDisconnect( - const std::string& endpoint_id, bool is_safe_to_disconnect, + absl::string_view endpoint_id, bool is_safe_to_disconnect, bool notify_stop_waiting) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return; + std::shared_ptr endpoint = GetEndpointData(endpoint_id); + if (endpoint == nullptr) return; LOG(INFO) << "[safe-to-disconnect] is_safe_to_disconnect= " << is_safe_to_disconnect << ", notify_stop_waiting= " << notify_stop_waiting << " for endpoint: " << endpoint_id; - { - MutexLock lock(&item->second.timeout_to_disconnected_mutex); - item->second.is_safe_to_disconnect = is_safe_to_disconnect; - if (!item->second.timeout_to_disconnected_enabled) return; - if (notify_stop_waiting) { - LOG(INFO) << "[safe-to-disconnect] Notify stop " - "waiting before timeout."; - item->second.timeout_to_disconnected.Notify(); - item->second.timeout_to_disconnected_notified = true; - } - } + endpoint->MarkEndpointStopWaitToDisconnect(is_safe_to_disconnect, + notify_stop_waiting); } -bool EndpointChannelManager::ChannelState::CreateNewTimeoutDisconnectedState( - const std::string& endpoint_id, absl::Duration timeout_millis) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return false; +bool EndpointChannelManager::ChannelState:: + IsWaitingForSafeToDisconnectTimeoutForTesting( + absl::string_view endpoint_id) { + std::shared_ptr endpoint = GetEndpointData(endpoint_id); + if (endpoint == nullptr) return false; + bool enabled = endpoint->IsWaitingForSafeToDisconnectTimeout(); LOG(INFO) << "[safe-to-disconnect] " - "Create TimeoutDisconnectedState for endpoint: " - << endpoint_id; - { - MutexLock lock(&item->second.timeout_to_disconnected_mutex); - item->second.timeout_to_disconnected_enabled = true; - item->second.timeout_to_disconnected_notified = false; - item->second.timeout_to_disconnected.Wait(timeout_millis); - LOG(INFO) << "[safe-to-disconnect] Wait is done with " - << (item->second.timeout_to_disconnected_notified ? "notification" - : "timeout"); - if (!item->second.timeout_to_disconnected_notified) - item->second.is_safe_to_disconnect = true; - item->second.timeout_to_disconnected_notified = false; - item->second.timeout_to_disconnected_enabled = false; - } - return true; -} -bool EndpointChannelManager::ChannelState::IsWaitingForSafeToDisconnectTimeout( - const std::string& endpoint_id) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return false; - { - MutexLock lock(&item->second.timeout_to_disconnected_mutex); - LOG(INFO) << "[safe-to-disconnect] " - "IsWaitingForSafeToDisconnectTimeout for endpoint: " - << endpoint_id << ": " - << item->second.timeout_to_disconnected_enabled; - return (item->second.timeout_to_disconnected_enabled); - } + "IsWaitingForSafeToDisconnectTimeout for endpoint: " + << endpoint_id << ": " << enabled; + return enabled; } bool EndpointChannelManager::ChannelState::IsSafeToDisconnect( - const std::string& endpoint_id) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return true; - { - MutexLock lock(&item->second.timeout_to_disconnected_mutex); - LOG(INFO) - << "[safe-to-disconnect] Get SafeToDisconnect status for endpoint: " - << endpoint_id << ": " << item->second.is_safe_to_disconnect; - return (item->second.is_safe_to_disconnect); - } + absl::string_view endpoint_id) { + std::shared_ptr endpoint = GetEndpointData(endpoint_id); + if (endpoint == nullptr) return true; + bool is_safe = endpoint->IsSafeToDisconnect(); + LOG(INFO) << "[safe-to-disconnect] Get SafeToDisconnect status for endpoint: " + << endpoint_id << ": " << is_safe; + return is_safe; } void EndpointChannelManager::ChannelState::RemoveTimeoutDisconnectedState( - const std::string& endpoint_id) { - auto item = endpoints_.find(endpoint_id); - if (item == endpoints_.end()) return; - { - MutexLock lock(&item->second.timeout_to_disconnected_mutex); - item->second.timeout_to_disconnected_notified = false; - item->second.timeout_to_disconnected_enabled = false; - } + absl::string_view endpoint_id) { + std::shared_ptr endpoint = GetEndpointData(endpoint_id); + if (endpoint == nullptr) return; + endpoint->RemoveTimeoutDisconnectedState(); } bool EndpointChannelManager::UnregisterChannelForEndpoint( - const std::string& endpoint_id, DisconnectionReason reason, + absl::string_view endpoint_id, DisconnectionReason reason, SafeDisconnectionResult result) { MutexLock lock(&mutex_); - auto safe_to_disconnect_enabled = - channel_state_.GetSafeToDisconnectForEndpoint(endpoint_id); - if (!channel_state_.RemoveEndpoint(endpoint_id, reason, - safe_to_disconnect_enabled, result)) { + if (!channel_state_.RemoveEndpoint(endpoint_id, reason)) { return false; } LOG(INFO) << "EndpointChannelManager unregistered channel for endpoint " diff --git a/connections/implementation/endpoint_channel_manager.h b/connections/implementation/endpoint_channel_manager.h index 47878023..eeb87df6 100644 --- a/connections/implementation/endpoint_channel_manager.h +++ b/connections/implementation/endpoint_channel_manager.h @@ -17,9 +17,11 @@ #include #include +#include #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" #include "absl/time/time.h" #include "connections/implementation/analytics/analytics_recorder.h" #include "connections/implementation/client_proxy.h" @@ -91,11 +93,11 @@ class EndpointChannelManager final { // EndpointManager methods that use a channel are running, it is better to // have a shared ownership. std::shared_ptr GetChannelForEndpoint( - const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + absl::string_view endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); // Returns true if 'endpoint_id' actually had a registered EndpointChannel. // IOW, a return of false signifies a no-op. - bool UnregisterChannelForEndpoint(const std::string& endpoint_id, + bool UnregisterChannelForEndpoint(absl::string_view endpoint_id, DisconnectionReason reason, SafeDisconnectionResult result) ABSL_LOCKS_EXCLUDED(mutex_); @@ -107,16 +109,19 @@ class EndpointChannelManager final { void UpdateSafeToDisconnectForEndpoint(const std::string& endpoint_id, bool safe_to_disconnect_enabled) ABSL_LOCKS_EXCLUDED(mutex_); - void MarkEndpointStopWaitToDisconnect(const std::string& endpoint_id, + bool CreateNewTimeoutDisconnectedState(absl::string_view endpoint_id, + absl::Duration timeout_millis) + ABSL_LOCKS_EXCLUDED(mutex_); + void MarkEndpointStopWaitToDisconnect(absl::string_view endpoint_id, bool is_safe_to_disconnect, bool notify_stop_waiting) ABSL_LOCKS_EXCLUDED(mutex_); - bool CreateNewTimeoutDisconnectedState(const std::string& endpoint_id, - absl::Duration timeout_millis) + + bool IsSafeToDisconnect(absl::string_view endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); - bool IsSafeToDisconnect(const std::string& endpoint_id) - ABSL_LOCKS_EXCLUDED(mutex_); - void RemoveTimeoutDisconnectedState(const std::string& endpoint_id) + bool IsWaitingForSafeToDisconnectTimeoutForTesting( + absl::string_view endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + void RemoveTimeoutDisconnectedState(absl::string_view endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); private: @@ -125,32 +130,69 @@ class EndpointChannelManager final { // been encrypted yet. class ChannelState { public: - struct EndpointData { + class EndpointData { + public: EndpointData() = default; EndpointData(EndpointData&&) = default; EndpointData& operator=(EndpointData&&) = default; ~EndpointData() { - if (channel != nullptr) { - channel->Close(disconnect_reason); + if (channel_ != nullptr) { + channel_->Close(disconnect_reason_); } } // True if we have a 'context' for the endpoint. - bool IsEncrypted() const { return context != nullptr; } + bool IsEncrypted() const { return context_ != nullptr; } - std::shared_ptr channel; - std::shared_ptr context; - DisconnectionReason disconnect_reason = + void CreateNewTimeoutDisconnectedState(absl::Duration timeout_millis) + ABSL_LOCKS_EXCLUDED(timeout_to_disconnected_mutex_); + void MarkEndpointStopWaitToDisconnect(bool is_safe_to_disconnect, + bool notify_stop_waiting) + ABSL_LOCKS_EXCLUDED(timeout_to_disconnected_mutex_); + bool IsWaitingForSafeToDisconnectTimeout() const + ABSL_LOCKS_EXCLUDED(timeout_to_disconnected_mutex_); + bool IsSafeToDisconnect() const + ABSL_LOCKS_EXCLUDED(timeout_to_disconnected_mutex_); + void RemoveTimeoutDisconnectedState() + ABSL_LOCKS_EXCLUDED(timeout_to_disconnected_mutex_); + bool EncryptChannel(); + + std::shared_ptr channel() const { return channel_; } + void set_channel(std::shared_ptr channel) { + channel_ = std::move(channel); + } + + std::shared_ptr context() const { return context_; } + void set_context(std::shared_ptr context) { + context_ = std::move(context); + } + + void set_disconnect_reason(DisconnectionReason disconnect_reason) { + disconnect_reason_ = disconnect_reason; + } + + bool safe_to_disconnect_enabled() const { + return safe_to_disconnect_enabled_; + } + void set_safe_to_disconnect_enabled(bool safe_to_disconnect_enabled) { + safe_to_disconnect_enabled_ = safe_to_disconnect_enabled; + } + + private: + std::shared_ptr channel_; + std::shared_ptr context_; + DisconnectionReason disconnect_reason_ = DisconnectionReason::UNKNOWN_DISCONNECTION_REASON; - bool safe_to_disconnect_enabled = false; - mutable Mutex timeout_to_disconnected_mutex; - ConditionVariable timeout_to_disconnected{&timeout_to_disconnected_mutex}; - bool timeout_to_disconnected_enabled - ABSL_GUARDED_BY(timeout_to_disconnected_mutex) = false; - bool timeout_to_disconnected_notified - ABSL_GUARDED_BY(timeout_to_disconnected_mutex) = false; - bool is_safe_to_disconnect - ABSL_GUARDED_BY(timeout_to_disconnected_mutex) = false; + bool safe_to_disconnect_enabled_ = false; + mutable Mutex timeout_to_disconnected_mutex_; + ConditionVariable timeout_to_disconnected_{ + &timeout_to_disconnected_mutex_}; + bool timeout_to_disconnected_enabled_ + ABSL_GUARDED_BY(timeout_to_disconnected_mutex_) = false; + bool timeout_to_disconnected_notified_ + ABSL_GUARDED_BY(timeout_to_disconnected_mutex_) = false; + bool is_safe_to_disconnect_ + ABSL_GUARDED_BY(timeout_to_disconnected_mutex_) = false; }; ChannelState() = default; @@ -160,57 +202,48 @@ class EndpointChannelManager final { // Provides a way to destroy contents of a container, while holding a lock. void DestroyAll(); + // Return pointer to endpoint data, or nullptr, it not found. - EndpointData* LookupEndpointData(const std::string& endpoint_id); + std::shared_ptr GetEndpointData( + absl::string_view endpoint_id); - // Stores a new EndpointChannel for the endpoint. - // Prevoius one is destroyed, if it existed. - void UpdateChannelForEndpoint(const std::string& endpoint_id, - std::shared_ptr channel); - - // Stores a new EncryptionContext for the endpoint. - // Prevoius one is destroyed, if it existed. - void UpdateEncryptionContextForEndpoint( - const std::string& endpoint_id, - std::unique_ptr context); + // Registers a new endpoint id. This is the only spot EndpointData is + // created. + std::shared_ptr RegisterEndpoint( + absl::string_view endpoint_id); void UpdateSafeToDisconnectForEndpoint(const std::string& endpoint_id, bool safe_to_disconnect_enabled); - bool GetSafeToDisconnectForEndpoint(const std::string& endpoint_id); // Removes all knowledge of this endpoint, cleaning up as necessary. // Returns false if the endpoint was not found. - bool RemoveEndpoint(const std::string& endpoint_id, - DisconnectionReason reason, - bool safe_to_disconnect_enabled, - SafeDisconnectionResult result); + bool RemoveEndpoint(absl::string_view endpoint_id, + DisconnectionReason reason); - bool EncryptChannel(EndpointData* endpoint); int GetConnectedEndpointsCount() const { return endpoints_.size(); } bool isWifiLanConnected() const; - void MarkEndpointStopWaitToDisconnect(const std::string& endpoint_id, + void MarkEndpointStopWaitToDisconnect(absl::string_view endpoint_id, bool is_safe_to_disconnect, bool notify_stop_waiting); - bool CreateNewTimeoutDisconnectedState(const std::string& endpoint_id, - absl::Duration timeout_millis); - bool IsWaitingForSafeToDisconnectTimeout(const std::string& endpoint_id); - bool IsSafeToDisconnect(const std::string& endpoint_id); - void RemoveTimeoutDisconnectedState(const std::string& endpoint_id); + bool IsWaitingForSafeToDisconnectTimeoutForTesting( + absl::string_view endpoint_id); + bool IsSafeToDisconnect(absl::string_view endpoint_id); + void RemoveTimeoutDisconnectedState(absl::string_view endpoint_id); private: // Endpoint ID -> EndpointData. Contains everything we know about the // endpoint. - absl::flat_hash_map endpoints_; + absl::flat_hash_map> endpoints_; }; - void SetActiveEndpointChannel(ClientProxy* client, - const std::string& endpoint_id, - std::shared_ptr channel, - bool enable_encryption) + void SetActiveEndpointChannel( + ClientProxy* client, const std::string& endpoint_id, + std::shared_ptr endpoint, + std::shared_ptr channel, bool enable_encryption) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); mutable Mutex mutex_; - ChannelState channel_state_; + ChannelState channel_state_ ABSL_GUARDED_BY(mutex_); }; } // namespace nearby::connections diff --git a/connections/implementation/endpoint_channel_manager_test.cc b/connections/implementation/endpoint_channel_manager_test.cc index 0bdbb6d1..546fea04 100644 --- a/connections/implementation/endpoint_channel_manager_test.cc +++ b/connections/implementation/endpoint_channel_manager_test.cc @@ -26,6 +26,7 @@ #include "gtest/gtest.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" #include "absl/time/time.h" #include "connections/implementation/analytics/analytics_recorder.h" #include "connections/implementation/base_endpoint_channel.h" @@ -210,16 +211,16 @@ TEST(BaseEndpointChannelManagerTest, RegisterChannelEncryptedReadwrite) { ASSERT_NE(context.second, nullptr); EndpointChannelManager ecm_a; - ecm_a.EncryptChannelForEndpoint(std::string(kEndpointId), - std::move(context.first)); ecm_a.RegisterChannelForEndpoint(&proxy_a, std::string(kEndpointId), std::move(channel_a)); + ecm_a.EncryptChannelForEndpoint(std::string(kEndpointId), + std::move(context.first)); EndpointChannelManager ecm_b; - ecm_b.EncryptChannelForEndpoint(std::string(kEndpointId), - std::move(context.second)); ecm_b.RegisterChannelForEndpoint(&proxy_b, std::string(kEndpointId), std::move(channel_b)); + ecm_b.EncryptChannelForEndpoint(std::string(kEndpointId), + std::move(context.second)); EXPECT_EQ(channel_a_raw->GetType(), "ENCRYPTED_BLUETOOTH"); EXPECT_EQ(channel_b_raw->GetType(), "ENCRYPTED_BLUETOOTH"); @@ -241,10 +242,10 @@ TEST(BaseEndpointChannelManagerTest, RegisterChannelEncryptedReadwrite) { channel_a_raw->Close(DisconnectionReason::LOCAL_DISCONNECTION); channel_b_raw->Close(DisconnectionReason::REMOTE_DISCONNECTION); ecm_a.UnregisterChannelForEndpoint( - std::string(kEndpointId), DisconnectionReason::LOCAL_DISCONNECTION, + kEndpointId, DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); ecm_b.UnregisterChannelForEndpoint( - std::string(kEndpointId), DisconnectionReason::REMOTE_DISCONNECTION, + kEndpointId, DisconnectionReason::REMOTE_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); } @@ -290,13 +291,26 @@ TEST(BaseEndpointChannelManagerTest, ReplaceChannelNoEncrypted) { ASSERT_NE(context.first, nullptr); ASSERT_NE(context.second, nullptr); + auto client_a_dummy = CreatePipe(); + auto server_a_dummy = CreatePipe(); + auto channel_a_init = std::make_shared( + server_a_dummy.first.get(), client_a_dummy.second.get()); + auto client_b_dummy = CreatePipe(); + auto server_b_dummy = CreatePipe(); + auto channel_b_init = std::make_shared( + server_b_dummy.first.get(), client_b_dummy.second.get()); + EndpointChannelManager ecm_a; + ecm_a.RegisterChannelForEndpoint(&proxy_a, std::string(kEndpointId), + std::move(channel_a_init)); ecm_a.EncryptChannelForEndpoint(std::string(kEndpointId), std::move(context.first)); ecm_a.ReplaceChannelForEndpoint(&proxy_a, std::string(kEndpointId), std::move(channel_a), false); EndpointChannelManager ecm_b; + ecm_b.RegisterChannelForEndpoint(&proxy_b, std::string(kEndpointId), + std::move(channel_b_init)); ecm_b.EncryptChannelForEndpoint(std::string(kEndpointId), std::move(context.second)); ecm_b.ReplaceChannelForEndpoint(&proxy_b, std::string(kEndpointId), @@ -309,12 +323,75 @@ TEST(BaseEndpointChannelManagerTest, ReplaceChannelNoEncrypted) { channel_a_raw->Close(DisconnectionReason::LOCAL_DISCONNECTION); channel_b_raw->Close(DisconnectionReason::REMOTE_DISCONNECTION); ecm_a.UnregisterChannelForEndpoint( - std::string(kEndpointId), DisconnectionReason::LOCAL_DISCONNECTION, + kEndpointId, DisconnectionReason::LOCAL_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); ecm_b.UnregisterChannelForEndpoint( - std::string(kEndpointId), DisconnectionReason::REMOTE_DISCONNECTION, + kEndpointId, DisconnectionReason::REMOTE_DISCONNECTION, SafeDisconnectionResult::kSafeDisconnection); } +TEST(BaseEndpointChannelManagerTest, + CreateNewTimeoutDisconnectedStateUnregisterDuringWait) { + ClientProxy proxy; + EndpointChannelManager ecm; + auto client = CreatePipe(); + auto server = CreatePipe(); + auto channel = std::make_shared(server.first.get(), + client.second.get()); + auto channel_raw = channel.get(); + + ON_CALL(*channel_raw, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + + ecm.RegisterChannelForEndpoint(&proxy, std::string(kEndpointId), + std::move(channel)); + + EXPECT_EQ(ecm.GetConnectedEndpointsCount(), 1); + + MultiThreadExecutor executor(1); + CountDownLatch start_latch(1); + CountDownLatch finish_latch(1); + bool wait_result = false; + + executor.Execute([&]() { + start_latch.CountDown(); + wait_result = + ecm.CreateNewTimeoutDisconnectedState(kEndpointId, absl::Seconds(5)); + finish_latch.CountDown(); + }); + + ASSERT_TRUE(start_latch.Await(absl::Seconds(1)).result()); + + // Wait for the endpoint to enter the waiting state. + absl::Time deadline = absl::Now() + absl::Seconds(1); + while (!ecm.IsWaitingForSafeToDisconnectTimeoutForTesting(kEndpointId)) { + ASSERT_TRUE(absl::Now() < deadline) + << "Timed out waiting for endpoint to enter wait state."; + absl::SleepFor(absl::Milliseconds(10)); + } + + // Close the channel first to prevent UnregisterChannelForEndpoint from + // attempting to write disconnection frames to it, bypassing the 500ms data + // transfer delay and potential segfaults. + channel_raw->Close(DisconnectionReason::LOCAL_DISCONNECTION); + + bool unregister_result = ecm.UnregisterChannelForEndpoint( + kEndpointId, DisconnectionReason::LOCAL_DISCONNECTION, + SafeDisconnectionResult::kSafeDisconnection); + EXPECT_TRUE(unregister_result); + + EXPECT_TRUE(finish_latch.Await(absl::Seconds(2)).result()); + EXPECT_TRUE(wait_result); + EXPECT_EQ(ecm.GetConnectedEndpointsCount(), 0); +} + +TEST(BaseEndpointChannelManagerTest, + CreateNewTimeoutDisconnectedStateReturnsFalseForNonexistentEndpoint) { + EndpointChannelManager ecm; + EXPECT_FALSE(ecm.CreateNewTimeoutDisconnectedState("NonexistentEndpoint", + absl::Seconds(1))); +} + } // namespace } // namespace nearby::connections diff --git a/connections/implementation/endpoint_manager.cc b/connections/implementation/endpoint_manager.cc index 31b4454b..29e3659a 100644 --- a/connections/implementation/endpoint_manager.cc +++ b/connections/implementation/endpoint_manager.cc @@ -28,11 +28,13 @@ #include "connections/implementation/client_proxy.h" #include "connections/implementation/endpoint_channel.h" #include "connections/implementation/endpoint_channel_manager.h" +#include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "connections/implementation/offline_frames.h" #include "connections/implementation/proto/offline_wire_formats.pb.h" #include "connections/implementation/service_id_constants.h" #include "connections/listeners.h" #include "connections/medium_selector.h" +#include "internal/flags/nearby_flags.h" #include "internal/platform/byte_array.h" #include "internal/platform/count_down_latch.h" #include "internal/platform/exception.h" @@ -64,6 +66,15 @@ constexpr absl::Time kInvalidTimestamp = absl::InfinitePast(); // The maximum time we will wait for the encryption setup during negotiating a // connection. constexpr absl::Duration kDecryptRetryTimeout = absl::Seconds(3); + +// Returns true if the given `frame_type` is allowed before the connection to +// the endpoint is confirmed (i.e., KEEP_ALIVE, CONNECTION_RESPONSE, and +// DISCONNECTION frames). +bool IsAllowedPreConfirmationFrameType(V1Frame::FrameType frame_type) { + return frame_type == V1Frame::KEEP_ALIVE || + frame_type == V1Frame::CONNECTION_RESPONSE || + frame_type == V1Frame::DISCONNECTION; +} } // namespace class EndpointManager::LockedFrameProcessor { @@ -112,7 +123,8 @@ class EndpointManager::LockedFrameProcessor { void EndpointManager::EndpointChannelLoopRunnable( const std::string& runnable_name, ClientProxy* client, const std::string& endpoint_id, - absl::AnyInvocable(EndpointChannel*)> handler) { + absl::AnyInvocable(std::shared_ptr)> + handler) { // EndpointChannelManager will not let multiple channels exist simultaneously // for the same endpoint_id; it will be closing "old" channels as new ones // come. @@ -143,7 +155,7 @@ void EndpointManager::EndpointChannelLoopRunnable( break; } - ExceptionOr keep_using_channel = handler(channel.get()); + ExceptionOr keep_using_channel = handler(channel); if (!keep_using_channel.ok()) { Exception exception = keep_using_channel.GetException(); @@ -195,7 +207,7 @@ void EndpointManager::EndpointChannelLoopRunnable( } ExceptionOr EndpointManager::TryDecryptFrame( - const ByteArray& data, EndpointChannel* endpoint_channel) { + const ByteArray& data, std::shared_ptr endpoint_channel) { auto start_time = SystemClock::ElapsedRealtime(); while (true) { ExceptionOr decrypted = endpoint_channel->TryDecrypt(data); @@ -222,7 +234,7 @@ ExceptionOr EndpointManager::TryDecryptFrame( ExceptionOr EndpointManager::HandleData( const std::string& endpoint_id, ClientProxy* client, - EndpointChannel* endpoint_channel) { + std::shared_ptr endpoint_channel) { bool try_decrypting = !endpoint_channel->IsEncrypted(); // Read as much as we can from the healthy EndpointChannel - when it is no // longer in good shape (i.e. our read from it throws an Exception), our @@ -274,8 +286,23 @@ ExceptionOr EndpointManager::HandleData( // Route the incoming offlineFrame to its registered processor. V1Frame::FrameType frame_type = parser::GetFrameType(frame); - LockedFrameProcessor frame_processor = GetFrameProcessor(frame_type); - if (!frame_processor) { + if (NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kFilterUnconfirmedEndpointFrames) && + client->HasPendingConnectionToEndpoint(endpoint_id) && + !IsAllowedPreConfirmationFrameType(frame_type)) { + LOG(WARNING) << "EndpointManager discarded unauthorized frame (" + << V1Frame::FrameType_Name(frame_type) + << ") from unconfirmed endpoint " << endpoint_id << "."; + continue; + } + + FrameProcessor* processor = nullptr; + { + LockedFrameProcessor frame_processor = GetFrameProcessor(frame_type); + processor = frame_processor.get(); + } + if (!processor) { // report messages without handlers, except KEEP_ALIVE, which has // no explicit handler. if (frame_type == V1Frame::KEEP_ALIVE) { @@ -310,14 +337,14 @@ ExceptionOr EndpointManager::HandleData( continue; } - frame_processor->OnIncomingFrame(frame, endpoint_id, client, - endpoint_channel->GetMedium()); + processor->OnIncomingFrame(frame, endpoint_id, client, + endpoint_channel->GetMedium()); } } void EndpointManager::ProcessDisconnectionFrame( ClientProxy* client, const std::string& endpoint_id, - EndpointChannel* endpoint_channel, OfflineFrame& frame) { + std::shared_ptr endpoint_channel, OfflineFrame& frame) { if (!client->IsSafeToDisconnectEnabled(endpoint_id)) { LOG(INFO) << "EndpointManager received a DISCONNECTION frame from endpoint " << endpoint_id << " on channel " << endpoint_channel->GetType() @@ -371,9 +398,9 @@ void EndpointManager::ProcessDisconnectionFrame( } ExceptionOr EndpointManager::HandleKeepAlive( - EndpointChannel* endpoint_channel, absl::Duration keep_alive_interval, - absl::Duration keep_alive_timeout, Mutex* keep_alive_waiter_mutex, - ConditionVariable* keep_alive_waiter) { + std::shared_ptr endpoint_channel, + absl::Duration keep_alive_interval, absl::Duration keep_alive_timeout, + Mutex* keep_alive_waiter_mutex, ConditionVariable* keep_alive_waiter) { // Check if it has been too long since we received a frame from our endpoint. absl::Time last_read_time = endpoint_channel->GetLastReadTimestamp(); absl::Duration duration_until_timeout = @@ -578,7 +605,8 @@ void EndpointManager::RegisterEndpoint( endpoint_state.StartEndpointReader([this, client, endpoint_id]() { EndpointChannelLoopRunnable( "Read", client, endpoint_id, - [this, client, endpoint_id](EndpointChannel* channel) { + [this, client, + endpoint_id](std::shared_ptr channel) { return HandleData(endpoint_id, client, channel); }); }); @@ -605,7 +633,7 @@ void EndpointManager::RegisterEndpoint( "KeepAliveManager", client, endpoint_id, [this, keep_alive_interval, keep_alive_timeout, keep_alive_waiter_mutex, - keep_alive_waiter](EndpointChannel* channel) { + keep_alive_waiter](std::shared_ptr channel) { return HandleKeepAlive( channel, keep_alive_interval, keep_alive_timeout, keep_alive_waiter_mutex, keep_alive_waiter); @@ -745,8 +773,8 @@ void EndpointManager::RemoveEndpoint(ClientProxy* client, SafeDisconnectionResult::kSafeDisconnection; // Grab the service ID before we destroy the channel. - EndpointChannel* channel = - channel_manager_->GetChannelForEndpoint(endpoint_id).get(); + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); std::string service_id = channel ? channel->GetServiceId() : std::string(kUnknownServiceId); @@ -784,9 +812,10 @@ void EndpointManager::RemoveEndpoint(ClientProxy* client, RemoveEndpointState(endpoint_id); } -bool EndpointManager::ApplySafeToDisconnect(const std::string& endpoint_id, - EndpointChannel* endpoint_channel, - DisconnectionReason reason) { +bool EndpointManager::ApplySafeToDisconnect( + const std::string& endpoint_id, + std::shared_ptr endpoint_channel, + DisconnectionReason reason) { LOG(INFO) << "[safe-to-disconnect] ApplySafeToDisconnect reason: " << reason; // TODO(b/303544913): clean up the safe-to-disconnect logic bool is_safe_disconnection = false; diff --git a/connections/implementation/endpoint_manager.h b/connections/implementation/endpoint_manager.h index 2250a958..5820c830 100644 --- a/connections/implementation/endpoint_manager.h +++ b/connections/implementation/endpoint_manager.h @@ -229,15 +229,14 @@ class EndpointManager { LockedFrameProcessor GetFrameProcessor( location::nearby::connections::V1Frame::FrameType frame_type); - ExceptionOr HandleData(const std::string& endpoint_id, - ClientProxy* client_proxy, - EndpointChannel* endpoint_channel); + ExceptionOr HandleData( + const std::string& endpoint_id, ClientProxy* client_proxy, + std::shared_ptr endpoint_channel); - ExceptionOr HandleKeepAlive(EndpointChannel* endpoint_channel, - absl::Duration keep_alive_interval, - absl::Duration keep_alive_timeout, - Mutex* keep_alive_waiter_mutex, - ConditionVariable* keep_alive_waiter); + ExceptionOr HandleKeepAlive( + std::shared_ptr endpoint_channel, + absl::Duration keep_alive_interval, absl::Duration keep_alive_timeout, + Mutex* keep_alive_waiter_mutex, ConditionVariable* keep_alive_waiter); // Waits for a given endpoint EndpointChannelLoopRunnable() workers to // terminate. @@ -249,7 +248,8 @@ class EndpointManager { void EndpointChannelLoopRunnable( const std::string& runnable_name, ClientProxy* client_proxy, const std::string& endpoint_id, - absl::AnyInvocable(EndpointChannel*)> handler); + absl::AnyInvocable(std::shared_ptr)> + handler); static void WaitForLatch(const std::string& method_name, CountDownLatch* latch); @@ -265,7 +265,7 @@ class EndpointManager { void RemoveEndpoint(ClientProxy* client, const std::string& endpoint_id, bool notify, DisconnectionReason reason); bool ApplySafeToDisconnect(const std::string& endpoint_id, - EndpointChannel* endpoint_channel, + std::shared_ptr endpoint_channel, DisconnectionReason reason); void WaitForEndpointDisconnectionProcessing(ClientProxy* client, const std::string& service_id, @@ -273,7 +273,7 @@ class EndpointManager { DisconnectionReason reason); void ProcessDisconnectionFrame( ClientProxy* client, const std::string& endpoint_id, - EndpointChannel* endpoint_channel, + std::shared_ptr endpoint_channel, location::nearby::connections::OfflineFrame& frame); CountDownLatch NotifyFrameProcessorsOnEndpointDisconnect( ClientProxy* client, const std::string& service_id, @@ -287,8 +287,8 @@ class EndpointManager { // Executes all jobs sequentially, on a serial_executor_. void RunOnEndpointManagerThread(const std::string& name, Runnable runnable); - ExceptionOr TryDecryptFrame(const ByteArray& data, - EndpointChannel* endpoint_channel); + ExceptionOr TryDecryptFrame( + const ByteArray& data, std::shared_ptr endpoint_channel); EndpointChannelManager* channel_manager_; RecursiveMutex frame_processors_lock_; diff --git a/connections/implementation/endpoint_manager_test.cc b/connections/implementation/endpoint_manager_test.cc index 70a85949..b71adb41 100644 --- a/connections/implementation/endpoint_manager_test.cc +++ b/connections/implementation/endpoint_manager_test.cc @@ -94,6 +94,10 @@ class SetSafeToDisconnect { config_package_nearby::nearby_connections_feature:: kSafeToDisconnectVersion, safe_to_disconnect_version); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kFilterUnconfirmedEndpointFrames, + false); } }; @@ -108,11 +112,11 @@ class EndpointManagerTest : public ::testing::Test { protected: void RegisterEndpoint(std::unique_ptr channel, bool should_close = true) { - CountDownLatch done(1); + auto done = std::make_shared(1); if (should_close) { ON_CALL(*channel, Close(_)) .WillByDefault( - [&done](DisconnectionReason reason) { done.CountDown(); }); + [done](DisconnectionReason reason) { done->CountDown(); }); } EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE)); EXPECT_CALL(*channel, GetLastReadTimestamp()) @@ -124,7 +128,7 @@ class EndpointManagerTest : public ::testing::Test { connection_options_, std::move(channel), listener_, connection_token_); if (should_close) { - EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); + EXPECT_TRUE(done->Await(absl::Milliseconds(1000)).result()); } } SetSafeToDisconnect set_safe_to_disconnect_{true, true, 5}; @@ -429,6 +433,70 @@ TEST_F(EndpointManagerTest, TryDecrypt) { RegisterEndpoint(std::move(endpoint_channel)); } +TEST_F(EndpointManagerTest, + FilterUnconfirmedFrames_DiscardsPayloadBeforeConfirmation) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kFilterUnconfirmedEndpointFrames, + true); + PayloadTransferFrame::PayloadHeader header; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + PayloadTransferFrame::PayloadChunk chunk; + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + std::string payload_bytes = parser::ForDataPayloadTransfer(header, chunk); + auto endpoint_channel = std::make_unique(); + auto payload_processor = std::make_unique(); + EXPECT_CALL(*payload_processor, OnIncomingFrame).Times(0); + EXPECT_CALL(*payload_processor, OnEndpointDisconnect); + EXPECT_CALL(*endpoint_channel, Read()) + .WillOnce(Return(ExceptionOr(ByteArray(payload_bytes)))) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + em_.RegisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, + payload_processor.get()); + processors_.emplace_back(std::move(payload_processor)); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, + FilterUnconfirmedFrames_AllowsPayloadAfterConfirmation) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kFilterUnconfirmedEndpointFrames, + true); + PayloadTransferFrame::PayloadHeader header; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + PayloadTransferFrame::PayloadChunk chunk; + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + std::string payload_bytes = parser::ForDataPayloadTransfer(header, chunk); + auto endpoint_channel = std::make_unique(); + auto payload_processor = std::make_unique(); + EXPECT_CALL(mock_listener_.accepted_cb, Call).Times(1); + EXPECT_CALL(*payload_processor, OnIncomingFrame).Times(1); + EXPECT_CALL(*payload_processor, OnEndpointDisconnect); + EXPECT_CALL(*endpoint_channel, Read()) + .WillOnce([this, payload_bytes]() { + client_->OnConnectionAccepted(endpoint_id_); + return ExceptionOr(ByteArray(payload_bytes)); + }) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + em_.RegisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, + payload_processor.get()); + processors_.emplace_back(std::move(payload_processor)); + RegisterEndpoint(std::move(endpoint_channel)); +} + // Regression test for b/278729669. // // During the destruction of NearbyConnections, Core (which owns ClientProxy) diff --git a/connections/implementation/fake_bwu_handler.h b/connections/implementation/fake_bwu_handler.h index dee9168b..804e7a6e 100644 --- a/connections/implementation/fake_bwu_handler.h +++ b/connections/implementation/fake_bwu_handler.h @@ -89,6 +89,7 @@ class FakeBwuHandler : public BaseBwuHandler { upgraded_channel->set_read_output( ExceptionOr(ByteArray(parser::ForBwuIntroduction( *handle_initialize_calls_[initialize_call_index].endpoint_id, + /*last_endpoint_id=*/"", false /* supports_disabling_encryption */)))); auto connection = std::make_unique(); connection->channel = std::move(upgraded_channel); diff --git a/connections/implementation/fake_endpoint_channel.h b/connections/implementation/fake_endpoint_channel.h index 01bb92fe..03c3a2af 100644 --- a/connections/implementation/fake_endpoint_channel.h +++ b/connections/implementation/fake_endpoint_channel.h @@ -93,6 +93,10 @@ class FakeEndpointChannel : public EndpointChannel { } void SetAnalyticsRecorder(analytics::AnalyticsRecorder* analytics_recorder, const std::string& endpoint_id) override {} + void SetLocalEndpointId(const std::string& local_endpoint_id) override { + local_endpoint_id_ = local_endpoint_id; + } + std::string GetLocalEndpointId() const override { return local_endpoint_id_; } void set_read_output(ExceptionOr output) { read_output_ = output; } void set_write_output(Exception output) { write_output_ = output; } @@ -113,6 +117,7 @@ class FakeEndpointChannel : public EndpointChannel { bool is_paused_ = false; location::nearby::proto::connections::DisconnectionReason disconnection_reason_; + std::string local_endpoint_id_; mutable uint32_t next_keep_alive_seq_no_ = 0; }; diff --git a/connections/implementation/flags/nearby_connections_feature_flags.h b/connections/implementation/flags/nearby_connections_feature_flags.h index 7f15f2c0..08f15334 100755 --- a/connections/implementation/flags/nearby_connections_feature_flags.h +++ b/connections/implementation/flags/nearby_connections_feature_flags.h @@ -83,6 +83,11 @@ constexpr auto kEnableWifiDirectGcOnly = // by default, enable Wi-Fi Hotspot client. constexpr auto kEnableWifiHotspotClient = flags::Flag(kConfigPackage, "45648734", true); +// Enforces frame filtering on unconfirmed endpoints in EndpointManager and +// BaseEndpointChannel so application payloads and upgrade requests are blocked +// before connection acceptance. +constexpr auto kFilterUnconfirmedEndpointFrames = + flags::Flag(kConfigPackage, "45813128", true); // When true, fix the BleServerSocket deadlock/use-after-free (b/494335036). constexpr auto kFixBleServerSocketDeadlock = flags::Flag(kConfigPackage, "45782647", true); diff --git a/connections/implementation/mediums/BUILD b/connections/implementation/mediums/BUILD index d533c0ae..04e292df 100644 --- a/connections/implementation/mediums/BUILD +++ b/connections/implementation/mediums/BUILD @@ -248,6 +248,7 @@ cc_test( "//connections/implementation:client_proxy", "//connections/implementation:endpoint_channel", "//connections/implementation:offline_frames", + "//connections/implementation:service_id_constants", "//connections/implementation/flags:connections_flags", "//internal/flags:nearby_flags", "//internal/platform:base", diff --git a/connections/implementation/mediums/advertisements/BUILD b/connections/implementation/mediums/advertisements/BUILD index 233aad0d..ff31ac4e 100644 --- a/connections/implementation/mediums/advertisements/BUILD +++ b/connections/implementation/mediums/advertisements/BUILD @@ -29,7 +29,6 @@ cc_library( "//internal/platform:base", "//internal/platform:logging", "//internal/platform:util", - "@com_google_absl//absl/strings", ], ) @@ -45,7 +44,6 @@ cc_library( "//internal/platform:logging", "//internal/platform:types", "//internal/platform:util", - "@com_google_absl//absl/strings", ], ) @@ -54,9 +52,7 @@ cc_library( srcs = ["advertisement_util.cc"], hdrs = ["advertisement_util.h"], deps = [ - ":dct_advertisement", "//internal/platform:base", - "//internal/platform:logging", "//internal/platform:util", "@com_google_absl//absl/strings:string_view", ], @@ -79,8 +75,6 @@ cc_test( srcs = ["dct_advertisement_test.cc"], deps = [ ":dct_advertisement", - "//internal/platform:base", - "//internal/platform:util", "//internal/platform/implementation/g3", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_googletest//:gtest_main", @@ -93,7 +87,6 @@ cc_test( deps = [ ":util", "//internal/platform:base", - "//internal/platform:util", "//internal/platform/implementation/g3", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_googletest//:gtest_main", diff --git a/connections/implementation/mediums/advertisements/dct_advertisement.cc b/connections/implementation/mediums/advertisements/dct_advertisement.cc index 59f527dc..c443fce3 100644 --- a/connections/implementation/mediums/advertisements/dct_advertisement.cc +++ b/connections/implementation/mediums/advertisements/dct_advertisement.cc @@ -129,7 +129,8 @@ std::optional DctAdvertisement::Parse( LOG(WARNING) << "Failed to read device information."; return std::nullopt; } - if (device_information->type() != kDataTypeDeviceInformation) { + if (device_information->type() != kDataTypeDeviceInformation || + device_information->value().empty()) { LOG(WARNING) << "Invalid device information."; return std::nullopt; } diff --git a/connections/implementation/mediums/advertisements/dct_advertisement_test.cc b/connections/implementation/mediums/advertisements/dct_advertisement_test.cc index eabf10d2..2377e49a 100644 --- a/connections/implementation/mediums/advertisements/dct_advertisement_test.cc +++ b/connections/implementation/mediums/advertisements/dct_advertisement_test.cc @@ -113,5 +113,10 @@ TEST(DctAdvertisementTest, ParseData) { EXPECT_EQ(dct_advertisement->GetPsm(), 192); } +TEST(DctAdvertisementTest, ParseWithEmptyDeviceInformationDataElement) { + std::string data = std::string("\x20\x25\0\0\x24\0\0\x07", 8); + EXPECT_FALSE(DctAdvertisement::Parse(data).has_value()); +} + } // namespace } // namespace nearby::connections::advertisements::ble diff --git a/connections/implementation/mediums/awdl_bwu_handler.cc b/connections/implementation/mediums/awdl_bwu_handler.cc index 8f8e5287..29df9878 100644 --- a/connections/implementation/mediums/awdl_bwu_handler.cc +++ b/connections/implementation/mediums/awdl_bwu_handler.cc @@ -182,6 +182,7 @@ AwdlBwuHandler::CreateUpgradedEndpointChannel( OperationResultCode::NEARBY_AWDL_ENDPOINT_CHANNEL_CREATION_FAILURE)}; } + awdl_medium_.StopDiscovery(upgrade_service_id); return {std::move(channel)}; } diff --git a/connections/implementation/mediums/awdl_bwu_handler_test.cc b/connections/implementation/mediums/awdl_bwu_handler_test.cc index 0080fec0..9beafa18 100644 --- a/connections/implementation/mediums/awdl_bwu_handler_test.cc +++ b/connections/implementation/mediums/awdl_bwu_handler_test.cc @@ -43,7 +43,8 @@ #include "internal/platform/mock_input_stream.h" #include "internal/platform/mock_output_stream.h" #include "internal/platform/nsd_service_info.h" -#include "internal/platform/output_stream.h" +#include "connections/implementation/service_id_constants.h" + namespace nearby { @@ -175,7 +176,9 @@ TEST_F(AwdlBwuHandlerTest, CreateUpgradedEndpointChannel_Success) { } return true; }); - EXPECT_CALL(*awdl_medium_mock, StopDiscovery(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*awdl_medium_mock, StopDiscovery(_)) + .Times(1) + .WillRepeatedly(Return(true)); EXPECT_CALL(*awdl_medium_mock, ConnectToService(_, _, _)) .WillOnce(Return(ByMove(std::move(awdl_socket)))); @@ -191,6 +194,8 @@ TEST_F(AwdlBwuHandlerTest, CreateUpgradedEndpointChannel_Success) { path_info); EXPECT_TRUE(result.has_value()); + EXPECT_FALSE(mediums_.GetAwdl().IsDiscovering( + WrapInitiatorUpgradeServiceId(kServiceId))); } TEST_F(AwdlBwuHandlerTest, diff --git a/connections/implementation/mediums/ble/discovered_peripheral_tracker.cc b/connections/implementation/mediums/ble/discovered_peripheral_tracker.cc index 51e2dd53..a239ebe5 100644 --- a/connections/implementation/mediums/ble/discovered_peripheral_tracker.cc +++ b/connections/implementation/mediums/ble/discovered_peripheral_tracker.cc @@ -66,8 +66,7 @@ constexpr absl::Duration kAdvertisementHeaderExpiry = absl::Seconds(15); // Private c'tor for testing. DiscoveredPeripheralTracker::DiscoveredPeripheralTracker( bool is_extended_advertisement_available, bool start_fetch_executor) - : is_extended_advertisement_available_( - is_extended_advertisement_available), + : is_extended_advertisement_available_(is_extended_advertisement_available), start_fetch_executor_(start_fetch_executor) {} DiscoveredPeripheralTracker::DiscoveredPeripheralTracker( @@ -75,9 +74,7 @@ DiscoveredPeripheralTracker::DiscoveredPeripheralTracker( : DiscoveredPeripheralTracker(is_extended_advertisement_available, /*start_fetch_executor=*/true) {} -DiscoveredPeripheralTracker::~DiscoveredPeripheralTracker() { - Shutdown(); -} +DiscoveredPeripheralTracker::~DiscoveredPeripheralTracker() { Shutdown(); } void DiscoveredPeripheralTracker::StartFetchExecutorIfNeeded() { if (executor_ != nullptr) { @@ -199,7 +196,8 @@ void DiscoveredPeripheralTracker::ProcessFoundBleAdvertisement( return; } - if (advertisement_data.service_data.contains(bleutils::kDctServiceUuid)) { + if (!dct_service_id_hash_to_service_id_map_.empty() && + advertisement_data.service_data.contains(bleutils::kDctServiceUuid)) { std::optional dct_advertisement_data = HandleDctAdvertisement(advertisement_data); @@ -749,15 +747,14 @@ void DiscoveredPeripheralTracker::HandleAdvertisementHeader( // support extended advertisement. if (!advertisement_header.IsSupportExtendedAdvertisement()) { for (auto& item : service_id_infos_) { - item.second.discovered_peripheral_callback - .legacy_device_discovered_cb(); + item.second.discovered_peripheral_callback.legacy_device_discovered_cb(); } } // Determine whether or not we need to read a fresh GATT advertisement. VLOG(1) << "Handle GATT advertisement header with hash " << absl::BytesToHexString( - advertisement_header.GetAdvertisementHash().AsStringView()) + advertisement_header.GetAdvertisementHash().AsStringView()) << " in thread"; if (!ShouldReadRawAdvertisementFromServer(advertisement_header)) { @@ -1015,7 +1012,7 @@ void DiscoveredPeripheralTracker::GattFetchingLoop() { } if (!found_task) { LOG(WARNING) << "No task found, skip to fetch raw advertisement."; - continue;; + continue; } // Check if the task is expired. @@ -1027,8 +1024,7 @@ void DiscoveredPeripheralTracker::GattFetchingLoop() { .AsStringView()) << " is expired, skip to fetch raw advertisement."; } else { - FetchRawAdvertisementsInThread(task.peripheral, - task.advertisement_header, + FetchRawAdvertisementsInThread(task.peripheral, task.advertisement_header, std::move(task.advertisement_fetcher)); } // Clear in progress header after the task is done. diff --git a/connections/implementation/mediums/wifi_direct.h b/connections/implementation/mediums/wifi_direct.h index 5d9d7603..bbb095e4 100644 --- a/connections/implementation/mediums/wifi_direct.h +++ b/connections/implementation/mediums/wifi_direct.h @@ -60,7 +60,8 @@ class WifiDirect { bool IsGOStarted() ABSL_LOCKS_EXCLUDED(mutex_); // Start WifiDirect Group Owner. Returns true if WifiDirect GO is successfully // started. - bool StartWifiDirect() ABSL_LOCKS_EXCLUDED(mutex_); + bool StartWifiDirect() + ABSL_LOCKS_EXCLUDED(mutex_); // Stop WifiDirect Group Owner bool StopWifiDirect() ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/connections/implementation/mediums/wifi_direct_bwu_handler.cc b/connections/implementation/mediums/wifi_direct_bwu_handler.cc index 55346fc7..4d5bb136 100644 --- a/connections/implementation/mediums/wifi_direct_bwu_handler.cc +++ b/connections/implementation/mediums/wifi_direct_bwu_handler.cc @@ -54,6 +54,11 @@ WifiDirectBwuHandler::WifiDirectBwuHandler( std::string WifiDirectBwuHandler::HandleInitializeUpgradedMediumForEndpoint( ClientProxy* client, const std::string& upgrade_service_id, const std::string& endpoint_id) { + auto remote_device_name = client->GetRemoteDeviceName(endpoint_id); + WifiDirectCredentials* wifi_direct_crendential = + wifi_direct_medium_.GetCredentials(upgrade_service_id); + wifi_direct_crendential->SetRemoteDeviceName(remote_device_name); + // Create WifiDirect GO if (!wifi_direct_medium_.StartWifiDirect()) { LOG(INFO) << "Failed to start Wifi Direct!"; @@ -82,7 +87,7 @@ std::string WifiDirectBwuHandler::HandleInitializeUpgradedMediumForEndpoint( // Note: Credentials are not generated until Medium StartWifiDirect() is // called and the server socket is created. Be careful moving this codeblock // around. - WifiDirectCredentials* wifi_direct_crendential = + wifi_direct_crendential = wifi_direct_medium_.GetCredentials(upgrade_service_id); std::string ssid = wifi_direct_crendential->GetSSID(); std::string password = wifi_direct_crendential->GetPassword(); diff --git a/connections/implementation/mediums/wifi_hotspot_bwu_handler.cc b/connections/implementation/mediums/wifi_hotspot_bwu_handler.cc index d31b8a15..7f9267f9 100644 --- a/connections/implementation/mediums/wifi_hotspot_bwu_handler.cc +++ b/connections/implementation/mediums/wifi_hotspot_bwu_handler.cc @@ -194,7 +194,9 @@ WifiHotspotBwuHandler::CreateUpgradedEndpointChannel( } // Add gateway and port to address candidates if address candidates is empty. if (service_addresses.empty() && - upgrade_path_info_credentials.has_gateway()) { + upgrade_path_info_credentials.has_gateway() && + upgrade_path_info_credentials.port() > 0 && + upgrade_path_info_credentials.port() <= 65535) { std::vector address_bytes = GatewayToAddressBytes(upgrade_path_info_credentials.gateway()); if (!address_bytes.empty()) { @@ -230,7 +232,8 @@ WifiHotspotBwuHandler::CreateUpgradedEndpointChannel( LOG(ERROR) << "WifiHotspotBwuHandler failed to connect to the WifiHotspot " "service for endpoint " << endpoint_id; - return {Error(socket_result.error().operation_result_code().value())}; + return {Error(socket_result.error().operation_result_code().value_or( + OperationResultCode::DETAIL_UNKNOWN))}; } VLOG(1) << "WifiHotspotBwuHandler successfully connected to WifiHotspot service " diff --git a/connections/implementation/mediums/wifi_hotspot_bwu_handler_test.cc b/connections/implementation/mediums/wifi_hotspot_bwu_handler_test.cc index fcd42428..b30513af 100644 --- a/connections/implementation/mediums/wifi_hotspot_bwu_handler_test.cc +++ b/connections/implementation/mediums/wifi_hotspot_bwu_handler_test.cc @@ -55,8 +55,9 @@ class WifiHotspotTest : public testing::Test { ~WifiHotspotTest() override { env_.Stop(); } void SetUp() override { nearby::NearbyFlags::GetInstance().OverrideInt64FlagValue( - platform::config_package_nearby::nearby_platform_feature:: - kWifiHotspotConnectionIntervalMillis, 1); + platform::config_package_nearby::nearby_platform_feature:: + kWifiHotspotConnectionIntervalMillis, + 1); } void TearDown() override { nearby::NearbyFlags::GetInstance().ResetOverridedValues(); @@ -89,9 +90,10 @@ TEST_F(WifiHotspotTest, SoftAPBWUInit_STACreateEndpointChannel) { ExceptionOr upgrade_frame; auto handler_1 = std::make_unique( - &mediums_HS_ap.GetWifiHotspot(), [&](ClientProxy* client, - std::unique_ptr - mutable_connection) { + &mediums_HS_ap.GetWifiHotspot(), + [&](ClientProxy* client, + std::unique_ptr + mutable_connection) { LOG(INFO) << "Server socket connection accept call back, Socket name: " << mutable_connection->socket->ToString(); accept_latch.CountDown(); @@ -169,5 +171,42 @@ TEST_F(WifiHotspotTest, SoftAPBWUInit_STACreateEndpointChannel) { EXPECT_FALSE(mediums_HS_sta.GetWifiHotspot().IsConnectedToHotspot()); } +TEST_F(WifiHotspotTest, CreateUpgradedEndpointChannel_RejectGatewayPort0) { + ClientProxy client; + client.AddCancellationFlag(std::string(kEndpointID)); + Mediums mediums; + WifiHotspotBwuHandler handler(&mediums.GetWifiHotspot(), nullptr); + + UpgradePathInfo path_info; + auto* credentials = path_info.mutable_wifi_hotspot_credentials(); + credentials->set_ssid("SSID"); + credentials->set_password("password"); + credentials->set_gateway("192.168.43.1"); + + // Port 0 + credentials->set_port(0); + auto result = handler.CreateUpgradedEndpointChannel( + &client, std::string(kServiceID), std::string(kEndpointID), path_info); + EXPECT_TRUE(result.has_error()); + EXPECT_EQ(result.error().operation_result_code().value(), + OperationResultCode::CONNECTIVITY_WIFI_HOTSPOT_INVALID_CREDENTIAL); + + // Port > 65535 + credentials->set_port(65536); + result = handler.CreateUpgradedEndpointChannel( + &client, std::string(kServiceID), std::string(kEndpointID), path_info); + EXPECT_TRUE(result.has_error()); + EXPECT_EQ(result.error().operation_result_code().value(), + OperationResultCode::CONNECTIVITY_WIFI_HOTSPOT_INVALID_CREDENTIAL); + + // Port < 0 + credentials->set_port(-1); + result = handler.CreateUpgradedEndpointChannel( + &client, std::string(kServiceID), std::string(kEndpointID), path_info); + EXPECT_TRUE(result.has_error()); + EXPECT_EQ(result.error().operation_result_code().value(), + OperationResultCode::CONNECTIVITY_WIFI_HOTSPOT_INVALID_CREDENTIAL); +} + } // namespace connections } // namespace nearby diff --git a/connections/implementation/mediums/wifi_lan_bwu_handler.cc b/connections/implementation/mediums/wifi_lan_bwu_handler.cc index a25cd81c..d5929c97 100644 --- a/connections/implementation/mediums/wifi_lan_bwu_handler.cc +++ b/connections/implementation/mediums/wifi_lan_bwu_handler.cc @@ -79,21 +79,42 @@ WifiLanBwuHandler::CreateUpgradedEndpointChannel( << address_candidate.ip_address().size(); continue; } + if (service_address.IsLoopbackAddress() || + service_address.IsLinkLocalAddress()) { + LOG(WARNING) << "Loopback/link-local address candidate is rejected."; + return { + Error(OperationResultCode::CONNECTIVITY_WIFI_LAN_INVALID_CREDENTIAL)}; + } address_candidates.push_back(std::move(service_address)); } // Only use ip_address and wifi_port if address_candidates is empty. if (address_candidates.empty()) { - address_candidates.push_back(ServiceAddress{ - .address = - std::vector(upgrade_path_info_socket.ip_address().begin(), - upgrade_path_info_socket.ip_address().end()), - .port = static_cast(upgrade_path_info_socket.wifi_port())}); + if (upgrade_path_info_socket.ip_address().size() != 4 || + upgrade_path_info_socket.wifi_port() <= 0 || + upgrade_path_info_socket.wifi_port() > 65535) { + LOG(ERROR) << "WifiLanBwuHandler: fallback ip_address size is not 4 " + << "or port is invalid (IPv4 only)."; + return { + Error(OperationResultCode::CONNECTIVITY_WIFI_LAN_IP_ADDRESS_ERROR)}; + } + ServiceAddress service_address; + service_address.address = {upgrade_path_info_socket.ip_address().begin(), + upgrade_path_info_socket.ip_address().end()}; + service_address.port = + static_cast(upgrade_path_info_socket.wifi_port()); + if (service_address.IsLoopbackAddress() || + service_address.IsLinkLocalAddress()) { + LOG(WARNING) << "Loopback/link-local fallback address is rejected."; + return { + Error(OperationResultCode::CONNECTIVITY_WIFI_LAN_INVALID_CREDENTIAL)}; + } + address_candidates.push_back(std::move(service_address)); } Error error; for (const auto& address_candidate : address_candidates) { VLOG(1) << "WifiLanBwuHandler is attempting to connect to available " - "WifiLan service (" << address_candidate << ") for endpoint " - << endpoint_id; + "WifiLan service (" + << address_candidate << ") for endpoint " << endpoint_id; std::shared_ptr cancellation_flag = client->GetCancellationFlag(endpoint_id); ErrorOr socket_result = wifi_lan_medium_.Connect( @@ -102,7 +123,8 @@ WifiLanBwuHandler::CreateUpgradedEndpointChannel( LOG(ERROR) << "WifiLanBwuHandler failed to connect to the WifiLan service (" << address_candidate << ") for endpoint " << endpoint_id; - error = Error(socket_result.error().operation_result_code().value()); + error = Error(socket_result.error().operation_result_code().value_or( + OperationResultCode::DETAIL_UNKNOWN)); continue; } VLOG(1) << "WifiLanBwuHandler successfully connected to WifiLan service (" diff --git a/connections/implementation/mediums/wifi_lan_bwu_handler_test.cc b/connections/implementation/mediums/wifi_lan_bwu_handler_test.cc index 1cf09752..e561640e 100644 --- a/connections/implementation/mediums/wifi_lan_bwu_handler_test.cc +++ b/connections/implementation/mediums/wifi_lan_bwu_handler_test.cc @@ -209,6 +209,98 @@ TEST_F(WifiLanBwuHandlerTest, EXPECT_TRUE(result.has_value()); }; +TEST_F(WifiLanBwuHandlerTest, + CreateUpgradedEndpointChannel_RejectLoopbackAndLinkLocalCandidates) { + ClientProxy client; + client.AddCancellationFlag(std::string(kEndpointId)); + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo path_info; + + // 1st candidate: Loopback + auto* address_candidate = + path_info.mutable_wifi_lan_socket()->add_address_candidates(); + address_candidate->set_ip_address(std::string("\x7f\x00\x00\x01", 4)); + address_candidate->set_port(8080); + + // 2nd candidate: Link-Local (169.254.1.1) + address_candidate = + path_info.mutable_wifi_lan_socket()->add_address_candidates(); + address_candidate->set_ip_address("\xa9\xfe\x01\x01"); + address_candidate->set_port(8080); + + // 3rd candidate: Valid IP + address_candidate = + path_info.mutable_wifi_lan_socket()->add_address_candidates(); + address_candidate->set_ip_address(kIpv4Address); + address_candidate->set_port(8080); + + auto result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), + std::move(path_info)); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(WifiLanBwuHandlerTest, + CreateUpgradedEndpointChannel_RejectLoopbackFallbackIp) { + ClientProxy client; + client.AddCancellationFlag(std::string(kEndpointId)); + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo path_info; + path_info.mutable_wifi_lan_socket()->set_ip_address( + std::string("\x7f\x00\x00\x01", 4)); + path_info.mutable_wifi_lan_socket()->set_wifi_port(8080); + + auto result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), + std::move(path_info)); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(WifiLanBwuHandlerTest, + CreateUpgradedEndpointChannel_RejectInvalidFallbackIpLength) { + ClientProxy client; + client.AddCancellationFlag(std::string(kEndpointId)); + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo path_info; + path_info.mutable_wifi_lan_socket()->set_ip_address("123"); + path_info.mutable_wifi_lan_socket()->set_wifi_port(8080); + + auto result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), + std::move(path_info)); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(WifiLanBwuHandlerTest, + CreateUpgradedEndpointChannel_RejectFallbackPort0) { + ClientProxy client; + client.AddCancellationFlag(std::string(kEndpointId)); + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo path_info; + path_info.mutable_wifi_lan_socket()->set_ip_address(kIpv4Address); + + // Port 0 + path_info.mutable_wifi_lan_socket()->set_wifi_port(0); + auto result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), path_info); + EXPECT_FALSE(result.has_value()); + + // Port > 65535 + path_info.mutable_wifi_lan_socket()->set_wifi_port(65536); + result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), path_info); + EXPECT_FALSE(result.has_value()); + + // Port < 0 + path_info.mutable_wifi_lan_socket()->set_wifi_port(-1); + result = handler_.CreateUpgradedEndpointChannel( + &client, std::string(kServiceId), std::string(kEndpointId), path_info); + EXPECT_FALSE(result.has_value()); +} + TEST_F(WifiLanBwuHandlerTest, InitializeUpgradedMediumForEndpoint_Success) { MediumEnvironment::Instance().Start({.use_simulated_clock = true}); ClientProxy client; @@ -289,9 +381,8 @@ TEST_F(WifiLanBwuHandlerTest, mediums_.GetWifiLan().IsAcceptingConnections("service_id_UPGRADE")); } -TEST_F( - WifiLanBwuHandlerTest, - InitializeUpgradedMediumForEndpoint_AlreadyAccepting_KeepAccepting) { +TEST_F(WifiLanBwuHandlerTest, + InitializeUpgradedMediumForEndpoint_AlreadyAccepting_KeepAccepting) { MediumEnvironment::Instance().Start({.use_simulated_clock = true}); ClientProxy client; client.AddCancellationFlag(std::string(kEndpointId)); diff --git a/connections/implementation/mock_endpoint_channel.h b/connections/implementation/mock_endpoint_channel.h index 14ea499e..9ad08f72 100644 --- a/connections/implementation/mock_endpoint_channel.h +++ b/connections/implementation/mock_endpoint_channel.h @@ -69,6 +69,9 @@ class MockEndpointChannel : public EndpointChannel { MOCK_METHOD(uint32_t, GetNextKeepAliveSeqNo, (), (const, override)); MOCK_METHOD(void, SetAnalyticsRecorder, (analytics::AnalyticsRecorder*, const std::string&), (override)); + MOCK_METHOD(void, SetLocalEndpointId, (const std::string& local_endpoint_id), + (override)); + MOCK_METHOD(std::string, GetLocalEndpointId, (), (const, override)); }; } // namespace nearby::connections diff --git a/connections/implementation/offline_frames.cc b/connections/implementation/offline_frames.cc index 14f2d983..5ebe9301 100644 --- a/connections/implementation/offline_frames.cc +++ b/connections/implementation/offline_frames.cc @@ -180,7 +180,8 @@ std::string ForConnectionRequestPresence( return frame.SerializeAsString(); } -std::string ForConnectionResponse(std::int32_t status, const OsInfo& os_info) { +std::string ForConnectionResponse(std::int32_t status, const OsInfo& os_info, + const std::string& device_name) { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -201,6 +202,7 @@ std::string ForConnectionResponse(std::int32_t status, const OsInfo& os_info) { NearbyFlags::GetInstance().GetInt64Flag( config_package_nearby::nearby_connections_feature:: kSafeToDisconnectVersion)); + sub_frame->set_wifi_direct_device_name(device_name); return frame.SerializeAsString(); } @@ -460,6 +462,7 @@ std::string ForBwuSafeToClose() { } std::string ForBwuIntroduction(const std::string& endpoint_id, + const std::string& last_endpoint_id, bool supports_disabling_encryption) { OfflineFrame frame; @@ -473,6 +476,9 @@ std::string ForBwuIntroduction(const std::string& endpoint_id, client_introduction->set_endpoint_id(endpoint_id); client_introduction->set_supports_disabling_encryption( supports_disabling_encryption); + if (!last_endpoint_id.empty()) { + client_introduction->set_last_endpoint_id(last_endpoint_id); + } return frame.SerializeAsString(); } @@ -498,16 +504,14 @@ std::string ForBwuFailure(const UpgradePathInfo& info) { v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); sub_frame->set_event_type(BandwidthUpgradeNegotiationFrame::UPGRADE_FAILURE); - auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); - *upgrade_path_info = info; - *sub_frame->mutable_upgrade_path_info() = info; return frame.SerializeAsString(); } -std::string ForBwuPathRequest(const std::vector& mediums, - const MediumRole& medium_role) { +std::string ForBwuPathRequest(Medium medium, const std::vector& mediums, + const MediumRole& medium_role, + bool supports_5_ghz) { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -516,11 +520,16 @@ std::string ForBwuPathRequest(const std::vector& mediums, auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); sub_frame->set_event_type( BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_REQUEST); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(MediumToUpgradePathInfoMedium(medium)); auto* upgrade_path_request = - sub_frame->mutable_upgrade_path_info()->mutable_upgrade_path_request(); + upgrade_path_info->mutable_upgrade_path_request(); for (const auto& medium : mediums) { upgrade_path_request->add_mediums(MediumToUpgradePathInfoMedium(medium)); } + LOG(INFO) << "ForBwuPathRequest: supports_5_ghz: " << supports_5_ghz; + upgrade_path_request->mutable_medium_meta_data()->set_supports_5_ghz( + supports_5_ghz); auto* role = upgrade_path_request->mutable_medium_meta_data()->mutable_medium_role(); role->MergeFrom(medium_role); diff --git a/connections/implementation/offline_frames.h b/connections/implementation/offline_frames.h index b2fce56f..6bc51c19 100644 --- a/connections/implementation/offline_frames.h +++ b/connections/implementation/offline_frames.h @@ -59,7 +59,8 @@ std::string ForConnectionRequestPresence( const location::nearby::connections::PresenceDevice& proto_presence_device, const ConnectionInfo& connection_info); std::string ForConnectionResponse( - std::int32_t status, const location::nearby::connections::OsInfo& os_info); + std::int32_t status, const location::nearby::connections::OsInfo& os_info, + const std::string& device_name); // Builds Payload transfer messages. std::string ForDataPayloadTransfer( @@ -76,6 +77,7 @@ std::string ForPayloadAckPayloadTransfer(std::int64_t payload_id); // Builds Bandwidth Upgrade [BWU] messages. std::string ForBwuIntroduction(const std::string& endpoint_id, + const std::string& last_endpoint_id, bool supports_disabling_encryption); std::string ForBwuIntroductionAck(); std::string ForBwuWifiHotspotPathAvailable( @@ -107,8 +109,9 @@ std::string ForBwuWebrtcPathAvailable( const location::nearby::connections::LocationHint& location_hint_a); std::string ForBwuFailure(const UpgradePathInfo& info); std::string ForBwuPathRequest( - const std::vector& mediums, - const location::nearby::connections::MediumRole& medium_role); + Medium medium, const std::vector& mediums, + const location::nearby::connections::MediumRole& medium_role, + bool supports_5_ghz); std::string ForBwuLastWrite(); std::string ForBwuSafeToClose(); diff --git a/connections/implementation/offline_frames_test.cc b/connections/implementation/offline_frames_test.cc index 1bb982f2..94cc3fa1 100644 --- a/connections/implementation/offline_frames_test.cc +++ b/connections/implementation/offline_frames_test.cc @@ -352,6 +352,7 @@ TEST(OfflineFramesTest, CanGenerateConnectionResponse) { os_info { type: LINUX } multiplex_socket_bitmask: 0 safe_to_disconnect_version: 5 + wifi_direct_device_name: "device_name" > >)pb"; @@ -361,7 +362,8 @@ TEST(OfflineFramesTest, CanGenerateConnectionResponse) { config_package_nearby::nearby_connections_feature:: kSafeToDisconnectVersion, 5); - auto response = FromBytes(ForConnectionResponse(1, os_info)); + auto response = FromBytes( + ForConnectionResponse(1, os_info, "device_name")); ASSERT_TRUE(response.ok()); OfflineFrame message = response.result(); EXPECT_THAT(message, EqualsProto(kExpected)); @@ -502,10 +504,7 @@ TEST(OfflineFramesTest, CanGenerateBwuWifiLanPathAvailable) { ip_address: "\x2a\x00\x79\xe0\x2e\x87\x00\x06\xb7\x28\x67\x45\x7a\xdd\x01\x53" port: 1234 > - address_candidates: < - ip_address: "\001\002\003\004" - port: 1234 - > + address_candidates: < ip_address: "\001\002\003\004" port: 1234 > > supports_client_introduction_ack: true > @@ -679,11 +678,13 @@ TEST(OfflineFramesTest, CanGenerateBwuIntroduction) { client_introduction: < endpoint_id: "ABC" supports_disabling_encryption: false + last_endpoint_id: "DEF" > > >)pb"; - auto response = FromBytes(ForBwuIntroduction( - std::string(kEndpointId), false /* supports_disabling_encryption */)); + auto response = + FromBytes(ForBwuIntroduction(std::string(kEndpointId), "DEF", + false /* supports_disabling_encryption */)); ASSERT_TRUE(response.ok()); OfflineFrame message = response.result(); EXPECT_THAT(message, EqualsProto(kExpected)); @@ -722,7 +723,6 @@ TEST(OfflineFramesTest, CanGenerateDisconnection) { EXPECT_THAT(message, EqualsProto(kExpected)); } - TEST(OfflineFramesTest, CanGenerateBwuPathRequest) { constexpr absl::string_view kExpected = R"pb( @@ -732,9 +732,11 @@ TEST(OfflineFramesTest, CanGenerateBwuPathRequest) { bandwidth_upgrade_negotiation: < event_type: UPGRADE_PATH_REQUEST upgrade_path_info: < + medium: WIFI_HOTSPOT upgrade_path_request: < mediums: WIFI_HOTSPOT medium_meta_data: < + supports_5_ghz: true medium_role: < support_wifi_hotspot_client: true > > > @@ -745,7 +747,9 @@ TEST(OfflineFramesTest, CanGenerateBwuPathRequest) { mediums.push_back(Medium::WIFI_HOTSPOT); MediumRole medium_role; medium_role.set_support_wifi_hotspot_client(true); - auto response = FromBytes(ForBwuPathRequest(mediums, medium_role)); + auto response = + FromBytes(ForBwuPathRequest(Medium::WIFI_HOTSPOT, mediums, medium_role, + /*supports_5_ghz=*/true)); ASSERT_TRUE(response.ok()); OfflineFrame message = response.result(); EXPECT_THAT(message, EqualsProto(kExpected)); diff --git a/connections/implementation/offline_frames_validator.cc b/connections/implementation/offline_frames_validator.cc index c42cd4a3..ecfe5d5a 100644 --- a/connections/implementation/offline_frames_validator.cc +++ b/connections/implementation/offline_frames_validator.cc @@ -19,6 +19,8 @@ #include //NOLINT #include +#include "absl/strings/escaping.h" +#include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "connections/implementation/internal_payload.h" #include "connections/implementation/offline_frames.h" @@ -26,9 +28,12 @@ #include "connections/medium_selector.h" #include "internal/platform/exception.h" #include "internal/platform/logging.h" +#include "internal/platform/service_address.h" +#include "sharing/internal/base/utf_string_conversions.h" namespace nearby { namespace connections { + namespace parser { namespace { @@ -72,7 +77,12 @@ constexpr int kWifiDirectPinMinLength = 0; constexpr int kWifiDirectPinMaxLength = 16; inline bool WithinRange(int value, int min, int max) { - return value >= min && value < max; + return value >= min && value <= max; +} + +bool IsValidWifiLanServiceAddress(const ServiceAddress& service_address) { + return !service_address.IsLoopbackAddress() && + !service_address.IsLinkLocalAddress(); } Exception EnsureValidConnectionRequestFrame( @@ -140,16 +150,25 @@ Exception EnsureValidPayloadTransferControlFrame( return {Exception::kSuccess}; } -bool CheckForIllegalCharacters(std::string toBeValidated, +bool CheckForIllegalCharacters(absl::string_view toBeValidated, const absl::string_view illegalPatterns[], size_t illegalPatternsSize) { if (toBeValidated.empty()) { return false; } + // Null bytes are rejected to prevent null-byte injection attacks. C-style + // APIs (like system file operations) treat '\0' as a string terminator, + // whereas C++ strings can contain them. This discrepancy can lead to + // validation bypasses (e.g., validating "file.sh\0.png" as a PNG but + // creating "file.sh" on disk). + if (absl::StrContains(toBeValidated, '\0') || + !nearby::utils::IsStringUtf8(toBeValidated)) { + return true; + } + for (int index = 0; index < illegalPatternsSize; index++) { - if (toBeValidated.find(std::string(illegalPatterns[index])) != - std::string::npos) { + if (absl::StrContains(toBeValidated, illegalPatterns[index])) { return true; } } @@ -178,20 +197,21 @@ Exception EnsureValidPayloadTransferFrame(const PayloadTransferFrame& frame) { location::nearby::connections::PayloadTransferFrame::PayloadHeader:: FILE) { if (frame.payload_header().has_file_name()) { - if (CheckForIllegalCharacters(frame.payload_header().file_name(), - kIllegalFileNamePatterns, + const std::string& file_name = frame.payload_header().file_name(); + if (CheckForIllegalCharacters(file_name, kIllegalFileNamePatterns, kIllegalFileNamePatternsSize)) { - LOG(ERROR) << "File name " << frame.payload_header().file_name() - << " has illegal characters"; + LOG(ERROR) << "File name (hex) " << absl::BytesToHexString(file_name) + << " has illegal characters or invalid UTF-8"; return {Exception::kIllegalCharacters}; } } if (frame.payload_header().has_parent_folder()) { - if (CheckForIllegalCharacters(frame.payload_header().parent_folder(), - kIllegalParentFolderPatterns, + const std::string& parent_folder = frame.payload_header().parent_folder(); + if (CheckForIllegalCharacters(parent_folder, kIllegalParentFolderPatterns, kIllegalParentFolderPatternsSize)) { - LOG(ERROR) << "Parent folder " << frame.payload_header().parent_folder() - << " has illegal characters"; + LOG(ERROR) << "Parent folder (hex) " + << absl::BytesToHexString(parent_folder) + << " has illegal characters or invalid UTF-8"; return {Exception::kIllegalCharacters}; } } @@ -234,21 +254,29 @@ Exception EnsureValidBandwidthUpgradeWifiHotspotPathAvailableFrame( !WithinRange(wifi_hotspot_credentials.password().length(), kWifiPasswordSsidMinLength, kWifiPasswordSsidMaxLength)) return {Exception::kInvalidProtocolBuffer}; - if (!wifi_hotspot_credentials.has_gateway() && - wifi_hotspot_credentials.address_candidates_size() == 0) + + if ((!wifi_hotspot_credentials.has_gateway() || + wifi_hotspot_credentials.gateway().empty()) && + wifi_hotspot_credentials.address_candidates_size() == 0) { return {Exception::kInvalidProtocolBuffer}; - const std::regex ip4_pattern(std::string(kIpv4PatternString).c_str()); - if (!wifi_hotspot_credentials.gateway().empty() && - !(std::regex_match(wifi_hotspot_credentials.gateway(), ip4_pattern))) { - return {Exception::kInvalidProtocolBuffer}; } - for (const auto& address_candidate : - wifi_hotspot_credentials.address_candidates()) { - if (!address_candidate.has_ip_address() || !address_candidate.has_port()) { + + const std::regex ip4_pattern(std::string(kIpv4PatternString).c_str()); + if (wifi_hotspot_credentials.has_gateway() && + !wifi_hotspot_credentials.gateway().empty()) { + if (!(std::regex_match(wifi_hotspot_credentials.gateway(), ip4_pattern))) { return {Exception::kInvalidProtocolBuffer}; } - if (address_candidate.ip_address().size() != 4 && - address_candidate.ip_address().size() != 16) { + if (!wifi_hotspot_credentials.has_port() || + !WithinRange(wifi_hotspot_credentials.port(), 1, 65535)) { + return {Exception::kInvalidProtocolBuffer}; + } + } + + for (const auto& address_candidate : + wifi_hotspot_credentials.address_candidates()) { + ServiceAddress service_address; + if (!ServiceAddressFromProto(address_candidate, service_address)) { return {Exception::kInvalidProtocolBuffer}; } } @@ -265,6 +293,29 @@ Exception EnsureValidBandwidthUpgradeWifiLanPathAvailableFrame( return {Exception::kInvalidProtocolBuffer}; } + if (wifi_lan_socket.has_ip_address()) { + location::nearby::connections::ServiceAddress proto; + proto.set_ip_address(wifi_lan_socket.ip_address()); + proto.set_port(wifi_lan_socket.wifi_port()); + ServiceAddress service_address; + if (!ServiceAddressFromProto(proto, service_address)) { + return {Exception::kInvalidProtocolBuffer}; + } + if (!IsValidWifiLanServiceAddress(service_address)) { + return {Exception::kInvalidProtocolBuffer}; + } + } + + for (const auto& address_candidate : wifi_lan_socket.address_candidates()) { + ServiceAddress service_address; + if (!ServiceAddressFromProto(address_candidate, service_address)) { + return {Exception::kInvalidProtocolBuffer}; + } + if (!IsValidWifiLanServiceAddress(service_address)) { + return {Exception::kInvalidProtocolBuffer}; + } + } + // For backwards compatibility reasons, no other fields should be null-checked // for this frame. Parameter checking (eg. must be within this range) is fine. return {Exception::kSuccess}; @@ -292,7 +343,7 @@ Exception EnsureValidBandwidthUpgradeWifiDirectPathAvailableFrame( std::string(kWifiDirectSsidPatternString).c_str()); bool ssid_valid = wifi_direct_credentials.has_ssid() && - wifi_direct_credentials.ssid().length() < kWifiDirectSsidMaxLength && + wifi_direct_credentials.ssid().length() <= kWifiDirectSsidMaxLength && std::regex_match(wifi_direct_credentials.ssid(), ssid_pattern); bool password_valid = wifi_direct_credentials.has_password() && @@ -300,8 +351,7 @@ Exception EnsureValidBandwidthUpgradeWifiDirectPathAvailableFrame( kWifiPasswordSsidMinLength, kWifiPasswordSsidMaxLength); bool device_name_valid = wifi_direct_credentials.has_device_name() && - wifi_direct_credentials.device_name().length() < - kWifiDirectSsidMaxLength; + wifi_direct_credentials.device_name().length() < kWifiDirectSsidMaxLength; bool pin_valid = wifi_direct_credentials.has_pin() && WithinRange(wifi_direct_credentials.pin().length(), diff --git a/connections/implementation/offline_frames_validator_test.cc b/connections/implementation/offline_frames_validator_test.cc index 37d92941..019847c9 100644 --- a/connections/implementation/offline_frames_validator_test.cc +++ b/connections/implementation/offline_frames_validator_test.cc @@ -36,6 +36,7 @@ using ::location::nearby::connections::BandwidthUpgradeNegotiationFrame; using ::location::nearby::connections::OfflineFrame; using ::location::nearby::connections::OsInfo; using ::location::nearby::connections::PayloadTransferFrame; +using ::location::nearby::connections::V1Frame; constexpr absl::string_view kEndpointId{"ABC"}; constexpr absl::string_view kEndpointName{"XYZ"}; @@ -122,7 +123,7 @@ TEST_F(OfflineFramesConnectionRequestTest, ValidatesAsFailWithEmptyEndpointIdInConnectionRequestFrame) { connection_info_.local_endpoint_id = ""; std::string bytes = ForConnectionRequestConnections({}, connection_info_); - location::nearby::connections::OfflineFrame frame; + OfflineFrame frame; frame.ParseFromString(bytes); frame.mutable_v1()->mutable_connection_request()->set_endpoint_id(""); ASSERT_TRUE(frame.v1().connection_request().has_endpoint_id()); @@ -179,7 +180,8 @@ TEST(OfflineFramesValidatorTest, OfflineFrame offline_frame; OsInfo os_info; - std::string bytes = ForConnectionResponse(kStatusAccepted, os_info); + std::string bytes = + ForConnectionResponse(kStatusAccepted, os_info, "device_name"); offline_frame.ParseFromString(bytes); auto ret_value = EnsureValidOfflineFrame(offline_frame); @@ -192,7 +194,8 @@ TEST(OfflineFramesValidatorTest, OfflineFrame offline_frame; OsInfo os_info; - std::string bytes = ForConnectionResponse(kStatusAccepted, os_info); + std::string bytes = + ForConnectionResponse(kStatusAccepted, os_info, "device_name"); offline_frame.ParseFromString(bytes); auto* v1_frame = offline_frame.mutable_v1(); @@ -208,7 +211,7 @@ TEST(OfflineFramesValidatorTest, OfflineFrame offline_frame; OsInfo os_info; - std::string bytes = ForConnectionResponse(-1, os_info); + std::string bytes = ForConnectionResponse(-1, os_info, "device_name"); offline_frame.ParseFromString(bytes); auto ret_value = EnsureValidOfflineFrame(offline_frame); @@ -365,6 +368,92 @@ TEST(OfflineFramesValidatorTest, EXPECT_EQ(ret_value.value, Exception::kIllegalCharacters); } +TEST(OfflineFramesValidatorTest, ValidatesAsFailedTypeFileWithNonUtf8FilePath) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::FILE); + header.set_total_size(100); + header.set_file_name(std::string("hello\xffworld")); + header.set_parent_folder(std::string()); + chunk.set_body("payload data"); + chunk.set_offset(0); + chunk.set_flags(1); + + OfflineFrame offline_frame; + std::string bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_EQ(ret_value.value, Exception::kIllegalCharacters); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailedTypeFileWithNonUtf8ParentFolder) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::FILE); + header.set_total_size(100); + header.set_file_name(std::string("valid.txt")); + header.set_parent_folder(std::string("folder\xff")); + chunk.set_body("payload data"); + chunk.set_offset(0); + chunk.set_flags(1); + + OfflineFrame offline_frame; + std::string bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_EQ(ret_value.value, Exception::kIllegalCharacters); +} + +TEST(OfflineFramesValidatorTest, ValidatesAsFailedTypeFileWithNullInFilePath) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::FILE); + header.set_total_size(100); + header.set_file_name(std::string("hello\0world", 11)); + header.set_parent_folder(std::string()); + chunk.set_body("payload data"); + chunk.set_offset(0); + chunk.set_flags(1); + + OfflineFrame offline_frame; + std::string bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_EQ(ret_value.value, Exception::kIllegalCharacters); +} + +TEST(OfflineFramesValidatorTest, + ValidatesAsFailedTypeFileWithNullInParentFolder) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::FILE); + header.set_total_size(100); + header.set_file_name(std::string("valid.txt")); + header.set_parent_folder(std::string("folder\0name", 11)); + chunk.set_body("payload data"); + chunk.set_offset(0); + chunk.set_flags(1); + + OfflineFrame offline_frame; + std::string bytes = ForDataPayloadTransfer(header, chunk); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_EQ(ret_value.value, Exception::kIllegalCharacters); +} + TEST(OfflineFramesValidatorTest, ValidatesAsFailWithNullPayloadTransferFrame) { PayloadTransferFrame::PayloadHeader header; PayloadTransferFrame::PayloadChunk chunk; @@ -677,6 +766,86 @@ TEST(OfflineFramesValidatorTest, EXPECT_FALSE(ret_value.Ok()); } +TEST(OfflineFramesValidatorTest, + ValidateHotspotUpgradeFrameWithLargePortCandidateFails) { + OfflineFrame offline_frame; + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials + credentials; + credentials.set_ssid(kSsid); + credentials.set_password(kPassword); + credentials.set_frequency(kHotspotFrequency); + auto* candidate = credentials.mutable_address_candidates()->Add(); + candidate->set_ip_address(std::string("\xc0\xa8\x00\x01", 4)); + candidate->set_port(70000); + std::string bytes = ForBwuWifiHotspotPathAvailable( + std::move(credentials), kSupportsDisablingEncryption); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateHotspotUpgradeFrameWithFallbackInvalidPortFails) { + OfflineFrame offline_frame; + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials + credentials; + credentials.set_ssid(kSsid); + credentials.set_password(kPassword); + credentials.set_frequency(kHotspotFrequency); + credentials.set_gateway(std::string(kWifiHotspotGateway)); + credentials.set_port(70000); + std::string bytes = ForBwuWifiHotspotPathAvailable( + std::move(credentials), kSupportsDisablingEncryption); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateHotspotUpgradeFrameWithEmptyGatewayAndNoCandidatesFails) { + OfflineFrame offline_frame; + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials + credentials; + credentials.set_ssid(kSsid); + credentials.set_password(kPassword); + credentials.set_frequency(kHotspotFrequency); + credentials.set_gateway(""); + std::string bytes = ForBwuWifiHotspotPathAvailable( + std::move(credentials), kSupportsDisablingEncryption); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateHotspotUpgradeFrameWithNoGatewayAndNoCandidatesFails) { + OfflineFrame offline_frame; + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials + credentials; + credentials.set_ssid(kSsid); + credentials.set_password(kPassword); + credentials.set_frequency(kHotspotFrequency); + // Do not set gateway + // Do not set address candidates + std::string bytes = ForBwuWifiHotspotPathAvailable( + std::move(credentials), kSupportsDisablingEncryption); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + TEST(OfflineFramesValidatorTest, ValidateWifiLanUpgradeFrameWithAddressCandidatesSucceeds) { OfflineFrame offline_frame; @@ -694,6 +863,144 @@ TEST(OfflineFramesValidatorTest, EXPECT_TRUE(ret_value.Ok()); } +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithLoopbackAddressCandidateFails) { + OfflineFrame offline_frame; + std::vector address_candidates = { + {{127, 0, 0, 1}, kPort}, + }; + std::string bytes = ForBwuWifiLanPathAvailable(address_candidates); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithLinkLocalAddressCandidateFails) { + OfflineFrame offline_frame; + std::vector address_candidates = { + {{169, 254, 1, 1}, kPort}, + }; + std::string bytes = ForBwuWifiLanPathAvailable(address_candidates); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithInvalidIpAddressSizeCandidateFails) { + OfflineFrame offline_frame; + std::vector address_candidates = { + {{1, 2, 3}, kPort}, + }; + std::string bytes = ForBwuWifiLanPathAvailable(address_candidates); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithZeroPortCandidateFails) { + OfflineFrame offline_frame; + std::vector address_candidates = { + {{192, 168, 1, 1}, 0}, + }; + std::string bytes = ForBwuWifiLanPathAvailable(address_candidates); + offline_frame.ParseFromString(bytes); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithLargePortCandidateFails) { + OfflineFrame offline_frame; + std::vector address_candidates = { + {{192, 168, 1, 1}, kPort}, + }; + std::string bytes = ForBwuWifiLanPathAvailable(address_candidates); + offline_frame.ParseFromString(bytes); + + auto* negotiation = + offline_frame.mutable_v1()->mutable_bandwidth_upgrade_negotiation(); + auto* wifi_lan_socket = + negotiation->mutable_upgrade_path_info()->mutable_wifi_lan_socket(); + if (wifi_lan_socket->address_candidates_size() > 0) { + wifi_lan_socket->mutable_address_candidates(0)->set_port(70000); + } + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithFallbackLoopbackAddressFails) { + OfflineFrame offline_frame; + offline_frame.set_version(OfflineFrame::V1); + auto* v1_frame = offline_frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* negotiation = v1_frame->mutable_bandwidth_upgrade_negotiation(); + negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = negotiation->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_LAN); + auto* wifi_lan_socket = upgrade_path_info->mutable_wifi_lan_socket(); + wifi_lan_socket->set_ip_address(std::string({127, 0, 0, 1})); + wifi_lan_socket->set_wifi_port(kPort); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithFallbackLinkLocalAddressFails) { + OfflineFrame offline_frame; + offline_frame.set_version(OfflineFrame::V1); + auto* v1_frame = offline_frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* negotiation = v1_frame->mutable_bandwidth_upgrade_negotiation(); + negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = negotiation->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_LAN); + auto* wifi_lan_socket = upgrade_path_info->mutable_wifi_lan_socket(); + wifi_lan_socket->set_ip_address(std::string({169, 254, 1, 1})); + wifi_lan_socket->set_wifi_port(kPort); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + +TEST(OfflineFramesValidatorTest, + ValidateWifiLanUpgradeFrameWithFallbackInvalidPortFails) { + OfflineFrame offline_frame; + offline_frame.set_version(OfflineFrame::V1); + auto* v1_frame = offline_frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* negotiation = v1_frame->mutable_bandwidth_upgrade_negotiation(); + negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = negotiation->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_LAN); + auto* wifi_lan_socket = upgrade_path_info->mutable_wifi_lan_socket(); + wifi_lan_socket->set_ip_address(std::string({192, 168, 1, 1})); + wifi_lan_socket->set_wifi_port(70000); + + auto ret_value = EnsureValidOfflineFrame(offline_frame); + + EXPECT_FALSE(ret_value.Ok()); +} + TEST(OfflineFramesValidatorTest, ValidatesAsFailWithNullBandwidthUpgradeNegotiationFrame) { OfflineFrame offline_frame; @@ -765,18 +1072,35 @@ TEST(OfflineFramesValidatorTest, OfflineFrame offline_frame_2; std::string wifi_direct_ssid{"DIRECT-A*-0123456789AB"}; - std::string wifi_direct_pin_wrong_length = "abcefghijklmnopqrstuvwxyz"; + std::string wifi_direct_pin_wrong_length = "01234567890123456"; std::string bytes = ForBwuWifiDirectPathAvailable( wifi_direct_ssid, std::string(kWifiDirectPassword), kPort, - kWifiDirectFrequency, kSupportsDisablingEncryption, - std::string(kGateway), std::string(kWifiDirectDeviceName), - wifi_direct_pin_wrong_length); + kWifiDirectFrequency, kSupportsDisablingEncryption, std::string(kGateway), + std::string(kWifiDirectDeviceName), wifi_direct_pin_wrong_length); offline_frame_1.ParseFromString(bytes); auto ret_value = EnsureValidOfflineFrame(offline_frame_1); EXPECT_FALSE(ret_value.Ok()); + std::string wifi_direct_ssid_64_length = "DIRECT-A0-" + std::string(54, 'A'); + bytes = ForBwuWifiDirectPathAvailable( + wifi_direct_ssid_64_length, std::string(kWifiDirectPassword), kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption, std::string(kGateway), + std::string(kWifiDirectDeviceName), /*pin=*/"01234567890123456"); + offline_frame_2.ParseFromString(bytes); + ret_value = EnsureValidOfflineFrame(offline_frame_2); + EXPECT_FALSE(ret_value.Ok()); + + std::string wifi_direct_pin_16_length = "0123456789012345"; + bytes = ForBwuWifiDirectPathAvailable( + std::string(kWifiDirectSsid), std::string(kWifiDirectPassword), kPort, + kWifiDirectFrequency, kSupportsDisablingEncryption, std::string(kGateway), + std::string(kWifiDirectDeviceName), wifi_direct_pin_16_length); + offline_frame_2.ParseFromString(bytes); + ret_value = EnsureValidOfflineFrame(offline_frame_2); + EXPECT_TRUE(ret_value.Ok()); + std::string wifi_direct_ssid_wrong_length = std::string{kWifiDirectSsid} + "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; std::string wifi_direct_device_name_wrong_length = @@ -784,9 +1108,8 @@ TEST(OfflineFramesValidatorTest, "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; bytes = ForBwuWifiDirectPathAvailable( wifi_direct_ssid_wrong_length, std::string(kWifiDirectPassword), kPort, - kWifiDirectFrequency, kSupportsDisablingEncryption, - std::string(kGateway), wifi_direct_device_name_wrong_length, - std::string(kWifiDirectPin)); + kWifiDirectFrequency, kSupportsDisablingEncryption, std::string(kGateway), + wifi_direct_device_name_wrong_length, std::string(kWifiDirectPin)); offline_frame_2.ParseFromString(bytes); ret_value = EnsureValidOfflineFrame(offline_frame_2); @@ -807,9 +1130,8 @@ TEST(OfflineFramesValidatorTest, "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789"; std::string bytes = ForBwuWifiDirectPathAvailable( std::string(kWifiDirectSsid), long_wifi_direct_password, kPort, - kWifiDirectFrequency, kSupportsDisablingEncryption, - std::string(kGateway), std::string(kWifiDirectDeviceName), - long_wifi_direct_pin); + kWifiDirectFrequency, kSupportsDisablingEncryption, std::string(kGateway), + std::string(kWifiDirectDeviceName), long_wifi_direct_pin); offline_frame_2.ParseFromString(bytes); auto ret_value = EnsureValidOfflineFrame(offline_frame_2); diff --git a/connections/implementation/p2p_cluster_pcp_handler.cc b/connections/implementation/p2p_cluster_pcp_handler.cc index c207e91b..19f60a99 100644 --- a/connections/implementation/p2p_cluster_pcp_handler.cc +++ b/connections/implementation/p2p_cluster_pcp_handler.cc @@ -307,6 +307,9 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { << bluetooth_classic_advertiser_client_id_; } + wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); + bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); @@ -316,9 +319,6 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { client->GetAdvertisingServiceId()); } - wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); - wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); - if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature::kEnableAwdl)) { awdl_medium_.StopAdvertising(client->GetAdvertisingServiceId()); @@ -1238,8 +1238,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::BluetoothConnectionAcceptedHandler, this, - client_proxy, local_endpoint_id, - options.listening_endpoint_type)); + client_proxy, options.listening_endpoint_type)); if (bluetooth_result.has_error()) { LOG(WARNING) << "Failed to start listening for incoming connections on Bluetooth"; @@ -1266,8 +1265,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::BleConnectionAcceptedHandler2, this, - client_proxy, local_endpoint_id, - options.listening_endpoint_type))) { + client_proxy, options.listening_endpoint_type))) { LOG(WARNING) << "Failed to start listening for incoming L2CAP " "connections on ble"; } else { @@ -1278,8 +1276,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler, - this, client_proxy, local_endpoint_id, - options.listening_endpoint_type))) { + this, client_proxy, options.listening_endpoint_type))) { LOG(WARNING) << "Failed to start listening for incoming L2CAP " "connections on ble"; } else { @@ -1295,8 +1292,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::BleConnectionAcceptedHandler2, this, - client_proxy, local_endpoint_id, - options.listening_endpoint_type))) { + client_proxy, options.listening_endpoint_type))) { LOG(WARNING) << "Failed to start listening for incoming connections on ble_v2"; } else { @@ -1307,8 +1303,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::BleConnectionAcceptedHandler, this, - client_proxy, local_endpoint_id, - options.listening_endpoint_type))) { + client_proxy, options.listening_endpoint_type))) { LOG(WARNING) << "Failed to start listening for incoming connections on ble"; } else { @@ -1327,7 +1322,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( std::string(service_id), absl::bind_front( &P2pClusterPcpHandler::WifiLanConnectionAcceptedHandler, this, - client_proxy, local_endpoint_id, "", + client_proxy, std::string(local_endpoint_id), options.listening_endpoint_type)); if (wifi_lan_result.has_error()) { LOG(WARNING) @@ -1680,8 +1675,7 @@ P2pClusterPcpHandler::UpdateDiscoveryOptionsImpl( restarted_mediums.push_back(AWDL); operation_result_with_mediums.push_back( GetOperationResultWithMediumByResultCode( - client, AWDL, update_index, - OperationResultCode::DETAIL_SUCCESS)); + client, AWDL, update_index, OperationResultCode::DETAIL_SUCCESS)); } else { ErrorOr awdl_result = StartAwdlDiscovery(client, std::string(service_id)); @@ -1739,15 +1733,8 @@ P2pClusterPcpHandler::UpdateDiscoveryOptionsImpl( } void P2pClusterPcpHandler::BluetoothConnectionAcceptedHandler( - ClientProxy* client, absl::string_view local_endpoint_info, - NearbyDevice::Type device_type, const std::string& service_id, - BluetoothSocket socket) { - if (!socket.IsValid()) { - LOG(WARNING) << "Invalid socket in accept callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } + ClientProxy* client, NearbyDevice::Type device_type, + const std::string& service_id, BluetoothSocket socket) { RunOnPcpHandlerThread( "p2p-bt-on-incoming-connection", [this, client, service_id, socket = std::move(socket), device_type]() @@ -1782,8 +1769,7 @@ ErrorOr P2pClusterPcpHandler::StartBluetoothAdvertising( service_id, absl::bind_front( &P2pClusterPcpHandler::BluetoothConnectionAcceptedHandler, this, - client, local_endpoint_info.AsStringView(), - NearbyDevice::Type::kConnectionsDevice)); + client, NearbyDevice::Type::kConnectionsDevice)); if (accept_result.has_error()) { error = {Error(accept_result.error().operation_result_code().value())}; } @@ -1987,15 +1973,8 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BluetoothConnectImpl( } void P2pClusterPcpHandler::BleConnectionAcceptedHandler( - ClientProxy* client, absl::string_view local_endpoint_info, - NearbyDevice::Type device_type, BleSocket socket, + ClientProxy* client, NearbyDevice::Type device_type, BleSocket socket, const std::string& service_id) { - if (!socket.IsValid()) { - LOG(WARNING) << "Invalid socket in accept callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } RunOnPcpHandlerThread( "p2p-ble-on-incoming-connection", [this, client, service_id, device_type, @@ -2010,15 +1989,8 @@ void P2pClusterPcpHandler::BleConnectionAcceptedHandler( } void P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler( - ClientProxy* client, absl::string_view local_endpoint_info, - NearbyDevice::Type device_type, BleL2capSocket socket, + ClientProxy* client, NearbyDevice::Type device_type, BleL2capSocket socket, const std::string& service_id) { - if (!socket.IsValid()) { - LOG(WARNING) << "Invalid socket in accept L2CAP callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } RunOnPcpHandlerThread( "p2p-ble-l2cap-on-incoming-connection", [this, client, service_id, device_type, @@ -2033,15 +2005,8 @@ void P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler( } void P2pClusterPcpHandler::BleConnectionAcceptedHandler2( - ClientProxy* client, absl::string_view local_endpoint_info, - NearbyDevice::Type device_type, std::unique_ptr socket, - const std::string& service_id) { - if (socket == nullptr || !socket->IsValid()) { - LOG(WARNING) << "Invalid socket in accept callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } + ClientProxy* client, NearbyDevice::Type device_type, + std::unique_ptr socket, const std::string& service_id) { RunOnPcpHandlerThread( "p2p-ble-on-incoming-connection", [this, client, service_id, device_type, socket = std::move(socket)]() @@ -2101,15 +2066,13 @@ ErrorOr P2pClusterPcpHandler::StartBleAdvertising( service_id, absl::bind_front( &P2pClusterPcpHandler::BleConnectionAcceptedHandler2, this, - client, local_endpoint_info.AsStringView(), - NearbyDevice::Type::kConnectionsDevice)); + client, NearbyDevice::Type::kConnectionsDevice)); } else { ble_l2cap_result = ble_medium_.StartAcceptingL2capConnections( service_id, absl::bind_front( &P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler, this, - client, local_endpoint_info.AsStringView(), - NearbyDevice::Type::kConnectionsDevice)); + client, NearbyDevice::Type::kConnectionsDevice)); } } @@ -2118,13 +2081,13 @@ ErrorOr P2pClusterPcpHandler::StartBleAdvertising( ble_result = ble_medium_.StartAcceptingConnections( service_id, absl::bind_front(&P2pClusterPcpHandler::BleConnectionAcceptedHandler2, - this, client, local_endpoint_info.AsStringView(), + this, client, NearbyDevice::Type::kConnectionsDevice)); } else { ble_result = ble_medium_.StartAcceptingConnections( service_id, absl::bind_front(&P2pClusterPcpHandler::BleConnectionAcceptedHandler, - this, client, local_endpoint_info.AsStringView(), + this, client, NearbyDevice::Type::kConnectionsDevice)); } if (ble_result.has_error() && ble_l2cap_result.has_error()) { @@ -2169,8 +2132,7 @@ ErrorOr P2pClusterPcpHandler::StartBleAdvertising( service_id, absl::bind_front( &P2pClusterPcpHandler::BluetoothConnectionAcceptedHandler, this, - client, local_endpoint_info.AsStringView(), - NearbyDevice::Type::kConnectionsDevice)); + client, NearbyDevice::Type::kConnectionsDevice)); if (accept_result.has_error()) { LOG(WARNING) << "In BT StartBleAdvertising(" @@ -2430,23 +2392,16 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BleConnectImpl( } void P2pClusterPcpHandler::AwdlConnectionAcceptedHandler( - ClientProxy* client, absl::string_view local_endpoint_id, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, - const std::string& service_id, AwdlSocket socket) { - if (!socket.IsValid()) { - LOG(WARNING) << "Invalid socket in accept callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } + ClientProxy* client, const std::string& local_endpoint_id, + NearbyDevice::Type device_type, const std::string& service_id, + AwdlSocket socket) { RunOnPcpHandlerThread( "p2p-awdl-on-incoming-connection", [this, client, local_endpoint_id, service_id, device_type, socket = std::move(socket)]() RUN_ON_PCP_HANDLER_THREAD() mutable { - std::string remote_service_name = std::string(local_endpoint_id); auto channel = std::make_unique( - service_id, /*channel_name=*/remote_service_name, socket); - ByteArray remote_service_name_byte{remote_service_name}; + service_id, /*channel_name=*/local_endpoint_id, socket); + ByteArray remote_service_name_byte{local_endpoint_id}; OnIncomingConnection(client, remote_service_name_byte, std::move(channel), AWDL, device_type); @@ -2454,23 +2409,16 @@ void P2pClusterPcpHandler::AwdlConnectionAcceptedHandler( } void P2pClusterPcpHandler::WifiLanConnectionAcceptedHandler( - ClientProxy* client, absl::string_view local_endpoint_id, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, - const std::string& service_id, WifiLanSocket socket) { - if (!socket.IsValid()) { - LOG(WARNING) << "Invalid socket in accept callback(" - << absl::BytesToHexString(local_endpoint_info) - << "), client=" << client->GetClientId(); - return; - } + ClientProxy* client, const std::string& local_endpoint_id, + NearbyDevice::Type device_type, const std::string& service_id, + WifiLanSocket socket) { RunOnPcpHandlerThread( "p2p-wifi-on-incoming-connection", [this, client, local_endpoint_id, service_id, device_type, socket = std::move(socket)]() RUN_ON_PCP_HANDLER_THREAD() mutable { - std::string remote_service_name = std::string(local_endpoint_id); auto channel = std::make_unique( - service_id, /*channel_name=*/remote_service_name, socket); - ByteArray remote_service_name_byte{remote_service_name}; + service_id, /*channel_name=*/local_endpoint_id, socket); + ByteArray remote_service_name_byte{local_endpoint_id}; OnIncomingConnection(client, remote_service_name_byte, std::move(channel), WIFI_LAN, device_type); @@ -2490,7 +2438,6 @@ ErrorOr P2pClusterPcpHandler::StartAwdlAdvertising( service_id, absl::bind_front(&P2pClusterPcpHandler::AwdlConnectionAcceptedHandler, this, client, local_endpoint_id, - local_endpoint_info.AsStringView(), NearbyDevice::Type::kConnectionsDevice)); if (awdl_result.has_error()) { LOG(WARNING) @@ -2608,7 +2555,6 @@ ErrorOr P2pClusterPcpHandler::StartWifiLanAdvertising( service_id, nsd_service_info, absl::bind_front(&P2pClusterPcpHandler::WifiLanConnectionAcceptedHandler, this, client, local_endpoint_id, - local_endpoint_info.AsStringView(), NearbyDevice::Type::kConnectionsDevice)); if (wifi_lan_result.has_error()) { LOG(WARNING) << "In StartWifiLanAdvertising(" diff --git a/connections/implementation/p2p_cluster_pcp_handler.h b/connections/implementation/p2p_cluster_pcp_handler.h index d5d2c345..065ddcfd 100644 --- a/connections/implementation/p2p_cluster_pcp_handler.h +++ b/connections/implementation/p2p_cluster_pcp_handler.h @@ -40,10 +40,13 @@ #include "connections/implementation/mediums/bluetooth_classic.h" #include "connections/implementation/mediums/bluetooth_radio.h" #include "connections/implementation/mediums/mediums.h" +#include "connections/implementation/mediums/webrtc.h" #include "connections/implementation/mediums/wifi_direct.h" #include "connections/implementation/mediums/wifi_hotspot.h" #include "connections/implementation/mediums/wifi_lan.h" +#include "connections/implementation/pcp.h" #include "connections/implementation/webrtc_state.h" +#include "connections/implementation/wifi_lan_service_info.h" #include "connections/medium_selector.h" #include "connections/out_of_band_connection_metadata.h" #include "connections/power_level.h" @@ -54,13 +57,10 @@ #include "internal/platform/ble.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/bluetooth_classic.h" -#include "internal/platform/nsd_service_info.h" -#include "internal/platform/wifi_lan.h" -#include "connections/implementation/mediums/webrtc.h" -#include "connections/implementation/pcp.h" -#include "connections/implementation/wifi_lan_service_info.h" #include "internal/platform/byte_array.h" #include "internal/platform/expected.h" +#include "internal/platform/nsd_service_info.h" +#include "internal/platform/wifi_lan.h" namespace nearby { namespace connections { @@ -192,7 +192,6 @@ class P2pClusterPcpHandler : public BasePcpHandler { const std::string& service_id, BluetoothDevice& device); void BluetoothConnectionAcceptedHandler(ClientProxy* client, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, const std::string& service_id, BluetoothSocket socket); @@ -232,19 +231,16 @@ class P2pClusterPcpHandler : public BasePcpHandler { bool fast_advertisement); void BleLegacyDeviceDiscoveredHandler(); void BleConnectionAcceptedHandler(ClientProxy* client, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, BleSocket socket, const std::string& service_id); void BleL2capConnectionAcceptedHandler(ClientProxy* client, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, BleL2capSocket socket, const std::string& service_id); // The refactor version of BleConnectionAcceptedHandler() and // BleL2capConnectionAcceptedHandler above. void BleConnectionAcceptedHandler2(ClientProxy* client, - absl::string_view local_endpoint_info, NearbyDevice::Type device_type, std::unique_ptr socket, const std::string& service_id); @@ -265,8 +261,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { void AwdlServiceLostHandler(ClientProxy* client, NsdServiceInfo service_info, const std::string& service_id); void AwdlConnectionAcceptedHandler(ClientProxy* client, - absl::string_view local_endpoint_id, - absl::string_view local_endpoint_info, + const std::string& local_endpoint_id, NearbyDevice::Type device_type, const std::string& service_id, AwdlSocket socket); @@ -290,8 +285,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { NsdServiceInfo service_info, const std::string& service_id); void WifiLanConnectionAcceptedHandler(ClientProxy* client, - absl::string_view local_endpoint_id, - absl::string_view local_endpoint_info, + const std::string& local_endpoint_id, NearbyDevice::Type device_type, const std::string& service_id, WifiLanSocket socket); diff --git a/connections/implementation/proto/offline_wire_formats.proto b/connections/implementation/proto/offline_wire_formats.proto index f54287ec..230dab44 100644 --- a/connections/implementation/proto/offline_wire_formats.proto +++ b/connections/implementation/proto/offline_wire_formats.proto @@ -157,6 +157,7 @@ message ConnectionResponseFrame { optional int32 safe_to_disconnect_version = 7; optional LocationHint location_hint = 8; optional int32 keep_alive_timeout_millis = 9; + optional string wifi_direct_device_name = 10; } message PayloadTransferFrame { diff --git a/internal/base/BUILD b/internal/base/BUILD index 771eeecc..0bc8e534 100644 --- a/internal/base/BUILD +++ b/internal/base/BUILD @@ -25,7 +25,6 @@ cc_library( visibility = [ "//internal/account:__subpackages__", "//internal/platform:__subpackages__", - "//internal/test:__pkg__", "//location/nearby/sharing/lib:__subpackages__", "//sharing:__subpackages__", ], diff --git a/internal/base/files.cc b/internal/base/files.cc index 2c76f78e..dc94ac08 100644 --- a/internal/base/files.cc +++ b/internal/base/files.cc @@ -142,4 +142,8 @@ std::optional Files::GetAvailableDiskSpaceInBytes( return std::nullopt; } +bool Files::IsAbsolutePath(const FilePath& path) { + return path.path_.is_absolute(); +} + } // namespace nearby diff --git a/internal/base/files.h b/internal/base/files.h index bf708786..11294728 100644 --- a/internal/base/files.h +++ b/internal/base/files.h @@ -75,6 +75,11 @@ class Files { // determined. static std::optional GetAvailableDiskSpaceInBytes( const FilePath& path); + + // Returns true if the path is an absolute path. + // On Posix systems, this is a path that starts with a `/`. + // On Windows, this is a path that starts with a drive letter or a UNC path. + static bool IsAbsolutePath(const FilePath& path); }; } // namespace nearby diff --git a/internal/base/files_test.cc b/internal/base/files_test.cc index 735b025d..6d861945 100644 --- a/internal/base/files_test.cc +++ b/internal/base/files_test.cc @@ -46,5 +46,21 @@ TEST(FilesTest, CreateHardLinkSuccess) { Files::RemoveFile(target); } +#if defined(_WIN32) +TEST(FilesTest, IsAbsolutePathWindows) { + EXPECT_TRUE(Files::IsAbsolutePath(FilePath("C:\\Users\\test\\file.txt"))); + EXPECT_TRUE(Files::IsAbsolutePath(FilePath("\\\\server\\share\\file.txt"))); + EXPECT_FALSE(Files::IsAbsolutePath(FilePath("file.txt"))); + EXPECT_FALSE(Files::IsAbsolutePath(FilePath("C:Users\\test\\file.txt"))); +} +#endif + +#if defined(__linux__) || defined(__APPLE__) +TEST(FilesTest, IsAbsolutePathPosix) { + EXPECT_TRUE(Files::IsAbsolutePath(FilePath("/Users/test/file.txt"))); + EXPECT_FALSE(Files::IsAbsolutePath(FilePath("file.txt"))); +} +#endif + } // namespace } // namespace nearby diff --git a/internal/crypto_cros/aead.cc b/internal/crypto_cros/aead.cc index 451d9d5f..31773b09 100644 --- a/internal/crypto_cros/aead.cc +++ b/internal/crypto_cros/aead.cc @@ -114,7 +114,7 @@ std::optional> Aead::Open( size_t output_length; if (!Open(ciphertext, nonce, additional_data, ret.data(), &output_length, max_output_length)) { - return absl::nullopt; + return std::nullopt; } ret.resize(output_length); diff --git a/internal/crypto_cros/encryptor.cc b/internal/crypto_cros/encryptor.cc index 2081091e..8de049b3 100644 --- a/internal/crypto_cros/encryptor.cc +++ b/internal/crypto_cros/encryptor.cc @@ -176,7 +176,7 @@ std::optional Encryptor::Crypt(bool do_encrypt, if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, reinterpret_cast(key.data()), iv_.data(), do_encrypt)) { - return absl::nullopt; + return std::nullopt; } // Encrypting needs a block size of space to allow for any padding. @@ -184,13 +184,13 @@ std::optional Encryptor::Crypt(bool do_encrypt, int out_len; if (!EVP_CipherUpdate(ctx.get(), output.data(), &out_len, input.data(), input.size())) - return absl::nullopt; + return std::nullopt; // Write out the final block plus padding (if any) to the end of the data // just written. int tail_len; if (!EVP_CipherFinal_ex(ctx.get(), output.data() + out_len, &tail_len)) - return absl::nullopt; + return std::nullopt; out_len += tail_len; DCHECK_LE(out_len, static_cast(output.size())); @@ -202,13 +202,13 @@ std::optional Encryptor::CryptCTR(bool do_encrypt, absl::Span output) { if (iv_.size() != AES_BLOCK_SIZE) { LOG(ERROR) << "Counter value not set in CTR mode."; - return absl::nullopt; + return std::nullopt; } AES_KEY aes_key; if (AES_set_encrypt_key(reinterpret_cast(key_->key().data()), key_->key().size() * 8, &aes_key) != 0) { - return absl::nullopt; + return std::nullopt; } uint8_t ecount_buf[AES_BLOCK_SIZE] = {0}; diff --git a/internal/flags/nearby_flags_test.cc b/internal/flags/nearby_flags_test.cc index 0d82b07e..4ccff4ed 100644 --- a/internal/flags/nearby_flags_test.cc +++ b/internal/flags/nearby_flags_test.cc @@ -89,28 +89,27 @@ TEST(NearbyFlags, SetFlagReader) { auto flag_reader = std::make_unique<::testing::NiceMock>(); NearbyFlags::GetInstance().SetFlagReader(*flag_reader.get()); EXPECT_CALL(*flag_reader, GetBoolFlag(::testing::_)) - .WillOnce(::testing::Invoke([=](const flags::Flag& flag) { + .WillOnce([=](const flags::Flag& flag) { return kTestBoolFlagTestValue; - })); + }); EXPECT_EQ(NearbyFlags::GetInstance().GetBoolFlag(kTestBoolFlag), kTestBoolFlagTestValue); EXPECT_CALL(*flag_reader, GetInt64Flag(::testing::_)) - .WillOnce(::testing::Invoke([=](const flags::Flag& flag) { + .WillOnce([=](const flags::Flag& flag) { return kTestInt64FlagTestValue; - })); + }); EXPECT_EQ(NearbyFlags::GetInstance().GetInt64Flag(kTestInt64Flag), kTestInt64FlagTestValue); EXPECT_CALL(*flag_reader, GetDoubleFlag(::testing::_)) - .WillOnce(::testing::Invoke([=](const flags::Flag& flag) { + .WillOnce([=](const flags::Flag& flag) { return kTestDoubleFlagTestValue; - })); + }); EXPECT_EQ(NearbyFlags::GetInstance().GetDoubleFlag(kTestDoubleFlag), kTestDoubleFlagTestValue); EXPECT_CALL(*flag_reader, GetStringFlag(::testing::_)) - .WillOnce( - ::testing::Invoke([=](const flags::Flag& flag) { - return std::string(kTestStringFlagTestValue); - })); + .WillOnce([=](const flags::Flag& flag) { + return std::string(kTestStringFlagTestValue); + }); EXPECT_EQ(NearbyFlags::GetInstance().GetStringFlag(kTestStringFlag), kTestStringFlagTestValue); } diff --git a/internal/platform/BUILD b/internal/platform/BUILD index e9efd49e..634a5002 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -80,8 +80,6 @@ cc_library( "@com_google_absl//absl/container:flat_hash_set", "@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/strings:str_format", "@com_google_absl//absl/strings:string_view", @@ -323,7 +321,6 @@ cc_library( ":types", ":uuid", "//connections/implementation/flags:connections_flags", - "//internal/base", "//internal/flags:nearby_flags", "//internal/platform/implementation:comm", "//internal/platform/implementation:platform", @@ -374,7 +371,6 @@ cc_library( ":mac_address", ":types", ":uuid", - "//internal/base", "//internal/platform/implementation:comm", "//internal/platform/implementation:webrtc_platform", "//internal/platform/implementation:wifi_utils", diff --git a/internal/platform/bluetooth_classic.cc b/internal/platform/bluetooth_classic.cc index 9ad171eb..6adcd0a9 100644 --- a/internal/platform/bluetooth_classic.cc +++ b/internal/platform/bluetooth_classic.cc @@ -19,7 +19,6 @@ #include #include "absl/container/flat_hash_map.h" -#include "absl/strings/string_view.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/implementation/bluetooth_classic.h" @@ -57,11 +56,6 @@ MediumSocket* BluetoothSocket::CreateVirtualSocket( } BluetoothClassicMedium::~BluetoothClassicMedium() { - LOG(INFO) << "~BluetoothClassicMedium: observer_list_ size: " - << observer_list_.size(); - if (!observer_list_.empty()) { - impl_->RemoveObserver(this); - } StopDiscovery(); LOG(INFO) << "eof ~BluetoothClassicMedium"; } @@ -160,71 +154,4 @@ bool BluetoothClassicMedium::StopDiscovery() { return impl_->StopDiscovery(); } -void BluetoothClassicMedium::AddObserver(Observer* observer) { - LOG(INFO) << "BT AddObserver; impl=" << &GetImpl(); - MutexLock lock(&mutex_); - if (observer_list_.empty()) { - impl_->AddObserver(this); - } - observer_list_.AddObserver(observer); - LOG(INFO) << "BT AddObserver done"; -} -void BluetoothClassicMedium::RemoveObserver(Observer* observer) { - LOG(INFO) << "BT RemoveObserver; impl=" << &GetImpl(); - MutexLock lock(&mutex_); - observer_list_.RemoveObserver(observer); - if (observer_list_.empty()) { - impl_->RemoveObserver(this); - } - LOG(INFO) << "BT RemoveObserver done"; -} - -// api::BluetoothClassicMedium::Observer methods -void BluetoothClassicMedium::DeviceAdded(api::BluetoothDevice& device) { - VLOG(1) << "BT DeviceAdded; name=" << device.GetName() - << ", address=" << device.GetMacAddress().ToString(); - BluetoothDevice bt_device(&device); - for (auto* observer : observer_list_.GetObservers()) { - observer->DeviceAdded(bt_device); - } -} -void BluetoothClassicMedium::DeviceRemoved(api::BluetoothDevice& device) { - VLOG(1) << "BT DeviceRemoved; name=" << device.GetName() - << ", address=" << device.GetMacAddress().ToString(); - BluetoothDevice bt_device(&device); - for (auto* observer : observer_list_.GetObservers()) { - observer->DeviceRemoved(bt_device); - } -} -void BluetoothClassicMedium::DeviceAddressChanged( - api::BluetoothDevice& device, absl::string_view old_address) { - VLOG(1) << "BT DeviceAddressChanged; name=" << device.GetName() - << ", address=" << device.GetMacAddress().ToString() - << ", old_address=" << old_address; - BluetoothDevice bt_device(&device); - for (auto* observer : observer_list_.GetObservers()) { - observer->DeviceAddressChanged(bt_device, old_address); - } -} -void BluetoothClassicMedium::DevicePairedChanged(api::BluetoothDevice& device, - bool new_paired_status) { - VLOG(1) << "BT DevicePairedChanged; name=" << device.GetName() - << ", address=" << device.GetMacAddress().ToString() - << ", status=" << new_paired_status; - BluetoothDevice bt_device(&device); - for (auto* observer : observer_list_.GetObservers()) { - observer->DevicePairedChanged(bt_device, new_paired_status); - } -} -void BluetoothClassicMedium::DeviceConnectedStateChanged( - api::BluetoothDevice& device, bool connected) { - VLOG(1) << "BT DeviceConnectedStateChanged: name=" << device.GetName() - << ", address=" << device.GetMacAddress().ToString() - << ", connected=" << connected; - BluetoothDevice bt_device(&device); - for (auto* observer : observer_list_.GetObservers()) { - observer->DeviceConnectedStateChanged(bt_device, connected); - } -} - } // namespace nearby diff --git a/internal/platform/bluetooth_classic.h b/internal/platform/bluetooth_classic.h index a23b047a..e917827c 100644 --- a/internal/platform/bluetooth_classic.h +++ b/internal/platform/bluetooth_classic.h @@ -26,7 +26,6 @@ #include "absl/container/flat_hash_map.h" #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" -#include "internal/base/observer_list.h" #include "internal/platform/blocking_queue_stream.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/byte_array.h" @@ -157,7 +156,7 @@ class BluetoothServerSocket final { BluetoothServerSocket& operator=(const BluetoothServerSocket&) = default; ~BluetoothServerSocket() = default; explicit BluetoothServerSocket( - std::unique_ptr socket) + std::shared_ptr socket) : impl_(std::move(socket)) {} // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() @@ -223,7 +222,7 @@ class BluetoothPairing final { // Container of operations that can be performed over the Bluetooth Classic // medium. -class BluetoothClassicMedium : public api::BluetoothClassicMedium::Observer { +class BluetoothClassicMedium { public: using Platform = api::ImplementationPlatform; struct DiscoveryCallback { @@ -246,39 +245,11 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium::Observer { BluetoothDevice device; }; - class Observer { - public: - virtual ~Observer() = default; - - // Called when a new `device` is added. The `device` parameter becomes - // invalid after the call. - virtual void DeviceAdded(BluetoothDevice& device) {} - - // Called when `device` is removed. The `device` parameter becomes invalid - // after the call. - virtual void DeviceRemoved(BluetoothDevice& device) {} - - // Called when the address of `device` changed due to pairing. The - // `device` parameter becomes invalid after the call. - virtual void DeviceAddressChanged(BluetoothDevice& device, - absl::string_view old_address) {} - - // Called when the paired property of `device` changed. The `device` - // parameter becomes invalid after the call. - virtual void DevicePairedChanged(BluetoothDevice& device, - bool new_paired_status) {} - - // Called when `device` has connected or disconnected. The `device` - // parameter becomes invalid after the call. - virtual void DeviceConnectedStateChanged(BluetoothDevice& device, - bool connected) {} - }; - explicit BluetoothClassicMedium(BluetoothAdapter& adapter) : impl_(Platform::CreateBluetoothClassicMedium(adapter.GetImpl())), adapter_(adapter) {} - ~BluetoothClassicMedium() override; + virtual ~BluetoothClassicMedium(); // NOTE(DiscoveryCallback): // BluetoothDevice is a proxy object created as a result of BT discovery. @@ -353,23 +324,6 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium::Observer { return BluetoothDevice(impl_->GetRemoteDevice(mac_address)); } - // Adds an observer. `observer` must be valid until RemoveObserver is called, - // or BluetoothClassicMedium is destroyed. - void AddObserver(Observer* observer); - - // Removes an observer. It's OK to remove an unregistered observer. - void RemoveObserver(Observer* observer); - - // api::BluetoothClassicMedium::Observer methods - void DeviceAdded(api::BluetoothDevice& device) override; - void DeviceRemoved(api::BluetoothDevice& device) override; - void DeviceAddressChanged(api::BluetoothDevice& device, - absl::string_view old_address) override; - void DevicePairedChanged(api::BluetoothDevice& device, - bool new_paired_status) override; - void DeviceConnectedStateChanged(api::BluetoothDevice& device, - bool connected) override; - private: Mutex mutex_; std::unique_ptr impl_; @@ -379,7 +333,6 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium::Observer { devices_ ABSL_GUARDED_BY(mutex_); DiscoveryCallback discovery_callback_ ABSL_GUARDED_BY(mutex_); bool discovery_enabled_ ABSL_GUARDED_BY(mutex_) = false; - ObserverList observer_list_; }; } // namespace nearby diff --git a/internal/platform/bluetooth_classic_test.cc b/internal/platform/bluetooth_classic_test.cc index f9cdae73..fd244c64 100644 --- a/internal/platform/bluetooth_classic_test.cc +++ b/internal/platform/bluetooth_classic_test.cc @@ -49,39 +49,6 @@ constexpr FeatureFlags kTestCases[] = { }, }; -class BluetoothClassicMediumObserver - : public BluetoothClassicMedium ::Observer { - public: - explicit BluetoothClassicMediumObserver( - CountDownLatch* device_added_latch, CountDownLatch* device_removed_latch, - CountDownLatch* device_paired_changed_latch) - : device_added_latch_(device_added_latch), - device_removed_latch_(device_removed_latch), - device_paired_changed_latch_(device_paired_changed_latch) {} - - void DeviceAdded(BluetoothDevice& device) override { - if (!device_added_latch_) return; - device_added_latch_->CountDown(); - } - - void DeviceRemoved(BluetoothDevice& device) override { - if (!device_removed_latch_) return; - device_removed_latch_->CountDown(); - } - - void DevicePairedChanged(BluetoothDevice& device, - bool new_paired_status) override { - if (!device_paired_changed_latch_) return; - paired_status_ = new_paired_status; - device_paired_changed_latch_->CountDown(); - } - - CountDownLatch* device_added_latch_; - CountDownLatch* device_removed_latch_; - CountDownLatch* device_paired_changed_latch_; - bool paired_status_ = false; -}; - class BluetoothClassicMediumTest : public ::testing::TestWithParam { protected: @@ -341,11 +308,6 @@ TEST_F(BluetoothClassicMediumTest, CanStartDiscovery) { adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); CountDownLatch found_latch(1); CountDownLatch lost_latch(1); - CountDownLatch device_added_latch(1); - CountDownLatch device_removed_latch(1); - BluetoothClassicMediumObserver observer(&device_added_latch, - &device_removed_latch, nullptr); - bt_a_->AddObserver(&observer); bt_a_->StartDiscovery(DiscoveryCallback{ .device_discovered_cb = @@ -365,21 +327,15 @@ TEST_F(BluetoothClassicMediumTest, CanStartDiscovery) { EXPECT_EQ(adapter_b_->GetScanMode(), BluetoothAdapter::ScanMode::kConnectableDiscoverable); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); - EXPECT_TRUE(device_added_latch.Await(absl::Milliseconds(1000)).result()); adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); EXPECT_FALSE(adapter_b_->IsEnabled()); EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result()); - EXPECT_TRUE(device_removed_latch.Await(absl::Milliseconds(1000)).result()); } TEST_F(BluetoothClassicMediumTest, DiscoveryCallbackAfterStopDiscovery) { SingleThreadExecutor executor; adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); CountDownLatch found_latch(1); - CountDownLatch device_added_latch(1); - BluetoothClassicMediumObserver observer(&device_added_latch, nullptr, - nullptr); - bt_a_->AddObserver(&observer); bt_a_->StartDiscovery(DiscoveryCallback{ .device_discovered_cb = @@ -397,7 +353,6 @@ TEST_F(BluetoothClassicMediumTest, DiscoveryCallbackAfterStopDiscovery) { BluetoothAdapter::ScanMode::kConnectableDiscoverable); bt_a_->StopDiscovery(); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); - EXPECT_TRUE(device_added_latch.Await(absl::Milliseconds(1000)).result()); executor.Shutdown(); } @@ -491,10 +446,6 @@ TEST_F(BluetoothClassicMediumTest, BluetoothPairingSuccess) { CountDownLatch paired_latch(1); CountDownLatch initiated_latch(1); CountDownLatch error_latch(1); - CountDownLatch device_paired_latch(1); - BluetoothClassicMediumObserver observer(nullptr, nullptr, - &device_paired_latch); - bt_a_->AddObserver(&observer); EXPECT_TRUE(bluetooth_pairing->InitiatePairing({ .on_paired_cb = [&]() { paired_latch.CountDown(); }, .on_pairing_error_cb = @@ -520,8 +471,6 @@ TEST_F(BluetoothClassicMediumTest, BluetoothPairingSuccess) { // Finishes pairing with remote device. EXPECT_TRUE(bluetooth_pairing->FinishPairing(received_passkey)); paired_latch.Await(); - device_paired_latch.Await(); - EXPECT_TRUE(observer.paired_status_); EXPECT_TRUE(bluetooth_pairing->IsPaired()); // Unpairs with remote device. diff --git a/internal/platform/implementation/BUILD b/internal/platform/implementation/BUILD index c0c94231..1e7612a9 100644 --- a/internal/platform/implementation/BUILD +++ b/internal/platform/implementation/BUILD @@ -190,6 +190,9 @@ cc_library( ], deps = [ ] + select({ + "@platforms//os:platform_macos": [ + "//internal/platform/implementation/apple", + ], "@platforms//os:windows": [ "//internal/platform/implementation/windows", ], diff --git a/internal/platform/implementation/apple/BUILD b/internal/platform/implementation/apple/BUILD index ec25ff43..897d9546 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -221,6 +221,7 @@ objc_library( "//internal/platform/implementation/apple/Mediums/BLE/Sockets:Peripheral", "//third_party/apple_frameworks:CoreBluetooth", "//third_party/apple_frameworks:Foundation", + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings", diff --git a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.m b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.m index 5bfc6d5c..f9ff6d86 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.m +++ b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.m @@ -386,6 +386,10 @@ static const int kMaxAdvertisementLengthOnIOS = 23; [_peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound]; return; } + if (request.offset > value.length) { + [_peripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset]; + return; + } request.value = [value subdataWithRange:NSMakeRange(request.offset, value.length - request.offset)]; [_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess]; diff --git a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPConnection.m b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPConnection.m index cbf9f013..15056414 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPConnection.m +++ b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPConnection.m @@ -24,6 +24,7 @@ #import "internal/platform/implementation/apple/Mediums/BLE/GNCMConnection.h" enum { kL2CAPPacketLength = 4 }; +static const NSUInteger kGNCBLEL2CAPMaxFrameLength = 5 * 1024 * 1024; // 5 MB static const CGFloat kRequestDataConnectionDelayInSeconds = 0.0; static const UInt8 kRequestDataConnectionTimeoutInSeconds = 5; @@ -266,6 +267,20 @@ static NSData *PrefixLengthData(NSData *data) { } _expectedDataLength = CFSwapInt32BigToHost( *(int *)([[data subdataWithRange:NSMakeRange(0, kL2CAPPacketLength)] bytes])); + if (_expectedDataLength == 0 || _expectedDataLength > kGNCBLEL2CAPMaxFrameLength) { + GNCLoggerError(@"[NEARBY] Rejecting L2CAP frame: declared length %lu out of range " + @"(max %lu); closing.", + (unsigned long)_expectedDataLength, + (unsigned long)kGNCBLEL2CAPMaxFrameLength); + _expectedDataLength = 0; + [_stream close]; + if (_connectionHandlers.disconnectedHandler) { + dispatch_async(_callbackQueue, ^{ + _connectionHandlers.disconnectedHandler(); + }); + } + return nil; + } } NSUInteger realDataLength = data.length - kL2CAPPacketLength; if (realDataLength < _expectedDataLength) { diff --git a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.m b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.m index a9ab14ee..47ae7429 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.m +++ b/internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.m @@ -74,6 +74,9 @@ enum { READ_BUFFER_SIZE = 409600 }; /// Whether the stream is closed. BOOL _closed; + + /// Buffer for reading data from the input stream. + uint8_t _readBuffer[READ_BUFFER_SIZE]; } #pragma mark Public @@ -319,12 +322,11 @@ enum { READ_BUFFER_SIZE = 409600 }; /// Receives data from device and invokes |_receivedDataBlock|. - (void)receiveStreamData { dispatch_assert_queue_debug(_streamQueue); - uint8_t readBuffer[READ_BUFFER_SIZE]; - NSInteger bytesRead = [self.inputStream read:readBuffer maxLength:READ_BUFFER_SIZE]; + NSInteger bytesRead = [self.inputStream read:_readBuffer maxLength:READ_BUFFER_SIZE]; if (bytesRead > 0) { NSMutableData *data = [NSMutableData data]; - [data appendBytes:readBuffer length:(NSUInteger)bytesRead]; + [data appendBytes:_readBuffer length:(NSUInteger)bytesRead]; if (_verboseLoggingEnabled) { GNCLoggerDebug(@"[NEARBY] Stream data from device of length %@", @(data.length)); diff --git a/internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.m b/internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.m index 7d41ebe0..9f96b95d 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.m +++ b/internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.m @@ -140,7 +140,11 @@ NS_ASSUME_NONNULL_BEGIN return; } - if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) { + // IntroductionFrame.service_id_hash. We MUST bounds-check before + // -subdataWithRange:, otherwise a short follow-up packet throws + // NSRangeException on CoreBluetooth's dispatch queue -> objc_terminate. + if (data.length < prefixLength || + ![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) { GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded"); return; } diff --git a/internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.mm b/internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.mm index c582ff11..fda04cf6 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.mm +++ b/internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.mm @@ -78,6 +78,11 @@ NSData *_Nullable GNCMParseBLEFramesIntroductionPacket(NSData *_Nullable data) { ::location::nearby::mediums::SocketVersion::V2 && socket_control_frame.introduction().has_service_id_hash()) { std::string service_id_hash = socket_control_frame.introduction().service_id_hash(); + // service_id_hash is attacker-supplied; clamp to the protocol-defined + // 3-byte length so it cannot be used to inflate prefixLength downstream. + if (service_id_hash.size() != GNCMBleAdvertisementLengthServiceIDHash) { + return nil; + } return [NSData dataWithBytes:service_id_hash.data() length:service_id_hash.length()]; } } diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m index af6e7e8b..607a33ec 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m @@ -111,15 +111,17 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f; - (void)addPeripheralServiceManager:(GNSPeripheralServiceManager *)peripheralServiceManager bleServiceAddedCompletion:(GNSErrorHandler)completion { - [_peripheralServiceManagers setObject:peripheralServiceManager - forKey:peripheralServiceManager.serviceUUID]; - [peripheralServiceManager addedToPeripheralManager:self bleServiceAddedCompletion:completion]; - if (_started) { - [self addBleServiceForServiceManager:peripheralServiceManager]; - } - // Update all advertised services to make sure that the right services are advertised in case - // all BLE services were already added. - [self updateAdvertisedServices]; + dispatch_async(_queue, ^{ + [self->_peripheralServiceManagers setObject:peripheralServiceManager + forKey:peripheralServiceManager.serviceUUID]; + [peripheralServiceManager addedToPeripheralManager:self bleServiceAddedCompletion:completion]; + if (self->_started) { + [self addBleServiceForServiceManager:peripheralServiceManager]; + } + // Update all advertised services to make sure that the right services are advertised in case + // all BLE services were already added. + [self updateAdvertisedServices]; + }); } - (void)start { @@ -211,18 +213,24 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f; - (void)removePeripheralServiceManagerForServiceUUID:(CBUUID *)serviceUUID bleServiceRemovedCompletion:(GNSErrorHandler)completion { - GNSPeripheralServiceManager *peripheralServiceManager = - [_peripheralServiceManagers objectForKey:serviceUUID]; - if (peripheralServiceManager == nil) { - completion(nil); - return; - } - [_cbPeripheralManager removeService:peripheralServiceManager.cbService]; - [_peripheralServiceManagers removeObjectForKey:serviceUUID]; - [peripheralServiceManager didRemoveCBService]; + dispatch_async(_queue, ^{ + GNSPeripheralServiceManager *peripheralServiceManager = + [self->_peripheralServiceManagers objectForKey:serviceUUID]; + if (peripheralServiceManager == nil) { + if (completion) { + completion(nil); + } + return; + } + [self->_cbPeripheralManager removeService:peripheralServiceManager.cbService]; + [self->_peripheralServiceManagers removeObjectForKey:serviceUUID]; + [peripheralServiceManager didRemoveCBService]; - [self updateAdvertisedServices]; - completion(nil); + [self updateAdvertisedServices]; + if (completion) { + completion(nil); + } + }); } - (void)removeAllBleServices { diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEGATTServerTest.mm b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEGATTServerTest.mm index 8e4882fd..4805c163 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEGATTServerTest.mm +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEGATTServerTest.mm @@ -29,6 +29,7 @@ static NSString *const kServiceUUID1 = @"0000FEF3-0000-1000-8000-00805F9B34FB"; static NSString *const kServiceUUID2 = @"0000FEF4-0000-1000-8000-00805F9B34FB"; static NSString *const kCharacteristicUUID1 = @"00000000-0000-3000-8000-000000000000"; static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-000000000001"; +static const NSTimeInterval kTestTimeout = 3.0; @interface GNCBLEGATTServerTest : XCTestCase @end @@ -92,28 +93,28 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer - createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - XCTAssertNotNil(characteristic); - XCTAssertNil(error); - XCTAssertEqual(fakePeripheralManager.services.count, 1); - XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1); - [expectation fulfill]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + XCTAssertNotNil(characteristic); + XCTAssertNil(error); + XCTAssertEqual(fakePeripheralManager.services.count, 1); + XCTAssertEqual( + fakePeripheralManager.services[0].characteristics.count, 1); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -132,41 +133,41 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation1 = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic 1."]; - XCTestExpectation *expectation2 = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic 2."]; + XCTestExpectation *expectation1 = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic 1."]; + XCTestExpectation *expectation2 = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic 2."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; - CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID1 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - XCTAssertNotNil(characteristic); - XCTAssertNil(error); - [expectation1 fulfill]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; + CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID1 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + XCTAssertNotNil(characteristic); + XCTAssertNil(error); + [expectation1 fulfill]; + }]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID2 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - XCTAssertNotNil(characteristic); - XCTAssertNil(error); - [expectation2 fulfill]; - }]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID2 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + XCTAssertNotNil(characteristic); + XCTAssertNil(error); + [expectation2 fulfill]; + }]; - [self waitForExpectations:@[ expectation1, expectation2 ] timeout:3]; - XCTAssertEqual(fakePeripheralManager.services.count, 1); - XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 2); + [self waitForExpectations:@[ expectation1, expectation2 ] timeout:kTestTimeout]; + XCTAssertEqual(fakePeripheralManager.services.count, 1); + XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 2); } } @@ -185,33 +186,33 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID1 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:nil]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID1 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:nil]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID1 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - XCTAssertNil(characteristic); - XCTAssertNotNil(error); - [expectation fulfill]; - }]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID1 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + XCTAssertNil(characteristic); + XCTAssertNotNil(error); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; - XCTAssertEqual(fakePeripheralManager.services.count, 1); - XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1); + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; + XCTAssertEqual(fakePeripheralManager.services.count, 1); + XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1); } } @@ -230,29 +231,29 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID1 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:nil]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID1 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:nil]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID1 - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - XCTAssertNil(characteristic); - XCTAssertNotNil(error); - [expectation fulfill]; - }]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID1 + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + XCTAssertNil(characteristic); + XCTAssertNotNil(error); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -271,25 +272,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - // The error occurs after completion, so its expected to fail - // silently. - XCTAssertNotNil(characteristic); - XCTAssertNil(error); - [expectation fulfill]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + // The error occurs after completion, so its expected to fail + // silently. + XCTAssertNotNil(characteristic); + XCTAssertNil(error); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -308,28 +309,30 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - fakePeripheralManager.didAddServiceError = [NSError errorWithDomain:@"fake" code:0 userInfo:nil]; - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + fakePeripheralManager.didAddServiceError = [NSError errorWithDomain:@"fake" + code:0 + userInfo:nil]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - // The error occurs after completion, so its expected to fail - // silently. - XCTAssertNotNil(characteristic); - XCTAssertNil(error); - [expectation fulfill]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + // The error occurs after completion, so its expected to fail + // silently. + XCTAssertNotNil(characteristic); + XCTAssertNil(error); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -350,34 +353,34 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - [gattServer updateCharacteristic:characteristic - value:[NSData data] - completionHandler:^(NSError *error) { - [expectation fulfill]; - }]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + [gattServer updateCharacteristic:characteristic + value:[NSData data] + completionHandler:^(NSError *error) { + [expectation fulfill]; + }]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; - [fakePeripheralManager - simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID - characteristic:characteristicUUID]; + [fakePeripheralManager + simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID + characteristic:characteristicUUID]; - [self waitForExpectations:@[ fakePeripheralManager.respondToRequestSuccessExpectation ] - timeout:3]; + [self waitForExpectations:@[ fakePeripheralManager.respondToRequestSuccessExpectation ] + timeout:kTestTimeout]; } } @@ -396,35 +399,36 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - [gattServer updateCharacteristic:characteristic - value:[NSData data] - completionHandler:^(NSError *error) { - [expectation fulfill]; - }]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + [gattServer updateCharacteristic:characteristic + value:[NSData data] + completionHandler:^(NSError *error) { + [expectation fulfill]; + }]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; - CBUUID *invalidCharacteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID2]; + CBUUID *invalidCharacteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID2]; - [fakePeripheralManager - simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID - characteristic:invalidCharacteristicUUID]; + [fakePeripheralManager + simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID + characteristic:invalidCharacteristicUUID]; - [self waitForExpectations:@[ fakePeripheralManager.respondToRequestErrorExpectation ] timeout:3]; + [self waitForExpectations:@[ fakePeripheralManager.respondToRequestErrorExpectation ] + timeout:kTestTimeout]; } } @@ -443,39 +447,140 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create characteristic."]; - CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; - [gattServer createCharacteristicWithServiceID:serviceUUID - characteristicUUID:characteristicUUID - permissions:CBAttributePermissionsReadable - properties:CBCharacteristicPropertyRead - completionHandler:^(GNCBLEGATTCharacteristic *characteristic, - NSError *error) { - [expectation fulfill]; - }]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; - CBUUID *invalidServiceUUID = [CBUUID UUIDWithString:kServiceUUID2]; + CBUUID *invalidServiceUUID = [CBUUID UUIDWithString:kServiceUUID2]; - // Expectation that we *should not* receive a response. - fakePeripheralManager.respondToRequestSuccessExpectation.inverted = YES; - fakePeripheralManager.respondToRequestErrorExpectation.inverted = YES; + // Expectation that we *should not* receive a response. + fakePeripheralManager.respondToRequestSuccessExpectation.inverted = YES; + fakePeripheralManager.respondToRequestErrorExpectation.inverted = YES; - [fakePeripheralManager - simulatePeripheralManagerDidReceiveReadRequestForService:invalidServiceUUID - characteristic:characteristicUUID]; + [fakePeripheralManager + simulatePeripheralManagerDidReceiveReadRequestForService:invalidServiceUUID + characteristic:characteristicUUID]; - [self waitForExpectations:@[ - fakePeripheralManager.respondToRequestSuccessExpectation, - fakePeripheralManager.respondToRequestErrorExpectation - ] - timeout:3]; + [self waitForExpectations:@[ + fakePeripheralManager.respondToRequestSuccessExpectation, + fakePeripheralManager.respondToRequestErrorExpectation + ] + timeout:kTestTimeout]; + } +} + +- (void)testReadRequestInvalidOffset { + for (NSNumber *enabled in @[ @NO, @YES ]) { + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::connections::config_package_nearby::nearby_connections_feature:: + kEnableSharedPeripheralManager, + enabled.boolValue); + GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init]; + + GNCBLEGATTServer *gattServer = + [[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager + queue:dispatch_get_main_queue()]; + if (enabled.boolValue) { + fakePeripheralManager.peripheralDelegate = gattServer; + } + + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + char bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + NSData *valueData = [NSData dataWithBytes:bytes length:sizeof(bytes)]; + + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + [gattServer updateCharacteristic:characteristic + value:valueData + completionHandler:^(NSError *error) { + [expectation fulfill]; + }]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; + + [fakePeripheralManager + simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID + characteristic:characteristicUUID + offset:6]; + + [self waitForExpectations:@[ fakePeripheralManager.respondToRequestErrorExpectation ] + timeout:kTestTimeout]; + XCTAssertEqual(fakePeripheralManager.lastResponseResult, CBATTErrorInvalidOffset); + } +} + +- (void)testReadRequestOffsetEqualLength { + for (NSNumber *enabled in @[ @NO, @YES ]) { + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::connections::config_package_nearby::nearby_connections_feature:: + kEnableSharedPeripheralManager, + enabled.boolValue); + GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init]; + + GNCBLEGATTServer *gattServer = + [[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager + queue:dispatch_get_main_queue()]; + if (enabled.boolValue) { + fakePeripheralManager.peripheralDelegate = gattServer; + } + + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1]; + CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1]; + char bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + NSData *valueData = [NSData dataWithBytes:bytes length:sizeof(bytes)]; + + [gattServer createCharacteristicWithServiceID:serviceUUID + characteristicUUID:characteristicUUID + permissions:CBAttributePermissionsReadable + properties:CBCharacteristicPropertyRead + completionHandler:^(GNCBLEGATTCharacteristic *characteristic, + NSError *error) { + [gattServer updateCharacteristic:characteristic + value:valueData + completionHandler:^(NSError *error) { + [expectation fulfill]; + }]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; + + [fakePeripheralManager + simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID + characteristic:characteristicUUID + offset:5]; + + [self waitForExpectations:@[ fakePeripheralManager.respondToRequestSuccessExpectation ] + timeout:kTestTimeout]; } } @@ -519,19 +624,19 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{} - completionHandler:^(NSError *error) { - XCTAssertNotNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{} + completionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -550,23 +655,23 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF3"]); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -585,25 +690,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{ - [CBUUID UUIDWithString:@"FEF3"] : [@"0123" dataUsingEncoding:NSUTF8StringEncoding], - } - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMw"); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF3"]); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{ + [CBUUID UUIDWithString:@"FEF3"] : [@"0123" dataUsingEncoding:NSUTF8StringEncoding], + } + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMw"); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -622,26 +727,26 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{ - [CBUUID UUIDWithString:@"FEF3"] : [@"012345678901234" dataUsingEncoding:NSUTF8StringEncoding], - } - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], - @"MDEyMzQ1Njc4OTAxMjM0"); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF3"]); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{ + [CBUUID UUIDWithString:@"FEF3"] : [@"012345678901234" dataUsingEncoding:NSUTF8StringEncoding], + } + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], + @"MDEyMzQ1Njc4OTAxMjM0"); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -660,23 +765,22 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer - startAdvertisingData:@{ - [CBUUID UUIDWithString:@"FEF3"] : - [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], - } - completionHandler:^(NSError *error) { - XCTAssertNotNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{ + [CBUUID UUIDWithString:@"FEF3"] : + [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], + } + completionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -695,26 +799,26 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer - startAdvertisingData:@{ - [CBUUID UUIDWithString:@"FEF3"] : [@"😁❤️🤡" dataUsingEncoding:NSUTF8StringEncoding], - } - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"8J-YgeKdpO-4j_CfpKE"); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF3"]); - [expectation fulfill]; - }]; + [gattServer + startAdvertisingData:@{ + [CBUUID UUIDWithString:@"FEF3"] : [@"😁❤️🤡" dataUsingEncoding:NSUTF8StringEncoding], + } + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"8J-YgeKdpO-4j_CfpKE"); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -733,24 +837,24 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{ - [CBUUID UUIDWithString:@"FEF3"] : - [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], - [CBUUID UUIDWithString:@"FEF4"] : - [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], - } - completionHandler:^(NSError *error) { - XCTAssertNotNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{ + [CBUUID UUIDWithString:@"FEF3"] : + [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], + [CBUUID UUIDWithString:@"FEF4"] : + [@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding], + } + completionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -769,18 +873,18 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} - completionHandler:^(NSError *error) { - // The error occurs after completion, so its expected to fail silently. - XCTAssertNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:^(NSError *error) { + // The error occurs after completion, so its expected to fail silently. + XCTAssertNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -799,23 +903,23 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake" - code:0 - userInfo:nil]; - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake" + code:0 + userInfo:nil]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} - completionHandler:^(NSError *error) { - // The error occurs after completion, so its expected to fail silently. - XCTAssertNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:^(NSError *error) { + // The error occurs after completion, so its expected to fail silently. + XCTAssertNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -834,25 +938,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake" - code:0 - userInfo:nil]; - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake" + code:0 + userInfo:nil]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} - completionHandler:nil]; + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:nil]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]} - completionHandler:^(NSError *error) { - XCTAssertNotNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [expectation fulfill]; - }]; + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]} + completionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [expectation fulfill]; + }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @@ -871,41 +975,39 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 fakePeripheralManager.peripheralDelegate = gattServer; } - [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; - XCTestExpectation *expectation = - [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; - [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF3"]); - [gattServer stopAdvertisingWithCompletionHandler:^(NSError *error) { + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:^(NSError *error) { XCTAssertNil(error); - XCTAssertFalse(fakePeripheralManager.isAdvertising); - [gattServer - startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]} - completionHandler:^(NSError *error) { - XCTAssertNil(error); - XCTAssertTrue(fakePeripheralManager.isAdvertising); - NSDictionary *data = - fakePeripheralManager.advertisementData; - XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); - XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], - [CBUUID UUIDWithString:@"FEF4"]); - [expectation fulfill]; - }]; + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [gattServer stopAdvertisingWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [gattServer + startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]} + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = + fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF4"]); + [expectation fulfill]; + }]; + }]; }]; - }]; - [self waitForExpectations:@[ expectation ] timeout:3]; + [self waitForExpectations:@[ expectation ] timeout:kTestTimeout]; } } @end - - diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPConnectionTest.m b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPConnectionTest.m index 37e5793d..d2384bbd 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPConnectionTest.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPConnectionTest.m @@ -140,6 +140,32 @@ static const NSTimeInterval kTestTimeout = 1.0; XCTAssertNil(realData); } +- (void)testExtractRealDataFromData_oversizedData { + _connection = [self createConnectionWithIncoming:YES]; + + XCTestExpectation *disconnectionExpectation = + [self expectationWithDescription:@"disconnection handler"]; + _connection.connectionHandlers = [GNCMConnectionHandlers + payloadHandler:^(NSData *data) { + XCTFail(@"Unexpected payload"); + } + disconnectedHandler:^{ + [disconnectionExpectation fulfill]; + }]; + + // 6 MB frame length + uint32_t oversizedLength = 6 * 1024 * 1024; + uint32_t lengthBigEndian = CFSwapInt32HostToBig(oversizedLength); + NSMutableData *prefixData = [NSMutableData dataWithCapacity:sizeof(uint32_t)]; + [prefixData appendBytes:&lengthBigEndian length:sizeof(uint32_t)]; + + NSData *realData = [_connection extractRealDataFromData:prefixData]; + XCTAssertNil(realData); + XCTAssertEqual(_connection.expectedDataLength, 0); + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + - (void)testExtractRealDataFromData_moreThanExpectedData { _connection = [self createConnectionWithIncoming:YES]; NSData *testData = [self createDataWithLength:10]; diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h index 9337a2fc..ed37d2ca 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h @@ -47,6 +47,9 @@ NS_ASSUME_NONNULL_BEGIN /** Expectation fulfilled when peripheral responds to a request with an error. */ @property(nonatomic, readonly) XCTestExpectation *respondToRequestErrorExpectation; +/** The last response result. */ +@property(nonatomic, assign) CBATTError lastResponseResult; + /** Expectation fulfilled when peripheral unpublishes an L2CAP channel. */ @property(nonatomic, readonly) XCTestExpectation *unpublishExpectation; @@ -117,6 +120,20 @@ NS_ASSUME_NONNULL_BEGIN - (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service characteristic:(CBUUID *)characteristic; +/** + * Simulates a read request event with an offset. + * + * Creates a fake read request with the given offset for the given service and characteristic UUIDs + * and calls the @c gnc_peripheralManager:didReceiveReadRequest: delegate method. + * + * @param service The service UUID of the characteristic to read from. + * @param characteristic The characteristic UUID to read from. + * @param offset The offset to read from. + */ +- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service + characteristic:(CBUUID *)characteristic + offset:(NSUInteger)offset; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.m b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.m index 3f18bf6a..6e95327e 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.m @@ -37,6 +37,9 @@ // Keep a strong reference to the service. @property(readwrite, nonatomic) CBService *service; +// Change property to readwrite for tests. +@property(readwrite, nonatomic) NSUInteger offset; + - (instancetype)initWithService:(CBUUID *)service characteristic:(CBUUID *)characteristic; @end @@ -115,6 +118,7 @@ static const uint16_t kPSM = 192; } - (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result { + self.lastResponseResult = result; if (result == CBATTErrorSuccess) { [_respondToRequestSuccessExpectation fulfill]; return; @@ -176,6 +180,15 @@ static const uint16_t kPSM = 192; [_peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request]; } +- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service + characteristic:(CBUUID *)characteristic + offset:(NSUInteger)offset { + CBATTRequest *request = [[CBATTRequest alloc] initWithService:service + characteristic:characteristic]; + request.offset = offset; + [_peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request]; +} + - (void)setDelegate:(id)delegate { self.peripheralDelegate = (id)delegate; } diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleConnectionTest.m b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleConnectionTest.m index a382ee08..fc02cec0 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleConnectionTest.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleConnectionTest.m @@ -241,4 +241,33 @@ static const NSTimeInterval kTimeout = 1.0; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } +- (void)testReceiveShortDataPacketAfterIntro { + _connection = [GNCMBleConnection connectionWithSocket:(GNSSocket *)_fakeSocket + serviceID:nil + expectedIntroPacket:YES + callbackQueue:_callbackQueue]; + + NSData *introPacket = GNCMGenerateBLEFramesIntroductionPacket(GNCMServiceIDHash(kServiceID)); + + // Receive the intro packet first to set `_serviceIDHash`. + [_fakeSocket simulateSocketDidReceiveData:introPacket]; + + // Receive a data packet that is shorter than the service ID hash length. + // This should not crash; it should just be discarded. + NSData *shortPacket = [@"ab" dataUsingEncoding:NSUTF8StringEncoding]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Payload handler not called"]; + expectation.inverted = YES; + + GNCMConnectionHandlers *handlers = [[GNCMConnectionHandlers alloc] init]; + handlers.payloadHandler = ^(NSData *data) { + [expectation fulfill]; + }; + _connection.connectionHandlers = handlers; + + [_fakeSocket simulateSocketDidReceiveData:shortPacket]; + + [self waitForExpectationsWithTimeout:kTimeout handler:nil]; +} + @end diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleUtilsTest.m b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleUtilsTest.m index 11ca0c50..480d0071 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleUtilsTest.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMBleUtilsTest.m @@ -45,6 +45,18 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5 XCTAssertEqualObjects(parsedHash, serviceIDHash); } +- (void)testParseBLEFramesIntroductionPacketFailure_InvalidHashLength { + // Too long hash (4 bytes, protocol expects 3 bytes) + NSData *longHash = [@"1234" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *longPacket = GNCMGenerateBLEFramesIntroductionPacket(longHash); + XCTAssertNil(GNCMParseBLEFramesIntroductionPacket(longPacket)); + + // Too short hash (2 bytes, protocol expects 3 bytes) + NSData *shortHash = [@"12" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *shortPacket = GNCMGenerateBLEFramesIntroductionPacket(shortHash); + XCTAssertNil(GNCMParseBLEFramesIntroductionPacket(shortPacket)); +} + - (void)testParseBLEFramesIntroductionPacketFailure_NilData { NSData *parsedHash = GNCMParseBLEFramesIntroductionPacket(nil); XCTAssertNil(parsedHash); diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWBrowseResultImpl.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWBrowseResultImpl.m index 519b5a19..48d4b630 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWBrowseResultImpl.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWBrowseResultImpl.m @@ -57,7 +57,10 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)getBonjourServiceNameFromEndpoint:(nw_endpoint_t)endpoint { const char *name = nw_endpoint_get_bonjour_service_name(endpoint); - return name ? @(name) : nil; + if (name == NULL) return nil; + // @() returns nil on non-UTF-8 input; the wire format does not guarantee UTF-8. + // Round-trip through Latin-1 so callers always get a non-nil NSString. + return @(name) ?: [NSString stringWithCString:name encoding:NSISOLatin1StringEncoding]; } @end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m index 7668a6a1..dc52494c 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m @@ -63,11 +63,14 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu block:^bool(const char *key, const nw_txt_record_find_key_t found, const uint8_t *value, const size_t value_len) { if (found == nw_txt_record_find_key_non_empty_value) { + NSString *keyString = @(key); NSString *valueString = [[NSString alloc] initWithBytes:value length:value_len encoding:NSUTF8StringEncoding]; - [txtRecords setValue:valueString forKey:@(key)]; + if (keyString != nil && valueString != nil) { + [txtRecords setObject:valueString forKey:keyString]; + } } return YES; }]; @@ -231,6 +234,11 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu [browseResultWrapper copyEndpointFromResult:new_result]; NSString *name = [browseResultWrapper getBonjourServiceNameFromEndpoint:endpoint]; + if (name == nil) { + GNCLoggerInfo( + @"Dropping mDNS result with unrepresentable name."); + break; + } NSDictionary *txtRecords = GNCTXTRecordForBrowseResult(new_result); serviceFoundHandler(name, txtRecords); @@ -250,6 +258,11 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu [browseResultWrapper copyEndpointFromResult:old_result]; NSString *oldName = [browseResultWrapper getBonjourServiceNameFromEndpoint:old_endpoint]; + if (oldName == nil) { + GNCLoggerInfo( + @"Dropping mDNS result with unrepresentable old name."); + break; + } NSDictionary *oldTXTRecords = GNCTXTRecordForBrowseResult(old_result); serviceLostHandler(oldName, oldTXTRecords); @@ -258,6 +271,11 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu [browseResultWrapper copyEndpointFromResult:new_result]; NSString *newName = [browseResultWrapper getBonjourServiceNameFromEndpoint:new_endpoint]; + if (newName == nil) { + GNCLoggerInfo( + @"Dropping mDNS result with unrepresentable new name."); + break; + } NSDictionary *newTXTRecords = GNCTXTRecordForBrowseResult(new_result); serviceFoundHandler(newName, newTXTRecords); @@ -276,6 +294,11 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu [browseResultWrapper copyEndpointFromResult:old_result]; NSString *name = [browseResultWrapper getBonjourServiceNameFromEndpoint:endpoint]; + if (name == nil) { + GNCLoggerInfo( + @"Dropping mDNS result with unrepresentable name."); + break; + } NSDictionary *txtRecords = GNCTXTRecordForBrowseResult(old_result); serviceLostHandler(name, txtRecords); diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.h index 1d86c517..a09119ff 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.h @@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) nw_browse_result_change_t getChangesFromResult; @property(nonatomic, nullable) nw_endpoint_t endpointFromResultResult; @property(nonatomic, nullable) NSString *getBonjourServiceNameFromEndpointResult; +@property(nonatomic) BOOL returnNilServiceName; @end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.m index 2355b6c2..11a8b3c0 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWBrowseResult.m @@ -67,6 +67,9 @@ NS_ASSUME_NONNULL_BEGIN } - (nullable NSString *)getBonjourServiceNameFromEndpoint:(nw_endpoint_t)endpoint { + if (self.returnNilServiceName) { + return nil; + } return self.getBonjourServiceNameFromEndpointResult ?: @"FakeService"; } diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h index 4d56fbca..eb827f36 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h @@ -53,16 +53,16 @@ NS_ASSUME_NONNULL_BEGIN * @param serviceName The name of the service found. * @param txtRecords The TXT records of the service found. */ -- (void)triggerServiceFound:(NSString*)serviceName - txtRecords:(NSDictionary*)txtRecords; +- (void)triggerServiceFound:(nullable NSString*)serviceName + txtRecords:(nullable NSDictionary*)txtRecords; /** * Triggers the service lost handler with the given service info. * * @param serviceName The name of the service lost. * @param txtRecords The TXT records of the service lost. */ -- (void)triggerServiceLost:(NSString*)serviceName - txtRecords:(NSDictionary*)txtRecords; +- (void)triggerServiceLost:(nullable NSString*)serviceName + txtRecords:(nullable NSDictionary*)txtRecords; @end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m index 76f7eb2f..e94a05f0 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m @@ -122,15 +122,15 @@ return serverSocket; } -- (void)triggerServiceFound:(NSString *)serviceName - txtRecords:(NSDictionary *)txtRecords { +- (void)triggerServiceFound:(nullable NSString *)serviceName + txtRecords:(nullable NSDictionary *)txtRecords { if (self.serviceFoundHandler) { self.serviceFoundHandler(serviceName, txtRecords); } } -- (void)triggerServiceLost:(NSString *)serviceName - txtRecords:(NSDictionary *)txtRecords { +- (void)triggerServiceLost:(nullable NSString *)serviceName + txtRecords:(nullable NSDictionary *)txtRecords { if (self.serviceLostHandler) { self.serviceLostHandler(serviceName, txtRecords); } diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWBrowseResultImplTest.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWBrowseResultImplTest.m index b415c876..b1192fdb 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWBrowseResultImplTest.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWBrowseResultImplTest.m @@ -67,4 +67,17 @@ XCTAssertEqualObjects(results[@"key2"], @"value2"); } +- (void)testGetBonjourServiceName_InvalidUTF8 { + GNCNWBrowseResultImpl *browseResult = [[GNCNWBrowseResultImpl alloc] init]; + const char *raw_invalid = "\xc3\x28" + "abc"; + nw_endpoint_t endpoint = + nw_endpoint_create_bonjour_service(raw_invalid, "_servicetype._tcp", "local."); + XCTAssertNotNil(endpoint); + + NSString *serviceName = [browseResult getBonjourServiceNameFromEndpoint:endpoint]; + XCTAssertNotNil(serviceName); + XCTAssertEqualObjects(serviceName, @"�(abc"); +} + @end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkTest.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkTest.m index e09375c9..4b985539 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkTest.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkTest.m @@ -61,7 +61,6 @@ static NSString *const kHostAddress = @"127.0.0.1"; _mockConnectionImpl = OCMClassMock([GNCNWConnectionImpl class]); } - - (void)testGNCNWFrameworkCanBeInstantiated { GNCNWFramework *framework = [[GNCNWFramework alloc] init]; XCTAssertNotNil(framework); @@ -272,6 +271,49 @@ static NSString *const kHostAddress = @"127.0.0.1"; XCTAssertEqualObjects(foundTXTRecords, @{@"key" : @"value"}); } +- (void)testStartDiscoveryForServiceTypeNilName API_AVAILABLE(ios(13.0)) { + GNCNWFramework *framework = [[GNCNWFramework alloc] init]; + GNCFakeNWBrowser *fakeBrowser = [[GNCFakeNWBrowser alloc] init]; + fakeBrowser.createWithDescriptorResult = (nw_browser_t)fakeBrowser; + OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser); + + XCTestExpectation *serviceFoundExpectation = [self expectationWithDescription:@"Service found"]; + serviceFoundExpectation.inverted = YES; + + NSError *error = nil; + BOOL result = [framework startDiscoveryForServiceType:kServiceType + serviceFoundHandler:^(NSString *serviceName, + NSDictionary *txtRecords) { + [serviceFoundExpectation fulfill]; + } + serviceLostHandler:^(NSString *serviceName, + NSDictionary *txtRecords) { + } + includePeerToPeer:NO + error:&error]; + + XCTAssertTrue(result); + XCTAssertNil(error); + + // Simulate a service being found with nil name. + GNCFakeNWBrowseResult *fakeBrowseResult = [[GNCFakeNWBrowseResult alloc] init]; + fakeBrowseResult.txtRecord = @{@"key" : @"value"}; + fakeBrowseResult.getChangesFromResult = nw_browse_result_change_result_added; + nw_endpoint_t fakeEndpoint = + nw_endpoint_create_host("localhost", [[NSString stringWithFormat:@"%ld", kPort] UTF8String]); + fakeBrowseResult.endpointFromResultResult = fakeEndpoint; + fakeBrowseResult.returnNilServiceName = YES; + OCMStub([_mockBrowseResultImpl sharedInstance]).andReturn(fakeBrowseResult); + + if (fakeBrowser.browseResultsChangedHandler) { + GNCFakeNWBrowseResult *oldFakeBrowseResult = [[GNCFakeNWBrowseResult alloc] init]; + fakeBrowser.browseResultsChangedHandler((nw_browse_result_t)oldFakeBrowseResult, + (nw_browse_result_t)fakeBrowseResult, true); + } + + [self waitForExpectations:@[ serviceFoundExpectation ] timeout:0.1]; +} + - (void)testStartDiscoveryForServiceTypeDuplicate API_AVAILABLE(ios(13.0)) { GNCNWFramework *framework = [[GNCNWFramework alloc] init]; GNCFakeNWBrowser *fakeBrowser = [[GNCFakeNWBrowser alloc] init]; @@ -449,12 +491,13 @@ static NSString *const kHostAddress = @"127.0.0.1"; // TODO: b/377543997 - Migrate to dependency injection and remove mocks. OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser); - __block BOOL serviceFound = NO; + XCTestExpectation *serviceFoundExpectation = [self expectationWithDescription:@"Service found"]; + serviceFoundExpectation.inverted = YES; NSError *error = nil; [framework startDiscoveryForServiceType:kServiceType serviceFoundHandler:^(NSString *serviceName, NSDictionary *txtRecords) { - serviceFound = YES; + [serviceFoundExpectation fulfill]; } serviceLostHandler:^(NSString *serviceName, NSDictionary *txtRecords) { @@ -478,14 +521,7 @@ static NSString *const kHostAddress = @"127.0.0.1"; (nw_browse_result_t)fakeBrowseResult, true); } - // Allow async blocks to run. - XCTestExpectation *delay = [[XCTestExpectation alloc] initWithDescription:@"delay"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [delay fulfill]; - }); - [self waitForExpectations:@[ delay ] timeout:0.5]; - - XCTAssertFalse(serviceFound); + [self waitForExpectations:@[ serviceFoundExpectation ] timeout:0.1]; } - (void)testStartDiscoveryIgnoresLoopbackRemove API_AVAILABLE(ios(13.0)) { @@ -495,7 +531,8 @@ static NSString *const kHostAddress = @"127.0.0.1"; // TODO: b/377543997 - Migrate to dependency injection and remove mocks. OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser); - __block BOOL serviceLost = NO; + XCTestExpectation *serviceLostExpectation = [self expectationWithDescription:@"Service lost"]; + serviceLostExpectation.inverted = YES; NSError *error = nil; [framework startDiscoveryForServiceType:kServiceType serviceFoundHandler:^(NSString *serviceName, @@ -503,7 +540,7 @@ static NSString *const kHostAddress = @"127.0.0.1"; } serviceLostHandler:^(NSString *serviceName, NSDictionary *txtRecords) { - serviceLost = YES; + [serviceLostExpectation fulfill]; } includePeerToPeer:NO error:&error]; @@ -524,14 +561,7 @@ static NSString *const kHostAddress = @"127.0.0.1"; (nw_browse_result_t)newFakeBrowseResult, true); } - // Allow async blocks to run. - XCTestExpectation *delay = [[XCTestExpectation alloc] initWithDescription:@"delay"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [delay fulfill]; - }); - [self waitForExpectations:@[ delay ] timeout:0.5]; - - XCTAssertFalse(serviceLost); + [self waitForExpectations:@[ serviceLostExpectation ] timeout:0.1]; } - (void)testStopDiscoveryForServiceType API_AVAILABLE(ios(13.0)) { @@ -722,8 +752,8 @@ static NSString *const kHostAddress = @"127.0.0.1"; GNCNWFrameworkSocket *socket = [framework connectToHost:address port:kPort includePeerToPeer:NO - cancelSource:nil - queue:nil + cancelSource:nil + queue:nil error:&error]; XCTAssertNotNil(socket); @@ -742,8 +772,8 @@ static NSString *const kHostAddress = @"127.0.0.1"; GNCNWFrameworkSocket *socket = [framework connectToHost:address port:kPort includePeerToPeer:NO - cancelSource:nil - queue:nil + cancelSource:nil + queue:nil error:&error]; XCTAssertNil(socket); diff --git a/internal/platform/implementation/apple/Tests/ble_medium_test.mm b/internal/platform/implementation/apple/Tests/ble_medium_test.mm index 2994284c..1c764387 100644 --- a/internal/platform/implementation/apple/Tests/ble_medium_test.mm +++ b/internal/platform/implementation/apple/Tests/ble_medium_test.mm @@ -208,7 +208,7 @@ static const char *const kTestServiceID = "TestServiceID"; } - (void)testStartMultipleServicesScanning_Success { - std::vector service_uuids = {nearby::Uuid(0, 0)}; + std::vector service_uuids = {nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB)}; nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow; bool result = _medium->StartMultipleServicesScanning(service_uuids, tx_power_level, {}); @@ -217,7 +217,7 @@ static const char *const kTestServiceID = "TestServiceID"; } - (void)testStartMultipleServicesScanning_Failure { - std::vector service_uuids = {nearby::Uuid(0, 0)}; + std::vector service_uuids = {nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB)}; nearby::api::ble::TxPowerLevel tx_power_level = nearby::api::ble::TxPowerLevel::kUltraLow; _fakeGNCBLEMedium.startScanningError = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; @@ -300,8 +300,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -326,8 +326,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -385,8 +385,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -413,8 +413,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -450,8 +450,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -483,8 +483,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -650,6 +650,239 @@ static const char *const kTestServiceID = "TestServiceID"; XCTAssertEqual(server_socket.get(), nullptr); } +- (void)testOpenServerSocket_Cleanup_InitialState { + id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]); + OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES); + + id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]); + + __block BOOL added = NO; + OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any] + bleServiceAddedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager, + void (^completion)(NSError *error)) { + added = YES; + completion(nil); + }); + + __block BOOL removed = NO; + OCMStub([mockPeripheralManager removePeripheralServiceManagerForServiceUUID:[OCMArg any] + bleServiceRemovedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, CBUUID *serviceUUID, + void (^completion)(NSError *error)) { + removed = YES; + completion(nil); + }); + + nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() { + return mockPeripheralManager; + }); + + // Open the server socket. + auto server_socket = _medium->OpenServerSocket(kTestServiceID); + XCTAssertNotEqual(server_socket.get(), nullptr); + XCTAssertTrue(added); + XCTAssertFalse(removed); + + server_socket->Close(); +} + +- (void)testOpenServerSocket_Cleanup_AcceptConnection { + id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]); + OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES); + + id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]); + + OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any] + bleServiceAddedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager, + void (^completion)(NSError *error)) { + completion(nil); + }); + + __block BOOL removed = NO; + OCMStub([mockPeripheralManager removePeripheralServiceManagerForServiceUUID:[OCMArg any] + bleServiceRemovedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, CBUUID *serviceUUID, + void (^completion)(NSError *error)) { + removed = YES; + completion(nil); + }); + + nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() { + return mockPeripheralManager; + }); + + __block BOOL (^capturedHandler)(GNSSocket *) = nil; + id mockServiceManagerClass = OCMClassMock([GNSPeripheralServiceManager class]); + OCMStub([mockServiceManagerClass alloc]).andReturn(mockServiceManagerClass); + OCMStub([mockServiceManagerClass initWithBleServiceUUID:[OCMArg any] + addPairingCharacteristic:NO + shouldAcceptSocketHandler:[OCMArg any]]) + .andDo(^(NSInvocation *invocation) { + BOOL (^handler)(GNSSocket *); + [invocation getArgument:&handler atIndex:4]; + capturedHandler = handler; + }) + .andReturn(mockServiceManagerClass); + + auto server_socket = _medium->OpenServerSocket(kTestServiceID); + XCTAssertNotEqual(server_socket.get(), nullptr); + XCTAssertNotNil(capturedHandler); + + // Simulate connection accepted -> client socket is created. + GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init]; + BOOL result = capturedHandler((GNSSocket *)fakeSocket); + XCTAssertTrue(result); + + XCTAssertFalse(removed); // Accepting socket shouldn't remove service manager. + + server_socket->Close(); +} + +- (void)testOpenServerSocket_Cleanup_CloseClientSocket { + id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]); + OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES); + + id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]); + + OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any] + bleServiceAddedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager, + void (^completion)(NSError *error)) { + completion(nil); + }); + + __block BOOL removed = NO; + OCMStub([mockPeripheralManager removePeripheralServiceManagerForServiceUUID:[OCMArg any] + bleServiceRemovedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, CBUUID *serviceUUID, + void (^completion)(NSError *error)) { + removed = YES; + completion(nil); + }); + + nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() { + return mockPeripheralManager; + }); + + __block BOOL (^capturedHandler)(GNSSocket *) = nil; + id mockServiceManagerClass = OCMClassMock([GNSPeripheralServiceManager class]); + OCMStub([mockServiceManagerClass alloc]).andReturn(mockServiceManagerClass); + OCMStub([mockServiceManagerClass initWithBleServiceUUID:[OCMArg any] + addPairingCharacteristic:NO + shouldAcceptSocketHandler:[OCMArg any]]) + .andDo(^(NSInvocation *invocation) { + BOOL (^handler)(GNSSocket *); + [invocation getArgument:&handler atIndex:4]; + capturedHandler = handler; + }) + .andReturn(mockServiceManagerClass); + + auto server_socket = _medium->OpenServerSocket(kTestServiceID); + XCTAssertNotEqual(server_socket.get(), nullptr); + + GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init]; + capturedHandler((GNSSocket *)fakeSocket); + [fakeSocket simulateSocketDidConnect]; + + __block std::unique_ptr client_socket = nullptr; + XCTestExpectation *acceptExpectation = [self expectationWithDescription:@"Accept connection"]; + nearby::api::ble::BleServerSocket *raw_server_socket = server_socket.get(); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + client_socket = raw_server_socket->Accept(); + [acceptExpectation fulfill]; + }); + + [self waitForExpectations:@[ acceptExpectation ] timeout:1.0]; + XCTAssertTrue(client_socket != nullptr); + + // Close the client socket. + client_socket->Close(); + + // Wait a bit for any async callbacks + XCTestExpectation *expectation2 = [self expectationWithDescription:@"Wait after client close"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [expectation2 fulfill]; + }); + [self waitForExpectations:@[ expectation2 ] timeout:1.0]; + + XCTAssertFalse(removed); // Closing client socket shouldn't remove service manager! + + server_socket->Close(); +} + +- (void)testOpenServerSocket_Cleanup_CloseServerSocket { + id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]); + OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES); + + id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]); + + OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any] + bleServiceAddedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager, + void (^completion)(NSError *error)) { + completion(nil); + }); + + __block BOOL removed = NO; + OCMStub([mockPeripheralManager removePeripheralServiceManagerForServiceUUID:[OCMArg any] + bleServiceRemovedCompletion:[OCMArg any]]) + .andDo(^(GNSPeripheralManager *localSelf, CBUUID *serviceUUID, + void (^completion)(NSError *error)) { + removed = YES; + completion(nil); + }); + + nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() { + return mockPeripheralManager; + }); + + __block BOOL (^capturedHandler)(GNSSocket *) = nil; + id mockServiceManagerClass = OCMClassMock([GNSPeripheralServiceManager class]); + OCMStub([mockServiceManagerClass alloc]).andReturn(mockServiceManagerClass); + OCMStub([mockServiceManagerClass initWithBleServiceUUID:[OCMArg any] + addPairingCharacteristic:NO + shouldAcceptSocketHandler:[OCMArg any]]) + .andDo(^(NSInvocation *invocation) { + BOOL (^handler)(GNSSocket *); + [invocation getArgument:&handler atIndex:4]; + capturedHandler = handler; + }) + .andReturn(mockServiceManagerClass); + + auto server_socket = _medium->OpenServerSocket(kTestServiceID); + XCTAssertNotEqual(server_socket.get(), nullptr); + + GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init]; + capturedHandler((GNSSocket *)fakeSocket); + [fakeSocket simulateSocketDidConnect]; + + __block std::unique_ptr client_socket = nullptr; + XCTestExpectation *acceptExpectation = [self expectationWithDescription:@"Accept connection"]; + nearby::api::ble::BleServerSocket *raw_server_socket = server_socket.get(); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + client_socket = raw_server_socket->Accept(); + [acceptExpectation fulfill]; + }); + + [self waitForExpectations:@[ acceptExpectation ] timeout:1.0]; + + // Close the server socket. + server_socket->Close(); + + // Wait a bit for any async callbacks + XCTestExpectation *expectation3 = [self expectationWithDescription:@"Wait after server close"]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [expectation3 fulfill]; + }); + [self waitForExpectations:@[ expectation3 ] timeout:1.0]; + + XCTAssertTrue(removed); // Closing server socket MUST remove service manager! +} + #pragma mark - Other Tests - (void)testIsExtendedAdvertisementsAvailable { @@ -669,8 +902,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -717,8 +950,8 @@ static const char *const kTestServiceID = "TestServiceID"; [expectation fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); @@ -742,8 +975,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation1 fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback1)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback1)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData1); } @@ -766,8 +999,8 @@ static const char *const kTestServiceID = "TestServiceID"; serviceData2[[CBUUID UUIDWithString:kTestServiceUUIDString]]); [expectation2 fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback2)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback2)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData2); @@ -799,8 +1032,8 @@ static const char *const kTestServiceID = "TestServiceID"; [expectation2 fulfill]; } })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -826,8 +1059,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation1 fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback1)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback1)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); } @@ -843,8 +1076,8 @@ static const char *const kTestServiceID = "TestServiceID"; const nearby::api::ble::BleAdvertisementData &advertisement) { [expectation2 fulfill]; })}; - _medium->StartScanning(nearby::Uuid(0, 0), nearby::api::ble::TxPowerLevel::kUltraLow, - std::move(callback2)); + _medium->StartScanning(nearby::Uuid(0x0000FE2C00001000, 0x800000805F9B34FB), + nearby::api::ble::TxPowerLevel::kUltraLow, std::move(callback2)); if (_fakeGNCBLEMedium.advertisementFoundHandler) { _fakeGNCBLEMedium.advertisementFoundHandler(fakePeripheral, serviceData); diff --git a/internal/platform/implementation/apple/Tests/network_utils_test.mm b/internal/platform/implementation/apple/Tests/network_utils_test.mm index 00e36665..527132bf 100644 --- a/internal/platform/implementation/apple/Tests/network_utils_test.mm +++ b/internal/platform/implementation/apple/Tests/network_utils_test.mm @@ -157,4 +157,30 @@ static NSString *const kServiceType = @"_test._tcp"; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } +- (void)testStartDiscoveryCallbacksWithNilName { + XCTestExpectation *foundExpectation = [self expectationWithDescription:@"Service found callback"]; + XCTestExpectation *lostExpectation = [self expectationWithDescription:@"Service lost callback"]; + + nearby::apple::network_utils::NetworkDiscoveredServiceCallback callback; + callback.network_service_discovered_cb = [&](const nearby::NsdServiceInfo &service_info) { + XCTAssertEqual(service_info.GetServiceName(), std::string("")); + XCTAssertEqual(service_info.GetServiceType(), kServiceType.UTF8String); + [foundExpectation fulfill]; + }; + callback.network_service_lost_cb = [&](const nearby::NsdServiceInfo &service_info) { + XCTAssertEqual(service_info.GetServiceName(), std::string("")); + XCTAssertEqual(service_info.GetServiceType(), kServiceType.UTF8String); + [lostExpectation fulfill]; + }; + + BOOL result = nearby::apple::network_utils::StartDiscovery( + _fakeNWFramework, kServiceType.UTF8String, std::move(callback), YES); + XCTAssertTrue(result); + + [_fakeNWFramework triggerServiceFound:nil txtRecords:@{}]; + [_fakeNWFramework triggerServiceLost:nil txtRecords:@{}]; + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} + @end diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index 34695757..ca3473e9 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -247,6 +247,7 @@ class BleMedium : public api::ble::BleMedium { GNSPeripheralManager *socketPeripheralManager_; absl::Mutex scanning_mutex_; + std::vector scanning_service_uuids_ ABSL_GUARDED_BY(scanning_mutex_); GNSCentralManager *socketCentralManager_ ABSL_GUARDED_BY(scanning_mutex_); // Used for the blocking version of StartAdvertising and only has an advertisement found callback. diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index 1120f8b6..361f1483 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "absl/algorithm/container.h" #import "internal/platform/implementation/apple/ble_medium.h" #import #import +#include #include #include #include @@ -157,8 +159,17 @@ void BleMedium::HandleAdvertisementFound(id peripheral, return; } + std::vector scanning_uuids; + { + absl::MutexLock lock(&scanning_mutex_); + scanning_uuids = scanning_service_uuids_; + } for (CBUUID *key in serviceData.allKeys) { - data.service_data[CPPUUIDFromObjC(key)] = ByteArrayFromNSData(serviceData[key]); + Uuid cpp_uuid = CPPUUIDFromObjC(key); + if (absl::c_find(scanning_uuids, cpp_uuid) == scanning_uuids.end()) { + continue; + } + data.service_data[cpp_uuid] = ByteArrayFromNSData(serviceData[key]); } // Add the peripheral to the map if we haven't discovered it yet. @@ -197,6 +208,7 @@ std::unique_ptr BleMedium::StartScanning( { absl::MutexLock lock(&scanning_mutex_); scanning_cb_ = std::make_shared(std::move(callback)); + scanning_service_uuids_ = {service_uuid}; if (central_manager_factory_) { socketCentralManager_ = central_manager_factory_(serviceUUID); @@ -273,6 +285,7 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel t { absl::MutexLock lock(&scanning_mutex_); scan_cb_ = std::make_shared(std::move(callback)); + scanning_service_uuids_ = {service_uuid}; if (central_manager_factory_) { socketCentralManager_ = central_manager_factory_(serviceUUID); @@ -331,6 +344,7 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector &service_u { absl::MutexLock lock(&scanning_mutex_); scan_cb_ = std::make_shared(std::move(callback)); + scanning_service_uuids_ = service_uuids; if (central_manager_factory_) { socketCentralManager_ = central_manager_factory_(serviceUUIDs[0]); @@ -379,6 +393,7 @@ bool BleMedium::StopScanning() { [socketCentralManager_ stopNoScanMode]; scan_cb_ = nullptr; scanning_cb_ = nullptr; + scanning_service_uuids_.clear(); } dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); @@ -534,6 +549,13 @@ std::unique_ptr BleMedium::OpenServerSocketWithDeadlo server_socket->SetCloseNotifier([this]() { absl::MutexLock lock(server_socket_mutex_); server_socket_ptr_ = nullptr; + if (socketPeripheralManager_ != nil && socketPeripheralServiceManager_ != nil) { + [socketPeripheralManager_ + removePeripheralServiceManagerForServiceUUID:socketPeripheralServiceManager_.serviceUUID + bleServiceRemovedCompletion:^(NSError *_Nullable error) { + GNCLoggerInfo(@"Weave service removed from peripheral manager."); + }]; + } }); socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc] @@ -549,15 +571,6 @@ std::unique_ptr BleMedium::OpenServerSocketWithDeadlo callbackQueue:connection_callback_queue_]; auto socket_wrapper = std::make_unique(connection); - socket_wrapper->SetCloseNotifier( - [socketPeripheralManager = socketPeripheralManager_, - serviceUUID = socketPeripheralServiceManager_.serviceUUID]() { - [socketPeripheralManager - removePeripheralServiceManagerForServiceUUID:serviceUUID - bleServiceRemovedCompletion:^(NSError *_Nullable error) { - GNCLoggerInfo(@"BleSocket is removed peripheral manager."); - }]; - }); connection.connectionHandlers = socket_wrapper->GetInputStream().GetConnectionHandlers(); @@ -612,6 +625,16 @@ std::unique_ptr BleMedium::OpenServerSocketLegacy( // Raw pointer for closure capture in the legacy path (risks use-after-free). BleServerSocket *server_socket_ptr = server_socket.get(); + server_socket->SetCloseNotifier([this]() { + if (socketPeripheralManager_ != nil && socketPeripheralServiceManager_ != nil) { + [socketPeripheralManager_ + removePeripheralServiceManagerForServiceUUID:socketPeripheralServiceManager_.serviceUUID + bleServiceRemovedCompletion:^(NSError *_Nullable error) { + GNCLoggerInfo(@"Weave service removed from peripheral manager."); + }]; + } + }); + socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc] initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID] addPairingCharacteristic:NO @@ -627,14 +650,6 @@ std::unique_ptr BleMedium::OpenServerSocketLegacy( callbackQueue:connection_callback_queue_]; auto socket = std::make_unique(connection); - socket->SetCloseNotifier([socketPeripheralManager = socketPeripheralManager_, - serviceUUID = socketPeripheralServiceManager_.serviceUUID]() { - [socketPeripheralManager - removePeripheralServiceManagerForServiceUUID:serviceUUID - bleServiceRemovedCompletion:^(NSError *_Nullable error) { - GNCLoggerInfo(@"BleSocket is removed peripheral manager."); - }]; - }); connection.connectionHandlers = socket->GetInputStream().GetConnectionHandlers(); if (server_socket_ptr) { diff --git a/internal/platform/implementation/apple/network_utils.mm b/internal/platform/implementation/apple/network_utils.mm index 30c06cfb..11d7e14b 100644 --- a/internal/platform/implementation/apple/network_utils.mm +++ b/internal/platform/implementation/apple/network_utils.mm @@ -63,7 +63,7 @@ bool StartDiscovery(GNCNWFramework* medium, const std::string& service_type, serviceFoundHandler:^(NSString* name, NSDictionary* txtRecords) { NsdServiceInfo nsd_service_info; nsd_service_info.SetServiceType([serviceType UTF8String]); - nsd_service_info.SetServiceName([name UTF8String]); + nsd_service_info.SetServiceName(name ? [name UTF8String] : ""); [txtRecords enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info]( NSString* key, NSString* val, BOOL* stop) { @@ -74,7 +74,7 @@ bool StartDiscovery(GNCNWFramework* medium, const std::string& service_type, serviceLostHandler:^(NSString* name, NSDictionary* txtRecords) { NsdServiceInfo nsd_service_info; nsd_service_info.SetServiceType([serviceType UTF8String]); - nsd_service_info.SetServiceName([name UTF8String]); + nsd_service_info.SetServiceName(name ? [name UTF8String] : ""); [txtRecords enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info]( NSString* key, NSString* val, BOOL* stop) { diff --git a/internal/platform/implementation/apple/platform.mm b/internal/platform/implementation/apple/platform.mm index e11b524b..62b5c4ef 100644 --- a/internal/platform/implementation/apple/platform.mm +++ b/internal/platform/implementation/apple/platform.mm @@ -51,8 +51,12 @@ namespace api { std::string ImplementationPlatform::GetCustomSavePath(const std::string& parent_folder, const std::string& file_name) { // Collapse any path escaping characters. - NSString* parentFolder = [@(parent_folder.c_str()) stringByReplacingOccurrencesOfString:@"../" - withString:@""]; + NSString* parentFolderRaw = @(parent_folder.c_str()); + if (parentFolderRaw == nil) { + return std::string(); + } + NSString* parentFolder = [parentFolderRaw stringByReplacingOccurrencesOfString:@"../" + withString:@""]; NSURL* parentFolderURL = [NSURL fileURLWithPath:parentFolder]; // The only reserved character in a file name on macOS is the forward-slash. It's unclear if iOS @@ -66,8 +70,12 @@ std::string ImplementationPlatform::GetCustomSavePath(const std::string& parent_ // """ // // See: https://en.wikipedia.org/wiki/Filename - NSString* fileName = [@(file_name.c_str()) stringByReplacingOccurrencesOfString:@"/" - withString:@":"]; + NSString* fileNameRaw = @(file_name.c_str()); + if (fileNameRaw == nil) { + return std::string(); + } + NSString* fileName = [fileNameRaw stringByReplacingOccurrencesOfString:@"/" + withString:@":"]; NSString* baseName = [fileName stringByDeletingPathExtension]; NSString* extension = [fileName pathExtension]; @@ -86,8 +94,12 @@ std::string ImplementationPlatform::GetCustomSavePath(const std::string& parent_ std::string ImplementationPlatform::GetDownloadPath(const std::string& parent_folder, const std::string& file_name) { + NSString* parentFolderRaw = @(parent_folder.c_str()); + if (parentFolderRaw == nil) { + return std::string(); + } NSString* customSavePath = - [NSTemporaryDirectory() stringByAppendingPathComponent:@(parent_folder.c_str())]; + [NSTemporaryDirectory() stringByAppendingPathComponent:parentFolderRaw]; return GetCustomSavePath(customSavePath.UTF8String, file_name); } diff --git a/internal/platform/implementation/apple/scheduled_executor.mm b/internal/platform/implementation/apple/scheduled_executor.mm index ce5f89fe..2f80b10a 100644 --- a/internal/platform/implementation/apple/scheduled_executor.mm +++ b/internal/platform/implementation/apple/scheduled_executor.mm @@ -21,6 +21,7 @@ #include #include "absl/time/time.h" +#import "internal/platform/implementation/apple/Log/GNCLogger.h" #include "internal/platform/runnable.h" // Defines the state of a scheduled task. This enum is at global scope @@ -93,7 +94,23 @@ class ExecutorCancelable : public nearby::api::Cancelable { namespace nearby { namespace apple { +namespace { +void ExecuteRunnable(Runnable &runnable) { + @try { + try { + runnable(); + } catch (const std::exception &e) { + GNCLoggerError(@"Runnable threw C++ exception: %s", e.what()); + } catch (...) { + GNCLoggerError(@"Runnable threw unknown C++ exception"); + } + } @catch (NSException *e) { + GNCLoggerError(@"Runnable threw ObjC exception: %@: %@", e.name, e.reason); + } +} + +} // namespace ScheduledExecutor::ScheduledExecutor() { impl_ = [GNCOperationQueueImpl implWithMaxConcurrency:1]; } @@ -106,7 +123,6 @@ ScheduledExecutor::~ScheduledExecutor() { impl_ = nil; } - std::shared_ptr ScheduledExecutor::Schedule(Runnable &&runnable, absl::Duration duration) { if (impl_.shuttingDown) return std::shared_ptr(nullptr); @@ -133,7 +149,7 @@ std::shared_ptr ScheduledExecutor::Schedule(Runnable &&runnable } GNCScheduledTaskState expected = GNCScheduledTaskState::kScheduled; if (task->_state.compare_exchange_strong(expected, GNCScheduledTaskState::kRunning)) { - task->_runnable(); + ExecuteRunnable(task->_runnable); task->_state.store(GNCScheduledTaskState::kDone); } }]; @@ -152,7 +168,7 @@ bool ScheduledExecutor::DoSubmit(Runnable &&runnable) { // Submit the runnable to the queue. __block Runnable local_runnable = std::move(runnable); [impl_.queue addOperationWithBlock:^{ - local_runnable(); + ExecuteRunnable(local_runnable); }]; return true; } diff --git a/internal/platform/implementation/bluetooth_classic.h b/internal/platform/implementation/bluetooth_classic.h index d5c38095..ff9daf7a 100644 --- a/internal/platform/implementation/bluetooth_classic.h +++ b/internal/platform/implementation/bluetooth_classic.h @@ -19,7 +19,6 @@ #include #include -#include "absl/base/attributes.h" #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "internal/platform/cancellation_flag.h" @@ -29,8 +28,7 @@ #include "internal/platform/mac_address.h" #include "internal/platform/output_stream.h" -namespace nearby { -namespace api { +namespace nearby::api { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice { @@ -213,29 +211,6 @@ class BluetoothClassicMedium { DefaultCallback(); }; - class Observer { - public: - virtual ~Observer() = default; - - // Called when a new `device` is added to the adapter. - virtual void DeviceAdded(BluetoothDevice& device) {} - - // Called when `device` is removed from the adapter. - virtual void DeviceRemoved(BluetoothDevice& device) {} - - // Called when the address of `device` changed due to pairing. - virtual void DeviceAddressChanged(BluetoothDevice& device, - absl::string_view old_address) {} - - // Called when the paired property of `device` changed. - virtual void DevicePairedChanged(BluetoothDevice& device, - bool new_paired_status) {} - - // Called when `device` has connected or disconnected. - virtual void DeviceConnectedStateChanged(BluetoothDevice& device, - bool connected) {} - }; - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() // // Returns true once the process of discovery has been initiated. @@ -273,7 +248,7 @@ class BluetoothClassicMedium { // UUID. // // Returns nullptr error. - virtual std::unique_ptr ListenForService( + virtual std::shared_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createBond() @@ -285,12 +260,8 @@ class BluetoothClassicMedium { BluetoothDevice& remote_device) = 0; virtual BluetoothDevice* GetRemoteDevice(MacAddress mac_address) = 0; - - virtual void AddObserver(Observer* observer) = 0; - virtual void RemoveObserver(Observer* observer) = 0; }; -} // namespace api -} // namespace nearby +} // namespace nearby::api #endif // PLATFORM_API_BLUETOOTH_CLASSIC_H_ diff --git a/internal/platform/implementation/device_info.h b/internal/platform/implementation/device_info.h index 3d3e859a..38101619 100644 --- a/internal/platform/implementation/device_info.h +++ b/internal/platform/implementation/device_info.h @@ -16,6 +16,7 @@ #define PLATFORM_API_DEVICE_INFO_H_ #include +#include #include #include #include @@ -39,6 +40,7 @@ class DeviceInfo { kWindows, kMacOS }; + enum class SuspendResumeEvent { kSuspend, kResume }; virtual ~DeviceInfo() = default; @@ -69,6 +71,14 @@ class DeviceInfo { // Control device sleep virtual bool PreventSleep() = 0; virtual bool AllowSleep() = 0; + + // Monitor suspend/resume events. + // Returns a listener id that can be used to unregister the listener. + virtual int64_t RegisterSuspendResumeListener( + std::function callback) { + return 0; + } + virtual void UnregisterSuspendResumeListener(int64_t listener_id) {} }; template diff --git a/internal/platform/implementation/g3/BUILD b/internal/platform/implementation/g3/BUILD index 6fb81f20..9df630ec 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -59,10 +59,11 @@ cc_library( "//internal/platform/implementation:types", "//internal/platform/implementation/shared:count_down_latch", "//internal/platform/implementation/shared:posix_mutex", - "//internal/test", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", @@ -151,13 +152,13 @@ cc_test( "//internal/platform:test_util", "//internal/platform:uuid", "//internal/platform/implementation:comm", + "//third_party/gloop/thread/fiber", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", - "@com_google_nisaba//nisaba/port:thread_pool/fiber", ], ) @@ -187,13 +188,11 @@ cc_library( visibility = [ "//connections:__subpackages__", "//connections:partners", - "//internal/account:__subpackages__", "//internal/auth:__subpackages__", "//internal/crypto:__subpackages__", "//internal/data:__subpackages__", "//internal/network:__subpackages__", "//internal/platform:__subpackages__", - "//internal/preferences:__subpackages__", "//internal/proto/analytics:__subpackages__", "//internal/weave:__subpackages__", "//location/nearby/sharing/sdk:__subpackages__", @@ -217,6 +216,7 @@ cc_library( "//internal/platform/implementation/shared:file", "//third_party/gloop/thread", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:no_destructor", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", @@ -237,3 +237,18 @@ cc_library( "@nlohmann_json//:json", ], ) + +cc_test( + name = "scheduled_executor_test", + srcs = ["scheduled_executor_test.cc"], + deps = [ + ":g3", + ":types", + "//internal/platform:base", + "//internal/platform:test_util", + "//internal/platform:types", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/internal/platform/implementation/g3/awdl_test.cc b/internal/platform/implementation/g3/awdl_test.cc index 5e890de9..4fc964da 100644 --- a/internal/platform/implementation/g3/awdl_test.cc +++ b/internal/platform/implementation/g3/awdl_test.cc @@ -19,11 +19,11 @@ #include "gtest/gtest.h" #include "absl/synchronization/notification.h" #include "absl/time/time.h" +#include "third_party/gloop/thread/fiber/fiber.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/implementation/awdl.h" #include "internal/platform/medium_environment.h" #include "internal/platform/nsd_service_info.h" -#include "thread/fiber/fiber.h" namespace nearby { namespace g3 { diff --git a/internal/platform/implementation/g3/bluetooth_classic.cc b/internal/platform/implementation/g3/bluetooth_classic.cc index ec780d33..df41fdb6 100644 --- a/internal/platform/implementation/g3/bluetooth_classic.cc +++ b/internal/platform/implementation/g3/bluetooth_classic.cc @@ -240,10 +240,10 @@ std::unique_ptr BluetoothClassicMedium::ConnectToService( return socket; } -std::unique_ptr +std::shared_ptr BluetoothClassicMedium::ListenForService(const std::string& service_name, const std::string& service_uuid) { - auto socket = std::make_unique(GetAdapter()); + auto socket = std::make_shared(GetAdapter()); socket->SetCloseNotifier([this, uuid = service_uuid]() { absl::MutexLock lock(mutex_); sockets_.erase(uuid); @@ -264,15 +264,5 @@ api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( return MediumEnvironment::Instance().FindBluetoothDevice(mac_address); } -void BluetoothClassicMedium::AddObserver( - api::BluetoothClassicMedium::Observer* observer) { - MediumEnvironment::Instance().AddObserver(observer); -} - -void BluetoothClassicMedium::RemoveObserver( - api::BluetoothClassicMedium::Observer* observer) { - MediumEnvironment::Instance().RemoveObserver(observer); -} - } // namespace g3 } // namespace nearby diff --git a/internal/platform/implementation/g3/bluetooth_classic.h b/internal/platform/implementation/g3/bluetooth_classic.h index 0826430d..b707abd0 100644 --- a/internal/platform/implementation/g3/bluetooth_classic.h +++ b/internal/platform/implementation/g3/bluetooth_classic.h @@ -195,7 +195,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { // UUID. // // Returns nullptr on error. - std::unique_ptr ListenForService( + std::shared_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); @@ -206,9 +206,6 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { api::BluetoothDevice* GetRemoteDevice(MacAddress mac_address) override; - void AddObserver(Observer* observer) override; - void RemoveObserver(Observer* observer) override; - private: absl::Mutex mutex_; BluetoothAdapter* adapter_; // Our device adapter; read-only. diff --git a/internal/platform/implementation/g3/platform.cc b/internal/platform/implementation/g3/platform.cc index dfa62fae..3de2e8f7 100644 --- a/internal/platform/implementation/g3/platform.cc +++ b/internal/platform/implementation/g3/platform.cc @@ -20,6 +20,7 @@ #include #include "absl/base/attributes.h" +#include "absl/base/no_destructor.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" @@ -239,7 +240,8 @@ ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { } std::unique_ptr ImplementationPlatform::CreateTimer() { - return std::make_unique(); + static absl::NoDestructor timer_executor; + return std::make_unique(timer_executor.get()); } std::unique_ptr diff --git a/internal/platform/implementation/g3/scheduled_executor_test.cc b/internal/platform/implementation/g3/scheduled_executor_test.cc new file mode 100644 index 00000000..29b35bcf --- /dev/null +++ b/internal/platform/implementation/g3/scheduled_executor_test.cc @@ -0,0 +1,133 @@ +// Copyright 2020 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 "internal/platform/scheduled_executor.h" + +#include + +#include "gtest/gtest.h" +#include "absl/time/time.h" +#include "internal/platform/cancelable.h" +#include "internal/platform/count_down_latch.h" +#include "internal/platform/medium_environment.h" + +namespace nearby { + +// kShortDelay must be significant enough to guarantee that OS under heavy load +// should be able to execute the non-blocking test paths within this time. +absl::Duration kShortDelay = absl::Milliseconds(200); + +// kLongDelay must be long enough to make sure that under OS under heavy load +// will let kShortDelay fire and jobs scheduled before the kLongDelay fires. +absl::Duration kLongDelay = 10 * kShortDelay; + +TEST(ScheduledExecutorTest, SimulatedClockCanSchedule) { + MediumEnvironment::Instance().Start({.use_simulated_clock = true}); + ScheduledExecutor executor; + std::atomic_int value = 0; + CountDownLatch first_task_latch(1); + CountDownLatch second_task_latch(1); + // schedule job due in kLongDelay. + executor.Schedule( + [&]() { + EXPECT_EQ(value, 1); + value = 5; + first_task_latch.CountDown(); + }, + kLongDelay); + // schedule job due in kShortDelay; must fire before the first one. + executor.Schedule( + [&]() { + EXPECT_EQ(value, 0); + value = 1; + second_task_latch.CountDown(); + }, + kShortDelay); + EXPECT_EQ(value, 0); + MediumEnvironment::Instance().FastForward(kShortDelay - + absl::Milliseconds(1)); + EXPECT_EQ(value, 0); + MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); + second_task_latch.Await(); + EXPECT_EQ(value, 1); + MediumEnvironment::Instance().FastForward(kLongDelay - kShortDelay); + first_task_latch.Await(); + EXPECT_EQ(value, 5); + // Very long sleep to make sure that the sleep is truly simulated. + MediumEnvironment::Instance().FastForward(absl::Minutes(30)); + MediumEnvironment::Instance().Stop(); +} + +TEST(ScheduledExecutorTest, + DestroyExecutorWithSimulatedClockIgnoresPendingTasks) { + MediumEnvironment::Instance().Start({.use_simulated_clock = true}); + { + ScheduledExecutor executor; + executor.Schedule( + [&]() { + // This task should never be executed. + EXPECT_TRUE(false); + }, + kShortDelay); + } + MediumEnvironment::Instance().FastForward(absl::Minutes(30)); + MediumEnvironment::Instance().Stop(); +} + +TEST(ScheduledExecutorTest, SimulatedClockCanScheduleRepeatedly) { + MediumEnvironment::Instance().Start({.use_simulated_clock = true}); + ScheduledExecutor executor; + std::atomic_int value = 0; + std::atomic_int i = 0; + CountDownLatch latch[] = {CountDownLatch(1), CountDownLatch(1)}; + + Cancelable cancelable = executor.ScheduleRepeatedly( + [&]() { + value++; + latch[i.fetch_add(1)].CountDown(); + }, + kShortDelay); + + EXPECT_EQ(value, 0); + // Advance to just before the first execution. + MediumEnvironment::Instance().FastForward(kShortDelay - + absl::Milliseconds(1)); + EXPECT_EQ(value, 0); + + // Advance past the first execution. + MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); + latch[0].Await(absl::Seconds(1)); + EXPECT_EQ(value, 1); + + // Advance to just before the second execution. + MediumEnvironment::Instance().FastForward(kShortDelay - + absl::Milliseconds(1)); + EXPECT_EQ(value, 1); + + // Advance past the second execution. + MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); + latch[1].Await(absl::Seconds(1)); + EXPECT_EQ(value, 2); + + // Cancel the task. + cancelable.Cancel(); + + // Advance a long time and make sure it doesn't run again. + MediumEnvironment::Instance().FastForward(kLongDelay * 5); + EXPECT_EQ(value, 2); + + MediumEnvironment::Instance().Stop(); +} + +} // namespace nearby diff --git a/internal/platform/implementation/g3/timer.h b/internal/platform/implementation/g3/timer.h index 3faa779f..61ef431a 100644 --- a/internal/platform/implementation/g3/timer.h +++ b/internal/platform/implementation/g3/timer.h @@ -19,10 +19,13 @@ #include #include +#include "absl/base/nullability.h" #include "absl/base/thread_annotations.h" +#include "absl/functional/any_invocable.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" -#include "internal/platform/implementation/g3/scheduled_executor.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/scheduled_executor.h" #include "internal/platform/implementation/timer.h" namespace nearby { @@ -30,8 +33,11 @@ namespace g3 { class Timer : public api::Timer { public: - Timer() = default; - ~Timer() override = default; + explicit Timer(api::ScheduledExecutor* absl_nonnull executor) + : executor_(executor) {}; + ~Timer() override { + Stop(); + }; bool Create(int delay, int interval, absl::AnyInvocable callback) override { @@ -52,13 +58,16 @@ class Timer : public api::Timer { task_.reset(); return result; } - return false; + return true; } private: bool Schedule(absl::Duration delay) { absl::MutexLock lock(mutex_); - task_ = executor_.Schedule([this]() { TriggerCallback(); }, delay); + if (is_stopped_) { + return false; + } + task_ = executor_->Schedule([this]() { TriggerCallback(); }, delay); return true; } @@ -77,7 +86,7 @@ class Timer : public api::Timer { std::atomic_bool is_stopped_; absl::Duration interval_; std::shared_ptr task_ ABSL_GUARDED_BY(mutex_); - ScheduledExecutor executor_; + api::ScheduledExecutor* absl_nonnull const executor_; }; } // namespace g3 diff --git a/internal/platform/implementation/scheduled_executor.h b/internal/platform/implementation/scheduled_executor.h index e1244494..de9bc6c3 100644 --- a/internal/platform/implementation/scheduled_executor.h +++ b/internal/platform/implementation/scheduled_executor.h @@ -15,7 +15,6 @@ #ifndef PLATFORM_API_SCHEDULED_EXECUTOR_H_ #define PLATFORM_API_SCHEDULED_EXECUTOR_H_ -#include #include #include "absl/time/time.h" diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index 94698475..b0021587 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -72,7 +72,13 @@ cc_library( "timer.h", "utils.h", ], - defines = ["_SILENCE_CLANG_COROUTINE_MESSAGE"], + defines = [ + "_SILENCE_CLANG_COROUTINE_MESSAGE", + "_WIN32_WINNT=_WIN32_WINNT_WIN10", + ], + linkopts = [ + "powrprof.lib", + ], tags = ["windows"], visibility = ["//visibility:private"], deps = [ diff --git a/internal/platform/implementation/windows/bluetooth_classic_medium.cc b/internal/platform/implementation/windows/bluetooth_classic_medium.cc index 87a80ffd..89f0644c 100644 --- a/internal/platform/implementation/windows/bluetooth_classic_medium.cc +++ b/internal/platform/implementation/windows/bluetooth_classic_medium.cc @@ -32,6 +32,7 @@ #include "internal/platform/implementation/bluetooth_classic.h" #include "internal/platform/implementation/windows/bluetooth_adapter.h" #include "internal/platform/implementation/windows/bluetooth_classic_device.h" +#include "internal/platform/implementation/windows/bluetooth_classic_server_socket.h" #include "internal/platform/implementation/windows/bluetooth_classic_socket.h" #include "internal/platform/implementation/windows/bluetooth_pairing.h" #include "internal/platform/implementation/windows/generated/winrt/Windows.Devices.Bluetooth.Rfcomm.h" @@ -43,8 +44,7 @@ #include "internal/platform/logging.h" #include "internal/platform/mac_address.h" -namespace nearby { -namespace windows { +namespace nearby::windows { namespace { using ::winrt::Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService; using ::winrt::Windows::Devices::Bluetooth::Rfcomm::RfcommServiceId; @@ -124,7 +124,13 @@ BluetoothClassicMedium::BluetoothClassicMedium( &BluetoothClassicMedium::OnScanModeChanged, this, std::placeholders::_1)); } -BluetoothClassicMedium::~BluetoothClassicMedium() {} +BluetoothClassicMedium::~BluetoothClassicMedium() { + // Clear the close notifier to prevent UAF if the server_socket_ outlives + // the BluetoothClassicMedium. + if (raw_server_socket_ != nullptr) { + raw_server_socket_->SetCloseNotifier(nullptr); + } +} bool BluetoothClassicMedium::StartDiscovery( BluetoothClassicMedium::DiscoveryCallback discovery_callback) { @@ -259,7 +265,7 @@ std::unique_ptr BluetoothClassicMedium::ConnectToService( // UUID. // // Returns nullptr error. -std::unique_ptr +std::shared_ptr BluetoothClassicMedium::ListenForService(const std::string& service_name, const std::string& service_uuid) { VLOG(1) << "ListenForService is called with service name: " << service_name @@ -283,14 +289,24 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name, bool radio_discoverable = scan_mode_ == BluetoothAdapter::ScanMode::kConnectableDiscoverable; - bool result = StartAdvertising(radio_discoverable); + if (rfcomm_provider_ != nullptr && + is_radio_discoverable_ == radio_discoverable) { + LOG(WARNING) << __func__ + << ": Ignore StartAdvertising due to no change to " + "current advertising."; + return server_socket_; + } - if (!result) { + auto server_socket = StartAdvertising(radio_discoverable); + + if (!server_socket) { LOG(ERROR) << __func__ << ": Failed to start listening."; return nullptr; } - return std::move(server_socket_); + raw_server_socket_ = server_socket.get(); + server_socket_ = std::move(server_socket); + return server_socket_; } api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( @@ -685,9 +701,6 @@ winrt::fire_and_forget BluetoothClassicMedium::DeviceWatcher_Added( if (discovery_callback_.device_discovered_cb != nullptr) { discovery_callback_.device_discovered_cb(*device); } - for (auto& observer : observers_.GetObservers()) { - observer->DeviceAdded(*device); - } return winrt::fire_and_forget(); } @@ -753,9 +766,6 @@ winrt::fire_and_forget BluetoothClassicMedium::DeviceWatcher_Updated( LOG(INFO) << __func__ << ": Notifying device paired changed: " << std::boolalpha << new_paired_status; - for (auto& observer : observers_.GetObservers()) { - observer->DevicePairedChanged(*device, new_paired_status); - } } return winrt::fire_and_forget(); @@ -803,10 +813,6 @@ winrt::fire_and_forget BluetoothClassicMedium::DeviceWatcher_Removed( discovery_callback_.device_lost_cb(*device); } - for (auto& observer : observers_.GetObservers()) { - observer->DeviceRemoved(*device); - } - RemoveRemoteDevice(mac_address); return winrt::fire_and_forget(); @@ -833,25 +839,19 @@ bool BluetoothClassicMedium::IsWatcherRunning() { (status == DeviceWatcherStatus::Stopping); } -bool BluetoothClassicMedium::StartAdvertising(bool radio_discoverable) { +std::shared_ptr +BluetoothClassicMedium::StartAdvertising(bool radio_discoverable) { LOG(INFO) << __func__ << ": StartAdvertising is called with radio_discoverable: " << radio_discoverable << "."; + std::shared_ptr server_socket; try { - if (rfcomm_provider_ != nullptr && - is_radio_discoverable_ == radio_discoverable) { - LOG(WARNING) << __func__ - << ": Ignore StartAdvertising due to no change to " - "current advertising."; - return true; - } - if (rfcomm_provider_ != nullptr && !StopAdvertising()) { LOG(WARNING) << __func__ << ": Failed to StartAdvertising due to cannot stop " "running advertising."; - return false; + return nullptr; } rfcomm_provider_ = @@ -859,79 +859,57 @@ bool BluetoothClassicMedium::StartAdvertising(bool radio_discoverable) { RfcommServiceId::FromUuid(winrt::guid(service_uuid_))) .get(); - server_socket_ = std::make_unique( + server_socket = BluetoothServerSocket::Create( winrt::to_string(rfcomm_provider_.ServiceId().AsString())); - raw_server_socket_ = server_socket_.get(); - - if (!server_socket_->listen()) { + if (!server_socket->listen()) { LOG(ERROR) << __func__ << ": Failed to StartAdvertising due to cannot start socket."; - server_socket_->Close(); - server_socket_ = nullptr; rfcomm_provider_ = nullptr; - return false; + return nullptr; } - server_socket_->SetCloseNotifier([&]() { StopAdvertising(); }); + server_socket->SetCloseNotifier([&]() { StopAdvertising(); }); // Set the SDP attributes and start Bluetooth advertising InitializeServiceSdpAttributes(rfcomm_provider_, service_name_); // Start to advertising. - rfcomm_provider_.StartAdvertising(server_socket_->stream_socket_listener(), + rfcomm_provider_.StartAdvertising(server_socket->stream_socket_listener(), radio_discoverable); is_radio_discoverable_ = radio_discoverable; LOG(INFO) << ": StartListening completed successfully."; - return true; + return server_socket; } catch (std::exception exception) { // We will log and eat the exception since the caller // expects nullptr if it fails LOG(ERROR) << __func__ << ": Exception setting up for listen: " << exception.what(); - if (server_socket_ != nullptr) { - server_socket_->Close(); - server_socket_ = nullptr; - } - if (rfcomm_provider_ != nullptr) { rfcomm_provider_ = nullptr; } - - return false; + return nullptr; } catch (const winrt::hresult_error& ex) { LOG(ERROR) << __func__ << ": Exception setting up for listen: " << ex.code() << ": " << winrt::to_string(ex.message()); - if (server_socket_ != nullptr) { - server_socket_->Close(); - server_socket_ = nullptr; - } - if (rfcomm_provider_ != nullptr) { rfcomm_provider_ = nullptr; } - - return false; + return nullptr; } catch (...) { LOG(ERROR) << __func__ << ": Unknown exception."; - if (server_socket_ != nullptr) { - server_socket_->Close(); - server_socket_ = nullptr; - } - if (rfcomm_provider_ != nullptr) { rfcomm_provider_ = nullptr; } - - return false; + return nullptr; } } bool BluetoothClassicMedium::StopAdvertising() { VLOG(1) << __func__ << ": StopAdvertising is called"; - + bool result = false; try { if (rfcomm_provider_ == nullptr) { LOG(ERROR) << __func__ @@ -940,12 +918,9 @@ bool BluetoothClassicMedium::StopAdvertising() { } rfcomm_provider_.StopAdvertising(); - rfcomm_provider_ = nullptr; - raw_server_socket_ = nullptr; - server_socket_ = nullptr; LOG(INFO) << ": StopAdvertising completed successfully."; - return true; + result = true; } catch (std::exception exception) { LOG(ERROR) << __func__ << ": StopAdvertising exception: " << exception.what(); @@ -957,9 +932,9 @@ bool BluetoothClassicMedium::StopAdvertising() { } rfcomm_provider_ = nullptr; - raw_server_socket_ = nullptr; server_socket_ = nullptr; - return false; + raw_server_socket_ = nullptr; + return result; } bool BluetoothClassicMedium::InitializeServiceSdpAttributes( @@ -988,5 +963,4 @@ bool BluetoothClassicMedium::InitializeServiceSdpAttributes( } } -} // namespace windows -} // namespace nearby +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/bluetooth_classic_medium.h b/internal/platform/implementation/windows/bluetooth_classic_medium.h index 5027e4eb..9dae9b50 100644 --- a/internal/platform/implementation/windows/bluetooth_classic_medium.h +++ b/internal/platform/implementation/windows/bluetooth_classic_medium.h @@ -21,7 +21,6 @@ #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/synchronization/mutex.h" -#include "internal/base/observer_list.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/implementation/bluetooth_adapter.h" #include "internal/platform/implementation/bluetooth_classic.h" @@ -34,8 +33,7 @@ #include "internal/platform/implementation/windows/generated/winrt/base.h" #include "internal/platform/mac_address.h" -namespace nearby { -namespace windows { +namespace nearby::windows { // Container of operations that can be performed over the Bluetooth Classic // medium. @@ -80,7 +78,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { // UUID. // // Returns nullptr error. - std::unique_ptr ListenForService( + std::shared_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) override; @@ -91,19 +89,11 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { std::unique_ptr CreatePairing( api::BluetoothDevice& remote_device) override; - void AddObserver(Observer* observer) override { - observers_.AddObserver(observer); - } - - // Removes an observer. It's OK to remove an unregistered observer. - void RemoveObserver(Observer* observer) override { - observers_.RemoveObserver(observer); - } - private: bool StartScanning(); bool StopScanning(); - bool StartAdvertising(bool radio_discoverable); + std::shared_ptr StartAdvertising( + bool radio_discoverable); bool StopAdvertising(); bool InitializeServiceSdpAttributes( ::winrt::Windows::Devices::Bluetooth::Rfcomm::RfcommServiceProvider @@ -185,13 +175,14 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { // Used for advertising. ::winrt::Windows::Devices::Bluetooth::Rfcomm::RfcommServiceProvider rfcomm_provider_ = nullptr; - std::unique_ptr server_socket_ = nullptr; + std::shared_ptr server_socket_; + // Raw pointer to the BluetoothServerSocket impl class that is held by the + // shared_ptr server_socket_. The lifetime of this pointer is guaranteed by + // the shared_ptr. BluetoothServerSocket* raw_server_socket_ = nullptr; bool is_radio_discoverable_ = false; - ObserverList observers_; }; -} // namespace windows -} // namespace nearby +} // namespace nearby::windows #endif // PLATFORM_IMPL_WINDOWS_BLUETOOTH_CLASSIC_MEDIUM_H_ diff --git a/internal/platform/implementation/windows/bluetooth_classic_server_socket.cc b/internal/platform/implementation/windows/bluetooth_classic_server_socket.cc index 63b5e1a6..f8a9cb40 100644 --- a/internal/platform/implementation/windows/bluetooth_classic_server_socket.cc +++ b/internal/platform/implementation/windows/bluetooth_classic_server_socket.cc @@ -26,10 +26,10 @@ #include "internal/platform/exception.h" #include "internal/platform/implementation/bluetooth_classic.h" #include "internal/platform/implementation/windows/bluetooth_classic_socket.h" +#include "internal/platform/implementation/windows/generated/winrt/Windows.Networking.Sockets.h" #include "internal/platform/logging.h" -namespace nearby { -namespace windows { +namespace nearby::windows { namespace { using ::winrt::Windows::Networking::Sockets::SocketProtectionLevel; using ::winrt::Windows::Networking::Sockets::SocketQualityOfService; @@ -132,7 +132,8 @@ bool BluetoothServerSocket::listen() { // Setup socket event of ConnectionReceived. listener_event_token_ = stream_socket_listener_.ConnectionReceived( - {this, &BluetoothServerSocket::Listener_ConnectionReceived}); + {shared_from_this(), + &BluetoothServerSocket::Listener_ConnectionReceived}); stream_socket_listener_ .BindServiceNameAsync(winrt::to_hstring(service_name_), @@ -167,5 +168,4 @@ bool BluetoothServerSocket::listen() { return ::winrt::fire_and_forget{}; } -} // namespace windows -} // namespace nearby +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/bluetooth_classic_server_socket.h b/internal/platform/implementation/windows/bluetooth_classic_server_socket.h index 2e3f2baf..4f1779cb 100644 --- a/internal/platform/implementation/windows/bluetooth_classic_server_socket.h +++ b/internal/platform/implementation/windows/bluetooth_classic_server_socket.h @@ -15,25 +15,30 @@ #ifndef PLATFORM_IMPL_WINDOWS_BLUETOOTH_CLASSIC_SERVER_SOCKET_H_ #define PLATFORM_IMPL_WINDOWS_BLUETOOTH_CLASSIC_SERVER_SOCKET_H_ -#include +#include #include -#include #include +#include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "internal/platform/exception.h" #include "internal/platform/implementation/bluetooth_classic.h" -#include "internal/platform/implementation/windows/bluetooth_classic_socket.h" +#include "internal/platform/implementation/windows/generated/winrt/Windows.Networking.Sockets.h" #include "internal/platform/implementation/windows/generated/winrt/base.h" -namespace nearby { -namespace windows { +namespace nearby::windows { -class BluetoothServerSocket : public api::BluetoothServerSocket { +class BluetoothServerSocket + : public api::BluetoothServerSocket, + public std::enable_shared_from_this { public: - explicit BluetoothServerSocket(absl::string_view service_name); + static std::shared_ptr Create( + absl::string_view service_name) { + return std::shared_ptr( + new BluetoothServerSocket(service_name)); + } ~BluetoothServerSocket() override; @@ -65,6 +70,9 @@ class BluetoothServerSocket : public api::BluetoothServerSocket { } private: + // BluetoothServerSocket must be created as a shared_ptr. + explicit BluetoothServerSocket(absl::string_view service_name); + // The listener is accepting incoming connections ::winrt::fire_and_forget Listener_ConnectionReceived( ::winrt::Windows::Networking::Sockets::StreamSocketListener listener, @@ -93,7 +101,6 @@ class BluetoothServerSocket : public api::BluetoothServerSocket { bool closed_ = false; }; -} // namespace windows -} // namespace nearby +} // namespace nearby::windows #endif // PLATFORM_IMPL_WINDOWS_BLUETOOTH_CLASSIC_SERVER_SOCKET_H_ diff --git a/internal/platform/implementation/windows/device_info.cc b/internal/platform/implementation/windows/device_info.cc index 348f7ce6..40d8192d 100644 --- a/internal/platform/implementation/windows/device_info.cc +++ b/internal/platform/implementation/windows/device_info.cc @@ -14,13 +14,19 @@ #include "internal/platform/implementation/windows/device_info.h" +// clang-format off #include #include #include +#include +#include +// clang-format on +#include #include #include #include +#include #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" @@ -30,10 +36,19 @@ #include "internal/platform/implementation/windows/device_paths.h" #include "internal/platform/implementation/windows/string_utils.h" #include "internal/platform/implementation/windows/utils.h" +#include "internal/platform/logging.h" namespace nearby::windows { - +namespace { using ::nearby::windows::string_utils::WideStringToString; +} // namespace + +DeviceInfo::~DeviceInfo() { + if (suspend_resume_notification_handle_ != nullptr) { + PowerUnregisterSuspendResumeNotification( + suspend_resume_notification_handle_); + } +} std::optional DeviceInfo::GetOsDeviceName() const { std::optional device_name = GetDnsHostName(); @@ -113,4 +128,61 @@ bool DeviceInfo::AllowSleep() { return session_manager_.AllowSleep(); } +ULONG DeviceInfo::PowerSuspendResumeCallback(PVOID context, ULONG type, + PVOID setting) { + api::DeviceInfo::SuspendResumeEvent event; + switch (type) { + case PBT_APMSUSPEND: + event = api::DeviceInfo::SuspendResumeEvent::kSuspend; + break; + case PBT_APMRESUMESUSPEND: + event = api::DeviceInfo::SuspendResumeEvent::kResume; + break; + default: + return 0; + } + DeviceInfo* device_info = static_cast(context); + device_info->OnSuspendResumeEvent(event); + return 0; +} + +int64_t DeviceInfo::RegisterSuspendResumeListener( + std::function callback) { + absl::MutexLock lock(suspend_resume_mutex_); + int64_t listener_id = ++next_suspend_resume_listener_id_; + suspend_resume_listeners_.emplace(listener_id, std::move(callback)); + if (suspend_resume_listeners_.size() == 1) { + DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS subscribe_params; + subscribe_params.Callback = PowerSuspendResumeCallback; + subscribe_params.Context = this; + PowerRegisterSuspendResumeNotification( + DEVICE_NOTIFY_CALLBACK, &subscribe_params, + &suspend_resume_notification_handle_); + } + return listener_id; +} + +void DeviceInfo::UnregisterSuspendResumeListener(int64_t listener_id) { + absl::MutexLock lock(suspend_resume_mutex_); + suspend_resume_listeners_.erase(listener_id); + if (suspend_resume_listeners_.empty()) { + if (suspend_resume_notification_handle_ != nullptr) { + PowerUnregisterSuspendResumeNotification( + suspend_resume_notification_handle_); + } + suspend_resume_notification_handle_ = nullptr; + } +} + +void DeviceInfo::OnSuspendResumeEvent( + api::DeviceInfo::SuspendResumeEvent event) { + LOG(INFO) << "OnSuspendResumeEvent: " + << (event == DeviceInfo::SuspendResumeEvent::kSuspend ? "kSuspend" + : "kResume"); + absl::MutexLock lock(suspend_resume_mutex_); + for (auto& it : suspend_resume_listeners_) { + it.second(event); + } +} + } // namespace nearby::windows diff --git a/internal/platform/implementation/windows/device_info.h b/internal/platform/implementation/windows/device_info.h index 8de0d388..278a2d68 100644 --- a/internal/platform/implementation/windows/device_info.h +++ b/internal/platform/implementation/windows/device_info.h @@ -15,11 +15,19 @@ #ifndef PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_ #define PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_ +// clang-format off +#include +#include +// clang-format on + +#include #include #include #include #include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "internal/base/file_path.h" @@ -31,7 +39,7 @@ namespace windows { class DeviceInfo : public api::DeviceInfo { public: - ~DeviceInfo() override = default; + ~DeviceInfo() override; std::optional GetOsDeviceName() const override; api::DeviceInfo::DeviceType GetDeviceType() const override; @@ -51,9 +59,26 @@ class DeviceInfo : public api::DeviceInfo { bool PreventSleep() override; bool AllowSleep() override; + int64_t RegisterSuspendResumeListener( + std::function callback) + override; + void UnregisterSuspendResumeListener(int64_t listener_id) override; + private: + static ULONG PowerSuspendResumeCallback(PVOID context, ULONG type, + PVOID setting); + void OnSuspendResumeEvent(SuspendResumeEvent event); + mutable absl::Mutex mutex_; SessionManager session_manager_ ABSL_GUARDED_BY(mutex_); + absl::Mutex suspend_resume_mutex_; + int64_t next_suspend_resume_listener_id_ ABSL_GUARDED_BY( + suspend_resume_mutex_) = 0; + absl::flat_hash_map< + int64_t, absl::AnyInvocable> + suspend_resume_listeners_ ABSL_GUARDED_BY(suspend_resume_mutex_); + HPOWERNOTIFY suspend_resume_notification_handle_ + ABSL_GUARDED_BY(suspend_resume_mutex_) = nullptr; }; } // namespace windows diff --git a/internal/platform/implementation/windows/mutex.h b/internal/platform/implementation/windows/mutex.h index 44488a23..44f88d79 100644 --- a/internal/platform/implementation/windows/mutex.h +++ b/internal/platform/implementation/windows/mutex.h @@ -19,6 +19,7 @@ #include "absl/synchronization/mutex.h" #include "internal/platform/implementation/mutex.h" +#include "internal/platform/condition_variable.h" namespace nearby { namespace windows { diff --git a/internal/platform/implementation/windows/wifi_direct.h b/internal/platform/implementation/windows/wifi_direct.h index 982fa6ba..895da675 100644 --- a/internal/platform/implementation/windows/wifi_direct.h +++ b/internal/platform/implementation/windows/wifi_direct.h @@ -239,7 +239,6 @@ class WifiDirectMedium : public api::WifiDirectMedium { std::unique_ptr ListenForService( int port) override; - // Advertiser start WiFiDirect GO with specific Credentials. bool StartWifiDirect(WifiDirectCredentials* wifi_direct_credentials) override; // Advertiser stop the current WiFiDirect GO. bool StopWifiDirect() override; @@ -347,6 +346,7 @@ class WifiDirectMedium : public api::WifiDirectMedium { std::string ip_address_local_; std::string ip_address_remote_; absl::CondVar is_ip_address_ready_; + std::string remote_device_name_; WifiDirectServerSocket* server_socket_ptr_ ABSL_GUARDED_BY(mutex_) = nullptr; SubmittableExecutor listener_executor_; diff --git a/internal/platform/implementation/windows/wifi_direct_medium.cc b/internal/platform/implementation/windows/wifi_direct_medium.cc index 7adc12eb..cb10ea1a 100644 --- a/internal/platform/implementation/windows/wifi_direct_medium.cc +++ b/internal/platform/implementation/windows/wifi_direct_medium.cc @@ -251,8 +251,10 @@ std::unique_ptr WifiDirectMedium::ListenForService( bool WifiDirectMedium::StartWifiDirect( WifiDirectCredentials* wifi_direct_credentials) { + remote_device_name_ = wifi_direct_credentials->GetRemoteDeviceName(); + LOG(INFO) << __func__ << ": remote_device_name from credentials: " + << remote_device_name_; absl::MutexLock lock(mutex_); - LOG(INFO) << __func__ << ": Start to create WiFiDirect."; if (IsBeaconing()) { LOG(WARNING) << "Cannot create WiFiDirect GO again when it is running."; return true; @@ -431,13 +433,19 @@ fire_and_forget WifiDirectMedium::OnConnectionRequested( LOG(INFO) << "Receive connection request from: " << winrt::to_string(device_name) << "; device ID: " << winrt::to_string(device_id); + if (!remote_device_name_.empty() && + !absl::EqualsIgnoreCase(remote_device_name_, + winrt::to_string(device_name))) { + LOG(INFO) << "Ignore the connection request from the unrelated device."; + return winrt::fire_and_forget(); + } DeviceInformation windows_device_info(connection_request.DeviceInformation()); auto deviceInfoP = std::make_unique(windows_device_info); { - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); connection_requested_devices_by_id_[device_id] = std::move(deviceInfoP); } @@ -500,7 +508,7 @@ fire_and_forget WifiDirectMedium::OnConnectionRequested( std::string remote_ip = winrt::to_string(pair.RemoteHostName().DisplayName()); - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); wifi_direct_device_ = device; ip_address_local_ = local_ip; ip_address_remote_ = remote_ip; @@ -749,7 +757,7 @@ fire_and_forget WifiDirectMedium::Watcher_DeviceAdded( << "; device name: " << winrt::to_string(device_info.Name()); winrt::hstring device_id = device_info.Id(); { - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); if (discovered_devices_by_id_.contains(device_id)) { return winrt::fire_and_forget(); } @@ -800,7 +808,7 @@ fire_and_forget WifiDirectMedium::Watcher_DeviceAdded( // Create a WiFiDirectDevice out of this id if (!is_paired) { LOG(INFO) << "GC paired failed!"; - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); if (connection_latch_) { connection_latch_->CountDown(); } diff --git a/internal/platform/implementation/windows/wifi_direct_server_socket.cc b/internal/platform/implementation/windows/wifi_direct_server_socket.cc index c72a5014..8bd93b28 100644 --- a/internal/platform/implementation/windows/wifi_direct_server_socket.cc +++ b/internal/platform/implementation/windows/wifi_direct_server_socket.cc @@ -14,7 +14,6 @@ #include -#include #include #include #include @@ -52,7 +51,7 @@ void WifiDirectServerSocket::SetIPAddress(std::string ip_address) { std::unique_ptr WifiDirectServerSocket::Accept() { { - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); if (closed_) return nullptr; if (server_socket_accepted_connection_) { LOG(INFO) << "Server socket has already accepted a connection. Return."; @@ -74,7 +73,7 @@ std::unique_ptr WifiDirectServerSocket::Accept() { LOG(INFO) << "Start to accept connection from WiFiDirect client."; auto client_socket = server_socket_.Accept(); - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); if (closed_ || client_socket == nullptr) { LOG(INFO) << "Accept server socket failed or closed."; return nullptr; diff --git a/internal/platform/medium_environment.cc b/internal/platform/medium_environment.cc index 46f8142e..842d2d02 100644 --- a/internal/platform/medium_environment.cc +++ b/internal/platform/medium_environment.cc @@ -192,9 +192,6 @@ void MediumEnvironment::OnBluetoothDeviceStateChanged( if (enable_notifications_) { VLOG(1) << "Notify about new discovered device"; info.callback.device_discovered_cb(device); - for (auto& observer : observers_.GetObservers()) { - observer->DeviceAdded(device); - } } } } else { @@ -215,9 +212,6 @@ void MediumEnvironment::OnBluetoothDeviceStateChanged( if (enable_notifications_) { VLOG(1) << "Notify about existing discovered device"; info.callback.device_discovered_cb(device); - for (auto& observer : observers_.GetObservers()) { - observer->DeviceAdded(device); - } } } } @@ -227,9 +221,6 @@ void MediumEnvironment::OnBluetoothDeviceStateChanged( if (enable_notifications_) { VLOG(1) << "Notify about removed device"; info.callback.device_lost_cb(device); - for (auto& observer : observers_.GetObservers()) { - observer->DeviceRemoved(device); - } } info.devices.erase(item); } @@ -1291,11 +1282,6 @@ bool MediumEnvironment::SetPairingState(api::BluetoothDevice* device, latch.CountDown(); }); latch.Await(); - if (enable_notifications_) { - for (auto& observer : observers_.GetObservers()) { - observer->DevicePairedChanged(*device, true); - } - } return updated; } @@ -1359,11 +1345,6 @@ bool MediumEnvironment::FinishPairing(api::BluetoothDevice* device) { pairing_context->pairing_error.value()); } else { pairing_context->is_paired = true; - if (enable_notifications_) { - for (auto& observer : observers_.GetObservers()) { - observer->DevicePairedChanged(*device, true); - } - } pairing_context->pairing_callback.on_paired_cb(); } return finshed; @@ -1409,18 +1390,6 @@ void MediumEnvironment::ClearBluetoothDevicesForPairing() { RunOnMediumEnvironmentThread([&]() { devices_pairing_contexts_.clear(); }); } -void MediumEnvironment::AddObserver( - api::BluetoothClassicMedium::Observer* observer) { - if (!enabled_) return; - observers_.AddObserver(observer); -} - -void MediumEnvironment::RemoveObserver( - api::BluetoothClassicMedium::Observer* observer) { - if (!enabled_) return; - observers_.RemoveObserver(observer); -} - void MediumEnvironment::SetBleExtendedAdvertisementsAvailable(bool enabled) { ble_extended_advertisements_available_ = enabled; } diff --git a/internal/platform/medium_environment.h b/internal/platform/medium_environment.h index 4ffce3ad..24feec29 100644 --- a/internal/platform/medium_environment.h +++ b/internal/platform/medium_environment.h @@ -27,7 +27,6 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" -#include "internal/base/observer_list.h" #include "internal/platform/borrowable.h" #include "internal/platform/implementation/awdl.h" #include "internal/platform/implementation/ble.h" @@ -377,9 +376,6 @@ class MediumEnvironment { // Clears the map `devices_pairing_contexts_`. void ClearBluetoothDevicesForPairing(); - void AddObserver(api::BluetoothClassicMedium::Observer* observer); - void RemoveObserver(api::BluetoothClassicMedium::Observer* observer); - // Sets the availability of BLE extended advertisements. It is false by // default. void SetBleExtendedAdvertisementsAvailable(bool enabled); @@ -514,7 +510,6 @@ class MediumEnvironment { bool use_valid_peer_connection_ = true; absl::Duration peer_connection_latency_ = absl::ZeroDuration(); std::shared_ptr simulated_clock_ ABSL_GUARDED_BY(mutex_); - ObserverList observers_; bool ble_extended_advertisements_available_ = false; }; diff --git a/internal/platform/scheduled_executor.h b/internal/platform/scheduled_executor.h index bb3196e8..af747a41 100644 --- a/internal/platform/scheduled_executor.h +++ b/internal/platform/scheduled_executor.h @@ -144,15 +144,23 @@ class ABSL_LOCKABLE ScheduledExecutor final : public Lockable { return; } + // Re-schedule the next execution before running the task to avoid clock + // slip as much as possible. + // Ideally the next event should be scheduled at + // (last scheduled time + delay). So that if the task takes too long to + // finish, we can still catch up the schedule. + // However, that would require changing the scheduler to allow scheduling + // an event at a specific time. + { + MutexLock lock(&executor_->mutex_); + ScheduleNextUnderLock(); + } + (*cancellable_task_)(); if (cancelled_.Get()) { return; } - - // Re-schedule the next execution. - MutexLock lock(&executor_->mutex_); - ScheduleNextUnderLock(); } void ScheduleNextUnderLock() diff --git a/internal/platform/scheduled_executor_test.cc b/internal/platform/scheduled_executor_test.cc index 80d79c25..a4f93ee1 100644 --- a/internal/platform/scheduled_executor_test.cc +++ b/internal/platform/scheduled_executor_test.cc @@ -24,7 +24,6 @@ #include "absl/time/time.h" #include "internal/platform/cancelable.h" #include "internal/platform/count_down_latch.h" -#include "internal/platform/medium_environment.h" namespace nearby { @@ -209,59 +208,6 @@ TEST(ScheduledExecutorTest, ExecuteDuringShutdownFails) { executor.Shutdown(); } -TEST(ScheduledExecutorTest, SimulatedClockCanSchedule) { - MediumEnvironment::Instance().Start({.use_simulated_clock = true}); - ScheduledExecutor executor; - std::atomic_int value = 0; - CountDownLatch first_task_latch(1); - CountDownLatch second_task_latch(1); - // schedule job due in kLongDelay. - executor.Schedule( - [&]() { - EXPECT_EQ(value, 1); - value = 5; - first_task_latch.CountDown(); - }, - kLongDelay); - // schedule job due in kShortDelay; must fire before the first one. - executor.Schedule( - [&]() { - EXPECT_EQ(value, 0); - value = 1; - second_task_latch.CountDown(); - }, - kShortDelay); - EXPECT_EQ(value, 0); - MediumEnvironment::Instance().FastForward(kShortDelay - - absl::Milliseconds(1)); - EXPECT_EQ(value, 0); - MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); - second_task_latch.Await(); - EXPECT_EQ(value, 1); - MediumEnvironment::Instance().FastForward(kLongDelay - kShortDelay); - first_task_latch.Await(); - EXPECT_EQ(value, 5); - // Very long sleep to make sure that the sleep is truly simulated. - MediumEnvironment::Instance().FastForward(absl::Minutes(30)); - MediumEnvironment::Instance().Stop(); -} - -TEST(ScheduledExecutorTest, - DestroyExecutorWithSimulatedClockIgnoresPendingTasks) { - MediumEnvironment::Instance().Start({.use_simulated_clock = true}); - { - ScheduledExecutor executor; - executor.Schedule( - [&]() { - // This task should never be executed. - EXPECT_TRUE(false); - }, - kShortDelay); - } - MediumEnvironment::Instance().FastForward(absl::Minutes(30)); - MediumEnvironment::Instance().Stop(); -} - struct ScheduledThreadCheckTestClass { ScheduledExecutor executor; int value ABSL_GUARDED_BY(executor) = 0; @@ -397,52 +343,4 @@ TEST(ScheduledExecutorTest, CanCancelOneOfTwoRepeatedTasks) { cancelableB.Cancel(); } -TEST(ScheduledExecutorTest, SimulatedClockCanScheduleRepeatedly) { - MediumEnvironment::Instance().Start({.use_simulated_clock = true}); - ScheduledExecutor executor; - std::atomic_int value = 0; - std::atomic_int i = 0; - CountDownLatch latch[] = {CountDownLatch(1), CountDownLatch(1)}; - - Cancelable cancelable = executor.ScheduleRepeatedly( - [&]() { - value++; - latch[i.fetch_add(1)].CountDown(); - }, - kShortDelay); - - EXPECT_EQ(value, 0); - // Advance to just before the first execution. - MediumEnvironment::Instance().FastForward(kShortDelay - - absl::Milliseconds(1)); - EXPECT_EQ(value, 0); - - // Advance past the first execution. - MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); - latch[0].Await(absl::Seconds(1)); - EXPECT_EQ(value, 1); - - // Wait for the second execution to schedule. - absl::SleepFor(kShortDelay); - - // Advance to just before the second execution. - MediumEnvironment::Instance().FastForward(kShortDelay - - absl::Milliseconds(1)); - EXPECT_EQ(value, 1); - - // Advance past the second execution. - MediumEnvironment::Instance().FastForward(absl::Milliseconds(1)); - latch[1].Await(absl::Seconds(1)); - EXPECT_EQ(value, 2); - - // Cancel the task. - cancelable.Cancel(); - - // Advance a long time and make sure it doesn't run again. - MediumEnvironment::Instance().FastForward(kLongDelay * 5); - EXPECT_EQ(value, 2); - - MediumEnvironment::Instance().Stop(); -} - } // namespace nearby diff --git a/internal/platform/service_address.cc b/internal/platform/service_address.cc index 90722d31..f2bf54d1 100644 --- a/internal/platform/service_address.cc +++ b/internal/platform/service_address.cc @@ -14,11 +14,17 @@ #include "internal/platform/service_address.h" +#include #include +#include "absl/strings/string_view.h" #include "connections/implementation/proto/offline_wire_formats.pb.h" namespace nearby { +namespace { +constexpr absl::string_view kIpv6LoopbackAddress( + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", 16); +} // namespace void ServiceAddressToProto( const ServiceAddress& service_address, @@ -31,9 +37,9 @@ void ServiceAddressToProto( bool ServiceAddressFromProto( const location::nearby::connections::ServiceAddress& proto, ServiceAddress& service_address) { - // Address must be either 4 or 16 bytes and port must be set. + // Address must be either 4 or 16 bytes and port must be valid (1 to 65535). if ((proto.ip_address().size() != 16 && proto.ip_address().size() != 4) || - proto.port() == 0) { + proto.port() <= 0 || proto.port() > 65535) { return false; } service_address.address = {proto.ip_address().begin(), @@ -42,4 +48,31 @@ bool ServiceAddressFromProto( return true; } +bool ServiceAddress::IsLoopbackAddress() const { + if (address.size() == 4) { + // IPv4 loopback: 127.0.0.0/8 + return address[0] == 127; + } else if (address.size() == 16) { + // IPv6 loopback: ::1 + return absl::string_view(address.data(), address.size()) == + kIpv6LoopbackAddress; + } + return false; +} + +bool ServiceAddress::IsLinkLocalAddress() const { + if (address.size() == 4) { + // IPv4 link-local: 169.254.0.0/16 + uint8_t b0 = static_cast(address[0]); + uint8_t b1 = static_cast(address[1]); + return b0 == 169 && b1 == 254; + } else if (address.size() == 16) { + // IPv6 link-local: fe80::/10 + uint8_t b0 = static_cast(address[0]); + uint8_t b1 = static_cast(address[1]); + return b0 == 0xfe && (b1 & 0xc0) == 0x80; + } + return false; +} + } // namespace nearby diff --git a/internal/platform/service_address.h b/internal/platform/service_address.h index 887e0b18..8a8ed1ed 100644 --- a/internal/platform/service_address.h +++ b/internal/platform/service_address.h @@ -16,7 +16,6 @@ #define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_SERVICE_ADDRESS_H_ #include -#include #include #include @@ -33,6 +32,14 @@ struct ServiceAddress { uint16_t port; bool operator==(const ServiceAddress& other) const = default; + + // Returns true if the address is a loopback address (IPv4 127.0.0.0/8 or + // IPv6 ::1). + bool IsLoopbackAddress() const; + + // Returns true if the address is a link-local address (IPv4 169.254.0.0/16 or + // IPv6 fe80::/10). + bool IsLinkLocalAddress() const; }; // Support logging of ServiceAddress. diff --git a/internal/platform/service_address_test.cc b/internal/platform/service_address_test.cc index 436a1966..c4fa2285 100644 --- a/internal/platform/service_address_test.cc +++ b/internal/platform/service_address_test.cc @@ -81,8 +81,18 @@ TEST(ServiceAddressTest, ServiceAddressFromProtoInvalidAddress) { TEST(ServiceAddressTest, ServiceAddressFromProtoInvalidPort) { ProtoServiceAddress proto; proto.set_ip_address(std::string("\x7f\0\0\1", 4)); - proto.set_port(0); ServiceAddress service_address; + + // Port 0 + proto.set_port(0); + EXPECT_FALSE(ServiceAddressFromProto(proto, service_address)); + + // Port > 65535 + proto.set_port(65536); + EXPECT_FALSE(ServiceAddressFromProto(proto, service_address)); + + // Port < 0 + proto.set_port(-1); EXPECT_FALSE(ServiceAddressFromProto(proto, service_address)); } @@ -118,5 +128,104 @@ TEST(ServiceAddressTest, ServiceAddressEquality) { EXPECT_EQ(service_address5, service_address6); } +TEST(ServiceAddressTest, IsLoopbackAddressIPv4) { + ServiceAddress service_address = { + .address = {127, 0, 0, 1}, + .port = 8080, + }; + EXPECT_TRUE(service_address.IsLoopbackAddress()); + + ServiceAddress not_loopback = { + .address = {10, 0, 0, 1}, + .port = 8080, + }; + EXPECT_FALSE(not_loopback.IsLoopbackAddress()); +} + +TEST(ServiceAddressTest, IsLoopbackAddressIPv6) { + ServiceAddress service_address = { + .address = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .port = 8080, + }; + EXPECT_TRUE(service_address.IsLoopbackAddress()); + + ServiceAddress not_loopback = { + .address = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, + .port = 8080, + }; + EXPECT_FALSE(not_loopback.IsLoopbackAddress()); +} + +TEST(ServiceAddressTest, IsLinkLocalAddressIPv4) { + ServiceAddress service_address = { + .address = {static_cast(169), static_cast(254), 0, 1}, + .port = 8080, + }; + EXPECT_TRUE(service_address.IsLinkLocalAddress()); + + ServiceAddress not_link_local = { + .address = {169, static_cast(253), 0, 1}, + .port = 8080, + }; + EXPECT_FALSE(not_link_local.IsLinkLocalAddress()); + + ServiceAddress not_link_local_b0 = { + .address = {100, static_cast(254), 0, 1}, + .port = 8080, + }; + EXPECT_FALSE(not_link_local_b0.IsLinkLocalAddress()); +} + +TEST(ServiceAddressTest, IsLinkLocalAddressIPv6) { + ServiceAddress service_address = { + .address = {static_cast(0xfe), static_cast(0x80), 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .port = 8080, + }; + EXPECT_TRUE(service_address.IsLinkLocalAddress()); + + ServiceAddress not_link_local = { + .address = {static_cast(0xfd), static_cast(0x80), 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + .port = 8080, + }; + EXPECT_FALSE(not_link_local.IsLinkLocalAddress()); + + ServiceAddress not_link_local_b1 = { + .address = {static_cast(0xfe), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1}, + .port = 8080, + }; + EXPECT_FALSE(not_link_local_b1.IsLinkLocalAddress()); +} + +TEST(ServiceAddressTest, IsLoopbackAddressInvalidLength) { + ServiceAddress service_address = { + .address = {127, 0, 0}, + .port = 8080, + }; + EXPECT_FALSE(service_address.IsLoopbackAddress()); + + ServiceAddress empty_address = { + .address = {}, + .port = 8080, + }; + EXPECT_FALSE(empty_address.IsLoopbackAddress()); +} + +TEST(ServiceAddressTest, IsLinkLocalAddressInvalidLength) { + ServiceAddress service_address = { + .address = {static_cast(169), static_cast(254), 0}, + .port = 8080, + }; + EXPECT_FALSE(service_address.IsLinkLocalAddress()); + + ServiceAddress empty_address = { + .address = {}, + .port = 8080, + }; + EXPECT_FALSE(empty_address.IsLinkLocalAddress()); +} + } // namespace } // namespace nearby diff --git a/internal/platform/wifi_credential.h b/internal/platform/wifi_credential.h index cc6c78a3..511be808 100644 --- a/internal/platform/wifi_credential.h +++ b/internal/platform/wifi_credential.h @@ -123,6 +123,12 @@ class WifiDirectCredentials { return technology_; } + // Get/Set Remote Device Name. + std::string GetRemoteDeviceName() const { return remote_device_name_; } + void SetRemoteDeviceName(const std::string& remote_device_name) { + remote_device_name_ = remote_device_name; + } + private: // There are 2 types of WifiDirectAuthType. // 1. Without Service Discovery: the credentials are ssid/password. @@ -137,6 +143,7 @@ class WifiDirectCredentials { std::string gateway_ = "0.0.0.0"; int port_ = 0; int frequency_ = -1; + std::string remote_device_name_; location::nearby::proto::connections::ConnectionBand band_; location::nearby::proto::connections::ConnectionTechnology technology_; }; diff --git a/minimum_os.bzl b/minimum_os.bzl index c7ad06b6..324a462c 100644 --- a/minimum_os.bzl +++ b/minimum_os.bzl @@ -14,5 +14,5 @@ """Minimum OS version definitions and related test setup""" -IOS_MINIMUM_OS = "13.7" +IOS_MINIMUM_OS = "15.0" IOS_LATEST_TEST_RUNNER = "//testing/utp/ios:IOS_LATEST" diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index e6b055bc..fa7e5964 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -395,9 +395,8 @@ enum EstablishConnectionStatus { /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; CONNECTION_STATUS_LOST_CONNECTIVITY = 9 /*[ status_bucket = STATUS_INTERRUPTION ]*/; - // TODO: b/341782941 - : Annote this status when it's confirmed by Nearby - // Connections team. - CONNECTION_STATUS_INVALID_ADVERTISEMENT = 10; + CONNECTION_STATUS_INVALID_ADVERTISEMENT = 10 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; } // The status of sending and receiving attachments. Used by SEND_ATTACHMENTS. @@ -848,6 +847,8 @@ enum SharingUseCase { // The user was redirected from Bluetooth sharing UI to Nearby Share USE_CASE_REDIRECTED_FROM_BLUETOOTH_SHARE = 8; USE_CASE_TAP_TO_SHARE = 9; + // Automatic backup use case. + USE_CASE_FILE_SYNC = 10; } enum SharingSurface { @@ -1068,3 +1069,11 @@ enum CloudActionType { // The action is a retry of a previous failed upload/download. CLOUD_ACTION_TYPE_RETRY = 2; } + +// Defines how the device is powered. +enum PowerStatus { + POWER_STATUS_UNKNOWN = 0; + POWER_STATUS_BATTERY = 1; + POWER_STATUS_AC = 2; + POWER_STATUS_BATTERY_AND_AC = 3; +} diff --git a/sharing/BUILD b/sharing/BUILD index d093ef0e..f3e410cb 100644 --- a/sharing/BUILD +++ b/sharing/BUILD @@ -250,6 +250,7 @@ cc_library( "//internal/base:files", "//internal/platform:types", "//location/nearby/sharing/lib/sync:sync_manager", + "//location/nearby/cpp/sharing/clients/cpp/common", "//proto:sharing_enums_cc_proto", "//sharing/analytics", "//sharing/certificates", @@ -393,6 +394,7 @@ cc_library( "//connections/implementation/analytics:analytics_recorder_impl", "//internal/base", "//internal/base:file_path", + "//internal/base:files", "//internal/flags:nearby_flags", "//internal/network:url", "//internal/platform:base", @@ -402,6 +404,7 @@ cc_library( "//internal/platform:types", "//internal/platform/implementation:types", #"//location/nearby/analytics/cpp/logging:event_logger", + "//location/nearby/cpp/sharing/clients/cpp/common", "//location/nearby/sharing/lib/account:account_manager", "//location/nearby/sharing/lib/rpc:grpc_async_client_factory", "//location/nearby/sharing/lib/rpc:sharing_rpc_client", @@ -425,6 +428,7 @@ cc_library( "//sharing/proto:share_cc_proto", "//sharing/proto:wire_format_cc_proto", "//sharing/scheduling", + "//third_party/gloop/util/time:protoutil", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:flat_hash_map", @@ -655,6 +659,7 @@ cc_test( ":transfer_metadata", ":transfer_metadata_matchers", ":types", + "//google/protobuf:timestamp_cc_proto", "//internal/base:file_path", "//internal/base:files", "//internal/flags:nearby_flags", diff --git a/sharing/analytics/analytics_recorder.h b/sharing/analytics/analytics_recorder.h index d1bbba4e..4b95707e 100644 --- a/sharing/analytics/analytics_recorder.h +++ b/sharing/analytics/analytics_recorder.h @@ -111,7 +111,9 @@ class AnalyticsRecorder { virtual void NewReceiveIntroduction( int64_t session_id, const ShareTarget& share_target, std::optional referrer_package, - location::nearby::proto::sharing::OSType share_target_os_type) = 0; + location::nearby::proto::sharing::OSType share_target_os_type, + location::nearby::proto::sharing::SharingUseCase sharing_use_case, + location::nearby::proto::sharing::PowerStatus power_status) = 0; virtual void NewRespondToIntroduction( location::nearby::proto::sharing::ResponseToIntroduction action, @@ -142,8 +144,7 @@ class AnalyticsRecorder { const AttachmentContainer& attachments, int transfer_position, int concurrent_connections, - bool advanced_protection_enabled, - bool advanced_protection_mismatch) = 0; + bool advanced_protection_enabled) = 0; virtual void NewSendFastInitialization() = 0; @@ -159,7 +160,8 @@ class AnalyticsRecorder { virtual void NewSendIntroduction( int64_t session_id, const ShareTarget& share_target, int transfer_position, int concurrent_connections, - location::nearby::proto::sharing::OSType share_target_os_type) = 0; + location::nearby::proto::sharing::OSType share_target_os_type, + location::nearby::proto::sharing::PowerStatus power_status) = 0; virtual void NewSetVisibility( nearby::sharing::proto::DeviceVisibility src_visibility, diff --git a/sharing/certificates/BUILD b/sharing/certificates/BUILD index b0073e37..f2205e16 100644 --- a/sharing/certificates/BUILD +++ b/sharing/certificates/BUILD @@ -63,8 +63,11 @@ cc_library( "//sharing/proto:enums_cc_proto", "//sharing/proto:share_cc_proto", "//sharing/scheduling", + "//third_party/gloop/util/time:protoutil", "//util/hash:highway_fingerprint", "@com_google_absl//absl/algorithm", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", @@ -145,6 +148,7 @@ cc_test( "//sharing/proto:share_cc_proto", "//sharing/scheduling", "//sharing/scheduling:test_support", + "//third_party/gloop/util/time:protoutil", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/status", diff --git a/sharing/certificates/fake_nearby_share_certificate_manager.cc b/sharing/certificates/fake_nearby_share_certificate_manager.cc index 459609b1..542cbd8d 100644 --- a/sharing/certificates/fake_nearby_share_certificate_manager.cc +++ b/sharing/certificates/fake_nearby_share_certificate_manager.cc @@ -20,9 +20,11 @@ #include #include #include +#include #include #include +#include "absl/strings/string_view.h" #include "internal/base/file_path.h" #include "sharing/certificates/nearby_share_certificate_manager.h" #include "sharing/certificates/nearby_share_encrypted_metadata_key.h" @@ -102,6 +104,12 @@ void FakeNearbyShareCertificateManager::ClearPublicCertificates( callback(true); } +void FakeNearbyShareCertificateManager::AddBindingToPublicCertificate( + absl::string_view certificate_id, absl::string_view binding_id) { + add_binding_to_public_certificate_calls_.push_back( + {std::string(certificate_id), std::string(binding_id)}); +} + std::optional FakeNearbyShareCertificateManager::GetValidPrivateCertificate( DeviceVisibility visibility) const { diff --git a/sharing/certificates/fake_nearby_share_certificate_manager.h b/sharing/certificates/fake_nearby_share_certificate_manager.h index c2508306..529d4131 100644 --- a/sharing/certificates/fake_nearby_share_certificate_manager.h +++ b/sharing/certificates/fake_nearby_share_certificate_manager.h @@ -24,6 +24,7 @@ #include #include +#include "absl/time/time.h" #include "internal/base/file_path.h" #include "sharing/certificates/nearby_share_certificate_manager.h" #include "sharing/certificates/nearby_share_certificate_manager_impl.h" @@ -89,6 +90,11 @@ class FakeNearbyShareCertificateManager : public NearbyShareCertificateManager { CertDecryptedCallback callback; }; + struct AddBindingToPublicCertificateCall { + std::string certificate_id; + std::string binding_id; + }; + FakeNearbyShareCertificateManager(); ~FakeNearbyShareCertificateManager() override; @@ -100,6 +106,13 @@ class FakeNearbyShareCertificateManager : public NearbyShareCertificateManager { void ForceUploadPrivateCertificates() override {}; void ClearPublicCertificates(std::function callback) override; void SetVendorId(int32_t vendor_id) override {} + void AddBindingToPublicCertificate( + absl::string_view certificate_id, absl::string_view binding_id) override; + void SetJoinBindingTime(absl::Time join_binding_time, + absl::Duration life_time) override { + join_binding_time_ = join_binding_time; + join_binding_life_time_ = life_time; + } std::string Dump() const override { return ""; } // Make protected methods from base class public in this fake class. @@ -125,6 +138,11 @@ class FakeNearbyShareCertificateManager : public NearbyShareCertificateManager { return get_decrypted_public_certificate_calls_; } + const std::vector& + add_binding_to_public_certificate_calls() const { + return add_binding_to_public_certificate_calls_; + } + private: // NearbyShareCertificateManager: void OnStartScheduledTasks() override {} @@ -139,7 +157,11 @@ class FakeNearbyShareCertificateManager : public NearbyShareCertificateManager { size_t num_clear_public_certificates_calls_ = 0; std::vector get_decrypted_public_certificate_calls_; + std::vector + add_binding_to_public_certificate_calls_; std::vector next_salt_; + absl::Time join_binding_time_; + absl::Duration join_binding_life_time_; }; } // namespace sharing diff --git a/sharing/certificates/fake_nearby_share_certificate_storage.cc b/sharing/certificates/fake_nearby_share_certificate_storage.cc index f06fecf3..155767fe 100644 --- a/sharing/certificates/fake_nearby_share_certificate_storage.cc +++ b/sharing/certificates/fake_nearby_share_certificate_storage.cc @@ -106,7 +106,16 @@ void FakeNearbyShareCertificateStorage::GetPublicCertificate( std::function< void(bool, std::unique_ptr)> callback) { - get_public_certificate_callback_ = std::move(callback); + get_public_certificate_callback_ = callback; + if (is_sync_mode_) { + for (const auto& cert : public_certificates_) { + if (cert.secret_id() == id) { + callback(true, std::make_unique(cert)); + return; + } + } + callback(false, nullptr); + } } std::vector @@ -135,6 +144,19 @@ void FakeNearbyShareCertificateStorage::AddPublicCertificates( public_certificates.end()), callback); if (is_sync_mode_) { + for (const auto& cert : public_certificates) { + bool found = false; + for (auto& existing_cert : public_certificates_) { + if (existing_cert.secret_id() == cert.secret_id()) { + existing_cert = cert; + found = true; + break; + } + } + if (!found) { + public_certificates_.push_back(cert); + } + } callback(add_public_certificates_result_); } } @@ -152,6 +174,12 @@ void FakeNearbyShareCertificateStorage::ClearPublicCertificates( clear_public_certificates_callbacks_.push_back(std::move(callback)); } +void FakeNearbyShareCertificateStorage::SetPublicCertificates( + absl::Span public_certificates) { + public_certificates_ = std::vector( + public_certificates.begin(), public_certificates.end()); +} + void FakeNearbyShareCertificateStorage::SetPublicCertificateIds( absl::Span ids) { public_certificate_ids_ = std::vector(ids.begin(), ids.end()); diff --git a/sharing/certificates/fake_nearby_share_certificate_storage.h b/sharing/certificates/fake_nearby_share_certificate_storage.h index a86a2d43..e545acc0 100644 --- a/sharing/certificates/fake_nearby_share_certificate_storage.h +++ b/sharing/certificates/fake_nearby_share_certificate_storage.h @@ -123,6 +123,9 @@ class FakeNearbyShareCertificateStorage : public NearbyShareCertificateStorage { void ClearPublicCertificates(ResultCallback callback) override; void SetPublicCertificateIds(absl::Span ids); + void SetPublicCertificates( + absl::Span + public_certificates); void SetNextPublicCertificateExpirationTime(absl::Time time); std::vector& get_public_certificates_callbacks() { @@ -156,6 +159,7 @@ class FakeNearbyShareCertificateStorage : public NearbyShareCertificateStorage { absl::Mutex mutex_; absl::Time next_public_certificate_expiration_time_ = absl::InfiniteFuture(); std::vector public_certificate_ids_; + std::vector public_certificates_; std::vector private_certificates_ ABSL_GUARDED_BY(mutex_); std::vector get_public_certificates_callbacks_; diff --git a/sharing/certificates/nearby_share_certificate_manager.cc b/sharing/certificates/nearby_share_certificate_manager.cc index c54f6e86..2d1cf898 100644 --- a/sharing/certificates/nearby_share_certificate_manager.cc +++ b/sharing/certificates/nearby_share_certificate_manager.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include "absl/types/span.h" @@ -73,6 +74,15 @@ NearbyShareCertificateManager::EncryptPrivateCertificateMetadataKey( return encrypted_key; } +std::optional +NearbyShareCertificateManager::GetPrivateCertificateId( + DeviceVisibility visibility) { + std::optional cert = + GetValidPrivateCertificate(visibility); + if (!cert) return std::nullopt; + return std::string(cert->id().begin(), cert->id().end()); +} + std::optional> NearbyShareCertificateManager::SignWithPrivateCertificate( DeviceVisibility visibility, absl::Span payload) const { diff --git a/sharing/certificates/nearby_share_certificate_manager.h b/sharing/certificates/nearby_share_certificate_manager.h index c76608bc..3fc76e9d 100644 --- a/sharing/certificates/nearby_share_certificate_manager.h +++ b/sharing/certificates/nearby_share_certificate_manager.h @@ -22,6 +22,8 @@ #include #include +#include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "absl/types/span.h" #include "internal/base/observer_list.h" #include "sharing/certificates/nearby_share_decrypted_public_certificate.h" @@ -80,6 +82,12 @@ class NearbyShareCertificateManager { std::optional EncryptPrivateCertificateMetadataKey(proto::DeviceVisibility visibility); + // Returns the id of the currently valid private certificate with + // |visibility|, or absl::nullopt if there is no valid private certificate + // with |visibility|. + std::optional GetPrivateCertificateId( + 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. @@ -119,9 +127,18 @@ class NearbyShareCertificateManager { // Sets the vendor ID to generate certificates for. virtual void SetVendorId(int32_t vendor_id) = 0; + // Sets the timestamp of the join binding rpc. This is used to ensure that the + // requested public certificates contain the ids of the binding. + // `life_time` determines how long this join_binding_time will be used. + virtual void SetJoinBindingTime(absl::Time join_binding_time, + absl::Duration life_time) = 0; + // Dump certificates ID information for troubleshooting. virtual std::string Dump() const = 0; + virtual void AddBindingToPublicCertificate( + absl::string_view certificate_id, absl::string_view binding_id) = 0; + protected: virtual void OnStartScheduledTasks() = 0; virtual void OnStopScheduledTasks() = 0; diff --git a/sharing/certificates/nearby_share_certificate_manager_impl.cc b/sharing/certificates/nearby_share_certificate_manager_impl.cc index 56526182..db2afa89 100644 --- a/sharing/certificates/nearby_share_certificate_manager_impl.cc +++ b/sharing/certificates/nearby_share_certificate_manager_impl.cc @@ -34,6 +34,7 @@ #include "location/nearby/sharing/lib/account/account_manager.h" #include "location/nearby/sharing/lib/rpc/sharing_rpc_client.h" #include "absl/algorithm/algorithm.h" +#include "absl/algorithm/container.h" #include "absl/base/nullability.h" #include "absl/container/flat_hash_map.h" #include "absl/memory/memory.h" @@ -41,9 +42,11 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" #include "absl/time/time.h" #include "absl/types/span.h" +#include "third_party/gloop/util/time/protoutil.h" #include "internal/base/file_path.h" #include "internal/flags/nearby_flags.h" #include "internal/platform/mac_address.h" @@ -383,6 +386,13 @@ void NearbyShareCertificateManagerImpl::CertificateDownloadContext:: page_number_++; QuerySharedCredentialsWithBindingIdsRequest request; request.set_name(absl::StrCat("devices/", device_id_)); + if (join_time_.has_value()) { + absl::StatusOr join_time = + util_time::EncodeGoogleApiProto(*join_time_); + if (join_time.ok()) { + *request.mutable_join_binding_time() = *join_time; + } + } if (next_page_token_.has_value()) { request.set_page_token(*next_page_token_); } @@ -408,7 +418,8 @@ void NearbyShareCertificateManagerImpl::CertificateDownloadContext:: << absl::BytesToHexString(credential.data()); continue; } - VLOG(1) << "Successfully parsed credential: " << credential.id(); + VLOG(1) << "Successfully parsed credential: " << credential.id() + << " with binding id: " << certificate.binding_id(); certificates_.push_back(certificate); } @@ -466,10 +477,20 @@ bool NearbyShareCertificateManagerImpl::DownloadPublicCertificatesInExecutor() { return true; } + // Clear join_time if it is expired. + std::optional join_time; + { + absl::MutexLock lock(join_time_mutex_); + if (join_time_.has_value() && + context_->GetClock()->Now() > join_time_discard_time_) { + join_time_.reset(); + } + join_time = join_time_; + } bool download_succeeded = false; absl::Notification notification; auto context = std::make_unique( - nearby_identity_client_, std::move(device_id), + nearby_identity_client_, std::move(device_id), join_time, [this, &download_succeeded, ¬ification]( absl::StatusOr> certificates_status) { if (!certificates_status.ok()) { @@ -489,7 +510,7 @@ bool NearbyShareCertificateManagerImpl::DownloadPublicCertificatesInExecutor() { notification.Notify(); }); if (NearbyFlags::GetInstance().GetBoolFlag( - config_package_nearby::nearby_sharing_feature::kEnableFileSync)) { + config_package_nearby::nearby_sharing_feature::kEnableBackup)) { context->QuerySharedCredentialsWithBindingIdsFetchNextPage(); } else { context->QuerySharedCredentialsFetchNextPage(); @@ -744,6 +765,13 @@ void NearbyShareCertificateManagerImpl::SetVendorId(int32_t vendor_id) { RegeneratePrivateCertificates(); } +void NearbyShareCertificateManagerImpl::SetJoinBindingTime( + absl::Time join_binding_time, absl::Duration life_time) { + absl::MutexLock lock(join_time_mutex_); + join_time_ = join_binding_time; + join_time_discard_time_ = context_->GetClock()->Now() + life_time; +} + std::string NearbyShareCertificateManagerImpl::Dump() const { std::stringstream sstream; sstream << "Public Certificates" << std::endl; @@ -892,6 +920,32 @@ bool NearbyShareCertificateManagerImpl::RefreshPrivateCertificatesInExecutor( return true; } +void NearbyShareCertificateManagerImpl::AddBindingToPublicCertificate( + absl::string_view certificate_id, absl::string_view binding_id) { + LOG(INFO) << "Adding binding to public certificate: " + << absl::BytesToHexString(certificate_id); + absl::Notification notification; + certificate_storage_->GetPublicCertificate( + certificate_id, + [this, id = std::string(binding_id), ¬ification]( + bool success, std::unique_ptr certificate) { + if (success && certificate != nullptr) { + certificate->set_binding_id(id); + certificate_storage_->AddPublicCertificates( + {*certificate}, [](bool success) { + if (!success) { + LOG(WARNING) + << "Failed to add binding to public certificate."; + } + }); + } else { + LOG(WARNING) << "Failed to add binding to public certificate."; + } + notification.Notify(); + }); + notification.WaitForNotification(); +} + void NearbyShareCertificateManagerImpl::ForceUploadPrivateCertificates() { executor_->PostTask([this]() { private_certificate_expiration_scheduler_->HandleResult( @@ -942,9 +996,9 @@ bool NearbyShareCertificateManagerImpl::UpdateAccountInfoInExecutor() { get_account_info_succeeded = true; const auto& capabilities = response->account_info().capabilities(); bool has_titanium_capability = - (std::find(capabilities.begin(), capabilities.end(), - google::nearby::identity::v1::AccountInfo:: - CAPABILITY_TITANIUM) != capabilities.end()); + (absl::c_find(capabilities, + google::nearby::identity::v1::AccountInfo:: + CAPABILITY_TITANIUM) != capabilities.end()); preference_manager_.SetBoolean(PrefNames::kAdvancedProtectionEnabled, has_titanium_capability); LOG(INFO) << "GetAccountInfo succeeded, advanced protection enabled: " diff --git a/sharing/certificates/nearby_share_certificate_manager_impl.h b/sharing/certificates/nearby_share_certificate_manager_impl.h index 5f93cad6..ad1d7fcf 100644 --- a/sharing/certificates/nearby_share_certificate_manager_impl.h +++ b/sharing/certificates/nearby_share_certificate_manager_impl.h @@ -26,8 +26,11 @@ #include "location/nearby/sharing/lib/account/account_manager.h" #include "location/nearby/sharing/lib/rpc/sharing_rpc_client.h" #include "absl/base/nullability.h" +#include "absl/base/thread_annotations.h" #include "absl/functional/any_invocable.h" #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "internal/base/file_path.h" #include "internal/platform/task_runner.h" @@ -92,7 +95,12 @@ class NearbyShareCertificateManagerImpl void ForceUploadPrivateCertificates() override; void ClearPublicCertificates(std::function callback) override; void SetVendorId(int32_t vendor_id) override; + void SetJoinBindingTime(absl::Time join_binding_time, + absl::Duration life_time) + ABSL_LOCKS_EXCLUDED(join_time_mutex_) override; std::string Dump() const override; + void AddBindingToPublicCertificate( + absl::string_view certificate_id, absl::string_view binding_id) override; private: // Class for maintaining a single instance of public certificate download @@ -104,12 +112,14 @@ class NearbyShareCertificateManagerImpl nearby::sharing::api::IdentityRpcClient* absl_nonnull nearby_identity_client, std::string device_id, + std::optional join_time, absl::AnyInvocable> certificates_status) &&> download_callback) : nearby_identity_client_(nearby_identity_client), device_id_(std::move(device_id)), + join_time_(join_time), download_callback_(std::move(download_callback)) {} // Fetches the next page of certificates by calling Identity API @@ -123,7 +133,8 @@ class NearbyShareCertificateManagerImpl private: nearby::sharing::api::IdentityRpcClient* absl_nonnull const nearby_identity_client_; - std::string device_id_; + const std::string device_id_; + const std::optional join_time_; std::optional next_page_token_; int page_number_ = 1; std::vector certificates_; @@ -219,6 +230,12 @@ class NearbyShareCertificateManagerImpl account_info_update_scheduler_; std::unique_ptr executor_; + absl::Mutex join_time_mutex_; + // Set to the transaction timestamp of the last successful pairing if + // available. This is returned from the phone in the BindingResponse message. + std::optional join_time_ ABSL_GUARDED_BY(join_time_mutex_); + // The time when the join_time_ will be discarded. + absl::Time join_time_discard_time_ ABSL_GUARDED_BY(join_time_mutex_); }; } // namespace nearby::sharing diff --git a/sharing/certificates/nearby_share_certificate_manager_impl_test.cc b/sharing/certificates/nearby_share_certificate_manager_impl_test.cc index ebd994a6..b8cc98d1 100644 --- a/sharing/certificates/nearby_share_certificate_manager_impl_test.cc +++ b/sharing/certificates/nearby_share_certificate_manager_impl_test.cc @@ -39,6 +39,7 @@ #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" +#include "third_party/gloop/util/time/protoutil.h" #include "internal/flags/nearby_flags.h" #include "internal/platform/mac_address.h" #include "sharing/certificates/constants.h" @@ -78,6 +79,7 @@ using ::google::nearby::identity::v1:: QuerySharedCredentialsWithBindingIdsResponse; using ::nearby::sharing::proto::DeviceVisibility; using ::nearby::sharing::proto::PublicCertificate; +using ::protobuf_matchers::EqualsProto; using ::testing::Not; using ::testing::ReturnRef; using ::testing::UnorderedElementsAreArray; @@ -634,6 +636,31 @@ TEST_F(NearbyShareCertificateManagerImplTest, DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS)); } +TEST_F(NearbyShareCertificateManagerImplTest, + GetPrivateCertificateIdWithNoCertificates) { + Initialize(); + cert_store_->ReplacePrivateCertificates({}); + EXPECT_FALSE(cert_manager_->GetPrivateCertificateId( + DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS).has_value()); +} + +TEST_F(NearbyShareCertificateManagerImplTest, + GetPrivateCertificateIdWithValidCertificate) { + Initialize(); + NearbySharePrivateCertificate private_certificate = + GetNearbyShareTestPrivateCertificate( + DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS); + cert_store_->ReplacePrivateCertificates({private_certificate}); + FastForward(GetNearbyShareTestNotBefore() + + kNearbyShareCertificateValidityPeriod * 0.5 - Now()); + ASSERT_TRUE(cert_manager_->GetPrivateCertificateId( + DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS).has_value()); + EXPECT_EQ(cert_manager_->GetPrivateCertificateId( + DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS).value(), + std::string(private_certificate.id().begin(), + private_certificate.id().end())); +} + TEST_F(NearbyShareCertificateManagerImplTest, SignWithPrivateCertificate) { Initialize(); NearbySharePrivateCertificate private_certificate = @@ -782,7 +809,7 @@ TEST_F(NearbyShareCertificateManagerImplTest, TEST_F(NearbyShareCertificateManagerImplTest, QuerySharedCredentialsWithBindingIdsSuccess) { NearbyFlags::GetInstance().OverrideBoolFlagValue( - config_package_nearby::nearby_sharing_feature::kEnableFileSync, true); + config_package_nearby::nearby_sharing_feature::kEnableBackup, true); Initialize(); ASSERT_NO_FATAL_FAILURE(QuerySharedCredentialsWithBindingIdsFlow( /*num_pages=*/2, DownloadPublicCertificatesResult::kSuccess)); @@ -791,12 +818,48 @@ TEST_F(NearbyShareCertificateManagerImplTest, TEST_F(NearbyShareCertificateManagerImplTest, QuerySharedCredentialsWithBindingIdsRPCFailure) { NearbyFlags::GetInstance().OverrideBoolFlagValue( - config_package_nearby::nearby_sharing_feature::kEnableFileSync, true); + config_package_nearby::nearby_sharing_feature::kEnableBackup, true); Initialize(); ASSERT_NO_FATAL_FAILURE(QuerySharedCredentialsWithBindingIdsFlow( /*num_pages=*/2, DownloadPublicCertificatesResult::kHttpError)); } +TEST_F(NearbyShareCertificateManagerImplTest, + QuerySharedCredentialsWithBindingIdsWithJoinTime) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_sharing_feature::kEnableBackup, true); + Initialize(); + cert_manager_->SetJoinBindingTime(absl::FromUnixSeconds(123456789), + absl::Seconds(30)); + ASSERT_NO_FATAL_FAILURE(QuerySharedCredentialsWithBindingIdsFlow( + /*num_pages=*/2, DownloadPublicCertificatesResult::kSuccess)); + std::vector requests = + identity_client_.query_shared_credentials_with_binding_ids_requests(); + for (const auto& request : requests) { + ASSERT_OK_AND_ASSIGN( + auto expected_time, + util_time::EncodeGoogleApiProto(absl::FromUnixSeconds(123456789))); + EXPECT_THAT(request.join_binding_time(), EqualsProto(expected_time)); + } +} + +TEST_F(NearbyShareCertificateManagerImplTest, + QuerySharedCredentialsWithBindingIdsWithJoinTimeExpiration) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_sharing_feature::kEnableBackup, true); + Initialize(); + cert_manager_->SetJoinBindingTime(absl::FromUnixSeconds(123456789), + absl::Seconds(30)); + FastForward(absl::Seconds(31)); + ASSERT_NO_FATAL_FAILURE(QuerySharedCredentialsWithBindingIdsFlow( + /*num_pages=*/2, DownloadPublicCertificatesResult::kSuccess)); + std::vector requests = + identity_client_.query_shared_credentials_with_binding_ids_requests(); + for (const auto& request : requests) { + EXPECT_FALSE(request.has_join_binding_time()); + } +} + TEST_F(NearbyShareCertificateManagerImplTest, ClearPublicCertificates) { Initialize(); cert_manager_->ClearPublicCertificates([&](bool result) {}); @@ -1110,4 +1173,24 @@ TEST_F(NearbyShareCertificateManagerImplTest, PrefNames::kAdvancedProtectionEnabled, /*default_value=*/false)); } +TEST_F(NearbyShareCertificateManagerImplTest, AddBindingToPublicCertificate) { + Initialize(); + + PublicCertificate cert; + cert.set_secret_id("test_cert_id"); + cert.set_binding_id("old_binding_id"); + + cert_store_->SetPublicCertificates({cert}); + cert_store_->SetAddPublicCertificatesResult(true); + + cert_manager_->AddBindingToPublicCertificate("test_cert_id", + "new_binding_id"); + + ASSERT_EQ(cert_store_->add_public_certificates_calls().size(), 1u); + const auto& call = cert_store_->add_public_certificates_calls().back(); + ASSERT_EQ(call.public_certificates.size(), 1u); + EXPECT_EQ(call.public_certificates[0].secret_id(), "test_cert_id"); + EXPECT_EQ(call.public_certificates[0].binding_id(), "new_binding_id"); +} + } // namespace nearby::sharing diff --git a/sharing/certificates/nearby_share_certificate_storage_impl.cc b/sharing/certificates/nearby_share_certificate_storage_impl.cc index b242e4b4..54e2d627 100644 --- a/sharing/certificates/nearby_share_certificate_storage_impl.cc +++ b/sharing/certificates/nearby_share_certificate_storage_impl.cc @@ -342,7 +342,8 @@ void NearbyShareCertificateStorageImpl::GetPublicCertificate( }); return; } - VLOG(1) << __func__ << ": Calling LoadCertificate on database, key: " << id; + VLOG(1) << __func__ << ": Calling LoadCertificate on database, key: " + << absl::BytesToHexString(id); public_certificate_database_->LoadCertificate(id, std::move(callback)); } diff --git a/sharing/certificates/nearby_share_certificate_storage_impl_test.cc b/sharing/certificates/nearby_share_certificate_storage_impl_test.cc index 3d791b2f..5c7ad9ee 100644 --- a/sharing/certificates/nearby_share_certificate_storage_impl_test.cc +++ b/sharing/certificates/nearby_share_certificate_storage_impl_test.cc @@ -211,20 +211,20 @@ TEST_F(NearbyShareCertificateStorageImplTest, InitializeRetrySucceed) { auto db = std::make_unique>(); MockPublicCertificateDb* mock_db = db.get(); EXPECT_CALL(*mock_db, Initialize(_)) - .WillOnce(Invoke( - [](absl::AnyInvocable + .WillOnce( + [](absl::AnyInvocable callback) { std::move(callback)(MockPublicCertificateDb::InitStatus::kError); - })) - .WillRepeatedly(Invoke( - [](absl::AnyInvocable + }) + .WillRepeatedly( + [](absl::AnyInvocable callback) { std::move(callback)(MockPublicCertificateDb::InitStatus::kOk); - })); + }); EXPECT_CALL(*mock_db, Destroy(_)) - .WillOnce(Invoke([](absl::AnyInvocable callback) { + .WillOnce([](absl::AnyInvocable callback) { std::move(callback)(true); - })); + }); auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create( preference_manager_, std::move(db)); @@ -243,11 +243,11 @@ TEST_F(NearbyShareCertificateStorageImplTest, InitializeRetryFailed) { auto db = std::make_unique>(); MockPublicCertificateDb* mock_db = db.get(); EXPECT_CALL(*mock_db, Initialize(_)) - .WillRepeatedly(Invoke( - [](absl::AnyInvocable + .WillRepeatedly( + [](absl::AnyInvocable callback) { std::move(callback)(MockPublicCertificateDb::InitStatus::kError); - })); + }); auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create( preference_manager_, std::move(db)); @@ -267,23 +267,23 @@ TEST_F(NearbyShareCertificateStorageImplTest, auto db = std::make_unique>(); MockPublicCertificateDb* mock_db = db.get(); EXPECT_CALL(*mock_db, Initialize(_)) - .WillOnce(Invoke( - [](absl::AnyInvocable + .WillOnce( + [](absl::AnyInvocable callback) { std::move(callback)(MockPublicCertificateDb::InitStatus::kCorrupt); - })) - .WillRepeatedly(Invoke( - [](absl::AnyInvocable + }) + .WillRepeatedly( + [](absl::AnyInvocable 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 callback) { + .WillRepeatedly([](absl::AnyInvocable callback) { std::move(callback)(true); - })); + }); auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create( preference_manager_, std::move(db)); @@ -301,15 +301,15 @@ TEST_F(NearbyShareCertificateStorageImplTest, InitializeCorruptDestroyFails) { auto db = std::make_unique>(); MockPublicCertificateDb* mock_db = db.get(); EXPECT_CALL(*mock_db, Initialize(_)) - .WillOnce(Invoke( - [](absl::AnyInvocable + .WillOnce( + [](absl::AnyInvocable callback) { std::move(callback)(MockPublicCertificateDb::InitStatus::kCorrupt); - })); + }); EXPECT_CALL(*mock_db, Destroy(_)) - .WillOnce(Invoke([](absl::AnyInvocable callback) { + .WillOnce([](absl::AnyInvocable callback) { std::move(callback)(false); - })); + }); auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create( preference_manager_, std::move(db)); @@ -333,18 +333,18 @@ TEST_F(NearbyShareCertificateStorageImplTest, DeferredCallbackQueue) { auto db = std::make_unique>(); MockPublicCertificateDb* mock_db = db.get(); EXPECT_CALL(*mock_db, Initialize(_)) - .WillOnce(Invoke( - [&](absl::AnyInvocable - callback) { init_status_callback = std::move(callback); })); + .WillOnce( + [&](absl::AnyInvocable + callback) { init_status_callback = std::move(callback); }); EXPECT_CALL(*mock_db, Destroy(_)) - .WillOnce(Invoke([&](absl::AnyInvocable callback) { + .WillOnce([&](absl::AnyInvocable callback) { destroy_callback = std::move(callback); - })); + }); EXPECT_CALL(*mock_db, LoadEntries(_)) - .WillOnce(Invoke( + .WillOnce( [&](absl::AnyInvocable>)&&> - callback) { load_callback = std::move(callback); })); + bool, std::unique_ptr>) &&> + callback) { load_callback = std::move(callback); }); auto cert_store = NearbyShareCertificateStorageImpl::Factory::Create( preference_manager_, std::move(db)); diff --git a/sharing/common/nearby_share_prefs.cc b/sharing/common/nearby_share_prefs.cc index 9d988ba5..279b352b 100644 --- a/sharing/common/nearby_share_prefs.cc +++ b/sharing/common/nearby_share_prefs.cc @@ -67,6 +67,7 @@ void RegisterNearbySharingPrefs(PreferenceManager& preference_manager, preference_manager.Remove(PrefNames::kUsers); preference_manager.SetBoolean(PrefNames::kAdvancedProtectionEnabled, false); + preference_manager.RemoveAllSyncConfigs(); preference_manager.RemoveAllBindingConfigs(); } diff --git a/sharing/flags/generated/nearby_sharing_feature_flags.h b/sharing/flags/generated/nearby_sharing_feature_flags.h index 6e61e601..701baec8 100755 --- a/sharing/flags/generated/nearby_sharing_feature_flags.h +++ b/sharing/flags/generated/nearby_sharing_feature_flags.h @@ -61,9 +61,9 @@ constexpr auto kShowAutoUpdateSetting = // The default time in milliseconds a cached entry can be in LOST state. constexpr auto kDiscoveryCacheLostExpiryMs = flags::Flag(kConfigPackage, "45658774", 15000); -// When true, enable file sync feature. -constexpr auto kEnableFileSync = - flags::Flag(kConfigPackage, "45762616", false); +// When true, enable suspend resume listener. +constexpr auto kEnableSuspendResumeListener = + flags::Flag(kConfigPackage, "45815625", false); // When true, enable wifi hotspot medium for HP Realtek devices. constexpr auto kEnableWifiHotspotForHpRealtekDevices = flags::Flag(kConfigPackage, "45673628", false); @@ -108,7 +108,7 @@ inline absl::btree_map&> GetBoolFlags() { {45411589, kEnableRetryResumeTransfer}, {45459748, kEnableSendingDesktopEvents}, {45409033, kShowAutoUpdateSetting}, - {45762616, kEnableFileSync}, + {45815625, kEnableSuspendResumeListener}, {45673628, kEnableWifiHotspotForHpRealtekDevices}, {45683539, kUseAlternateServiceUuidForDiscovery}, {45776229, kEnableBackup}, diff --git a/sharing/internal/api/BUILD b/sharing/internal/api/BUILD index aaa5a5a3..5f970791 100644 --- a/sharing/internal/api/BUILD +++ b/sharing/internal/api/BUILD @@ -63,6 +63,7 @@ cc_library( "//internal/platform/implementation:types", "//location/nearby/sharing/lib/account:account_manager", "//location/nearby/sharing/lib/sync:sync_binding_prefs_cc_proto", + "//location/nearby/sharing/lib/sync:sync_config_prefs_cc_proto", "//sharing/proto:share_cc_proto", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings:string_view", diff --git a/sharing/internal/api/preference_manager.h b/sharing/internal/api/preference_manager.h index 58fba5aa..c1978415 100644 --- a/sharing/internal/api/preference_manager.h +++ b/sharing/internal/api/preference_manager.h @@ -23,6 +23,7 @@ #include #include "location/nearby/sharing/lib/sync/sync_binding_prefs.pb.h" +#include "location/nearby/sharing/lib/sync/sync_config_prefs.pb.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" @@ -80,6 +81,10 @@ class PreferenceManager { virtual void RemoveDictionaryItem(absl::string_view key, absl::string_view dictionary_item) = 0; + virtual void SetSyncConfigValue( + absl::string_view binding_id, + const nearby::sharing::sync::SyncConfigPrefs& value) = 0; + virtual void SetSyncBindingValue( const nearby::sharing::sync::SyncBindingPrefs& value) = 0; @@ -122,12 +127,19 @@ class PreferenceManager { virtual std::optional GetDictionaryStringValue( absl::string_view key, absl::string_view dictionary_item) const = 0; + virtual std::optional + GetSyncConfigValue(absl::string_view binding_id) const = 0; + + virtual void RemoveSyncConfigPref(absl::string_view binding_id) = 0; + virtual std::optional GetSyncBindingValue() const = 0; // Removes preferences virtual void Remove(absl::string_view key) = 0; - // Removes all binding configs. + // Removes all sync configs. + // Observers are not notified for each removed config. + virtual void RemoveAllSyncConfigs() = 0; // Removes all binding configs. // Observers are not notified for each removed config. virtual void RemoveAllBindingConfigs() = 0; diff --git a/sharing/internal/public/pref_names.h b/sharing/internal/public/pref_names.h index 6daef139..e3b3b1b0 100644 --- a/sharing/internal/public/pref_names.h +++ b/sharing/internal/public/pref_names.h @@ -58,10 +58,18 @@ class PrefNames { // Binding configs preferences are stored in pref keys: // kBindingConfigPrefix + // Example: "nearby_sharing.binding_config.FileSync" - // TODO: b/485304482 - define data format for binding configs. + // For FileSync service, data stored in binding config prefs is a + // nearby::sharing::sync::SyncBindingPrefs proto. static constexpr absl::string_view kBindingConfigPrefix = "nearby_sharing.binding_config."; -}; + + // Sync configs preferences are stored in pref keys: + // kSyncConfigPrefix + + // Example: "nearby_sharing.sync_config.01243347-2343-4324-3423-432432432432" + // Data stored in sync config prefs is a + // nearby::sharing::sync::SyncConfigPrefs proto. + static constexpr absl::string_view kSyncConfigPrefix = + "nearby_sharing.sync_config.";}; } // namespace nearby::sharing diff --git a/sharing/internal/test/BUILD b/sharing/internal/test/BUILD index a6069b4c..0342ae4c 100644 --- a/sharing/internal/test/BUILD +++ b/sharing/internal/test/BUILD @@ -41,6 +41,7 @@ cc_library( "//internal/platform:types", "//internal/test", "//location/nearby/sharing/lib/sync:sync_binding_prefs_cc_proto", + "//location/nearby/sharing/lib/sync:sync_config_prefs_cc_proto", "//sharing/internal/api:platform", "//sharing/internal/public:pref_names", "//sharing/internal/public:types", diff --git a/sharing/internal/test/fake_preference_manager.cc b/sharing/internal/test/fake_preference_manager.cc index 27066249..099f9170 100644 --- a/sharing/internal/test/fake_preference_manager.cc +++ b/sharing/internal/test/fake_preference_manager.cc @@ -23,6 +23,7 @@ #include #include "location/nearby/sharing/lib/sync/sync_binding_prefs.pb.h" +#include "location/nearby/sharing/lib/sync/sync_config_prefs.pb.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -36,6 +37,7 @@ namespace nearby { using ::nearby::sharing::PrefNames; using ::nearby::sharing::api::PrivateCertificateData; using ::nearby::sharing::sync::SyncBindingPrefs; +using ::nearby::sharing::sync::SyncConfigPrefs; // Preference suffix for the sync binding information. constexpr absl::string_view kFileSyncBindingName = "FileSync"; @@ -243,6 +245,16 @@ void FakePreferenceManager::RemoveDictionaryItem( NotifyPreferenceChanged(key); } +void FakePreferenceManager::SetSyncConfigValue(absl::string_view binding_id, + const SyncConfigPrefs& value) { + SetValue(absl::StrCat(PrefNames::kSyncConfigPrefix, binding_id), + value.SerializeAsString()); +} + +void FakePreferenceManager::RemoveSyncConfigPref(absl::string_view binding_id) { + Remove(absl::StrCat(PrefNames::kSyncConfigPrefix, binding_id)); +} + void FakePreferenceManager::SetSyncBindingValue( const SyncBindingPrefs& value) { SetValue(absl::StrCat(PrefNames::kBindingConfigPrefix, kFileSyncBindingName), @@ -333,6 +345,21 @@ std::optional FakePreferenceManager::GetDictionaryStringValue( return GetDictionaryValue(key, dictionary_item); } +std::optional FakePreferenceManager::GetSyncConfigValue( + absl::string_view binding_id) const { + std::string serialized_sync_config; + serialized_sync_config = + GetString(absl::StrCat(PrefNames::kSyncConfigPrefix, binding_id), ""); + if (serialized_sync_config.empty()) { + return std::nullopt; + } + SyncConfigPrefs sync_config; + if (!sync_config.ParseFromString(serialized_sync_config)) { + return std::nullopt; + } + return sync_config; +} + std::optional FakePreferenceManager::GetSyncBindingValue() const { std::string serialized_sync_binding; @@ -358,6 +385,13 @@ void FakePreferenceManager::Remove(absl::string_view key) { NotifyPreferenceChanged(key); } +void FakePreferenceManager::RemoveAllSyncConfigs() { + absl::MutexLock lock(mutex_); + absl::erase_if(values_, [](const auto& item) { + return item.first.starts_with(PrefNames::kSyncConfigPrefix); + }); +} + void FakePreferenceManager::RemoveAllBindingConfigs() { absl::MutexLock lock(mutex_); absl::erase_if(values_, [](const auto& item) { diff --git a/sharing/internal/test/fake_preference_manager.h b/sharing/internal/test/fake_preference_manager.h index 1fb023cf..bb7657e8 100644 --- a/sharing/internal/test/fake_preference_manager.h +++ b/sharing/internal/test/fake_preference_manager.h @@ -24,6 +24,7 @@ #include #include "location/nearby/sharing/lib/sync/sync_binding_prefs.pb.h" +#include "location/nearby/sharing/lib/sync/sync_config_prefs.pb.h" #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" @@ -75,6 +76,9 @@ class FakePreferenceManager : public nearby::sharing::api::PreferenceManager { void RemoveDictionaryItem(absl::string_view key, absl::string_view dictionary_item) override; + void SetSyncConfigValue( + absl::string_view binding_id, + const nearby::sharing::sync::SyncConfigPrefs& value) override; void SetSyncBindingValue( const nearby::sharing::sync::SyncBindingPrefs& value) override; @@ -111,10 +115,14 @@ class FakePreferenceManager : public nearby::sharing::api::PreferenceManager { absl::string_view key, absl::string_view dictionary_item) const override; std::optional GetDictionaryStringValue( absl::string_view key, absl::string_view dictionary_item) const override; + std::optional + GetSyncConfigValue(absl::string_view binding_id) const override; + void RemoveSyncConfigPref(absl::string_view binding_id) override; std::optional GetSyncBindingValue() const override; void Remove(absl::string_view key) override; + void RemoveAllSyncConfigs() override; void RemoveAllBindingConfigs() override; void AddObserver( diff --git a/sharing/nearby_connections_manager_impl.cc b/sharing/nearby_connections_manager_impl.cc index 0b57e8c0..85a28e92 100644 --- a/sharing/nearby_connections_manager_impl.cc +++ b/sharing/nearby_connections_manager_impl.cc @@ -821,7 +821,6 @@ void NearbyConnectionsManagerImpl::DeleteUnknownFilePayloadAndCancel( std::optional< std::weak_ptr> NearbyConnectionsManagerImpl::GetStatusListenerForId(int64_t payload_id) const { - MutexLock lock(&mutex_); auto listener_it = payload_status_listeners_.find(payload_id); if (listener_it == payload_status_listeners_.end()) { return std::nullopt; @@ -840,12 +839,12 @@ NearbyConnectionImpl* NearbyConnectionsManagerImpl::GetConnectionForId( void NearbyConnectionsManagerImpl::RemoveStatusListenerForPayloadId( int64_t payload_id) { - MutexLock lock(&mutex_); payload_status_listeners_.erase(payload_id); } void NearbyConnectionsManagerImpl::OnPayloadTransferUpdate( absl::string_view endpoint_id, const PayloadTransferUpdate& update) { + MutexLock lock(&mutex_); VLOG(1) << "Received payload transfer update id=" << update.payload_id << ",status=" << PayloadStatusToString(update.status) << ",total=" << update.total_bytes @@ -879,16 +878,21 @@ void NearbyConnectionsManagerImpl::OnPayloadTransferUpdate( // If this is an incoming payload that we have not registered for, then // we'll treat it as a control frame (e.g. IntroductionFrame) and // forward it to the associated NearbyConnection. - auto payload = GetIncomingPayload(update.payload_id); - if (payload == nullptr) return; + if (update.status == PayloadStatus::kInProgress) { + return; + } + + auto node = incoming_payloads_.extract(update.payload_id); + if (node.empty()) return; if (update.status != PayloadStatus::kSuccess) return; - NearbyConnectionImpl* connection = GetConnectionForId(endpoint_id); - if (connection == nullptr) return; + auto connection_it = connections_.find(endpoint_id); + if (connection_it == connections_.end()) return; VLOG(1) << "Writing incoming byte message to NearbyConnection."; - connection->WriteMessage(payload->content.bytes_payload.bytes); + connection_it->second->WriteMessage( + std::move(node.mapped().content.bytes_payload.bytes)); } void NearbyConnectionsManagerImpl::Reset() { diff --git a/sharing/nearby_connections_manager_impl.h b/sharing/nearby_connections_manager_impl.h index 174f83f8..b5f65ced 100644 --- a/sharing/nearby_connections_manager_impl.h +++ b/sharing/nearby_connections_manager_impl.h @@ -128,13 +128,13 @@ class NearbyConnectionsManagerImpl : public NearbyConnectionsManager { absl::flat_hash_set GetUnknownFilePathsToDelete(); std::optional> GetStatusListenerForId( - int64_t payload_id) const ABSL_LOCKS_EXCLUDED(mutex_); + int64_t payload_id) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); NearbyConnectionImpl* GetConnectionForId(absl::string_view endpoint_id) const ABSL_LOCKS_EXCLUDED(mutex_); void RemoveStatusListenerForPayloadId(int64_t payload_id) - ABSL_LOCKS_EXCLUDED(mutex_); + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); void Reset(); diff --git a/sharing/nearby_sharing_service_impl.cc b/sharing/nearby_sharing_service_impl.cc index 03fb483b..2e4c9211 100644 --- a/sharing/nearby_sharing_service_impl.cc +++ b/sharing/nearby_sharing_service_impl.cc @@ -31,6 +31,7 @@ #include #include +#include "location/nearby/cpp/sharing/clients/cpp/common/nearby_sharing_common.h" #include "location/nearby/sharing/lib/account/account_manager.h" #include "location/nearby/sharing/lib/rpc/sharing_rpc_client.h" #include "location/nearby/sharing/lib/sync/sync_binding_prefs.pb.h" @@ -43,8 +44,10 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "absl/types/span.h" +#include "third_party/gloop/util/time/protoutil.h" #include "internal/base/file_path.h" #include "internal/flags/nearby_flags.h" #include "internal/network/url.h" @@ -133,6 +136,17 @@ constexpr absl::Duration kProcessShutdownPendingTimerDelay = // NOLINT absl::Seconds(15); constexpr absl::Duration kProcessNetworkChangeTimerDelay = absl::Seconds(1); +// Delay invalidating the surface state after a resume event. +// Network activities can cause system to resume for short periods before +// suspending again. This delay allows us to ignore those and only resume +// fully when the system is stable. +constexpr absl::Duration kResumeDelay = absl::Milliseconds(500); + +// The duration to use join binding time when downloading public certificates. +// The default BE database query staleness is 30s. We extend this t0 40s to +// ensure that we have some overlap. +constexpr absl::Duration kJoinBindingTimeLifeTime = absl::Seconds(40); + // The maximum number of certificate downloads that can be performed during a // discovery session. // Assuming a 2min discovery session and 10s download interval. @@ -251,6 +265,24 @@ sync::SyncBinding::SourceDeviceType ShareTargetTypeToSourceDeviceType( return sync::SyncBinding::SOURCE_DEVICE_TYPE_UNKNOWN; } } + +location::nearby::proto::sharing::SharingUseCase +IntroductionUseCaseToLoggingUseCase( + IntroductionFrame::SharingUseCase use_case) { + switch (use_case) { + case IntroductionFrame::NEARBY_SHARE: + return location::nearby::proto::sharing::SharingUseCase:: + USE_CASE_NEARBY_SHARE; + case IntroductionFrame::TAP_TO_SHARE: + return location::nearby::proto::sharing::SharingUseCase:: + USE_CASE_TAP_TO_SHARE; + case IntroductionFrame::FILE_SYNC: + return location::nearby::proto::sharing::SharingUseCase:: + USE_CASE_FILE_SYNC; + default: + return location::nearby::proto::sharing::SharingUseCase::USE_CASE_UNKNOWN; + } +} } // namespace NearbySharingServiceImpl::NearbySharingServiceImpl( @@ -318,6 +350,13 @@ NearbySharingServiceImpl::NearbySharingServiceImpl( OnLockStateChanged(screen_status == nearby::api::DeviceInfo::ScreenStatus::kLocked); }); + if (NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_sharing_feature:: + kEnableSuspendResumeListener)) { + suspend_resume_listener_id_ = + device_info_.RegisterSuspendResumeListener(absl::bind_front( + &NearbySharingServiceImpl::OnSuspendResumeEvent, this)); + } account_manager_.AddObserver(this); settings_->AddSettingsObserver(this); @@ -365,6 +404,8 @@ void NearbySharingServiceImpl::Shutdown( background_receive_callbacks_map_.clear(); device_info_.UnregisterScreenLockedListener(kScreenStateListenerName); + device_info_.UnregisterSuspendResumeListener( + suspend_resume_listener_id_); settings_->RemoveSettingsObserver(this); @@ -668,7 +709,7 @@ void NearbySharingServiceImpl::RegisterReceiveSurface( VLOG(1) << "[Call Identity API] ForceUploadPrivateCertificates."; certificate_manager_->ForceUploadPrivateCertificates(); } - InvalidateReceiveSurfaceState(); + InvalidateAdvertisingState(); status_codes_callback(StatusCodes::kOk); }); } @@ -1396,6 +1437,42 @@ void NearbySharingServiceImpl::OnLockStateChanged(bool locked) { }); } +void NearbySharingServiceImpl::OnSuspendResumeEvent( + nearby::api::DeviceInfo::SuspendResumeEvent event) { + bool suspended = + event == nearby::api::DeviceInfo::SuspendResumeEvent::kSuspend; + { + absl::MutexLock lock(suspend_mutex_); + suspended_ = suspended; + if (!suspended) { + resume_delay_timer_ = std::make_unique( + *service_thread_, "suspend_resume_timer", kResumeDelay, [this]() { + { + absl::MutexLock lock(suspend_mutex_); + if (suspended_) { + return; + } + } + LOG(INFO) << "InvalidateSurfaceState due to system resume"; + InvalidateSurfaceState(); + }); + } + } + if (suspended) { + RunOnNearbySharingServiceThread("on_suspend", [this]() { + LOG(INFO) << "InvalidateSurfaceState due to system suspend"; + { + absl::MutexLock lock(suspend_mutex_); + if (!suspended_) { + return; + } + resume_delay_timer_.reset(); + } + InvalidateSurfaceState(); + }); + } +} + void NearbySharingServiceImpl::AdapterPresentChanged( sharing::api::BluetoothAdapter* adapter, bool present) { RunOnNearbySharingServiceThread("bt_adapter_present_changed", [this, adapter, @@ -1845,7 +1922,7 @@ bool NearbySharingServiceImpl::HasAvailableConnectionMediums() { void NearbySharingServiceImpl::InvalidateSurfaceState() { InvalidateSendSurfaceState(); - InvalidateReceiveSurfaceState(); + InvalidateAdvertisingState(); } void NearbySharingServiceImpl::InvalidateSendSurfaceState() { @@ -1854,6 +1931,16 @@ void NearbySharingServiceImpl::InvalidateSendSurfaceState() { } void NearbySharingServiceImpl::InvalidateScanningState() { + { + absl::MutexLock lock(suspend_mutex_); + if (suspended_) { + StopScanning(); + VLOG(1) << __func__ + << ": Stopping discovery because the system is suspended."; + return; + } + } + // Stop scanning when screen is off. if (is_screen_locked_) { StopScanning(); @@ -1891,6 +1978,17 @@ void NearbySharingServiceImpl::InvalidateScanningState() { } void NearbySharingServiceImpl::InvalidateFastInitiationAdvertising() { + { + absl::MutexLock lock(suspend_mutex_); + if (suspended_) { + StopFastInitiationAdvertising(); + VLOG(1) << __func__ + << ": Stopping fast initiation advertising because the " + "system is suspended."; + return; + } + } + // Screen is off. Do no work. if (is_screen_locked_) { StopFastInitiationAdvertising(); @@ -1929,13 +2027,26 @@ void NearbySharingServiceImpl::InvalidateFastInitiationAdvertising() { StartFastInitiationAdvertising(); } -void NearbySharingServiceImpl::InvalidateReceiveSurfaceState() { - InvalidateAdvertisingState(); -} - void NearbySharingServiceImpl::InvalidateAdvertisingState() { - // Do not advertise on lock screen unless Self Share is enabled. - if (is_screen_locked_) { + { + absl::MutexLock lock(suspend_mutex_); + if (suspended_) { + StopAdvertising(); + VLOG(1) << __func__ + << ": Stopping advertising because the system is suspended."; + return; + } + } + + bool supports_advertising_on_lock_screen = + NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_sharing_feature:: + kEnableBackup) && + sync_manager_.HasSyncBindings(); + DeviceVisibility visibility = settings_->GetVisibility(); + // Do not advertise on lock screen unless Self Share is enabled, or Backup is + // enabled and there are existing SyncBindings. + if (is_screen_locked_ && !supports_advertising_on_lock_screen) { StopAdvertising(); VLOG(1) << __func__ << ": Stopping advertising because the screen is locked."; @@ -1969,29 +2080,51 @@ void NearbySharingServiceImpl::InvalidateAdvertisingState() { // We should only advertise if the user has set the visibility to something // other than HIDDEN or UNSPECIFIED. - if (!IsVisibleInBackground(settings_->GetVisibility())) { + if (!IsVisibleInBackground(visibility)) { StopAdvertising(); VLOG(1) << __func__ << ": Stopping advertising because device is visible to NO_ONE."; return; } + if (is_screen_locked_ && supports_advertising_on_lock_screen) { + if (visibility != DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE) { + VLOG(1) << __func__ + << ": Restarting advertising to SELF_SHARE on lock screen."; + visibility = DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE; + } + } + + bool need_to_restart_advertising = false; + + // This is used to set up the PairedKeyVerificationRunner correctly on + // incoming connections. + advertising_on_screen_locked_ = is_screen_locked_; + + if (last_advertised_device_visibility_ != + DeviceVisibility::DEVICE_VISIBILITY_UNSPECIFIED && + visibility != last_advertised_device_visibility_) { + StopAdvertising(); + need_to_restart_advertising = true; + VLOG(1) << __func__ + << ": Restarting advertising because visibility has changed."; + } + PowerLevel power_level; if (!foreground_receive_callbacks_map_.empty()) { power_level = PowerLevel::kHighPower; } else { power_level = PowerLevel::kLowPower; } - DataUsage data_usage = settings_->GetDataUsage(); - if (advertising_power_level_ != PowerLevel::kUnknown) { + if (!need_to_restart_advertising && + advertising_power_level_ != PowerLevel::kUnknown) { if (power_level == advertising_power_level_) { VLOG(1) << __func__ << ": Ignoring, already advertising with power level " << PowerLevelToString(advertising_power_level_) << " and data usage preference " << static_cast(data_usage); return; } - StopAdvertising(); VLOG(1) << __func__ << ": Restart advertising with power level " << PowerLevelToString(power_level) << " and data usage preference " @@ -1999,7 +2132,6 @@ void NearbySharingServiceImpl::InvalidateAdvertisingState() { } std::optional device_name; - DeviceVisibility visibility = settings_->GetVisibility(); if (visibility == DeviceVisibility::DEVICE_VISIBILITY_EVERYONE) { device_name = local_device_data_manager_->GetDeviceName(); } @@ -2043,11 +2175,13 @@ void NearbySharingServiceImpl::InvalidateAdvertisingState() { force_new_endpoint_id_ = false; advertising_power_level_ = power_level; + last_advertised_device_visibility_ = visibility; VLOG(1) << __func__ << ": StartAdvertising requested over Nearby Connections: " << " power level: " << PowerLevelToString(power_level) + << " screen locked: " << is_screen_locked_ << " visibility: " - << DeviceVisibility_Name(settings_->GetVisibility()) + << DeviceVisibility_Name(visibility) << " data usage: " << DataUsage_Name(data_usage) << " advertise device name?: " << (device_name.has_value() ? "yes" : "no"); @@ -2090,7 +2224,7 @@ void NearbySharingServiceImpl::StartScanning() { scanning_start_timestamp_ = context_->GetClock()->Now(); share_foreground_send_surface_start_timestamp_ = absl::InfinitePast(); is_scanning_ = true; - InvalidateReceiveSurfaceState(); + InvalidateAdvertisingState(); outgoing_targets_manager_.AllTargetsLost( Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( @@ -2333,6 +2467,15 @@ void NearbySharingServiceImpl::OnIncomingTransferUpdate( LOG(WARNING) << __func__ << ": Unknown file paths are not empty."; } } + // If backup session, update last backup time in preference. + if (session.session_usage() == ShareSessionUsage::kFileSync) { + if (session.certificate()) { + sync_manager_.SetSyncConfigBackupTime( + session.certificate()->binding_id(), + metadata.status() == TransferMetadata::Status::kComplete, + context_->GetClock()->Now()); + } + } } else if (metadata.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) { OnTransferStarted(/*is_incoming=*/true); @@ -2463,6 +2606,7 @@ void NearbySharingServiceImpl::OnIncomingDecryptedCertificate( .visibility = settings_->GetVisibility(), .last_visibility = settings_->GetLastVisibility(), .last_visibility_time = settings_->GetLastVisibilityTimestamp(), + .screen_locked_advertising = advertising_on_screen_locked_, }, GetCertificateManager(), absl::bind_front( @@ -2588,8 +2732,7 @@ void NearbySharingServiceImpl::BeginOutgoingTransfer( bool protection_enabled = preference_manager_.GetBoolean(PrefNames::kAdvancedProtectionEnabled, /*default_value=*/false); - session.SetAdvancedProtectionStatus(protection_enabled, - /*advanced_protection_mismatch=*/false); + session.SetAdvancedProtectionStatus(protection_enabled); if (session.token().empty() || !protection_enabled) { // Auto accept if no token or if advanced protection is disabled. OutgoingSessionAccept(session); @@ -2624,6 +2767,20 @@ void NearbySharingServiceImpl::BeginOutgoingPairing( }); } +std::vector NearbySharingServiceImpl::GetCertIdsForSyncBinding() { + std::vector cert_ids; + cert_ids.reserve(2); + if (auto id = certificate_manager_->GetPrivateCertificateId( + proto::DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE)) { + cert_ids.push_back(*id); + } + if (auto id = certificate_manager_->GetPrivateCertificateId( + proto::DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS)) { + cert_ids.push_back(*id); + } + return cert_ids; +} + void NearbySharingServiceImpl::OnInitiateSyncBindingResponse( int64_t share_target_id, absl::StatusOr binding_status) { RunOnNearbySharingServiceThread( @@ -2642,11 +2799,10 @@ void NearbySharingServiceImpl::OnInitiateSyncBindingResponse( LOG(INFO) << __func__ << ": Sync binding rpc succeeded: id=" << binding_id; session->StartPeerBinding( - binding_id, BindingRequest::FILESYNC, - [this, share_target_id, - binding_id](BindingResponse::Status status) { - OnPeerSyncBindingComplete(share_target_id, binding_id, status); - }); + binding_id, BindingRequest::FILESYNC, GetCertIdsForSyncBinding(), + absl::bind_front( + &NearbySharingServiceImpl::OnPeerSyncBindingComplete, this, + share_target_id, binding_id)); } else { LOG(INFO) << __func__ << ": Sync binding rpc failed."; session->Abort(TransferMetadata::Status::kFailed); @@ -2656,7 +2812,7 @@ void NearbySharingServiceImpl::OnInitiateSyncBindingResponse( void NearbySharingServiceImpl::OnPeerSyncBindingComplete( int64_t share_target_id, absl::string_view binding_id, - BindingResponse::Status status) { + const BindingResponse& binding_response) { OutgoingShareSession* session = outgoing_targets_manager_.GetOutgoingShareSession(share_target_id); if (!session || !session->IsConnected()) { @@ -2664,7 +2820,7 @@ void NearbySharingServiceImpl::OnPeerSyncBindingComplete( << share_target_id; return; } - if (status != BindingResponse::SUCCESS) { + if (binding_response.status() != BindingResponse::SUCCESS) { LOG(INFO) << __func__ << ": Sync binding response failed."; session->Abort(TransferMetadata::Status::kFailed); return; @@ -2690,9 +2846,28 @@ void NearbySharingServiceImpl::OnPeerSyncBindingComplete( .set_binding_id(binding_id) .set_status(TransferMetadata::Status::kComplete) .build()); + // Update binding id in peer certificates so we can identify the sync peer + // immediately without waiting for cert sync from Backend. + for (const auto& cert_id : binding_response.cert_ids()) { + certificate_manager_->AddBindingToPublicCertificate(cert_id, binding_id); + } - // Download public certificates again to update the newly added sync binding. - certificate_manager_->DownloadPublicCertificates(); + if (binding_response.has_join_binding_time()) { + auto join_binding_time = + util_time::DecodeGoogleApiProto(binding_response.join_binding_time()); + if (join_binding_time.ok()) { + certificate_manager_->SetJoinBindingTime(join_binding_time.value(), + kJoinBindingTimeLifeTime); + } + } + // TODO: Remove this check after BE supports cert download without join time. + // Without join time, the new download can overwrite the bindings we added + // from the binding response. + if (binding_response.has_join_binding_time()) { + // Download public certificates again to update the newly added sync + // binding. + certificate_manager_->DownloadPublicCertificates(); + } } void NearbySharingServiceImpl::OnReceivedIntroduction( @@ -2737,7 +2912,9 @@ void NearbySharingServiceImpl::OnReceivedIntroduction( // Log analytics event of receiving introduction. analytics_recorder_.NewReceiveIntroduction( session.session_id(), session.share_target(), - /*referrer_package=*/std::nullopt, session.os_type()); + /*referrer_package=*/std::nullopt, session.os_type(), + IntroductionUseCaseToLoggingUseCase(frame.use_case()), + nearby::sharing::cpp::common::GetPowerStatus()); std::optional available_storage = device_info_.GetAvailableDiskSpaceInBytes(save_path); diff --git a/sharing/nearby_sharing_service_impl.h b/sharing/nearby_sharing_service_impl.h index b4d763be..5a045971 100644 --- a/sharing/nearby_sharing_service_impl.h +++ b/sharing/nearby_sharing_service_impl.h @@ -31,11 +31,13 @@ #include "location/nearby/sharing/lib/rpc/sharing_rpc_client.h" #include "location/nearby/sharing/lib/sync/sync_manager.h" #include "absl/base/nullability.h" +#include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/functional/any_invocable.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "absl/types/span.h" #include "internal/platform/clock.h" @@ -219,6 +221,9 @@ class NearbySharingServiceImpl // Handle the state changes of screen lock. void OnLockStateChanged(bool locked); + void OnSuspendResumeEvent( + nearby::api::DeviceInfo::SuspendResumeEvent event); + // Handle the state changes of bluetooth adapter. void AdapterPresentChanged(sharing::api::BluetoothAdapter* adapter, bool present) override; @@ -281,7 +286,6 @@ class NearbySharingServiceImpl void InvalidateSendSurfaceState(); void InvalidateScanningState(); void InvalidateFastInitiationAdvertising(); - void InvalidateReceiveSurfaceState(); void InvalidateAdvertisingState(); void StopAdvertising(); void StartScanning(); @@ -406,13 +410,14 @@ class NearbySharingServiceImpl bool OutgoingSessionAccept(OutgoingShareSession& session); void OnIncomingFilesMetadataUpdated(int64_t share_target_id, TransferMetadata metadata, bool success); + std::vector GetCertIdsForSyncBinding(); // Called when InitiateBinding rpc returns. void OnInitiateSyncBindingResponse( int64_t share_target_id, absl::StatusOr binding_status); // Called when Bindings response frame is received from the peer. void OnPeerSyncBindingComplete( int64_t share_target_id, absl::string_view binding_id, - service::proto::BindingResponse::Status status); + const service::proto::BindingResponse& binding_response); // Notify all registered send surfaces of share target state changes. void NotifyShareTargetDiscovered(const ShareTarget& share_target); @@ -546,6 +551,16 @@ class NearbySharingServiceImpl bool force_new_endpoint_id_ = false; OutgoingTargetsManager outgoing_targets_manager_; nearby::sharing::SyncManager sync_manager_; + + // Visibility used for the last advertisement. + proto::DeviceVisibility last_advertised_device_visibility_ = + proto::DeviceVisibility::DEVICE_VISIBILITY_UNSPECIFIED; + bool advertising_on_screen_locked_ = false; + int64_t suspend_resume_listener_id_ = 0; + absl::Mutex suspend_mutex_; + bool suspended_ ABSL_GUARDED_BY(suspend_mutex_) = false; + std::unique_ptr resume_delay_timer_ + ABSL_GUARDED_BY(suspend_mutex_); }; } // namespace nearby::sharing diff --git a/sharing/nearby_sharing_service_impl_test.cc b/sharing/nearby_sharing_service_impl_test.cc index 6814ec81..c4a3eefb 100644 --- a/sharing/nearby_sharing_service_impl_test.cc +++ b/sharing/nearby_sharing_service_impl_test.cc @@ -30,6 +30,7 @@ #include #include +#include "google/protobuf/timestamp.pb.h" #include "location/nearby/analytics/cpp/logging/mock_event_logger.h" #include "location/nearby/sharing/lib/account/fake_account_manager.h" #include "location/nearby/sharing/lib/account/mock_account_observer.h" @@ -4638,6 +4639,34 @@ TEST_F(NearbySharingServiceImplTest, ScreenLocksDuringAdvertising) { EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } +TEST_F(NearbySharingServiceImplTest, + ScreenLocksDuringAdvertisingWithBackupAndSyncBindings) { + // Enable Backup flag. + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_sharing_feature::kEnableBackup, true); + + // Add SyncBindings. + nearby::sharing::sync::SyncBindingPrefs sync_binding_prefs; + auto* binding = sync_binding_prefs.add_sync_bindings(); + binding->set_binding_id("test_binding_id"); + service_->GetSettings()->SetSyncBindingPrefs(sync_binding_prefs); + + SetLanConnected(true); + SetVisibility(DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS); + MockTransferUpdateCallback callback; + NearbySharingService::StatusCodes result = RegisterReceiveSurface( + &callback, NearbySharingService::ReceiveSurfaceState::kForeground); + EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk); + ScopedReceiveSurface r(service_.get(), &callback); + EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); + + // Screen locks, but we should STILL be advertising. + SetScreenLocked(true); + EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); +} + TEST_F(NearbySharingServiceImplTest, ScreenLocksDuringDiscovery) { SetLanConnected(true); MockTransferUpdateCallback transfer_callback; @@ -5151,6 +5180,7 @@ TEST_F(NearbySharingServiceImplTest, EXPECT_EQ(frame->v1().bindings().binding_request().binding_id(), kBindingId); EXPECT_EQ(frame->v1().bindings().binding_request().type(), service::proto::BindingRequest::FILESYNC); + EXPECT_EQ(frame->v1().bindings().binding_request().cert_ids_size(), 2); // BindingResponse frame timeout. FastForward(absl::Seconds(60)); @@ -5201,15 +5231,18 @@ TEST_F(NearbySharingServiceImplTest, InitiatePairingSuccess) { EXPECT_EQ(frame->v1().bindings().binding_request().type(), service::proto::BindingRequest::FILESYNC); - preference_manager_.SetString(PrefNames::kCustomSavePath, "Downloads"); + FilePath custom_save_path = Files::GetTemporaryDirectory(); + preference_manager_.SetString(PrefNames::kCustomSavePath, + custom_save_path.ToString()); Frame binding_response_frame; binding_response_frame.set_version(Frame::V1); binding_response_frame.mutable_v1()->set_type( service::proto::V1Frame::BINDINGS); - binding_response_frame.mutable_v1() + auto* binding_response = binding_response_frame.mutable_v1() ->mutable_bindings() - ->mutable_binding_response() - ->set_status(service::proto::BindingResponse::SUCCESS); + ->mutable_binding_response(); + binding_response->set_status(service::proto::BindingResponse::SUCCESS); + binding_response->mutable_join_binding_time()->set_seconds(1234567890); std::vector result_bytes(binding_response_frame.ByteSizeLong()); binding_response_frame.SerializeToArray(result_bytes.data(), result_bytes.size()); @@ -5230,7 +5263,7 @@ TEST_F(NearbySharingServiceImplTest, InitiatePairingSuccess) { expected_binding.set_binding_id(kBindingId); expected_binding.set_source_name(kDeviceName); expected_binding.set_destination_directory( - FilePath("Downloads").append(FilePath(kDeviceName)).ToString()); + FilePath(custom_save_path).append(FilePath(kDeviceName)).ToString()); expected_binding.set_source_device_type( sync::SyncBinding::SOURCE_DEVICE_TYPE_PHONE); EXPECT_THAT(binding->sync_bindings(0), EqualsProto(expected_binding)); @@ -5305,7 +5338,9 @@ TEST_F(NearbySharingServiceImplTest, EXPECT_EQ(frame->v1().bindings().binding_request().type(), service::proto::BindingRequest::FILESYNC); - preference_manager_.SetString(PrefNames::kCustomSavePath, "Downloads"); + FilePath custom_save_path = Files::GetTemporaryDirectory(); + preference_manager_.SetString(PrefNames::kCustomSavePath, + custom_save_path.ToString()); Frame binding_response_frame; binding_response_frame.set_version(Frame::V1); binding_response_frame.mutable_v1()->set_type( @@ -5332,7 +5367,7 @@ TEST_F(NearbySharingServiceImplTest, expected_binding.set_binding_id(kBindingId); expected_binding.set_source_name(kDeviceName); expected_binding.set_destination_directory( - FilePath("Downloads").append(FilePath(kDeviceName)).ToString()); + FilePath(custom_save_path).append(FilePath(kDeviceName)).ToString()); expected_binding.set_source_device_type( sync::SyncBinding::SOURCE_DEVICE_TYPE_PHONE); EXPECT_THAT(binding->sync_bindings(0), EqualsProto(expected_binding)); diff --git a/sharing/nearby_sharing_settings.cc b/sharing/nearby_sharing_settings.cc index 8d915aa9..8bee5def 100644 --- a/sharing/nearby_sharing_settings.cc +++ b/sharing/nearby_sharing_settings.cc @@ -26,6 +26,8 @@ #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" +#include "internal/base/file_path.h" +#include "internal/base/files.h" #include "internal/platform/clock.h" #include "internal/platform/implementation/device_info.h" #include "internal/platform/task_runner.h" @@ -184,8 +186,12 @@ void NearbyShareSettings::RestoreFallbackVisibility() { } std::string NearbyShareSettings::GetCustomSavePath() const { - return preference_manager_.GetString( + std::string custom_save_path = preference_manager_.GetString( PrefNames::kCustomSavePath, device_info_.GetDownloadPath().ToString()); + if (Files::IsAbsolutePath(FilePath(custom_save_path))) { + return custom_save_path; + } + return device_info_.GetDownloadPath().ToString(); } SyncBindingPrefs NearbyShareSettings::GetSyncBindingPrefs() const { @@ -347,6 +353,10 @@ void NearbyShareSettings::SetFallbackVisibility(DeviceVisibility visibility) { void NearbyShareSettings::SetCustomSavePathAsync( absl::string_view save_path, const std::function& callback) { + if (!Files::IsAbsolutePath(FilePath(save_path))) { + callback(); + return; + } absl::MutexLock lock(mutex_); preference_manager_.SetString(PrefNames::kCustomSavePath, save_path); callback(); diff --git a/sharing/outgoing_share_session.cc b/sharing/outgoing_share_session.cc index 7b9b52a9..d1fa96dc 100644 --- a/sharing/outgoing_share_session.cc +++ b/sharing/outgoing_share_session.cc @@ -23,9 +23,11 @@ #include #include +#include "location/nearby/cpp/sharing/clients/cpp/common/nearby_sharing_common.h" #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "internal/base/file_path.h" #include "internal/base/files.h" #include "internal/platform/clock.h" @@ -372,8 +374,7 @@ void OutgoingShareSession::SendPayloads( analytics_recorder().NewSendAttachmentsStart( session_id(), attachment_container(), /*transfer_position=*/1, - /*concurrent_connections=*/1, advanced_protection_enabled_, - advanced_protection_mismatch_); + /*concurrent_connections=*/1, advanced_protection_enabled_); VLOG(1) << "The connection was accepted. Payloads are now being sent."; InitializePayloadTracker(std::move(payload_transder_update_callback)); SendNextPayload(); @@ -423,10 +424,11 @@ bool OutgoingShareSession::SendIntroduction( } WriteFrame(frame); // Log analytics event of sending introduction. - analytics_recorder().NewSendIntroduction(session_id(), share_target(), - /*transfer_position=*/1, - /*concurrent_connections=*/1, - os_type()); + analytics_recorder().NewSendIntroduction( + session_id(), share_target(), + /*transfer_position=*/1, + /*concurrent_connections=*/1, os_type(), + nearby::sharing::cpp::common::GetPowerStatus()); VLOG(1) << "Successfully wrote the introduction frame"; ready_for_accept_ = true; mutual_acceptance_timeout_ = std::make_unique( @@ -646,7 +648,8 @@ OutgoingShareSession::ProcessPayloadTransferUpdates() { void OutgoingShareSession::StartPeerBinding( std::string binding_id, BindingRequest::Type binding_type, - absl::AnyInvocable callback) { + absl::Span cert_ids, + absl::AnyInvocable callback) { Frame frame; frame.set_version(Frame::V1); V1Frame* v1_frame = frame.mutable_v1(); @@ -655,6 +658,7 @@ void OutgoingShareSession::StartPeerBinding( v1_frame->mutable_bindings()->mutable_binding_request(); binding_request->set_binding_id(binding_id); binding_request->set_type(binding_type); + binding_request->mutable_cert_ids()->Add(cert_ids.begin(), cert_ids.end()); WriteFrame(frame); LOG(INFO) << "Waiting for bindings response frame from " << share_target().id; UpdateTransferMetadata( @@ -667,19 +671,19 @@ void OutgoingShareSession::StartPeerBinding( nearby::sharing::service::proto::V1Frame::BINDINGS, [callback = std::move(callback)]( bool is_timeout, std::optional frame) mutable { + BindingResponse failure_response; + failure_response.set_status(BindingResponse::FAILURE); if (!frame.has_value()) { - std::move(callback)(BindingResponse::FAILURE); + std::move(callback)(failure_response); return; } if (!frame->has_bindings() || - !frame->bindings().has_binding_response() || - frame->bindings().binding_response().status() != - BindingResponse::SUCCESS) { - std::move(callback)(BindingResponse::FAILURE); + !frame->bindings().has_binding_response()) { + std::move(callback)(failure_response); return; } // Peer binding flow completed successfully. - std::move(callback)(BindingResponse::SUCCESS); + std::move(callback)(frame->bindings().binding_response()); }, kReadResponseFrameTimeout); } diff --git a/sharing/outgoing_share_session.h b/sharing/outgoing_share_session.h index fb28496a..1731d04d 100644 --- a/sharing/outgoing_share_session.h +++ b/sharing/outgoing_share_session.h @@ -26,6 +26,7 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "internal/platform/clock.h" #include "internal/platform/task_runner.h" #include "sharing/analytics/analytics_recorder.h" @@ -141,10 +142,8 @@ class OutgoingShareSession : public ShareSession { std::optional ProcessPayloadTransferUpdates(); - void SetAdvancedProtectionStatus(bool advanced_protection_enabled, - bool advanced_protection_mismatch) { + void SetAdvancedProtectionStatus(bool advanced_protection_enabled) { advanced_protection_enabled_ = advanced_protection_enabled; - advanced_protection_mismatch_ = advanced_protection_mismatch; } // Returns true if the session is connected or in the process of connecting. @@ -166,13 +165,16 @@ class OutgoingShareSession : public ShareSession { // Initiates the peer binding message exchange with the remote device. // `binding_id` is the result of a successful call to InitiateBinding rpc. + // `cert_ids` are the public certificate ids that can be used to identify this + // device. // `callback` is called when either a BindingResponse frame is received or a // timeout occurs. void StartPeerBinding( std::string binding_id, nearby::sharing::service::proto::BindingRequest::Type binding_type, + absl::Span cert_ids, absl::AnyInvocable< - void(nearby::sharing::service::proto::BindingResponse::Status)> + void(const nearby::sharing::service::proto::BindingResponse&)> callback); protected: @@ -210,7 +212,6 @@ class OutgoingShareSession : public ShareSession { // Timeout waiting for remote disconnect in order to complete transfer. std::unique_ptr disconnection_timeout_; bool advanced_protection_enabled_ = false; - bool advanced_protection_mismatch_ = false; bool is_connecting_ = false; // Session can be for transfer or pairing. bool is_transfer_session_ = false; diff --git a/sharing/outgoing_share_session_test.cc b/sharing/outgoing_share_session_test.cc index 21b01d6c..f13e04b9 100644 --- a/sharing/outgoing_share_session_test.cc +++ b/sharing/outgoing_share_session_test.cc @@ -74,6 +74,7 @@ using ::nearby::sharing::service::proto::V1Frame; using ::nearby::sharing::service::proto::WifiCredentials; using ::testing::_; using ::testing::AllOf; +using ::testing::ElementsAre; using ::testing::Eq; using ::protobuf_matchers::EqualsProto; using ::testing::InSequence; @@ -676,11 +677,7 @@ TEST_F(OutgoingShareSessionTest, SendPayloads) { Log(Matcher(AllOf( (HasCategory(EventCategory::SENDING_EVENT), HasEventType(EventType::SEND_ATTACHMENTS_START), - ProtoField<"send_attachments_start", "session_id">(1234), - ProtoField<"send_attachments_start", "advanced_protection_enabled">( - false), - ProtoField<"send_attachments_start", "advanced_protection_mismatch">( - false)))))); + ProtoField<"send_attachments_start", "session_id">(1234)))))); NearbyConnectionImpl connection(device_info_); ConnectionSuccess(&connection); @@ -717,15 +714,12 @@ TEST_F(OutgoingShareSessionTest, SendPayloadsSetsAdvancedProtectionFlags) { HasEventType(EventType::SEND_ATTACHMENTS_START), ProtoField<"send_attachments_start", "session_id">(1234), ProtoField<"send_attachments_start", "advanced_protection_enabled">( - true), - ProtoField<"send_attachments_start", "advanced_protection_mismatch">( true)))))); NearbyConnectionImpl connection(device_info_); ConnectionSuccess(&connection); - session_.SetAdvancedProtectionStatus(/*advanced_protection_enabled=*/true, - /*advanced_protection_mismatch=*/true); + session_.SetAdvancedProtectionStatus(/*advanced_protection_enabled=*/true); session_.SendPayloads([](bool is_timeout, std::optional frame) {}, payload_transder_update_callback.AsStdFunction()); @@ -759,8 +753,6 @@ TEST_F(OutgoingShareSessionTest, SendNextPayload) { HasEventType(EventType::SEND_ATTACHMENTS_START), ProtoField<"send_attachments_start", "session_id">(1234), ProtoField<"send_attachments_start", "advanced_protection_enabled">( - false), - ProtoField<"send_attachments_start", "advanced_protection_mismatch">( false)))))); NearbyConnectionImpl connection(device_info_); ConnectionSuccess(&connection); @@ -937,6 +929,8 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingSuccess) { binding_request { binding_id: "test_binding_id" type: FILESYNC + cert_ids: "cert_id_1" + cert_ids: "cert_id_2" } } } @@ -954,12 +948,12 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingSuccess) { AllOf(HasStatus(TransferMetadata::Status::kAwaitingRemoteAcceptance), HasUsage(ShareSessionUsage::kPairing)))); - BindingResponse::Status binding_response_status = BindingResponse::FAILURE; - session_.StartPeerBinding("test_binding_id", BindingRequest::FILESYNC, - [&binding_response_status]( - BindingResponse::Status status) { - binding_response_status = status; - }); + BindingResponse binding_response; + session_.StartPeerBinding( + "test_binding_id", BindingRequest::FILESYNC, {"cert_id_1", "cert_id_2"}, + [&binding_response](const BindingResponse& response) { + binding_response = response; + }); Frame frame; ASSERT_THAT(frame.ParseFromArray(frame_data.data(), frame_data.size()), @@ -976,6 +970,8 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingSuccess) { bindings { binding_response { status: SUCCESS + cert_ids: "cert_id_3" + cert_ids: "cert_id_4" } } } @@ -987,7 +983,9 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingSuccess) { IsTrue()); connection.WriteMessage(std::move(data)); - EXPECT_THAT(binding_response_status, Eq(BindingResponse::SUCCESS)); + EXPECT_THAT(binding_response.status(), Eq(BindingResponse::SUCCESS)); + EXPECT_THAT(binding_response.cert_ids(), + ElementsAre("cert_id_3", "cert_id_4")); } TEST_F(OutgoingShareSessionTest, StartPeerBindingTimeout) { @@ -1022,12 +1020,12 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingTimeout) { AllOf(HasStatus(TransferMetadata::Status::kAwaitingRemoteAcceptance), HasUsage(ShareSessionUsage::kPairing)))); - BindingResponse::Status binding_response_status = BindingResponse::FAILURE; - session_.StartPeerBinding("test_binding_id", BindingRequest::FILESYNC, - [&binding_response_status]( - BindingResponse::Status status) { - binding_response_status = status; - }); + BindingResponse binding_response; + session_.StartPeerBinding( + "test_binding_id", BindingRequest::FILESYNC, {}, + [&binding_response](const BindingResponse& response) { + binding_response = response; + }); Frame frame; ASSERT_THAT(frame.ParseFromArray(frame_data.data(), frame_data.size()), @@ -1038,7 +1036,7 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingTimeout) { fake_clock_.FastForward(absl::Seconds(60)); fake_task_runner_.SyncWithTimeout(absl::Milliseconds(100)); - EXPECT_THAT(binding_response_status, Eq(BindingResponse::FAILURE)); + EXPECT_THAT(binding_response.status(), Eq(BindingResponse::FAILURE)); } TEST_F(OutgoingShareSessionTest, StartPeerBindingFailure) { @@ -1073,12 +1071,12 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingFailure) { AllOf(HasStatus(TransferMetadata::Status::kAwaitingRemoteAcceptance), HasUsage(ShareSessionUsage::kPairing)))); - BindingResponse::Status binding_response_status = BindingResponse::FAILURE; - session_.StartPeerBinding("test_binding_id", BindingRequest::FILESYNC, - [&binding_response_status]( - BindingResponse::Status status) { - binding_response_status = status; - }); + BindingResponse binding_response; + session_.StartPeerBinding( + "test_binding_id", BindingRequest::FILESYNC, {}, + [&binding_response](const BindingResponse& response) { + binding_response = response; + }); Frame frame; ASSERT_THAT(frame.ParseFromArray(frame_data.data(), frame_data.size()), @@ -1106,7 +1104,8 @@ TEST_F(OutgoingShareSessionTest, StartPeerBindingFailure) { IsTrue()); connection.WriteMessage(std::move(data)); - EXPECT_THAT(binding_response_status, Eq(BindingResponse::FAILURE)); + EXPECT_THAT(binding_response.status(), Eq(BindingResponse::FAILURE)); + EXPECT_THAT(binding_response.cert_ids(), IsEmpty()); } } // namespace diff --git a/sharing/paired_key_verification_runner.cc b/sharing/paired_key_verification_runner.cc index 213d6799..d25006af 100644 --- a/sharing/paired_key_verification_runner.cc +++ b/sharing/paired_key_verification_runner.cc @@ -140,18 +140,8 @@ void PairedKeyVerificationRunner::OnReadPairedKeyEncryptionFrame( OSType::UNKNOWN_OS_TYPE); return; } - PairedKeyVerificationResult auth_token_hash_result = - VerifyAuthTokenHashWithPrivateCertificate(visibility_history_.visibility, - *frame); - - if (auth_token_hash_result != PairedKeyVerificationResult::kSuccess) { - if (IsVisibilityRecentlyUpdated()) { - auth_token_hash_result = VerifyAuthTokenHashWithPrivateCertificate( - visibility_history_.last_visibility, *frame); - } - } - + VerifyAuthTokenHashWithPrivateCertificates(*frame); if (auth_token_hash_result == PairedKeyVerificationResult::kUnable) { if (share_target_is_incoming_ && visibility_history_.visibility != @@ -255,9 +245,18 @@ void PairedKeyVerificationRunner::SendPairedKeyEncryptionFrame() { share_target_is_incoming_ ? kNearbyShareReceiverVerificationPrefix : kNearbyShareSenderVerificationPrefix, raw_token_); + DeviceVisibility primary_visibility; + DeviceVisibility secondary_visibility; + if (visibility_history_.screen_locked_advertising) { + primary_visibility = DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE; + secondary_visibility = visibility_history_.visibility; + } else { + primary_visibility = visibility_history_.visibility; + secondary_visibility = visibility_history_.last_visibility; + } std::optional> signature = - certificate_manager_.SignWithPrivateCertificate( - visibility_history_.visibility, padded_token); + certificate_manager_.SignWithPrivateCertificate(primary_visibility, + padded_token); if (!signature.has_value() || signature->empty()) { signature = GenerateRandomBytes(kNearbyShareNumBytesRandomSignature); } @@ -282,8 +281,8 @@ void PairedKeyVerificationRunner::SendPairedKeyEncryptionFrame() { LOG(INFO) << "Attempts to sign authentication token with a previous private key."; std::optional> optional_signature = - certificate_manager_.SignWithPrivateCertificate( - visibility_history_.last_visibility, padded_token); + certificate_manager_.SignWithPrivateCertificate(secondary_visibility, + padded_token); if (optional_signature.has_value()) { encryption_frame->set_optional_signed_data(optional_signature->data(), @@ -297,22 +296,36 @@ void PairedKeyVerificationRunner::SendPairedKeyEncryptionFrame() { } PairedKeyVerificationRunner::PairedKeyVerificationResult -PairedKeyVerificationRunner::VerifyAuthTokenHashWithPrivateCertificate( - DeviceVisibility visibility, +PairedKeyVerificationRunner::VerifyAuthTokenHashWithPrivateCertificates( const nearby::sharing::service::proto::V1Frame& frame) { - std::optional> hash = - certificate_manager_.HashAuthenticationTokenWithPrivateCertificate( - visibility, raw_token_); - const std::string& frame_hash = frame.paired_key_encryption().secret_id_hash(); std::vector frame_hash_data{frame_hash.begin(), frame_hash.end()}; - if (hash.has_value() && *hash == frame_hash_data) { - VLOG(1) << __func__ << ": Successfully verified remote public certificate."; - return PairedKeyVerificationResult::kSuccess; + std::vector visibilities_to_check; + // At most 3 visibilities to check. + visibilities_to_check.reserve(3); + // If we are advertising under lock screen then verify against self share + // private certificate first. + if (visibility_history_.screen_locked_advertising) { + visibilities_to_check.push_back( + DeviceVisibility::DEVICE_VISIBILITY_SELF_SHARE); } + visibilities_to_check.push_back(visibility_history_.visibility); + if (IsVisibilityRecentlyUpdated()) { + visibilities_to_check.push_back(visibility_history_.last_visibility); + } + for (const auto& visibility : visibilities_to_check) { + std::optional> hash = + certificate_manager_.HashAuthenticationTokenWithPrivateCertificate( + visibility, raw_token_); + if (hash.has_value() && *hash == frame_hash_data) { + VLOG(1) << __func__ + << ": Successfully verified remote public certificate."; + return PairedKeyVerificationResult::kSuccess; + } + } VLOG(1) << __func__ << ": Unable to verify remote public certificate."; return PairedKeyVerificationResult::kUnable; } diff --git a/sharing/paired_key_verification_runner.h b/sharing/paired_key_verification_runner.h index 372e656c..8a455b64 100644 --- a/sharing/paired_key_verification_runner.h +++ b/sharing/paired_key_verification_runner.h @@ -51,6 +51,8 @@ class PairedKeyVerificationRunner proto::DeviceVisibility visibility; proto::DeviceVisibility last_visibility; absl::Time last_visibility_time; + // Set to true if device is advertising under lock screen. + bool screen_locked_advertising = false; }; PairedKeyVerificationRunner( @@ -85,10 +87,10 @@ class PairedKeyVerificationRunner void OnReadPairedKeyResultFrame( std::optional frame); void SendPairedKeyResultFrame(PairedKeyVerificationResult result); - // Verifies auth token hash in frame using private certificate for visibility. - // Returns either kSuccess or kUnable. This function never returns kFail. - PairedKeyVerificationResult VerifyAuthTokenHashWithPrivateCertificate( - proto::DeviceVisibility visibility, + // Verifies auth token hash in frame using private certificates in + // `visibility_history_`. Returns either kSuccess or kUnable. This function + // never returns kFail. + PairedKeyVerificationResult VerifyAuthTokenHashWithPrivateCertificates( const nearby::sharing::service::proto::V1Frame& frame); PairedKeyVerificationResult VerifyPairedKeyEncryptionFrame( const nearby::sharing::service::proto::V1Frame& frame); diff --git a/sharing/paired_key_verification_runner_test.cc b/sharing/paired_key_verification_runner_test.cc index 6b02462d..2362f35f 100644 --- a/sharing/paired_key_verification_runner_test.cc +++ b/sharing/paired_key_verification_runner_test.cc @@ -126,9 +126,13 @@ GenerateVisibilityHistory() { DeviceVisibility::DEVICE_VISIBILITY_EVERYONE, }; std::list result; - for (DeviceVisibility visibility : kValidVisibilities) { - for (DeviceVisibility last_visibility : kValidVisibilities) { - result.push_back({visibility, last_visibility, absl::UnixEpoch()}); + for (bool screen_locked_advertising : {true, false}) { + for (DeviceVisibility visibility : kValidVisibilities) { + for (DeviceVisibility last_visibility : kValidVisibilities) { + result.push_back( + {visibility, last_visibility, absl::UnixEpoch(), + screen_locked_advertising}); + } } } return result; @@ -476,10 +480,12 @@ TEST_P(ParameterisedPairedKeyVerificationRunnerTest, std::get<2>(GetParam()); PairedKeyVerificationRunner::PairedKeyVerificationResult result = params.result; - // If our visibility has no certificates, then downgrade expected result to - // kUnable if it is not expected to fail. + // If our visibility has no certificates (i.e. EVERYONE and not under lock + // screen), then downgrade expected result to kUnable if it is not expected to + // fail. if ((visibility_history.visibility == - DeviceVisibility::DEVICE_VISIBILITY_EVERYONE) && + DeviceVisibility::DEVICE_VISIBILITY_EVERYONE && + !visibility_history.screen_locked_advertising) && !(visibility_history.last_visibility != DeviceVisibility::DEVICE_VISIBILITY_EVERYONE && (params.encryption_frame_type == @@ -511,7 +517,9 @@ TEST_P(ParameterisedPairedKeyVerificationRunnerTest, << ", expected_result=" << (int)expected_result << ", result_frame=" << (int)result_frame.status() << ", visibility=" << (int)visibility_history.visibility - << ", last_visibility=" << (int)visibility_history.last_visibility; + << ", last_visibility=" << (int)visibility_history.last_visibility + << ", screen_locked_advertising=" + << visibility_history.screen_locked_advertising; SetUpPairedKeyEncryptionFrame(params.encryption_frame_type); bool encryption_frame_timeout = diff --git a/sharing/proto/BUILD b/sharing/proto/BUILD index c2fb5be7..e8f615dc 100644 --- a/sharing/proto/BUILD +++ b/sharing/proto/BUILD @@ -42,7 +42,10 @@ proto_library( name = "wire_format_proto", srcs = ["wire_format.proto"], visibility = ["//visibility:public"], - deps = ["//proto:sharing_enums_proto"], + deps = [ + "//google/protobuf:timestamp", + "//proto:sharing_enums_proto", + ], ) cc_proto_library( diff --git a/sharing/proto/wire_format.proto b/sharing/proto/wire_format.proto index 01f5a4d1..60d26bbd 100644 --- a/sharing/proto/wire_format.proto +++ b/sharing/proto/wire_format.proto @@ -16,6 +16,8 @@ syntax = "proto2"; package nearby.sharing.service.proto; +import "google/protobuf/timestamp.proto"; + // import "storage/datapol/annotations/proto/semantic_annotations.proto"; import "proto/sharing_enums.proto"; @@ -233,6 +235,7 @@ message IntroductionFrame { repeated StreamMetadata stream_metadata = 7; optional SharingUseCase use_case = 8; repeated int64 preview_payload_ids = 9; + optional string transfer_id = 10; } // A progress update packet sent by the sending side. Contains transfer progress @@ -265,6 +268,13 @@ message BindingRequest { } optional string binding_id = 1; optional Type type = 2; + // A list of public cert ids that can be used to identify the sender. + // If binding is successful, the receiver can add the binding ids to these + // public certs to ensure that it can immediately identify the sender without + // having to download public certs from the BE. + // The sender does not need to provide all its future public certs, just the + // current ones will be sufficient. + repeated bytes cert_ids = 3; } message BindingResponse { @@ -274,6 +284,10 @@ message BindingResponse { FAILURE = 2; // TODO: b/485307320 - Add more specific error codes. } optional Status status = 1; + optional google.protobuf.Timestamp join_binding_time = 2; + // A list of public cert ids that can be used to identify the receiver. + // See the comment of cert_ids in BindingRequest for more details. + repeated bytes cert_ids = 3; } // A response packet sent by the receiving side. Accepts or rejects the list of