blob: a4b3efc8e3a086521eec2448ea15b403d6de2069 [file] [log] [blame]
// Copyright 2018 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 "device/fido/ble/fido_ble_transaction.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "device/bluetooth/test/bluetooth_test.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/ble/fido_ble_connection.h"
#include "device/fido/ble/fido_ble_frames.h"
#include "device/fido/ble/mock_fido_ble_connection.h"
#include "device/fido/fido_constants.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
constexpr uint16_t kDefaultControlPointLength = 20;
using FrameCallbackReceiver =
test::ValueCallbackReceiver<base::Optional<FidoBleFrame>>;
std::vector<std::vector<uint8_t>> ToByteFragments(const FidoBleFrame& frame) {
std::vector<std::vector<uint8_t>> byte_fragments;
auto fragments_pair = frame.ToFragments(kDefaultControlPointLength);
byte_fragments.reserve(1 + fragments_pair.second.size());
byte_fragments.emplace_back();
byte_fragments.back().reserve(kDefaultControlPointLength);
fragments_pair.first.Serialize(&byte_fragments.back());
while (!fragments_pair.second.empty()) {
byte_fragments.emplace_back();
byte_fragments.back().reserve(kDefaultControlPointLength);
fragments_pair.second.front().Serialize(&byte_fragments.back());
fragments_pair.second.pop();
}
return byte_fragments;
}
} // namespace
class FidoBleTransactionTest : public ::testing::Test {
public:
base::test::ScopedTaskEnvironment& scoped_task_environment() {
return scoped_task_environment_;
}
MockFidoBleConnection& connection() { return connection_; }
FidoBleTransaction& transaction() { return *transaction_; }
void ResetTransaction(uint16_t control_point_length) {
transaction_ = std::make_unique<FidoBleTransaction>(&connection_,
control_point_length);
}
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
scoped_refptr<BluetoothAdapter> adapter_ =
base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>();
MockFidoBleConnection connection_{adapter_.get(),
BluetoothTestBase::kTestDeviceAddress1};
std::unique_ptr<FidoBleTransaction> transaction_ =
std::make_unique<FidoBleTransaction>(&connection_,
kDefaultControlPointLength);
};
// Tests a case where the control point write fails.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_FailWrite) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(false /* success */); }));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(FidoBleFrame(), receiver.callback());
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a case where the control point write succeeds.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_Success) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
for (auto&& byte_fragment : ToByteFragments(frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(frame, receiver.value());
}
// Tests a scenario where the full response frame is obtained before the control
// point write was acknowledged. The response callback should only be run once
// the ACK is received.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_DelayedWriteAck) {
FidoBleConnection::WriteCallback delayed_write_callback;
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[&](auto&&, auto* cb) { delayed_write_callback = std::move(*cb); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
for (auto&& byte_fragment : ToByteFragments(frame))
transaction().OnResponseFragment(std::move(byte_fragment));
scoped_task_environment().RunUntilIdle();
EXPECT_FALSE(receiver.was_called());
std::move(delayed_write_callback).Run(true);
receiver.WaitForCallback();
EXPECT_EQ(frame, receiver.value());
}
// Tests a scenario where keep alive frames are obtained before the control
// point write was acknowledged. The keep alive should be processed.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_DelayedWriteAck_KeepAlive) {
FidoBleConnection::WriteCallback delayed_write_callback;
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[&](auto&&, auto* cb) { delayed_write_callback = std::move(*cb); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FidoBleFrame tup_needed_frame(
FidoBleDeviceCommand::kKeepAlive,
{base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::TUP_NEEDED)});
FrameCallbackReceiver receiver;
// Send two keep alives then the actual response.
transaction().WriteRequestFrame(frame, receiver.callback());
for (auto&& byte_fragment : ToByteFragments(tup_needed_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
for (auto&& byte_fragment : ToByteFragments(tup_needed_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
for (auto&& byte_fragment : ToByteFragments(frame))
transaction().OnResponseFragment(std::move(byte_fragment));
scoped_task_environment().RunUntilIdle();
EXPECT_FALSE(receiver.was_called());
std::move(delayed_write_callback).Run(true);
receiver.WaitForCallback();
EXPECT_EQ(frame, receiver.value());
}
// Tests a case where the control point length is too small.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_ControlPointLength_TooSmall) {
static constexpr uint16_t kTooSmallControlPointLength = 2u;
ResetTransaction(kTooSmallControlPointLength);
EXPECT_CALL(connection(), WriteControlPointPtr).Times(0);
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests that valid KeepaliveCodes are ignored, and only a valid
// response frame completes the request.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_IgnoreValidKeepAlives) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
FidoBleFrame tup_needed_frame(
FidoBleDeviceCommand::kKeepAlive,
{base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::TUP_NEEDED)});
for (auto&& byte_fragment : ToByteFragments(tup_needed_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
scoped_task_environment().RunUntilIdle();
EXPECT_FALSE(receiver.was_called());
FidoBleFrame processing_frame(
FidoBleDeviceCommand::kKeepAlive,
{base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::PROCESSING)});
for (auto&& byte_fragment : ToByteFragments(processing_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
scoped_task_environment().RunUntilIdle();
EXPECT_FALSE(receiver.was_called());
for (auto&& byte_fragment : ToByteFragments(frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(frame, receiver.value());
}
// Tests that an invalid KeepaliveCode is treated as an error.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidKeepAlive_Fail) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
// This frame is invalid, as it does not contain data.
FidoBleFrame keep_alive_frame(FidoBleDeviceCommand::kKeepAlive, {});
for (auto&& byte_fragment : ToByteFragments(keep_alive_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a scenario where the response frame contains a valid error command.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_ValidErrorCommand) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(ping_frame, receiver.callback());
FidoBleFrame error_frame(
FidoBleDeviceCommand::kError,
{base::strict_cast<uint8_t>(FidoBleFrame::ErrorCode::INVALID_CMD)});
for (auto&& byte_fragment : ToByteFragments(error_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(error_frame, receiver.value());
}
// Tests a scenario where the response frame contains an invalid error command.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidErrorCommand) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(ping_frame, receiver.callback());
// This frame is invalid, as it does not contain data.
FidoBleFrame error_frame(FidoBleDeviceCommand::kError, {});
for (auto&& byte_fragment : ToByteFragments(error_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a scenario where the command of the response frame does not match the
// command of the request frame.
TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidResponseFrameCommand) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillOnce(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(10));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(ping_frame, receiver.callback());
FidoBleFrame message_frame(FidoBleDeviceCommand::kMsg,
std::vector<uint8_t>(kDefaultControlPointLength));
for (auto&& byte_fragment : ToByteFragments(message_frame))
transaction().OnResponseFragment(std::move(byte_fragment));
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a scenario where the response initialization fragment is invalid.
TEST_F(FidoBleTransactionTest,
WriteRequestFrame_InvalidResponseInitializationFragment) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillRepeatedly(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(kDefaultControlPointLength));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
auto byte_fragments = ToByteFragments(frame);
ASSERT_EQ(2u, byte_fragments.size());
transaction().OnResponseFragment(std::move(byte_fragments.back()));
transaction().OnResponseFragment(std::move(byte_fragments.front()));
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a scenario where a response continuation fragment is invalid.
TEST_F(FidoBleTransactionTest,
WriteRequestFrame_InvalidResponseContinuationFragment) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillRepeatedly(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(kDefaultControlPointLength));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
// Provide the initialization fragment twice. The second time should be an
// error, as it's not a valid continuation fragment.
auto byte_fragments = ToByteFragments(frame);
ASSERT_EQ(2u, byte_fragments.size());
transaction().OnResponseFragment(byte_fragments.front());
transaction().OnResponseFragment(byte_fragments.front());
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
// Tests a scenario where the order of response continuation fragments is
// invalid.
TEST_F(FidoBleTransactionTest,
WriteRequestFrame_InvalidOrderResponseContinuationFragments) {
EXPECT_CALL(connection(), WriteControlPointPtr)
.WillRepeatedly(::testing::Invoke(
[](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }));
FidoBleFrame frame(FidoBleDeviceCommand::kPing,
std::vector<uint8_t>(kDefaultControlPointLength * 2));
FrameCallbackReceiver receiver;
transaction().WriteRequestFrame(frame, receiver.callback());
// Provide the continuation fragments in the wrong order.
auto byte_fragments = ToByteFragments(frame);
ASSERT_EQ(3u, byte_fragments.size());
transaction().OnResponseFragment(byte_fragments[0]);
transaction().OnResponseFragment(byte_fragments[2]);
transaction().OnResponseFragment(byte_fragments[1]);
receiver.WaitForCallback();
EXPECT_EQ(base::nullopt, receiver.value());
}
} // namespace device