blob: d71d16273104825bbdfb0077eab77a1453431820 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/security_key/security_key_ipc_client.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "ipc/ipc_channel.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "remoting/host/security_key/fake_security_key_ipc_server.h"
#include "remoting/host/security_key/security_key_ipc_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const int kTestConnectionId = 1;
const char kNonexistentIpcChannelName[] = "Nonexistent_IPC_Channel";
const char kValidIpcChannelName[] = "SecurityKeyIpcClientTest";
const int kLargeMessageSizeBytes = 256 * 1024;
} // namespace
namespace remoting {
class SecurityKeyIpcClientTest : public testing::Test {
public:
SecurityKeyIpcClientTest();
~SecurityKeyIpcClientTest() override;
// Passed to the object used for testing to be called back to signal
// completion of an IPC channel state change or reception of an IPC message.
void OperationComplete(bool failed);
// Callback used to signal when the IPC channel is ready for messages.
void ConnectionStateHandler(bool established);
// Callback used to drive the |fake_ipc_server_| connection behavior.
void SendConnectionMessage();
// Used as a callback given to the object under test, expected to be called
// back when a security key request is received by it.
void SendMessageToClient(int connection_id, const std::string& data);
// Used as a callback given to the object under test, expected to be called
// back when a security key response is sent.
void ClientMessageReceived(const std::string& response_payload);
protected:
// testing::Test interface.
void SetUp() override;
// Waits until the current |run_loop_| instance is signaled, then resets it.
void WaitForOperationComplete();
// Waits until all tasks have been run on the current message loop.
void RunPendingTasks();
// Sets up an active IPC connection between |security_key_ipc_client_|
// and |fake_ipc_server_|. |expect_connected| defines whether the operation
// is result in a usable IPC connection. |expect_error| defines whether the
// the error callback should be invoked during the connection process.
void EstablishConnection(bool expect_connected, bool expect_error);
// Sends a security key request from |security_key_ipc_client_| and
// a response from |fake_ipc_server_| and verifies the payloads for both.
void SendRequestAndResponse(const std::string& request_data,
const std::string& response_data);
// Creates a unique IPC channel name to use for testing.
mojo::NamedPlatformChannel::ServerName GenerateUniqueTestChannelName();
// IPC tests require a valid MessageLoop to run.
base::MessageLoopForIO message_loop_;
// Used to allow |message_loop_| to run during tests. The instance is reset
// after each stage of the tests has been completed.
std::unique_ptr<base::RunLoop> run_loop_;
// The object under test.
SecurityKeyIpcClient security_key_ipc_client_;
// Used to send/receive security key IPC messages for testing.
FakeSecurityKeyIpcServer fake_ipc_server_;
// Stores the current session ID on supported platforms.
uint32_t session_id_ = 0;
// Tracks the success/failure of the last async operation.
bool operation_failed_ = false;
// Tracks whether the IPC channel connection has been established.
bool connection_established_ = false;
// Used to drive invalid session behavior for testing the client.
bool simulate_invalid_session_ = false;
// Used to validate the object under test uses the correct ID when
// communicating over the IPC channel.
int last_connection_id_received_ = -1;
// Stores the contents of the last IPC message received for validation.
std::string last_message_received_;
private:
DISALLOW_COPY_AND_ASSIGN(SecurityKeyIpcClientTest);
};
SecurityKeyIpcClientTest::SecurityKeyIpcClientTest()
: run_loop_(new base::RunLoop()),
fake_ipc_server_(
kTestConnectionId,
/*client_session_details=*/nullptr,
/*initial_connect_timeout=*/base::TimeDelta::FromMilliseconds(500),
base::Bind(&SecurityKeyIpcClientTest::SendMessageToClient,
base::Unretained(this)),
base::Bind(&SecurityKeyIpcClientTest::SendConnectionMessage,
base::Unretained(this)),
base::Bind(&SecurityKeyIpcClientTest::OperationComplete,
base::Unretained(this),
/*failed=*/false)) {}
SecurityKeyIpcClientTest::~SecurityKeyIpcClientTest() = default;
void SecurityKeyIpcClientTest::SetUp() {
#if defined(OS_WIN)
DWORD session_id = 0;
// If we are on Windows, then we need to set the correct session ID or the
// IPC connection will not be created successfully.
ASSERT_TRUE(ProcessIdToSessionId(GetCurrentProcessId(), &session_id));
session_id_ = session_id;
security_key_ipc_client_.SetExpectedIpcServerSessionIdForTest(session_id_);
#endif // defined(OS_WIN)
}
void SecurityKeyIpcClientTest::OperationComplete(bool failed) {
operation_failed_ |= failed;
run_loop_->Quit();
}
void SecurityKeyIpcClientTest::ConnectionStateHandler(bool established) {
connection_established_ = established;
OperationComplete(/*failed=*/false);
}
void SecurityKeyIpcClientTest::SendConnectionMessage() {
if (simulate_invalid_session_) {
fake_ipc_server_.SendInvalidSessionMessage();
} else {
fake_ipc_server_.SendConnectionReadyMessage();
}
}
void SecurityKeyIpcClientTest::WaitForOperationComplete() {
run_loop_->Run();
run_loop_.reset(new base::RunLoop());
}
void SecurityKeyIpcClientTest::RunPendingTasks() {
// Run until there are no pending work items in the queue.
base::RunLoop().RunUntilIdle();
}
void SecurityKeyIpcClientTest::SendMessageToClient(int connection_id,
const std::string& data) {
last_connection_id_received_ = connection_id;
last_message_received_ = data;
OperationComplete(/*failed=*/false);
}
void SecurityKeyIpcClientTest::ClientMessageReceived(
const std::string& response_payload) {
last_message_received_ = response_payload;
OperationComplete(/*failed=*/false);
}
mojo::NamedPlatformChannel::ServerName
SecurityKeyIpcClientTest::GenerateUniqueTestChannelName() {
return mojo::NamedPlatformChannel::ServerNameFromUTF8(
GetChannelNamePathPrefixForTest() + kValidIpcChannelName +
IPC::Channel::GenerateUniqueRandomChannelID());
}
void SecurityKeyIpcClientTest::EstablishConnection(bool expect_connected,
bool expect_error) {
// Start up the security key forwarding session IPC channel first, that way
// we can provide the channel using the fake SecurityKeyAuthHandler later on.
mojo::NamedPlatformChannel::ServerName server_name =
GenerateUniqueTestChannelName();
security_key_ipc_client_.SetIpcChannelHandleForTest(server_name);
ASSERT_TRUE(fake_ipc_server_.CreateChannel(
server_name,
/*request_timeout=*/base::TimeDelta::FromMilliseconds(500)));
ASSERT_TRUE(security_key_ipc_client_.CheckForSecurityKeyIpcServerChannel());
// Establish the IPC channel so we can begin sending and receiving security
// key messages.
security_key_ipc_client_.EstablishIpcConnection(
base::Bind(&SecurityKeyIpcClientTest::ConnectionStateHandler,
base::Unretained(this)),
base::Bind(&SecurityKeyIpcClientTest::OperationComplete,
base::Unretained(this), /*failed=*/true));
WaitForOperationComplete();
RunPendingTasks();
ASSERT_EQ(expect_connected, connection_established_);
ASSERT_EQ(expect_error, operation_failed_);
}
void SecurityKeyIpcClientTest::SendRequestAndResponse(
const std::string& request_data,
const std::string& response_data) {
ASSERT_TRUE(security_key_ipc_client_.SendSecurityKeyRequest(
request_data, base::Bind(&SecurityKeyIpcClientTest::ClientMessageReceived,
base::Unretained(this))));
WaitForOperationComplete();
ASSERT_FALSE(operation_failed_);
ASSERT_EQ(kTestConnectionId, last_connection_id_received_);
ASSERT_EQ(request_data, last_message_received_);
ASSERT_TRUE(fake_ipc_server_.SendResponse(response_data));
WaitForOperationComplete();
ASSERT_FALSE(operation_failed_);
ASSERT_EQ(response_data, last_message_received_);
}
TEST_F(SecurityKeyIpcClientTest, GenerateSingleSecurityKeyRequest) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
SendRequestAndResponse("Auth me!", "You've been authed!");
security_key_ipc_client_.CloseIpcConnection();
}
TEST_F(SecurityKeyIpcClientTest, GenerateLargeSecurityKeyRequest) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
SendRequestAndResponse(std::string(kLargeMessageSizeBytes, 'Y'),
std::string(kLargeMessageSizeBytes, 'Z'));
security_key_ipc_client_.CloseIpcConnection();
}
TEST_F(SecurityKeyIpcClientTest, GenerateReallyLargeSecurityKeyRequest) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
SendRequestAndResponse(std::string(kLargeMessageSizeBytes * 2, 'Y'),
std::string(kLargeMessageSizeBytes * 2, 'Z'));
security_key_ipc_client_.CloseIpcConnection();
}
TEST_F(SecurityKeyIpcClientTest, GenerateMultipleSecurityKeyRequest) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
SendRequestAndResponse("Auth me 1!", "You've been authed once!");
SendRequestAndResponse("Auth me 2!", "You've been authed twice!");
SendRequestAndResponse("Auth me 3!", "You've been authed thrice!");
security_key_ipc_client_.CloseIpcConnection();
}
TEST_F(SecurityKeyIpcClientTest, ServerClosesConnectionAfterRequestTimeout) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
fake_ipc_server_.CloseChannel();
WaitForOperationComplete();
ASSERT_FALSE(operation_failed_);
}
TEST_F(SecurityKeyIpcClientTest,
SecondSecurityKeyRequestBeforeFirstResponseReceived) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
ASSERT_TRUE(security_key_ipc_client_.SendSecurityKeyRequest(
"First Request",
base::Bind(&SecurityKeyIpcClientTest::ClientMessageReceived,
base::Unretained(this))));
WaitForOperationComplete();
ASSERT_FALSE(operation_failed_);
ASSERT_FALSE(security_key_ipc_client_.SendSecurityKeyRequest(
"Second Request",
base::Bind(&SecurityKeyIpcClientTest::ClientMessageReceived,
base::Unretained(this))));
}
TEST_F(SecurityKeyIpcClientTest, ReceiveSecurityKeyResponseWithEmptyPayload) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
ASSERT_TRUE(security_key_ipc_client_.SendSecurityKeyRequest(
"Valid request",
base::Bind(&SecurityKeyIpcClientTest::ClientMessageReceived,
base::Unretained(this))));
WaitForOperationComplete();
ASSERT_FALSE(operation_failed_);
ASSERT_TRUE(fake_ipc_server_.SendResponse(""));
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
}
TEST_F(SecurityKeyIpcClientTest, SendRequestBeforeEstablishingConnection) {
// Sending a request will fail since the IPC connection has not been
// established.
ASSERT_FALSE(security_key_ipc_client_.SendSecurityKeyRequest(
"Too soon!!", base::Bind(&SecurityKeyIpcClientTest::ClientMessageReceived,
base::Unretained(this))));
}
TEST_F(SecurityKeyIpcClientTest, NonExistentIpcServerChannel) {
security_key_ipc_client_.SetIpcChannelHandleForTest(
mojo::NamedPlatformChannel::ServerNameFromUTF8(
kNonexistentIpcChannelName));
// Attempt to establish the conection (should fail since the IPC channel does
// not exist).
security_key_ipc_client_.EstablishIpcConnection(
base::Bind(&SecurityKeyIpcClientTest::ConnectionStateHandler,
base::Unretained(this)),
base::Bind(&SecurityKeyIpcClientTest::OperationComplete,
base::Unretained(this), /*failed=*/true));
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
}
TEST_F(SecurityKeyIpcClientTest, MultipleConnectionReadyMessagesReceived) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
ASSERT_FALSE(operation_failed_);
// Send a second ConnectionReady message to trigger the error callback.
SendConnectionMessage();
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
// Send a third message to ensure no crash occurs both callbacks will have
// been called and cleared so we don't wait for the operation to complete.
SendConnectionMessage();
ASSERT_TRUE(operation_failed_);
}
TEST_F(SecurityKeyIpcClientTest, UnexpectedInvalidSessionMessagesReceived) {
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/false);
ASSERT_FALSE(operation_failed_);
// Send an InvalidSession message to trigger the error callback.
simulate_invalid_session_ = true;
SendConnectionMessage();
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
// Send a third message to ensure no crash occurs both callbacks will have
// been called and cleared so we don't wait for the operation to complete.
SendConnectionMessage();
ASSERT_TRUE(operation_failed_);
}
#if defined(OS_WIN)
TEST_F(SecurityKeyIpcClientTest, SecurityKeyIpcServerRunningInWrongSession) {
// Set the expected session Id to a different session than we are running in.
security_key_ipc_client_.SetExpectedIpcServerSessionIdForTest(session_id_ +
1);
// Attempting to establish a connection should fail here since the IPC Server
// is 'running' in a different session than expected. The success callback
// will be called by the IPC server since it thinks the connection is valid,
// but we will have already started tearing down the connection since it
// failed at the client end.
EstablishConnection(/*expect_connected=*/true, /*expect_error=*/true);
}
TEST_F(SecurityKeyIpcClientTest, SecurityKeyIpcClientRunningInWrongSession) {
// Attempting to establish a connection should fail here since the IPC Client
// is 'running' in the non-remoted session.
simulate_invalid_session_ = true;
EstablishConnection(/*expect_connected=*/false, /*expect_error=*/false);
}
TEST_F(SecurityKeyIpcClientTest, MultipleInvalidSessionMessagesReceived) {
// Attempting to establish a connection should fail here since the IPC Server
// is 'running' in a different session than expected.
simulate_invalid_session_ = true;
EstablishConnection(/*expect_connected=*/false, /*expect_error=*/false);
SendConnectionMessage();
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
// Send a third message to ensure no crash occurs both callbacks will have
// been called and cleared so we don't wait for the operation to complete.
SendConnectionMessage();
ASSERT_TRUE(operation_failed_);
}
TEST_F(SecurityKeyIpcClientTest, UnexpectedConnectionReadyMessagesReceived) {
// Attempting to establish a connection should fail here since the IPC Server
// is 'running' in a different session than expected.
simulate_invalid_session_ = true;
EstablishConnection(/*expect_connected=*/false, /*expect_error=*/false);
// Send an InvalidSession message to trigger the error callback.
simulate_invalid_session_ = false;
SendConnectionMessage();
WaitForOperationComplete();
ASSERT_TRUE(operation_failed_);
// Send a third message to ensure no crash occurs both callbacks will have
// been called and cleared so we don't wait for the operation to complete.
SendConnectionMessage();
ASSERT_TRUE(operation_failed_);
}
#endif // defined(OS_WIN)
} // namespace remoting