chromium/chrome/browser/nearby_sharing/incoming_frames_reader_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/nearby_sharing/incoming_frames_reader.h"

#include <vector>

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/services/sharing/public/proto/wire_format.pb.h"
#include "chromeos/ash/components/nearby/common/connections_manager/fake_nearby_connection.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_nearby_process_manager.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_nearby_sharing_decoder.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr base::TimeDelta kTimeout = base::Milliseconds(1000);

std::vector<uint8_t> GetIntroductionFrame() {
  sharing::nearby::Frame frame = sharing::nearby::Frame();
  sharing::nearby::V1Frame* v1frame = frame.mutable_v1();
  v1frame->set_type(sharing::nearby::V1Frame_FrameType_INTRODUCTION);
  v1frame->mutable_introduction();

  std::vector<uint8_t> data;
  data.resize(frame.ByteSize());
  EXPECT_TRUE(frame.SerializeToArray(&data[0], frame.ByteSize()));

  return data;
}

std::vector<uint8_t> GetCancelFrame() {
  sharing::nearby::Frame frame = sharing::nearby::Frame();
  sharing::nearby::V1Frame* v1frame = frame.mutable_v1();
  v1frame->set_type(sharing::nearby::V1Frame_FrameType_CANCEL);

  std::vector<uint8_t> data;
  data.resize(frame.ByteSize());
  EXPECT_TRUE(frame.SerializeToArray(&data[0], frame.ByteSize()));

  return data;
}

void ExpectIntroductionFrame(
    const std::optional<sharing::mojom::V1FramePtr>& frame) {
  ASSERT_TRUE(frame);
  EXPECT_TRUE((*frame)->is_introduction());
}

void ExpectCancelFrame(const std::optional<sharing::mojom::V1FramePtr>& frame) {
  ASSERT_TRUE(frame);
  EXPECT_TRUE((*frame)->is_cancel_frame());
}

}  // namespace

class IncomingFramesReaderTest : public testing::Test {
 public:
  IncomingFramesReaderTest()
      : frames_reader_(&mock_process_manager_, &mock_nearby_connection_) {}

  ~IncomingFramesReaderTest() override = default;

  void SetUp() override {
    EXPECT_CALL(mock_process_manager_, GetNearbyProcessReference)
        .WillRepeatedly([&](ash::nearby::NearbyProcessManager::
                                NearbyProcessStoppedCallback) {
          auto mock_reference_ptr =
              std::make_unique<ash::nearby::MockNearbyProcessManager::
                                   MockNearbyProcessReference>();

          EXPECT_CALL(*(mock_reference_ptr.get()), GetNearbySharingDecoder)
              .WillRepeatedly(
                  testing::ReturnRef(mock_decoder_.shared_remote()));

          return mock_reference_ptr;
        });
  }

  FakeNearbyConnection& connection() { return mock_nearby_connection_; }

  testing::StrictMock<ash::nearby::MockNearbySharingDecoder>& decoder() {
    return mock_decoder_;
  }

  IncomingFramesReader& frames_reader() { return frames_reader_; }

 private:
  content::BrowserTaskEnvironment task_environment_;
  FakeNearbyConnection mock_nearby_connection_;
  testing::StrictMock<ash::nearby::MockNearbyProcessManager>
      mock_process_manager_;
  testing::StrictMock<ash::nearby::MockNearbySharingDecoder> mock_decoder_;
  IncomingFramesReader frames_reader_;
};

TEST_F(IncomingFramesReaderTest, ReadTimedOut) {
  EXPECT_CALL(decoder(), DecodeFrame(testing::_, testing::_)).Times(0);

  base::RunLoop run_loop;
  frames_reader().ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindLambdaForTesting(
          [&](std::optional<sharing::mojom::V1FramePtr> frame) {
            EXPECT_FALSE(frame);
            run_loop.Quit();
          }),
      kTimeout);
  run_loop.Run();

  // Ensure that the OnDataReadFromConnection callback is not run since the read
  // timed out.
  EXPECT_FALSE(connection().has_read_callback_been_run());
  // Ensure that the IncomingFramesReader does not close the connection.
  EXPECT_FALSE(connection().IsClosed());
}

