Files
nearby/connections/implementation/endpoint_channel_manager_test.cc
hai007 5bdc1ab09c Automated Code Change
PiperOrigin-RevId: 806630829
2025-09-13 05:04:07 -07:00

324 lines
13 KiB
C++

// 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 "connections/implementation/endpoint_channel_manager.h"
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "securegcm/ukey2_handshake.h"
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "connections/implementation/base_endpoint_channel.h"
#include "connections/implementation/client_proxy.h"
#include "connections/implementation/encryption_runner.h"
#include "connections/implementation/endpoint_channel.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/logging.h"
#include "internal/platform/multi_thread_executor.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/pipe.h"
#include "internal/proto/analytics/connections_log.pb.h"
#include "proto/connections_enums.pb.h"
namespace nearby {
namespace connections {
namespace {
using ::location::nearby::analytics::proto::ConnectionsLog;
using ::location::nearby::proto::connections::DisconnectionReason;
using ::location::nearby::proto::connections::Medium;
using EncryptionContext = BaseEndpointChannel::EncryptionContext;
constexpr size_t kChunkSize = 64 * 1024;
constexpr absl::string_view kEndpointId = "EndpointId";
constexpr absl::string_view kMonitorA = "MonitorA";
constexpr absl::string_view kMonitorB = "MonitorB";
constexpr absl::string_view kPumpA = "PumpA";
constexpr absl::string_view kPumpB = "PumpB";
class MockEndpointChannel : public BaseEndpointChannel {
public:
explicit MockEndpointChannel(InputStream* input, OutputStream* output)
: BaseEndpointChannel("service_id", "channel", input, output) {}
MOCK_METHOD(Medium, GetMedium, (), (const, override));
MOCK_METHOD(void, CloseImpl, (), (override));
};
std::function<void()> MakeDataPump(
absl::string_view label, InputStream* input, OutputStream* output,
std::function<void(const ByteArray&)> monitor = nullptr) {
return [label, input, output, monitor]() {
LOG(INFO) << "streaming data through '" << label << "'";
while (true) {
auto read_response = input->Read(kChunkSize);
if (!read_response.ok()) {
LOG(INFO) << "Peer reader closed on '" << label << "'";
output->Close();
break;
}
if (monitor) {
monitor(read_response.result());
}
auto write_response = output->Write(read_response.result());
if (write_response.Raised()) {
LOG(INFO) << "Peer writer closed on '" << label << "'";
input->Close();
break;
}
}
LOG(INFO) << "streaming terminated on '" << label << "'";
};
}
std::function<void(const ByteArray&)> MakeDataMonitor(absl::string_view label,
std::string* capture,
absl::Mutex* mutex) {
return [label, capture, mutex](const ByteArray& input) mutable {
std::string s = std::string(input);
{
absl::MutexLock lock(*mutex);
*capture += s;
}
LOG(INFO) << "source='" << label << "'"
<< "; message='" << s << "'";
};
}
std::pair<std::unique_ptr<EncryptionContext>,
std::unique_ptr<EncryptionContext>>
DoDhKeyExchange(BaseEndpointChannel* channel_a,
BaseEndpointChannel* channel_b) {
std::unique_ptr<EncryptionContext> context_a;
std::unique_ptr<EncryptionContext> context_b;
EncryptionRunner crypto_a;
EncryptionRunner crypto_b;
ClientProxy proxy_a;
ClientProxy proxy_b;
CountDownLatch latch(2);
crypto_a.StartClient(
&proxy_a, std::string(kEndpointId), channel_a,
{
.on_success_cb =
[&latch, &context_a](
const std::string& endpoint_id,
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
const std::string& auth_token,
const ByteArray& raw_auth_token) {
LOG(INFO) << "client-A side key negotiation done";
EXPECT_TRUE(ukey2->VerifyHandshake());
auto context = ukey2->ToConnectionContext();
EXPECT_NE(context, nullptr);
context_a = std::move(context);
latch.CountDown();
},
.on_failure_cb =
[&latch](const std::string& endpoint_id,
EndpointChannel* channel) {
LOG(INFO) << "client-A side key negotiation failed";
latch.CountDown();
},
});
crypto_b.StartServer(
&proxy_b, std::string(kEndpointId), channel_b,
{
.on_success_cb =
[&latch, &context_b](
const std::string& endpoint_id,
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
const std::string& auth_token,
const ByteArray& raw_auth_token) {
LOG(INFO) << "client-B side key negotiation done";
EXPECT_TRUE(ukey2->VerifyHandshake());
auto context = ukey2->ToConnectionContext();
EXPECT_NE(context, nullptr);
context_b = std::move(context);
latch.CountDown();
},
.on_failure_cb =
[&latch](const std::string& endpoint_id,
EndpointChannel* channel) {
LOG(INFO) << "client-B side key negotiation failed";
latch.CountDown();
},
});
EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result());
return std::make_pair(std::move(context_a), std::move(context_b));
}
TEST(BaseEndpointChannelManagerTest, RegisterChannelEncryptedReadwrite) {
// Setup test communication environment.
absl::Mutex mutex;
std::string capture_a;
std::string capture_b;
ClientProxy proxy_a;
ClientProxy proxy_b;
auto client_a =
CreatePipe(); // Channel "a" writes to client "a", reads from server "a".
auto client_b =
CreatePipe(); // Channel "b" writes to client "b", reads from server "b".
auto server_a = CreatePipe(); // Data pump "a" reads from client "a", writes
// to server "b".
auto server_b = CreatePipe(); // Data pump "b" reads from client "b", writes
// to server "a".
auto channel_a = std::make_unique<MockEndpointChannel>(server_a.first.get(),
client_a.second.get());
auto channel_b = std::make_unique<MockEndpointChannel>(server_b.first.get(),
client_b.second.get());
auto channel_a_raw = channel_a.get();
auto channel_b_raw = channel_b.get();
ON_CALL(*channel_a_raw, GetMedium).WillByDefault([]() {
return Medium::BLUETOOTH;
});
ON_CALL(*channel_b_raw, GetMedium).WillByDefault([]() {
return Medium::BLUETOOTH;
});
MultiThreadExecutor executor(2);
executor.Execute(
MakeDataPump(kPumpA, client_a.first.get(), server_b.second.get(),
MakeDataMonitor(kMonitorA, &capture_a, &mutex)));
executor.Execute(
MakeDataPump(kPumpB, client_b.first.get(), server_a.second.get(),
MakeDataMonitor(kMonitorB, &capture_b, &mutex)));
// Run DH key exchange; setup encryption contexts for channels.
auto context = DoDhKeyExchange(channel_a.get(), channel_b.get());
ASSERT_NE(context.first, nullptr);
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));
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));
EXPECT_EQ(channel_a_raw->GetType(), "ENCRYPTED_BLUETOOTH");
EXPECT_EQ(channel_b_raw->GetType(), "ENCRYPTED_BLUETOOTH");
ByteArray tx_message{"data message"};
channel_a_raw->Write(tx_message);
ByteArray rx_message = std::move(channel_b_raw->Read().result());
// Verify expectations.
EXPECT_EQ(rx_message, tx_message);
{
absl::MutexLock lock(mutex);
std::string message{tx_message};
EXPECT_TRUE(capture_a.find(message) == std::string::npos &&
capture_b.find(message) == std::string::npos);
}
// Shutdown test environment.
channel_a_raw->Close(DisconnectionReason::LOCAL_DISCONNECTION);
channel_b_raw->Close(DisconnectionReason::REMOTE_DISCONNECTION);
ecm_a.UnregisterChannelForEndpoint(
std::string(kEndpointId), DisconnectionReason::LOCAL_DISCONNECTION,
ConnectionsLog::EstablishedConnection::SAFE_DISCONNECTION);
ecm_b.UnregisterChannelForEndpoint(
std::string(kEndpointId), DisconnectionReason::REMOTE_DISCONNECTION,
ConnectionsLog::EstablishedConnection::SAFE_DISCONNECTION);
}
TEST(BaseEndpointChannelManagerTest, ReplaceChannelNoEncrypted) {
// Setup test communication environment.
absl::Mutex mutex;
std::string capture_a;
std::string capture_b;
ClientProxy proxy_a;
ClientProxy proxy_b;
auto client_a =
CreatePipe(); // Channel "a" writes to client "a", reads from server "a".
auto client_b =
CreatePipe(); // Channel "b" writes to client "b", reads from server "b".
auto server_a = CreatePipe(); // Data pump "a" reads from client "a", writes
// to server "b".
auto server_b = CreatePipe(); // Data pump "b" reads from client "b", writes
// to server "a".
auto channel_a = std::make_unique<MockEndpointChannel>(server_a.first.get(),
client_a.second.get());
auto channel_b = std::make_unique<MockEndpointChannel>(server_b.first.get(),
client_b.second.get());
auto channel_a_raw = channel_a.get();
auto channel_b_raw = channel_b.get();
ON_CALL(*channel_a_raw, GetMedium).WillByDefault([]() {
return Medium::BLUETOOTH;
});
ON_CALL(*channel_b_raw, GetMedium).WillByDefault([]() {
return Medium::BLUETOOTH;
});
MultiThreadExecutor executor(2);
executor.Execute(
MakeDataPump(kPumpA, client_a.first.get(), server_b.second.get(),
MakeDataMonitor(kMonitorA, &capture_a, &mutex)));
executor.Execute(
MakeDataPump(kPumpB, client_b.first.get(), server_a.second.get(),
MakeDataMonitor(kMonitorB, &capture_b, &mutex)));
// Run DH key exchange; setup encryption contexts for channels.
auto context = DoDhKeyExchange(channel_a.get(), channel_b.get());
ASSERT_NE(context.first, nullptr);
ASSERT_NE(context.second, nullptr);
EndpointChannelManager ecm_a;
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.EncryptChannelForEndpoint(std::string(kEndpointId),
std::move(context.second));
ecm_b.ReplaceChannelForEndpoint(&proxy_b, std::string(kEndpointId),
std::move(channel_b), false);
EXPECT_EQ(channel_a_raw->GetType(), "BLUETOOTH");
EXPECT_EQ(channel_b_raw->GetType(), "BLUETOOTH");
// Shutdown test environment.
channel_a_raw->Close(DisconnectionReason::LOCAL_DISCONNECTION);
channel_b_raw->Close(DisconnectionReason::REMOTE_DISCONNECTION);
ecm_a.UnregisterChannelForEndpoint(
std::string(kEndpointId), DisconnectionReason::LOCAL_DISCONNECTION,
ConnectionsLog::EstablishedConnection::SAFE_DISCONNECTION);
ecm_b.UnregisterChannelForEndpoint(
std::string(kEndpointId), DisconnectionReason::REMOTE_DISCONNECTION,
ConnectionsLog::EstablishedConnection::SAFE_DISCONNECTION);
}
} // namespace
} // namespace connections
} // namespace nearby