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

#include "content/browser/loader/merkle_integrity_source_stream.h"

#include <stdint.h>

#include "base/base64.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_view_util.h"
#include "net/base/io_buffer.h"
#include "net/base/test_completion_callback.h"
#include "net/filter/mock_source_stream.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

const int kBigBufferSize = 4096;
const int kSmallBufferSize = 1;

const char kMIEmptyBody[] =
    "mi-sha256-03=bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=";
const char kMISingleRecord[] =
    "mi-sha256-03=dcRDgR2GM35DluAV13PzgnG6+pvQwPywfFvAu1UeFrs=";
const char kMIMultipleRecords[] =
    "mi-sha256-03=IVa9shfs0nyKEhHqtB3WVNANJ2Njm5KjQLjRtnbkYJ4=";
const char kMIWholeNumberOfRecords[] =
    "mi-sha256-03=L2vdwBplKvIr0ZPkcuskWZfEVDgVdHa6aD363UpKuZs=";

enum class ReadResultType {
  // Each call to AddReadResult is a separate read from the lower layer
  // SourceStream.
  EVERYTHING_AT_ONCE,
  // Whenever AddReadResult is called, each byte is actually a separate read
  // result.
  ONE_BYTE_AT_A_TIME,
};

struct MerkleIntegrityTestParam {
  MerkleIntegrityTestParam(int buf_size,
                           net::MockSourceStream::Mode read_mode,
                           ReadResultType read_result_type)
      : buffer_size(buf_size),
        mode(read_mode),
        read_result_type(read_result_type) {}

  const int buffer_size;
  const net::MockSourceStream::Mode mode;
  const ReadResultType read_result_type;
};

}  // namespace

class MerkleIntegritySourceStreamTest
    : public ::testing::TestWithParam<MerkleIntegrityTestParam> {
 protected:
  MerkleIntegritySourceStreamTest()
      : output_buffer_size_(GetParam().buffer_size) {}

  void Init(const std::string& mi_header_value) {
    output_buffer_ =
        base::MakeRefCounted<net::IOBufferWithSize>(output_buffer_size_);
    std::unique_ptr<net::MockSourceStream> source(new net::MockSourceStream());
    if (GetParam().read_result_type == ReadResultType::ONE_BYTE_AT_A_TIME) {
      source->set_read_one_byte_at_a_time(true);
    }
    source_ = source.get();
    stream_ = std::make_unique<MerkleIntegritySourceStream>(mi_header_value,
                                                            std::move(source));
  }

  // If MockSourceStream::Mode is ASYNC, completes reads from |mock_stream|
  // until there's no pending read, and then returns |callback|'s result, once
  // it's invoked. If Mode is not ASYNC, does nothing and returns
  // |previous_result|.
  int CompleteReadsIfAsync(int previous_result,
                           net::TestCompletionCallback* callback,
                           net::MockSourceStream* mock_stream) {
    if (GetParam().mode == net::MockSourceStream::ASYNC) {
      EXPECT_EQ(net::ERR_IO_PENDING, previous_result);
      while (mock_stream->awaiting_completion())
        mock_stream->CompleteNextRead();
      return callback->WaitForResult();
    }
    return previous_result;
  }

  net::MockSourceStream* source() { return source_; }
  MerkleIntegritySourceStream* stream() { return stream_.get(); }

  // Reads from |stream_| until an error occurs or the EOF is reached.
  // When an error occurs, returns the net error code. When an EOF is reached,
  // returns the number of bytes read and appends data read to |output|.
  int ReadStream(std::string* output) {
    int bytes_read = 0;
    while (true) {
      net::TestCompletionCallback callback;
      int rv = stream_->Read(output_buffer_.get(), output_buffer_size_,
                             callback.callback());
      if (rv == net::ERR_IO_PENDING) {
        rv = CompleteReadsIfAsync(rv, &callback, source());
      }
      if (rv == net::OK) {
        break;
      }
      if (rv < net::OK) {
        return rv;
      }
      EXPECT_GT(rv, net::OK);
      bytes_read += rv;
      output->append(base::as_string_view(output_buffer_->first(rv)));
    }
    return bytes_read;
  }

  std::vector<uint8_t> Base64Decode(std::string_view hash) {
    std::optional<std::vector<uint8_t>> out = base::Base64Decode(hash);
    EXPECT_TRUE(out);
    EXPECT_EQ(32u, out->size());
    return std::move(out).value();
  }

 private:
  scoped_refptr<net::IOBuffer> output_buffer_;
  int output_buffer_size_;

  std::unique_ptr<MerkleIntegritySourceStream> stream_;
  raw_ptr<net::MockSourceStream> source_;
};

