// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "connections/implementation/endpoint_manager.h" #include #include #include #include #include #include "gmock/gmock.h" #include "protobuf-matchers/protocol-buffer-matchers.h" #include "gtest/gtest.h" #include "testing/fuzzing/fuzztest.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "connections/connection_options.h" #include "connections/implementation/analytics/analytics_recorder.h" #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/listeners.h" #include "connections/status.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" #include "internal/platform/logging.h" #include "internal/platform/single_thread_executor.h" #include "internal/test/fake_single_thread_executor.h" #include "proto/connections_enums.pb.h" namespace nearby::connections { namespace { using ::fuzztest::Filter; using ::fuzztest::Map; using ::fuzztest::String; using ::location::nearby::connections::OfflineFrame; using ::location::nearby::connections::PayloadTransferFrame; using ::location::nearby::connections::V1Frame; using ::location::nearby::proto::connections::DisconnectionReason; using ::location::nearby::proto::connections::Medium; using ::testing::_; using ::testing::Eq; using ::testing::MockFunction; using ::testing::Return; using ::testing::StrictMock; class MockEndpointChannel : public EndpointChannel { public: MOCK_METHOD(ExceptionOr, Read, (), (override)); MOCK_METHOD(ExceptionOr, Read, (PacketMetaData & packet_meta_data), (override)); MOCK_METHOD(Exception, Write, (absl::string_view data), (override)); MOCK_METHOD(Exception, Write, (absl::string_view data, PacketMetaData& packet_meta_data), (override)); MOCK_METHOD(void, Close, (), (override)); MOCK_METHOD(void, Close, (DisconnectionReason reason), (override)); MOCK_METHOD(void, Close, (DisconnectionReason reason, location::nearby::analytics::proto::ConnectionsLog:: EstablishedConnection::SafeDisconnectionResult result), (override)); MOCK_METHOD(location::nearby::proto::connections::ConnectionTechnology, GetTechnology, (), (const, override)); MOCK_METHOD(location::nearby::proto::connections::ConnectionBand, GetBand, (), (const, override)); MOCK_METHOD(int, GetFrequency, (), (const, override)); MOCK_METHOD(int, GetTryCount, (), (const, override)); MOCK_METHOD(std::string, GetType, (), (const, override)); MOCK_METHOD(std::string, GetServiceId, (), (const, override)); MOCK_METHOD(std::string, GetName, (), (const, override)); MOCK_METHOD(Medium, GetMedium, (), (const, override)); MOCK_METHOD(int, GetMaxTransmitPacketSize, (), (const, override)); MOCK_METHOD(void, EnableEncryption, (std::shared_ptr context), (override)); MOCK_METHOD(void, DisableEncryption, (), (override)); MOCK_METHOD(bool, IsPaused, (), (const, override)); MOCK_METHOD(bool, IsEncrypted, (), (override)); MOCK_METHOD(ExceptionOr, TryDecrypt, (const ByteArray& data), (override)); MOCK_METHOD(void, Pause, (), (override)); MOCK_METHOD(void, Resume, (), (override)); MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const, override)); MOCK_METHOD(absl::Time, GetLastWriteTimestamp, (), (const, override)); MOCK_METHOD(uint32_t, GetNextKeepAliveSeqNo, (), (const, override)); MOCK_METHOD(void, SetAnalyticsRecorder, (analytics::AnalyticsRecorder*, const std::string&), (override)); bool IsClosed() const override { absl::MutexLock lock(mutex_); return closed_; } void DoClose() { absl::MutexLock lock(mutex_); closed_ = true; } private: mutable absl::Mutex mutex_; bool closed_ = false; }; class MockFrameProcessor : public EndpointManager::FrameProcessor { public: MOCK_METHOD(void, OnIncomingFrame, (OfflineFrame & offline_frame, const std::string& from_endpoint_id, ClientProxy* to_client, Medium current_medium, PacketMetaData& packet_meta_data), (override)); MOCK_METHOD(void, OnEndpointDisconnect, (ClientProxy * client, const std::string& service_id, const std::string& endpoint_id, CountDownLatch barrier, DisconnectionReason reason), (override)); }; class SetSafeToDisconnect { public: SetSafeToDisconnect(bool safe_to_disconnect, bool payload_received_ack, std::int32_t safe_to_disconnect_version) { NearbyFlags::GetInstance().OverrideBoolFlagValue( config_package_nearby::nearby_connections_feature:: kEnableSafeToDisconnect, safe_to_disconnect); NearbyFlags::GetInstance().OverrideBoolFlagValue( config_package_nearby::nearby_connections_feature:: kEnablePayloadReceivedAck, payload_received_ack); NearbyFlags::GetInstance().OverrideInt64FlagValue( config_package_nearby::nearby_connections_feature:: kSafeToDisconnectVersion, safe_to_disconnect_version); } }; class TestEndpointManager : public EndpointManager { public: TestEndpointManager(EndpointChannelManager* manager, std::unique_ptr serial_executor) : EndpointManager(manager, std::move(serial_executor)) {} }; class EndpointManagerTest : public ::testing::Test { protected: void RegisterEndpoint(std::unique_ptr channel, bool should_close = true) { CountDownLatch done(1); if (should_close) { ON_CALL(*channel, Close(_)) .WillByDefault( [&done](DisconnectionReason reason) { done.CountDown(); }); } EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE)); EXPECT_CALL(*channel, GetLastReadTimestamp()) .WillRepeatedly(Return(start_time_)); EXPECT_CALL(*channel, GetLastWriteTimestamp()) .WillRepeatedly(Return(start_time_)); EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1); em_.RegisterEndpoint(client_.get(), endpoint_id_, info_, connection_options_, std::move(channel), listener_, connection_token_); if (should_close) { EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); } } SetSafeToDisconnect set_safe_to_disconnect_{true, true, 5}; std::unique_ptr client_ = std::make_unique(); ConnectionOptions connection_options_{ .keep_alive_interval_millis = 5000, .keep_alive_timeout_millis = 30000, }; std::vector> processors_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; std::string endpoint_id_ = "endpoint_id"; ConnectionResponseInfo info_ = { .remote_endpoint_info = ByteArray{"info"}, .authentication_token = "auth_token", .raw_authentication_token = ByteArray{"auth_token"}, .is_incoming_connection = true, }; struct MockConnectionListener { StrictMock> initiated_cb; StrictMock> accepted_cb; StrictMock> rejected_cb; StrictMock> disconnected_cb; StrictMock> bandwidth_changed_cb; } mock_listener_; ConnectionListener listener_{ .initiated_cb = mock_listener_.initiated_cb.AsStdFunction(), .accepted_cb = mock_listener_.accepted_cb.AsStdFunction(), .rejected_cb = mock_listener_.rejected_cb.AsStdFunction(), .disconnected_cb = mock_listener_.disconnected_cb.AsStdFunction(), .bandwidth_changed_cb = mock_listener_.bandwidth_changed_cb.AsStdFunction(), }; std::string connection_token_ = "conntokn"; absl::Time start_time_{absl::Now()}; }; TEST_F(EndpointManagerTest, ConstructorDestructorWorks) { SUCCEED(); } TEST_F(EndpointManagerTest, RegisterEndpointCallsOnConnectionInitiated) { auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read()) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, Close(_)).Times(1); RegisterEndpoint(std::move(endpoint_channel)); } TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) { // auto endpoint_channel = std::make_unique(); // EXPECT_CALL(*endpoint_channel, Read()) // .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); RegisterEndpoint(std::make_unique()); // NOTE: disconnect_cb is not called, because we did not reach fully connected // state. On top of that, UnregisterEndpoint is suppressing this notification. // (IMO, it should be called as long as any connection callback was called // before. (in this case initiated_cb is called)). // Test captures current protocol behavior. em_.UnregisterEndpoint(client_.get(), endpoint_id_); } TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnectedSafeToDisconnect) { RegisterEndpoint(std::make_unique()); // NOTE: disconnect_cb is not called, because we did not reach fully connected // state. On top of that, UnregisterEndpoint is suppressing this notification. // (IMO, it should be called as long as any connection callback was called // before. (in this case initiated_cb is called)). // Test captures current protocol behavior. client_->SetRemoteSafeToDisconnectVersion(endpoint_id_, 5); ecm_.UpdateSafeToDisconnectForEndpoint(endpoint_id_, true); em_.UnregisterEndpoint(client_.get(), endpoint_id_); } TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { auto endpoint_channel = std::make_unique(); auto connect_request = std::make_unique(); ByteArray endpoint_info{"endpoint_name"}; ConnectionInfo connection_info{ "endpoint_id", endpoint_info, 1234 /*nonce*/, false /*supports_5_ghz*/, "" /*bssid*/, 2412 /*ap_frequency*/, std::vector{Medium::BLE} /*supported_mediums*/, 0 /*keep_alive_interval_millis*/, 0 /*keep_alive_timeout_millis*/}; std::string read_data = parser::ForConnectionRequestConnections({}, connection_info); EXPECT_CALL(*connect_request, OnIncomingFrame); EXPECT_CALL(*connect_request, OnEndpointDisconnect); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce(Return(ExceptionOr(ByteArray(read_data)))) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, Write(_)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); // Register frame processor, then register endpoint. // Endpoint will read one frame, then fail to read more and terminate. // On disconnection, it will notify frame processor and we verify that. em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, connect_request.get()); processors_.emplace_back(std::move(connect_request)); RegisterEndpoint(std::move(endpoint_channel)); } TEST_F(EndpointManagerTest, UnregisterFrameProcessorWorks) { auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read()) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, Write(_)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); // We should not receive any notifications to frame processor. auto connect_request = std::make_unique>(); // Register frame processor and immediately unregister it. em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, connect_request.get()); em_.UnregisterFrameProcessor(V1Frame::CONNECTION_REQUEST, connect_request.get()); processors_.emplace_back(std::move(connect_request)); // Endpoint will not send OnDisconnect notification to frame processor. RegisterEndpoint(std::move(endpoint_channel), false); em_.UnregisterEndpoint(client_.get(), endpoint_id_); } TEST_F(EndpointManagerTest, SendControlMessageAndPayloadAckWorks) { auto endpoint_channel = std::make_unique(); PayloadTransferFrame::PayloadHeader header; PayloadTransferFrame::ControlMessage control; header.set_id(12345); header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); header.set_total_size(1024); control.set_offset(150); control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); ON_CALL(*endpoint_channel, Read(_)) .WillByDefault([channel = endpoint_channel.get()]() { if (channel->IsClosed()) return ExceptionOr(Exception::kIo); LOG(INFO) << "Simulate read delay: wait"; absl::SleepFor(absl::Milliseconds(100)); LOG(INFO) << "Simulate read delay: done"; if (channel->IsClosed()) return ExceptionOr(Exception::kIo); return ExceptionOr(ByteArray{}); }); ON_CALL(*endpoint_channel, Close(_)) .WillByDefault( [channel = endpoint_channel.get()](DisconnectionReason reason) { channel->DoClose(); LOG(INFO) << "Channel closed"; }); EXPECT_CALL(*endpoint_channel, Write(_, _)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); RegisterEndpoint(std::move(endpoint_channel), false); auto failed_ids_1 = em_.SendControlMessage(header, control, std::vector{endpoint_id_}); EXPECT_EQ(failed_ids_1, std::vector{}); auto failed_ids_2 = em_.SendPayloadAck(header.id(), std::vector{endpoint_id_}); EXPECT_EQ(failed_ids_2, std::vector{}); LOG(INFO) << "Will unregister endpoint now"; em_.UnregisterEndpoint(client_.get(), endpoint_id_); LOG(INFO) << "Will call destructors now"; } TEST_F(EndpointManagerTest, SingleReadOnReadError) { auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce( Return(ExceptionOr(Exception::kInvalidProtocolBuffer))); EXPECT_CALL(*endpoint_channel, Write(_)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); EXPECT_CALL(*endpoint_channel, Close(_)).Times(1); RegisterEndpoint(std::move(endpoint_channel)); } TEST_F(EndpointManagerTest, ReadInvalidUnencryptedPayloadIgnoresFrame) { // 1. EndpointChannel is unencrypted. // 2. EndpointManager receives an invalid unencrypted frame. // 3. EndpointManager calls EndpointChannel::TryDecrypt(), which keeps failing // because the channel is not encrypted. // 4. Invalid frame is ignored. No bad side effects. CountDownLatch latch(1); const ByteArray payload("not a valid frame"); auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce(Return(ExceptionOr(payload))) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, TryDecrypt(Eq(payload))) .WillRepeatedly(Return(ExceptionOr(Exception::kFailed))); EXPECT_CALL(*endpoint_channel, Write(_)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); EXPECT_CALL(*endpoint_channel, Close(_)) .WillOnce([&](DisconnectionReason reason) { latch.CountDown(); }); RegisterEndpoint(std::move(endpoint_channel), false); latch.Await(); em_.UnregisterEndpoint(client_.get(), endpoint_id_); } class EndpointManagerFuzzTest : public fuzztest::PerIterationFixtureAdapter { public: void ReadInvalidEncryptedPayloadIgnoresFrame(const ByteArray payload) { // 1. EndpointChannel is unencrypted. // 2. EndpointManager receives an invalid encrypted frame. // 3. EndpointManager calls EndpointChannel::TryDecrypt(), decryption fails // too. // 4. Invalid frame is ignored. No bad side effects. auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce(Return(ExceptionOr(payload))) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, TryDecrypt(Eq(payload))) .WillOnce(Return(ExceptionOr(Exception::kFailed))) .WillRepeatedly(Return(ExceptionOr(Exception::kExecution))); // Only write once when disconnecting EXPECT_CALL(*endpoint_channel, Write(_)).Times(1); RegisterEndpoint(std::move(endpoint_channel)); } void ReadInvalidPayloadFromEncryptedChannelIgnoresFrame(ByteArray payload) { // 1. EndpointChannel is encrypted. // 2. EndpointManager receives an invalid encrypted frame. // 3. No calls to TryDecrypt. auto endpoint_channel = std::make_unique(); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce(Return(ExceptionOr(payload))) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, IsEncrypted()).WillRepeatedly(Return(true)); EXPECT_CALL(*endpoint_channel, TryDecrypt(Eq(payload))).Times(0); // Only write once when disconnecting EXPECT_CALL(*endpoint_channel, Write(_)).Times(1); RegisterEndpoint(std::move(endpoint_channel)); } }; auto InvalidPayloadDomain() { return Filter( [](ByteArray payload) { return !parser::FromBytes(payload.AsStringView()).ok(); }, Map([](std::string payloadString) { return ByteArray(payloadString); }, String())); } FUZZ_TEST_F(EndpointManagerFuzzTest, ReadInvalidEncryptedPayloadIgnoresFrame) .WithDomains(InvalidPayloadDomain()); FUZZ_TEST_F(EndpointManagerFuzzTest, ReadInvalidPayloadFromEncryptedChannelIgnoresFrame) .WithDomains(InvalidPayloadDomain()); TEST_F(EndpointManagerTest, TryDecrypt) { // 1. EndpointChannel is unencrypted. // 2. EndpointManager receives a valid encrypted frame alas it's interpreted // as unencrypted at first. // 3. EndpointManager calls EndpointChannel::TryDecrypt(), decryption works. // 4. Frame is processed. const ByteArray payload("valid encrypted frame"); auto endpoint_channel = std::make_unique(); auto connect_request = std::make_unique(); ByteArray endpoint_info{"endpoint_name"}; ConnectionInfo connection_info{ "endpoint_id", endpoint_info, 1234 /*nonce*/, false /*supports_5_ghz*/, "" /*bssid*/, 2412 /*ap_frequency*/, std::vector{Medium::BLE} /*supported_mediums*/, 0 /*keep_alive_interval_millis*/, 0 /*keep_alive_timeout_millis*/}; std::string decrypted_data = parser::ForConnectionRequestConnections({}, connection_info); EXPECT_CALL(*connect_request, OnIncomingFrame); EXPECT_CALL(*connect_request, OnEndpointDisconnect); EXPECT_CALL(*endpoint_channel, Read(_)) .WillOnce(Return(ExceptionOr(payload))) .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); EXPECT_CALL(*endpoint_channel, TryDecrypt(Eq(payload))) .WillOnce(Return(ExceptionOr(Exception::kFailed))) .WillOnce(Return(ExceptionOr(ByteArray(decrypted_data)))); EXPECT_CALL(*endpoint_channel, Write(_)) .WillRepeatedly(Return(Exception{Exception::kSuccess})); em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, connect_request.get()); processors_.emplace_back(std::move(connect_request)); RegisterEndpoint(std::move(endpoint_channel)); } // Regression test for b/278729669. // // During the destruction of NearbyConnections, Core (which owns ClientProxy) // is destructed before ServiceController (which owns EndpointManager), which // means any pending tasks on the EndpointManager than use ClientProxy will // be using garbage memory, and cause crashes. This test enforces the fix. TEST_F(EndpointManagerTest, DisconnectEndpointDuringDestruction) { // This test uses a `FakeSingleThreadExecutor` in order to control when // tasks are executed in order to simulate the scenario where // `DiscardEndpoint` is posted to the executor before the EndpointManager // is destructed, and executed during it's destruction. std::unique_ptr serial_executor = std::make_unique(); FakeSingleThreadExecutor* fake_serial_executor = static_cast(serial_executor.get()); std::unique_ptr endpoint_manager = std::make_unique(&ecm_, std::move(serial_executor)); // DiscardEndpoint posts a task to the executor to run "discard-endpoint", // however the `FakeSingleThreadExecutor` will not run this task // immediately. fake_serial_executor->SetRunExecutablesImmediately( /*run_executables_immediately=*/false); endpoint_manager->DiscardEndpoint(client_.get(), endpoint_id_, DisconnectionReason::IO_ERROR); // Simulate Core destruction of ClientProxy by destroying `client_`. client_.reset(); // Simulate ServiceController destruction of EndpointManager by destroying // `endpoint_manager`, and set the `FakeSingleThreadExecutor` to run // executables on calls `Execute`. When `endpoint_manager` is destructed, it // will block on calls to `Execute` to run all pending executables, notably // "discard-endpoint" from above. However, "discard-endpoint" will have a // reference to a destroyed ClientProxy. // // Expect no crash when "discard-endpoints" is executed during the // destruction. fake_serial_executor->SetRunExecutablesImmediately( /*run_executables_immediately=*/true); endpoint_manager.reset(); } } // namespace } // namespace nearby::connections