TEST_F(IncomingFramesReaderTest, ReadAnyFrameSuccessful) {
  std::vector<uint8_t> introduction_frame = GetIntroductionFrame();
  connection().AppendReadableData(introduction_frame);

  EXPECT_CALL(decoder(),
              DecodeFrame(testing::Eq(introduction_frame), testing::_))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewIntroduction(
                    sharing::mojom::IntroductionFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }));

  base::RunLoop run_loop;
  frames_reader().ReadFrame(base::BindLambdaForTesting(
      [&](std::optional<sharing::mojom::V1FramePtr> frame) {
        ExpectIntroductionFrame(frame);
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(IncomingFramesReaderTest, ReadSuccessful) {
  std::vector<uint8_t> introduction_frame = GetIntroductionFrame();
  connection().AppendReadableData(introduction_frame);

  EXPECT_CALL(decoder(),
              DecodeFrame(testing::Eq(introduction_frame), testing::_))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewIntroduction(
                    sharing::mojom::IntroductionFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }));

  base::RunLoop run_loop;
  frames_reader().ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindLambdaForTesting(
          [&](std::optional<sharing::mojom::V1FramePtr> frame) {
            ExpectIntroductionFrame(frame);
            run_loop.Quit();
          }),
      kTimeout);
  run_loop.Run();
}

TEST_F(IncomingFramesReaderTest, ReadSuccessful_JumbledFramesOrdering) {
  std::vector<uint8_t> cancel_frame = GetCancelFrame();
  connection().AppendReadableData(cancel_frame);

  std::vector<uint8_t> introduction_frame = GetIntroductionFrame();
  connection().AppendReadableData(introduction_frame);

  EXPECT_CALL(decoder(), DecodeFrame(testing::_, testing::_))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            EXPECT_EQ(cancel_frame, data);
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewCancelFrame(
                    sharing::mojom::CancelFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            EXPECT_EQ(introduction_frame, data);
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewIntroduction(
                    sharing::mojom::IntroductionFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }));

  base::RunLoop run_loop_introduction;
  frames_reader().ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindLambdaForTesting(
          [&](std::optional<sharing::mojom::V1FramePtr> frame) {
            ExpectIntroductionFrame(frame);
            run_loop_introduction.Quit();
          }),
      kTimeout);
  run_loop_introduction.Run();
}

TEST_F(IncomingFramesReaderTest, JumbledFramesOrdering_ReadFromCache) {
  std::vector<uint8_t> cancel_frame = GetCancelFrame();
  connection().AppendReadableData(cancel_frame);

  std::vector<uint8_t> introduction_frame = GetIntroductionFrame();
  connection().AppendReadableData(introduction_frame);

  EXPECT_CALL(decoder(), DecodeFrame(testing::_, testing::_))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            EXPECT_EQ(cancel_frame, data);
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewCancelFrame(
                    sharing::mojom::CancelFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }))
      .WillOnce(testing::Invoke(
          [&](const std::vector<uint8_t>& data,
              ash::nearby::MockNearbySharingDecoder::DecodeFrameCallback
                  callback) {
            EXPECT_EQ(introduction_frame, data);
            sharing::mojom::V1FramePtr mojo_v1frame =
                sharing::mojom::V1Frame::NewIntroduction(
                    sharing::mojom::IntroductionFrame::New());

            sharing::mojom::FramePtr mojo_frame =
                sharing::mojom::Frame::NewV1(std::move(mojo_v1frame));
            std::move(callback).Run(std::move(mojo_frame));
          }));

  base::RunLoop run_loop_introduction;
  frames_reader().ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindLambdaForTesting(
          [&](std::optional<sharing::mojom::V1FramePtr> frame) {
            ExpectIntroductionFrame(frame);
            run_loop_introduction.Quit();
          }),
      kTimeout);
  run_loop_introduction.Run();

  // Reading any frame should return CancelFrame.
  base::RunLoop run_loop_cancel;
  frames_reader().ReadFrame(base::BindLambdaForTesting(
      [&](std::optional<sharing::mojom::V1FramePtr> frame) {
        ExpectCancelFrame(frame);
        run_loop_cancel.Quit();
      }));
  run_loop_cancel.Run();
}

TEST_F(IncomingFramesReaderTest, ReadAfterConnectionClosed) {
  EXPECT_CALL(decoder(), DecodeFrame(testing::_, testing::_)).Times(0);

  base::RunLoop run_loop_before_close;
  frames_reader().ReadFrame(
      sharing::mojom::V1Frame::Tag::kIntroduction,
      base::BindLambdaForTesting(
          [&](std::optional<sharing::mojom::V1FramePtr> frame) {
            EXPECT_FALSE(frame);
            run_loop_before_close.Quit();
          }),
      kTimeout);

  connection().Close();
  run_loop_before_close.Run();
}