INSTANTIATE_TEST_SUITE_P(
    MerkleIntegritySourceStreamTests,
    MerkleIntegritySourceStreamTest,
    ::testing::Values(
        MerkleIntegrityTestParam(kBigBufferSize,
                                 net::MockSourceStream::SYNC,
                                 ReadResultType::EVERYTHING_AT_ONCE),
        MerkleIntegrityTestParam(kSmallBufferSize,
                                 net::MockSourceStream::SYNC,
                                 ReadResultType::EVERYTHING_AT_ONCE),
        MerkleIntegrityTestParam(kBigBufferSize,
                                 net::MockSourceStream::ASYNC,
                                 ReadResultType::EVERYTHING_AT_ONCE),
        MerkleIntegrityTestParam(kSmallBufferSize,
                                 net::MockSourceStream::ASYNC,
                                 ReadResultType::EVERYTHING_AT_ONCE),
        MerkleIntegrityTestParam(kBigBufferSize,
                                 net::MockSourceStream::SYNC,
                                 ReadResultType::ONE_BYTE_AT_A_TIME),
        MerkleIntegrityTestParam(kSmallBufferSize,
                                 net::MockSourceStream::SYNC,
                                 ReadResultType::ONE_BYTE_AT_A_TIME),
        MerkleIntegrityTestParam(kBigBufferSize,
                                 net::MockSourceStream::ASYNC,
                                 ReadResultType::ONE_BYTE_AT_A_TIME),
        MerkleIntegrityTestParam(kSmallBufferSize,
                                 net::MockSourceStream::ASYNC,
                                 ReadResultType::ONE_BYTE_AT_A_TIME)));

TEST_P(MerkleIntegritySourceStreamTest, EmptyStream) {
  Init(kMIEmptyBody);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::OK, result);
}

TEST_P(MerkleIntegritySourceStreamTest, EmptyStreamWrongHash) {
  Init(kMISingleRecord);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, TooShortMIHeader) {
  Init("z");
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, MalformedMIHeader) {
  Init("invalid-MI-header-value");
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, WrongMIAttributeName) {
  Init("mi-sha256-01=bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=");
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, HashTooShort) {
  Init("mi-sha256-03=bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoA==");
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, HashTooLong) {
  Init("mi-sha256-03=bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0A");
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, RecordSizeOnly) {
  Init(kMIEmptyBody);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 10};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, TruncatedRecordSize) {
  Init(kMIEmptyBody);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 1};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, RecordSizeOnlyWrongHash) {
  Init(kMISingleRecord);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 10};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, RecordSizeHuge) {
  Init(kMIEmptyBody);
  // 2^64 - 1 is far too large.
  const uint8_t kRecordSize[] = {0xff, 0xff, 0xff, 0xff,
                                 0xff, 0xff, 0xff, 0xff};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, RecordSizeTooBig) {
  Init(kMIEmptyBody);
  // 2^16 + 1 just exceeds the limit.
  const uint8_t kRecordSize[] = {0x00, 0x00, 0x00, 0x00,
                                 0x00, 0x00, 0x40, 0x01};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

TEST_P(MerkleIntegritySourceStreamTest, RecordSizeZero) {
  Init(kMIEmptyBody);
  // Zero is not a valid record size.
  const uint8_t kRecordSize[] = {0x00, 0x00, 0x00, 0x00,
                                 0x00, 0x00, 0x00, 0x00};
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  std::string actual_output;
  int result = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, result);
}

// https://tools.ietf.org/html/draft-thomson-http-mice-02#section-4.1
TEST_P(MerkleIntegritySourceStreamTest, SingleRecord) {
  Init(kMISingleRecord);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 41};
  const std::string kMessage("When I grow up, I want to be a watermelon");
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage), net::OK,
                          GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(static_cast<int>(kMessage.size()), rv);
  EXPECT_EQ(kMessage, actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, SingleRecordWrongHash) {
  Init(kMIEmptyBody);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 41};
  const std::string kMessage("When I grow up, I want to be a watermelon");
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage), net::OK,
                          GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ(0u, actual_output.size());
}

// The final record may not be larger than the record size.
TEST_P(MerkleIntegritySourceStreamTest, SingleRecordTooLarge) {
  Init(kMISingleRecord);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 40};
  const std::string kMessage("When I grow up, I want to be a watermelon");
  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage), net::OK,
                          GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ("", actual_output);
}

