| // Copyright 2020 The Chromium OS 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 <limits> |
| #include <memory> |
| #include <vector> |
| |
| #include <base/test/task_environment.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "libec/fingerprint/fp_frame_command.h" |
| |
| namespace ec { |
| namespace { |
| |
| using ::testing::Return; |
| |
| constexpr int kValidMaxWriteSize = 128; |
| constexpr int kValidFrameSize = 4096; |
| constexpr int kValidIndex = 0; |
| |
| TEST(FpFrameCommand, FpFrameCommand) { |
| auto cmd = |
| FpFrameCommand::Create(kValidIndex, kValidFrameSize, kValidMaxWriteSize); |
| EXPECT_TRUE(cmd); |
| EXPECT_EQ(cmd->Version(), 0); |
| EXPECT_EQ(cmd->Command(), EC_CMD_FP_FRAME); |
| } |
| |
| TEST(FpFrameCommand, InvalidReadSize) { |
| constexpr int kInvalidMaxReadSize = kMaxPacketSize + 1; |
| EXPECT_FALSE(FpFrameCommand::Create(kValidIndex, kValidFrameSize, |
| kInvalidMaxReadSize)); |
| } |
| |
| TEST(FpFrameCommand, InvalidReadSizeZero) { |
| constexpr int kInvalidMaxReadSize = 0; |
| EXPECT_FALSE(FpFrameCommand::Create(kValidIndex, kValidFrameSize, |
| kInvalidMaxReadSize)); |
| } |
| |
| TEST(FpFrameCommand, MaxReadSizeEqualsMaxPacketSize) { |
| constexpr int kValidReadSize = kMaxPacketSize; |
| EXPECT_TRUE( |
| FpFrameCommand::Create(kValidIndex, kValidFrameSize, kValidReadSize)); |
| } |
| |
| TEST(FpFrameCommand, ZeroFrameSize) { |
| constexpr int kInvalidFrameSize = 0; |
| EXPECT_FALSE(FpFrameCommand::Create(kValidIndex, kInvalidFrameSize, |
| kValidMaxWriteSize)); |
| } |
| |
| // Mock the underlying EcCommand to test |
| class FpFrameCommandTest : public testing::Test { |
| public: |
| class MockFpFrameCommand : public FpFrameCommand { |
| public: |
| MockFpFrameCommand(int index, uint32_t frame_size, ssize_t max_read_size) |
| : FpFrameCommand(index, frame_size, max_read_size) { |
| // Unless overridden, return packets full of zeroes. |
| static FpFramePacket packet = {0}; |
| ON_CALL(*this, Resp).WillByDefault(Return(&packet)); |
| } |
| MOCK_METHOD(bool, EcCommandRun, (int fd), (override)); |
| MOCK_METHOD(FpFramePacket*, Resp, (), (override)); |
| MOCK_METHOD(uint32_t, Result, (), (const, override)); |
| }; |
| |
| protected: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| }; |
| |
| TEST_F(FpFrameCommandTest, SmallTemplateSuccess) { |
| FpFramePacket packet = {1, 2, 3, 4, 5}; |
| EXPECT_EQ(packet.size(), 544); |
| // Ec command payload is empty until EcCommandRun is called. |
| FpFramePacket payload = {0}; |
| |
| constexpr int kMaxReadSize = 536; // SPI max packet size is 544. Subtract |
| // the sizeof(struct ec_host_response) |
| // to get 536. |
| static_assert(kMaxReadSize <= packet.size(), |
| "Read size must be less than or equal to packet size"); |
| |
| // Create a frame that has two full packets worth of data and one partial |
| // packet. |
| constexpr int kFrameSize = kMaxReadSize + kMaxReadSize + 10; |
| auto mock_fp_frame_command = |
| FpFrameCommand::Create<MockFpFrameCommand>(0, kFrameSize, kMaxReadSize); |
| |
| EXPECT_CALL(*mock_fp_frame_command, Resp).WillRepeatedly(Return(&payload)); |
| |
| EXPECT_CALL(*mock_fp_frame_command, EcCommandRun) |
| .WillRepeatedly( |
| // Command is run, so start returning actual packet. |
| [&payload, &packet](int fd) { |
| payload = packet; |
| return true; |
| }); |
| EXPECT_TRUE(mock_fp_frame_command->Run(-1)); |
| const auto& frame = mock_fp_frame_command->frame(); |
| |
| // First chunk |
| std::vector<uint8_t> packet_vec(packet.begin(), |
| packet.begin() + kMaxReadSize); |
| auto frame_begin = frame->begin(); |
| auto frame_end = frame->begin() + packet_vec.size(); |
| std::vector<uint8_t> frame_chunk_1(frame_begin, frame_end); |
| EXPECT_EQ(packet_vec, frame_chunk_1); |
| |
| // Second chunk |
| frame_begin += packet_vec.size(); |
| frame_end += packet_vec.size(); |
| std::vector<uint8_t> frame_chunk_2(frame_begin, frame_end); |
| EXPECT_EQ(packet_vec, frame_chunk_2); |
| |
| // Last chunk (short) |
| frame_begin += packet_vec.size(); |
| frame_end += 10; |
| packet_vec.resize(10); |
| std::vector<uint8_t> frame_chunk_3(frame_begin, frame_end); |
| EXPECT_EQ(packet_vec, frame_chunk_3); |
| } |
| |
| TEST_F(FpFrameCommandTest, LargeTemplateSuccess) { |
| FpFramePacket packet = {1, 2, 3, 4, 5}; |
| EXPECT_EQ(packet.size(), 544); |
| // Ec command payload is empty until EcCommandRun is called. |
| FpFramePacket payload = {0}; |
| |
| constexpr int kMaxReadSize = 536; // SPI max packet size is 544. Subtract |
| // the sizeof(struct ec_host_response) |
| // to get 536. |
| static_assert(kMaxReadSize <= packet.size(), |
| "Read size must be less than or equal to packet size"); |
| |
| // Create a frame that is larger than the max uint16_t value. |
| constexpr int kFrameSize = std::numeric_limits<uint16_t>::max() + 10; |
| auto mock_fp_frame_command = |
| FpFrameCommand::Create<MockFpFrameCommand>(0, kFrameSize, kMaxReadSize); |
| |
| EXPECT_CALL(*mock_fp_frame_command, Resp).WillRepeatedly(Return(&payload)); |
| |
| EXPECT_CALL(*mock_fp_frame_command, EcCommandRun) |
| .WillRepeatedly( |
| // Command is run, so start returning actual packet. |
| [&payload, &packet](int fd) { |
| payload = packet; |
| return true; |
| }); |
| EXPECT_TRUE(mock_fp_frame_command->Run(-1)); |
| const auto& frame = mock_fp_frame_command->frame(); |
| |
| std::vector<uint8_t> packet_vec(packet.begin(), |
| packet.begin() + kMaxReadSize); |
| auto frame_begin = frame->begin(); |
| auto frame_end = frame->begin() + packet_vec.size(); |
| while (frame_end + packet_vec.size() < frame->end()) { |
| std::vector<uint8_t> chunk(frame_begin, frame_end); |
| EXPECT_EQ(packet_vec, chunk); |
| frame_begin += packet_vec.size(); |
| frame_end += packet_vec.size(); |
| } |
| |
| // Last chunk (short). |
| frame_begin += packet_vec.size(); |
| frame_end += kFrameSize % kMaxReadSize; |
| EXPECT_EQ(frame_end, frame->end()); |
| packet_vec.resize(kFrameSize % kMaxReadSize); |
| std::vector<uint8_t> chunk(frame_begin, frame_end); |
| EXPECT_EQ(packet_vec, chunk); |
| } |
| |
| TEST_F(FpFrameCommandTest, RetriesWhenBusy) { |
| auto mock_fp_frame_command = |
| FpFrameCommand::Create<MockFpFrameCommand>(0, 4096, 128); |
| EXPECT_TRUE(mock_fp_frame_command); |
| EXPECT_CALL(*mock_fp_frame_command, Result).WillOnce(Return(EC_RES_BUSY)); |
| EXPECT_CALL(*mock_fp_frame_command, EcCommandRun) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_TRUE(mock_fp_frame_command->Run(-1)); |
| } |
| |
| TEST_F(FpFrameCommandTest, FailsIfBusyAfterFirstRequest) { |
| auto mock_fp_frame_command = |
| FpFrameCommand::Create<MockFpFrameCommand>(0, 4096, 128); |
| EXPECT_TRUE(mock_fp_frame_command); |
| EXPECT_CALL(*mock_fp_frame_command, Result).WillOnce(Return(EC_RES_BUSY)); |
| EXPECT_CALL(*mock_fp_frame_command, EcCommandRun) |
| .WillOnce(Return(false)) // Failure on first request; triggers retry. |
| .WillOnce(Return(true)) // Retry succeeds. |
| .WillOnce(Return(false)); // Next request fails. Since it wasn't first |
| // request, no more retries. |
| |
| EXPECT_FALSE(mock_fp_frame_command->Run(-1)); |
| } |
| |
| TEST_F(FpFrameCommandTest, StopsBusyRetriesAfterMaxAttempts) { |
| auto mock_fp_frame_command = |
| FpFrameCommand::Create<MockFpFrameCommand>(0, 4096, 128); |
| EXPECT_TRUE(mock_fp_frame_command); |
| EXPECT_CALL(*mock_fp_frame_command, EcCommandRun) |
| .Times(51) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(*mock_fp_frame_command, Result) |
| .Times(51) |
| .WillRepeatedly(Return(EC_RES_BUSY)); |
| EXPECT_FALSE(mock_fp_frame_command->Run(-1)); |
| } |
| |
| } // namespace |
| } // namespace ec |