// https://tools.ietf.org/html/draft-thomson-http-mice-02#section-4.2
TEST_P(MerkleIntegritySourceStreamTest, MultipleRecords) {
  Init(kMIMultipleRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a watermelon");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("iPMpmgExHPrbEX3/RvwP4d16fWlK4l++p75PUu/KyN0=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(32u), net::OK,
                          GetParam().mode);

  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(static_cast<int>(kMessage.size()), rv);
  EXPECT_EQ(kMessage, actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, MultipleRecordsAllAtOnce) {
  Init(kMIMultipleRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a watermelon");

  std::string body(base::as_string_view(kRecordSize));
  body += kMessage.substr(0, 16);
  body += base::as_string_view(
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A="));
  body += kMessage.substr(16, 16);
  body += base::as_string_view(
      Base64Decode("iPMpmgExHPrbEX3/RvwP4d16fWlK4l++p75PUu/KyN0="));
  body += kMessage.substr(32);

  source()->AddReadResult(base::as_byte_span(body), net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(static_cast<int>(kMessage.size()), rv);
  EXPECT_EQ(kMessage, actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, MultipleRecordsWrongLastRecordHash) {
  Init(kMIMultipleRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a watermelon!");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("iPMpmgExHPrbEX3/RvwP4d16fWlK4l++p75PUu/KyN0=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(32u), net::OK,
                          GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);

  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ(kMessage.substr(0, 32), actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, MultipleRecordsWrongFirstRecordHash) {
  Init(kMIEmptyBody);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a watermelon!");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);

  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ("", actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, TrailingNetError) {
  Init(kMIMultipleRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a watermelon");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("iPMpmgExHPrbEX3/RvwP4d16fWlK4l++p75PUu/KyN0=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(32u), net::OK,
                          GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::ERR_FAILED,
                          GetParam().mode);

  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_FAILED, rv);
  // MerkleIntegritySourceStream cannot read the last record without a clean EOF
  // to denote its end.
  EXPECT_EQ(kMessage.substr(0, 32), actual_output);
}

// Test that truncations are noticed, by way of the final record not matching
// the hash.
TEST_P(MerkleIntegritySourceStreamTest, Truncated) {
  Init(kMIMultipleRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage("When I grow up, I want to be a w");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("OElbplJlPK+Rv6JNK6p5/515IaoPoZo+2elWL7OQ60A=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("iPMpmgExHPrbEX3/RvwP4d16fWlK4l++p75PUu/KyN0=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  // |hash2| is the hash of "atermelon", but this stream ends early. Decoding
  // thus should fail.
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);

  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ(kMessage, actual_output);
}

// Test that the final record is not allowed to be empty.
TEST_P(MerkleIntegritySourceStreamTest, EmptyFinalRecord) {
  Init("mi-sha256-03=JJnIuaOEc2247K9V88VQAQy1GJuQ6ylaVM7mG69QkE4=");
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage(
      "When I grow up, I want to be a watermelon!! \xf0\x9f\x8d\x89");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("hhJEKpkbuZoWUjzBPAZxMUN2DXdJ6epkS0McZh77IXo=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("RKTTVSMiH3bkxUQKreVATPL1KUd5eqRdmDgRQcZq/80=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(32u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash3 =
      Base64Decode("bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=");
  source()->AddReadResult(hash3, net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);

  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, rv);
  EXPECT_EQ(kMessage, actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, WholeNumberOfRecords) {
  Init(kMIWholeNumberOfRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage(
      "When I grow up, I want to be a watermelon!! \xf0\x9f\x8d\x89");

  source()->AddReadResult(kRecordSize, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).first(16u), net::OK,
                          GetParam().mode);
  std::vector<uint8_t> hash1 =
      Base64Decode("2s+MNG6NrTt556s//HYnQTjG3WOktEcXZ61O8mzG9f4=");
  source()->AddReadResult(hash1, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(16u, 16u),
                          net::OK, GetParam().mode);
  std::vector<uint8_t> hash2 =
      Base64Decode("qa/cQSMjFyZsm0cnYG4H6LqwOM/hzMSclK6I8iVoZYQ=");
  source()->AddReadResult(hash2, net::OK, GetParam().mode);
  source()->AddReadResult(base::as_byte_span(kMessage).subspan(32u, 16u),
                          net::OK, GetParam().mode);

  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(static_cast<int>(kMessage.size()), rv);
  EXPECT_EQ(kMessage, actual_output);
}

TEST_P(MerkleIntegritySourceStreamTest, WholeNumberOfRecordsAllAtOnce) {
  Init(kMIWholeNumberOfRecords);
  const uint8_t kRecordSize[] = {0, 0, 0, 0, 0, 0, 0, 16};
  const std::string kMessage(
      "When I grow up, I want to be a watermelon!! \xf0\x9f\x8d\x89");
  std::string body(base::as_string_view(kRecordSize));
  body += kMessage.substr(0, 16);
  body += base::as_string_view(
      Base64Decode("2s+MNG6NrTt556s//HYnQTjG3WOktEcXZ61O8mzG9f4="));
  body += kMessage.substr(16, 16);
  body += base::as_string_view(
      Base64Decode("qa/cQSMjFyZsm0cnYG4H6LqwOM/hzMSclK6I8iVoZYQ="));
  body += kMessage.substr(32, 16);

  source()->AddReadResult(base::as_byte_span(body), net::OK, GetParam().mode);
  source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
  std::string actual_output;
  int rv = ReadStream(&actual_output);
  EXPECT_EQ(static_cast<int>(kMessage.size()), rv);
  EXPECT_EQ(kMessage, actual_output);
}

}  // namespace content
