tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestEncryptedStream.cpp (33379B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include <algorithm>
      8 #include <cstdint>
      9 #include <new>
     10 #include <numeric>
     11 #include <ostream>
     12 #include <string>
     13 #include <utility>
     14 #include <vector>
     15 
     16 #include "ErrorList.h"
     17 #include "gtest/gtest.h"
     18 #include "mozilla/Assertions.h"
     19 #include "mozilla/Attributes.h"
     20 #include "mozilla/FixedBufferOutputStream.h"
     21 #include "mozilla/NotNull.h"
     22 #include "mozilla/RefPtr.h"
     23 #include "mozilla/Span.h"
     24 #include "mozilla/UniquePtr.h"
     25 #include "mozilla/dom/SafeRefPtr.h"
     26 #include "mozilla/dom/quota/DecryptingInputStream_impl.h"
     27 #include "mozilla/dom/quota/DummyCipherStrategy.h"
     28 #include "mozilla/dom/quota/EncryptedBlock.h"
     29 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
     30 #include "mozilla/dom/quota/NSSCipherStrategy.h"
     31 #include "nsCOMPtr.h"
     32 #include "nsError.h"
     33 #include "nsICloneableInputStream.h"
     34 #include "nsIInputStream.h"
     35 #include "nsIOutputStream.h"
     36 #include "nsISeekableStream.h"
     37 #include "nsISupports.h"
     38 #include "nsITellableStream.h"
     39 #include "nsStreamUtils.h"
     40 #include "nsString.h"
     41 #include "nsStringFwd.h"
     42 #include "nsTArray.h"
     43 #include "nscore.h"
     44 #include "nss.h"
     45 
     46 namespace mozilla::dom::quota {
     47 
     48 // Similar to ArrayBufferInputStream from netwerk/base/ArrayBufferInputStream.h,
     49 // but this is initialized from a Span on construction, rather than lazily from
     50 // a JS ArrayBuffer.
     51 class ArrayBufferInputStream : public nsIInputStream,
     52                               public nsISeekableStream,
     53                               public nsICloneableInputStream {
     54 public:
     55  explicit ArrayBufferInputStream(mozilla::Span<const uint8_t> aData);
     56  bool SetCloseOnEOF(bool value) { return mCloseOnEOF = value; }
     57 
     58  NS_DECL_THREADSAFE_ISUPPORTS
     59  NS_DECL_NSIINPUTSTREAM
     60  NS_DECL_NSITELLABLESTREAM
     61  NS_DECL_NSISEEKABLESTREAM
     62  NS_DECL_NSICLONEABLEINPUTSTREAM
     63 
     64 private:
     65  virtual ~ArrayBufferInputStream() = default;
     66 
     67  mozilla::UniquePtr<char[]> mArrayBuffer;
     68  uint32_t mBufferLength;
     69  uint32_t mPos;
     70  bool mClosed;
     71  bool mCloseOnEOF;
     72 };
     73 
     74 NS_IMPL_ADDREF(ArrayBufferInputStream);
     75 NS_IMPL_RELEASE(ArrayBufferInputStream);
     76 
     77 NS_INTERFACE_MAP_BEGIN(ArrayBufferInputStream)
     78  NS_INTERFACE_MAP_ENTRY(nsIInputStream)
     79  NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
     80  NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
     81  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
     82 NS_INTERFACE_MAP_END
     83 
     84 ArrayBufferInputStream::ArrayBufferInputStream(
     85    mozilla::Span<const uint8_t> aData)
     86    : mArrayBuffer(MakeUnique<char[]>(aData.Length())),
     87      mBufferLength(aData.Length()),
     88      mPos(0),
     89      mClosed(false),
     90      mCloseOnEOF(false) {
     91  std::copy(aData.cbegin(), aData.cend(), mArrayBuffer.get());
     92 }
     93 
     94 NS_IMETHODIMP
     95 ArrayBufferInputStream::Close() {
     96  mClosed = true;
     97  return NS_OK;
     98 }
     99 
    100 NS_IMETHODIMP
    101 ArrayBufferInputStream::Available(uint64_t* aCount) {
    102  if (mClosed) {
    103    return NS_BASE_STREAM_CLOSED;
    104  }
    105 
    106  if (mArrayBuffer) {
    107    *aCount = mBufferLength ? mBufferLength - mPos : 0;
    108  } else {
    109    *aCount = 0;
    110  }
    111 
    112  return NS_OK;
    113 }
    114 
    115 NS_IMETHODIMP
    116 ArrayBufferInputStream::StreamStatus() {
    117  return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
    118 }
    119 
    120 NS_IMETHODIMP
    121 ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
    122                             uint32_t* aReadCount) {
    123  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
    124 }
    125 
    126 NS_IMETHODIMP
    127 ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
    128                                     uint32_t aCount, uint32_t* result) {
    129  MOZ_RELEASE_ASSERT(result, "null ptr");
    130  MOZ_RELEASE_ASSERT(mBufferLength >= mPos, "bad stream state");
    131 
    132  if (mClosed) {
    133    *result = 0;
    134    return NS_OK;
    135  }
    136 
    137  MOZ_RELEASE_ASSERT(mArrayBuffer || (mPos == mBufferLength),
    138                     "stream inited incorrectly");
    139 
    140  *result = 0;
    141  while (mPos < mBufferLength) {
    142    uint32_t remaining = mBufferLength - mPos;
    143    MOZ_RELEASE_ASSERT(mArrayBuffer);
    144 
    145    uint32_t count = std::min(aCount, remaining);
    146    if (count == 0) {
    147      break;
    148    }
    149 
    150    uint32_t written;
    151    nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count,
    152                         &written);
    153    if (NS_FAILED(rv)) {
    154      // InputStreams do not propagate errors to caller.
    155      return NS_OK;
    156    }
    157 
    158    MOZ_RELEASE_ASSERT(
    159        written <= count,
    160        "writer should not write more than we asked it to write");
    161    mPos += written;
    162    *result += written;
    163    aCount -= written;
    164  }
    165 
    166  if (*result == 0 && mCloseOnEOF) {
    167    Close();
    168  }
    169 
    170  return NS_OK;
    171 }
    172 
    173 NS_IMETHODIMP
    174 ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
    175  // Actually, the stream never blocks, but we lie about it because of the
    176  // assumptions in DecryptingInputStream.
    177  *aNonBlocking = false;
    178  return NS_OK;
    179 }
    180 
    181 NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) {
    182  MOZ_RELEASE_ASSERT(aRetval);
    183 
    184  if (mClosed) {
    185    return NS_BASE_STREAM_CLOSED;
    186  }
    187  *aRetval = mPos;
    188 
    189  return NS_OK;
    190 }
    191 
    192 NS_IMETHODIMP ArrayBufferInputStream::Seek(const int32_t aWhence,
    193                                           const int64_t aOffset) {
    194  if (mClosed) {
    195    return NS_BASE_STREAM_CLOSED;
    196  }
    197 
    198  // XXX This is not safe. it's hard to use CheckedInt here, though. As long as
    199  // the class is only used for testing purposes, that's probably fine.
    200 
    201  int32_t newPos = mPos;
    202  switch (aWhence) {
    203    case NS_SEEK_SET:
    204      newPos = aOffset;
    205      break;
    206    case NS_SEEK_CUR:
    207      newPos += aOffset;
    208      break;
    209    case NS_SEEK_END:
    210      newPos = mBufferLength;
    211      newPos += aOffset;
    212      break;
    213    default:
    214      return NS_ERROR_ILLEGAL_VALUE;
    215  }
    216  if (newPos < 0 || static_cast<uint32_t>(newPos) > mBufferLength) {
    217    return NS_ERROR_ILLEGAL_VALUE;
    218  }
    219  mPos = newPos;
    220 
    221  return NS_OK;
    222 }
    223 
    224 NS_IMETHODIMP ArrayBufferInputStream::SetEOF() {
    225  // Truncating is not supported on a read-only stream.
    226  return NS_ERROR_NOT_IMPLEMENTED;
    227 }
    228 
    229 NS_IMETHODIMP ArrayBufferInputStream::GetCloneable(bool* aCloneable) {
    230  *aCloneable = true;
    231  return NS_OK;
    232 }
    233 
    234 NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) {
    235  *_retval = MakeAndAddRef<ArrayBufferInputStream>(
    236                 AsBytes(Span{mArrayBuffer.get(), mBufferLength}))
    237                 .take();
    238 
    239  return NS_OK;
    240 }
    241 }  // namespace mozilla::dom::quota
    242 
    243 using namespace mozilla;
    244 using namespace mozilla::dom::quota;
    245 
    246 class DOM_Quota_EncryptedStream : public ::testing::Test {
    247 public:
    248  static void SetUpTestCase() {
    249    // Do this only once, do not tear it down per test case.
    250    if (!sNssContext) {
    251      sNssContext.reset(
    252          NSS_InitContext("", "", "", "", nullptr,
    253                          NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
    254                              NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
    255                              NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT));
    256    }
    257  }
    258 
    259  static void TearDownTestCase() { sNssContext = nullptr; }
    260 
    261 private:
    262  struct NSSInitContextDeleter {
    263    void operator()(NSSInitContext* p) { NSS_ShutdownContext(p); }
    264  };
    265  static std::unique_ptr<NSSInitContext, NSSInitContextDeleter> sNssContext;
    266 };
    267 
    268 constinit std::unique_ptr<NSSInitContext,
    269                          DOM_Quota_EncryptedStream::NSSInitContextDeleter>
    270    DOM_Quota_EncryptedStream::sNssContext;
    271 
    272 enum struct FlushMode { AfterEachChunk, Never };
    273 enum struct ChunkSize { SingleByte, Unaligned, DataSize };
    274 
    275 using PackedTestParams =
    276    std::tuple<size_t, ChunkSize, ChunkSize, size_t, FlushMode, bool>;
    277 
    278 static size_t EffectiveChunkSize(const ChunkSize aChunkSize,
    279                                 const size_t aDataSize) {
    280  switch (aChunkSize) {
    281    case ChunkSize::SingleByte:
    282      return 1;
    283    case ChunkSize::Unaligned:
    284      return 17;
    285    case ChunkSize::DataSize:
    286      return aDataSize;
    287  }
    288  MOZ_CRASH("Unknown ChunkSize");
    289 }
    290 
    291 struct TestParams {
    292  MOZ_IMPLICIT constexpr TestParams(const PackedTestParams& aPackedParams)
    293      : mDataSize(std::get<0>(aPackedParams)),
    294        mWriteChunkSize(std::get<1>(aPackedParams)),
    295        mReadChunkSize(std::get<2>(aPackedParams)),
    296        mBlockSize(std::get<3>(aPackedParams)),
    297        mFlushMode(std::get<4>(aPackedParams)),
    298        mCloseOnEOF(std::get<5>(aPackedParams)) {}
    299 
    300  constexpr size_t DataSize() const { return mDataSize; }
    301 
    302  size_t EffectiveWriteChunkSize() const {
    303    return EffectiveChunkSize(mWriteChunkSize, mDataSize);
    304  }
    305 
    306  size_t EffectiveReadChunkSize() const {
    307    return EffectiveChunkSize(mReadChunkSize, mDataSize);
    308  }
    309 
    310  constexpr size_t BlockSize() const { return mBlockSize; }
    311 
    312  constexpr enum FlushMode FlushMode() const { return mFlushMode; }
    313 
    314  constexpr bool CloseOnEOF() const { return mCloseOnEOF; }
    315 
    316 private:
    317  size_t mDataSize;
    318 
    319  ChunkSize mWriteChunkSize;
    320  ChunkSize mReadChunkSize;
    321 
    322  size_t mBlockSize;
    323  enum FlushMode mFlushMode;
    324  bool mCloseOnEOF;
    325 };
    326 
    327 std::string TestParamToString(
    328    const testing::TestParamInfo<PackedTestParams>& aTestParams) {
    329  const TestParams& testParams = aTestParams.param;
    330 
    331  static constexpr char kSeparator[] = "_";
    332 
    333  std::stringstream ss;
    334  ss << "data" << testParams.DataSize() << kSeparator << "writechunk"
    335     << testParams.EffectiveWriteChunkSize() << kSeparator << "readchunk"
    336     << testParams.EffectiveReadChunkSize() << kSeparator << "block"
    337     << testParams.BlockSize() << kSeparator;
    338  switch (testParams.FlushMode()) {
    339    case FlushMode::Never:
    340      ss << "FlushNever";
    341      break;
    342    case FlushMode::AfterEachChunk:
    343      ss << "FlushAfterEachChunk";
    344      break;
    345  };
    346  ss << kSeparator
    347     << (testParams.CloseOnEOF() ? "closeOnEOF" : "keepOpenOnEOF");
    348  return ss.str();
    349 }
    350 
    351 class ParametrizedCryptTest
    352    : public DOM_Quota_EncryptedStream,
    353      public testing::WithParamInterface<PackedTestParams> {};
    354 
    355 static auto MakeTestData(const size_t aDataSize) {
    356  auto data = nsTArray<uint8_t>();
    357  data.SetLength(aDataSize);
    358  std::iota(data.begin(), data.end(), 0);
    359  return data;
    360 }
    361 
    362 template <typename CipherStrategy>
    363 static void WriteTestData(nsCOMPtr<nsIOutputStream>&& aBaseOutputStream,
    364                          const Span<const uint8_t> aData,
    365                          const size_t aWriteChunkSize, const size_t aBlockSize,
    366                          const typename CipherStrategy::KeyType& aKey,
    367                          const FlushMode aFlushMode) {
    368  auto outStream = MakeSafeRefPtr<EncryptingOutputStream<CipherStrategy>>(
    369      std::move(aBaseOutputStream), aBlockSize, aKey);
    370 
    371  for (auto remaining = aData; !remaining.IsEmpty();) {
    372    auto [currentChunk, newRemaining] =
    373        remaining.SplitAt(std::min(aWriteChunkSize, remaining.Length()));
    374    remaining = newRemaining;
    375 
    376    uint32_t written;
    377    EXPECT_EQ(NS_OK, outStream->Write(
    378                         reinterpret_cast<const char*>(currentChunk.Elements()),
    379                         currentChunk.Length(), &written));
    380    EXPECT_EQ(currentChunk.Length(), written);
    381 
    382    if (aFlushMode == FlushMode::AfterEachChunk) {
    383      outStream->Flush();
    384    }
    385  }
    386 
    387  // Close explicitly so we can check the result.
    388  EXPECT_EQ(NS_OK, outStream->Close());
    389 }
    390 
    391 template <typename CipherStrategy>
    392 static void NoExtraChecks(DecryptingInputStream<CipherStrategy>& aInputStream,
    393                          Span<const uint8_t> aExpectedData,
    394                          Span<const uint8_t> aRemainder) {}
    395 
    396 template <typename CipherStrategy,
    397          typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
    398 static void ReadTestData(
    399    DecryptingInputStream<CipherStrategy>& aDecryptingInputStream,
    400    const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
    401    const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
    402  auto readData = nsTArray<uint8_t>();
    403  readData.SetLength(aReadChunkSize);
    404 
    405  // sanity check: total file length and expectedData length must always match
    406  uint64_t availableBytes = 0;
    407  EXPECT_EQ(NS_OK, aDecryptingInputStream.Available(&availableBytes));
    408  EXPECT_EQ(aExpectedData.LengthBytes(), availableBytes);
    409 
    410  for (auto remainder = aExpectedData; !remainder.IsEmpty();) {
    411    auto [currentExpected, newExpectedRemainder] =
    412        remainder.SplitAt(std::min(aReadChunkSize, remainder.Length()));
    413    remainder = newExpectedRemainder;
    414 
    415    uint32_t read;
    416    EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
    417                         reinterpret_cast<char*>(readData.Elements()),
    418                         currentExpected.Length(), &read));
    419    EXPECT_EQ(currentExpected.Length(), read);
    420    EXPECT_EQ(currentExpected,
    421              Span{readData}.First(currentExpected.Length()).AsConst());
    422 
    423    aExtraChecks(aDecryptingInputStream, aExpectedData, remainder);
    424  }
    425 
    426  // Expect EOF.
    427  uint32_t read;
    428  EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
    429                       reinterpret_cast<char*>(readData.Elements()),
    430                       readData.Length(), &read));
    431  EXPECT_EQ(0u, read);
    432 }
    433 
    434 template <typename CipherStrategy,
    435          typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
    436 static auto ReadTestData(
    437    MovingNotNull<nsCOMPtr<nsIInputStream>>&& aBaseInputStream,
    438    const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
    439    const size_t aBlockSize, const typename CipherStrategy::KeyType& aKey,
    440    const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
    441  auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
    442      std::move(aBaseInputStream), aBlockSize, aKey);
    443 
    444  ReadTestData(*inStream, aExpectedData, aReadChunkSize, aExtraChecks);
    445 
    446  return inStream;
    447 }
    448 
    449 // XXX Change to return the buffer instead.
    450 template <typename CipherStrategy,
    451          typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
    452 static RefPtr<FixedBufferOutputStream> DoRoundtripTest(
    453    const size_t aDataSize, const size_t aWriteChunkSize,
    454    const size_t aReadChunkSize, const size_t aBlockSize,
    455    const typename CipherStrategy::KeyType& aKey, const FlushMode aFlushMode,
    456    bool aCloseOnEOF,
    457    const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
    458  // XXX Add deduction guide for RefPtr from already_AddRefed
    459  const auto baseOutputStream = WrapNotNull(
    460      RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
    461 
    462  const auto data = MakeTestData(aDataSize);
    463 
    464  WriteTestData<CipherStrategy>(
    465      nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
    466      aWriteChunkSize, aBlockSize, aKey, aFlushMode);
    467 
    468  const auto baseInputStream =
    469      MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
    470 
    471  baseInputStream->SetCloseOnEOF(aCloseOnEOF);
    472 
    473  ReadTestData<CipherStrategy>(
    474      WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
    475      aReadChunkSize, aBlockSize, aKey, aExtraChecks);
    476 
    477  return baseOutputStream;
    478 }
    479 
    480 TEST_P(ParametrizedCryptTest, NSSCipherStrategy) {
    481  using CipherStrategy = NSSCipherStrategy;
    482  const TestParams& testParams = GetParam();
    483 
    484  auto keyOrErr = CipherStrategy::GenerateKey();
    485  ASSERT_FALSE(keyOrErr.isErr());
    486 
    487  DoRoundtripTest<CipherStrategy>(
    488      testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
    489      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    490      keyOrErr.unwrap(), testParams.FlushMode(), testParams.CloseOnEOF());
    491 }
    492 
    493 TEST_P(ParametrizedCryptTest, NSSCipherStrategy_Available) {
    494  using CipherStrategy = NSSCipherStrategy;
    495  const TestParams& testParams = GetParam();
    496 
    497  DoRoundtripTest<CipherStrategy>(
    498      testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
    499      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    500      CipherStrategy::KeyType{}, testParams.FlushMode(),
    501      testParams.CloseOnEOF(),
    502      [](auto& inStream, Span<const uint8_t> expectedData,
    503         Span<const uint8_t> remainder) {
    504        // Check that Available tells the right remainder.
    505        uint64_t available;
    506        EXPECT_EQ(NS_OK, inStream.Available(&available));
    507        EXPECT_EQ(remainder.Length(), available);
    508      });
    509 }
    510 
    511 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) {
    512  using CipherStrategy = DummyCipherStrategy;
    513  const TestParams& testParams = GetParam();
    514 
    515  const auto encryptedDataStream = DoRoundtripTest<CipherStrategy>(
    516      testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
    517      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    518      CipherStrategy::KeyType{}, testParams.FlushMode(),
    519      testParams.CloseOnEOF());
    520 
    521  if (HasFailure()) {
    522    return;
    523  }
    524 
    525  const auto encryptedData = encryptedDataStream->WrittenData();
    526  const auto encryptedDataSpan = AsBytes(Span(encryptedData));
    527 
    528  const auto plainTestData = MakeTestData(testParams.DataSize());
    529  auto encryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength,
    530                                       DummyCipherStrategy::BasicBlockSize>{
    531      testParams.BlockSize(),
    532  };
    533  for (auto [encryptedRemainder, plainRemainder] =
    534           std::pair(encryptedDataSpan, Span(plainTestData));
    535       !encryptedRemainder.IsEmpty();) {
    536    const auto [currentBlock, newEncryptedRemainder] =
    537        encryptedRemainder.SplitAt(testParams.BlockSize());
    538    encryptedRemainder = newEncryptedRemainder;
    539 
    540    std::copy(currentBlock.cbegin(), currentBlock.cend(),
    541              encryptedBlock.MutableWholeBlock().begin());
    542 
    543    ASSERT_FALSE(plainRemainder.IsEmpty());
    544    const auto [currentPlain, newPlainRemainder] =
    545        plainRemainder.SplitAt(encryptedBlock.ActualPayloadLength());
    546    plainRemainder = newPlainRemainder;
    547 
    548    const auto pseudoIV = encryptedBlock.CipherPrefix();
    549    const auto payload = encryptedBlock.Payload();
    550 
    551    EXPECT_EQ(Span(DummyCipherStrategy::MakeBlockPrefix()), pseudoIV);
    552 
    553    auto untransformedPayload = nsTArray<uint8_t>();
    554    untransformedPayload.SetLength(testParams.BlockSize());
    555    DummyCipherStrategy::DummyTransform(payload, untransformedPayload);
    556 
    557    EXPECT_EQ(
    558        currentPlain,
    559        Span(untransformedPayload).AsConst().First(currentPlain.Length()));
    560  }
    561 }
    562 
    563 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Tell) {
    564  using CipherStrategy = DummyCipherStrategy;
    565  const TestParams& testParams = GetParam();
    566 
    567  DoRoundtripTest<CipherStrategy>(
    568      testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
    569      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    570      CipherStrategy::KeyType{}, testParams.FlushMode(),
    571      testParams.CloseOnEOF(),
    572      [](auto& inStream, Span<const uint8_t> expectedData,
    573         Span<const uint8_t> remainder) {
    574        // Check that Tell tells the right position.
    575        int64_t pos;
    576        EXPECT_EQ(NS_OK, inStream.Tell(&pos));
    577        EXPECT_EQ(expectedData.Length() - remainder.Length(),
    578                  static_cast<uint64_t>(pos));
    579      });
    580 }
    581 
    582 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Available) {
    583  using CipherStrategy = DummyCipherStrategy;
    584  const TestParams& testParams = GetParam();
    585 
    586  DoRoundtripTest<CipherStrategy>(
    587      testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
    588      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    589      CipherStrategy::KeyType{}, testParams.FlushMode(),
    590      testParams.CloseOnEOF(),
    591      [](auto& inStream, Span<const uint8_t> expectedData,
    592         Span<const uint8_t> remainder) {
    593        // Check that Available tells the right remainder.
    594        uint64_t available;
    595        EXPECT_EQ(NS_OK, inStream.Available(&available));
    596        // stream should still be valid.
    597        EXPECT_EQ(NS_OK, inStream.BaseStreamStatus());
    598        EXPECT_EQ(remainder.Length(), available);
    599      });
    600 }
    601 
    602 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Clone) {
    603  using CipherStrategy = DummyCipherStrategy;
    604  const TestParams& testParams = GetParam();
    605 
    606  // XXX Add deduction guide for RefPtr from already_AddRefed
    607  const auto baseOutputStream = WrapNotNull(
    608      RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
    609 
    610  const auto data = MakeTestData(testParams.DataSize());
    611 
    612  WriteTestData<CipherStrategy>(
    613      nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
    614      testParams.EffectiveWriteChunkSize(), testParams.BlockSize(),
    615      CipherStrategy::KeyType{}, testParams.FlushMode());
    616 
    617  const auto baseInputStream =
    618      MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
    619 
    620  const auto inStream = ReadTestData<CipherStrategy>(
    621      WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
    622      testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
    623      CipherStrategy::KeyType{});
    624 
    625  nsCOMPtr<nsIInputStream> clonedInputStream;
    626  EXPECT_EQ(NS_OK, inStream->Clone(getter_AddRefs(clonedInputStream)));
    627 
    628  ReadTestData(
    629      static_cast<DecryptingInputStream<CipherStrategy>&>(*clonedInputStream),
    630      Span{data}, testParams.EffectiveReadChunkSize());
    631 }
    632 
    633 // XXX This test is actually only parametrized on the block size.
    634 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_IncompleteBlock) {
    635  using CipherStrategy = DummyCipherStrategy;
    636  const TestParams& testParams = GetParam();
    637 
    638  // Provide half a block, content doesn't matter.
    639  nsTArray<uint8_t> data;
    640  data.SetLength(testParams.BlockSize() / 2);
    641 
    642  const auto baseInputStream = MakeRefPtr<ArrayBufferInputStream>(data);
    643 
    644  const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
    645      WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
    646      testParams.BlockSize(), CipherStrategy::KeyType{});
    647 
    648  nsTArray<uint8_t> readData;
    649  readData.SetLength(testParams.BlockSize());
    650  uint32_t read;
    651  EXPECT_EQ(NS_ERROR_CORRUPTED_CONTENT,
    652            inStream->Read(reinterpret_cast<char*>(readData.Elements()),
    653                           readData.Length(), &read));
    654 }
    655 
    656 TEST_P(ParametrizedCryptTest, zeroInitializedEncryptedBlock) {
    657  const TestParams& testParams = GetParam();
    658 
    659  using EncryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength,
    660                                        DummyCipherStrategy::BasicBlockSize>;
    661 
    662  EncryptedBlock encryptedBlock{testParams.BlockSize()};
    663  auto firstBlock =
    664      encryptedBlock.WholeBlock().First<DummyCipherStrategy::BasicBlockSize>();
    665  auto unusedBytesInFirstBlock = firstBlock.from(sizeof(uint16_t));
    666 
    667  EXPECT_TRUE(std::all_of(unusedBytesInFirstBlock.begin(),
    668                          unusedBytesInFirstBlock.end(),
    669                          [](const auto& e) { return 0ul == e; }));
    670 }
    671 
    672 enum struct SeekOffset {
    673  Zero,
    674  MinusHalfDataSize,
    675  PlusHalfDataSize,
    676  PlusDataSize,
    677  MinusDataSize,
    678  MinusDataSizeAndOne,
    679  PlusOne,
    680  MinusOne
    681 };
    682 using SeekOp = std::tuple<int32_t, SeekOffset, nsresult>;
    683 
    684 using PackedSeekTestParams =
    685    std::tuple<size_t, size_t, std::vector<SeekOp>, bool>;
    686 
    687 struct SeekTestParams {
    688  size_t mDataSize;
    689  size_t mBlockSize;
    690  std::vector<SeekOp> mSeekOps;
    691  bool mCloseOnEOF;
    692 
    693  MOZ_IMPLICIT SeekTestParams(const PackedSeekTestParams& aPackedParams)
    694      : mDataSize(std::get<0>(aPackedParams)),
    695        mBlockSize(std::get<1>(aPackedParams)),
    696        mSeekOps(std::get<2>(aPackedParams)),
    697        mCloseOnEOF(std::get<3>(aPackedParams)) {}
    698 };
    699 
    700 std::string SeekTestParamToString(
    701    const testing::TestParamInfo<PackedSeekTestParams>& aTestParams) {
    702  const SeekTestParams& testParams = aTestParams.param;
    703 
    704  static constexpr char kSeparator[] = "_";
    705 
    706  std::stringstream ss;
    707  ss << "data" << testParams.mDataSize << kSeparator << "writechunk"
    708     << testParams.mBlockSize << kSeparator;
    709  for (const auto& seekOp : testParams.mSeekOps) {
    710    switch (std::get<0>(seekOp)) {
    711      case nsISeekableStream::NS_SEEK_SET:
    712        ss << "Set";
    713        break;
    714      case nsISeekableStream::NS_SEEK_CUR:
    715        ss << "Cur";
    716        break;
    717      case nsISeekableStream::NS_SEEK_END:
    718        ss << "End";
    719        break;
    720      default:
    721        MOZ_CRASH("Unknown whence");
    722    };
    723    switch (std::get<1>(seekOp)) {
    724      case SeekOffset::Zero:
    725        ss << "Zero";
    726        break;
    727      case SeekOffset::MinusHalfDataSize:
    728        ss << "MinusHalfDataSize";
    729        break;
    730      case SeekOffset::PlusHalfDataSize:
    731        ss << "PlusHalfDataSize";
    732        break;
    733      case SeekOffset::MinusDataSize:
    734        ss << "MinusDataSize";
    735        break;
    736      case SeekOffset::MinusDataSizeAndOne:
    737        ss << "MinusDataSizeAndOne";
    738        break;
    739      case SeekOffset::PlusDataSize:
    740        ss << "PlusDataSize";
    741        break;
    742      case SeekOffset::PlusOne:
    743        ss << "PlusOne";
    744        break;
    745      case SeekOffset::MinusOne:
    746        ss << "MinusOne";
    747        break;
    748    };
    749  }
    750  ss << kSeparator << (testParams.mCloseOnEOF ? "closeOnEOF" : "keepOpenOnEOF");
    751 
    752  return ss.str();
    753 }
    754 
    755 class ParametrizedSeekCryptTest
    756    : public DOM_Quota_EncryptedStream,
    757      public testing::WithParamInterface<PackedSeekTestParams> {
    758 public:
    759  template <typename CipherStrategy>
    760  void DoSeekTest() {
    761    const SeekTestParams& testParams = GetParam();
    762 
    763    const auto baseOutputStream = WrapNotNull(
    764        RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
    765 
    766    const auto data = MakeTestData(testParams.mDataSize);
    767 
    768    WriteTestData<CipherStrategy>(
    769        nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
    770        testParams.mDataSize, testParams.mBlockSize,
    771        typename CipherStrategy::KeyType{}, FlushMode::Never);
    772 
    773    const auto baseInputStream =
    774        MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
    775 
    776    const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
    777        WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
    778        testParams.mBlockSize, typename CipherStrategy::KeyType{});
    779 
    780    baseInputStream->SetCloseOnEOF(testParams.mCloseOnEOF);
    781 
    782    uint32_t accumulatedOffset = 0;
    783    for (const auto& seekOp : testParams.mSeekOps) {
    784      const auto offset = [offsetKind = std::get<1>(seekOp),
    785                           dataSize = testParams.mDataSize]() -> int64_t {
    786        switch (offsetKind) {
    787          case SeekOffset::Zero:
    788            return 0;
    789          case SeekOffset::MinusHalfDataSize:
    790            return -static_cast<int64_t>(dataSize) / 2;
    791          case SeekOffset::PlusHalfDataSize:
    792            return static_cast<int64_t>(dataSize) / 2;
    793          case SeekOffset::MinusDataSize:
    794            return -static_cast<int64_t>(dataSize);
    795          case SeekOffset::MinusDataSizeAndOne:
    796            return -static_cast<int64_t>(dataSize + 1);
    797          case SeekOffset::PlusDataSize:
    798            return static_cast<int64_t>(dataSize);
    799          case SeekOffset::PlusOne:
    800            return 1;
    801          case SeekOffset::MinusOne:
    802            return -1;
    803        }
    804        MOZ_CRASH("Unknown SeekOffset");
    805      }();
    806      nsresult rv = inStream->Seek(std::get<0>(seekOp), offset);
    807      EXPECT_EQ(std::get<2>(seekOp), rv);
    808      if (NS_SUCCEEDED(rv)) {
    809        switch (std::get<0>(seekOp)) {
    810          case nsISeekableStream::NS_SEEK_SET:
    811            accumulatedOffset = offset;
    812            break;
    813          case nsISeekableStream::NS_SEEK_CUR:
    814            accumulatedOffset += offset;
    815            break;
    816          case nsISeekableStream::NS_SEEK_END:
    817            accumulatedOffset = testParams.mDataSize + offset;
    818            break;
    819          default:
    820            MOZ_CRASH("Unknown whence");
    821        }
    822      }
    823    }
    824 
    825    {
    826      int64_t actualOffset;
    827      EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset));
    828 
    829      EXPECT_EQ(actualOffset, accumulatedOffset);
    830    }
    831 
    832    auto readData = nsTArray<uint8_t>();
    833    readData.SetLength(data.Length());
    834    uint32_t read;
    835    EXPECT_EQ(NS_OK,
    836              inStream->Read(reinterpret_cast<char*>(readData.Elements()),
    837                             readData.Length(), &read));
    838    // XXX Or should 'read' indicate the actual number of bytes read,
    839    // including the encryption overhead?
    840    EXPECT_EQ(testParams.mDataSize - accumulatedOffset, read);
    841    EXPECT_EQ(Span{data}.SplitAt(accumulatedOffset).second,
    842              Span{readData}.First(read).AsConst());
    843 
    844    // For some closeOnEOF combinations, above Read method can lead to stream
    845    // closure. Skip calling Tell method below if the underlying stream was
    846    // already closed.
    847    if (!testParams.mCloseOnEOF ||
    848        baseInputStream->StreamStatus() != NS_BASE_STREAM_CLOSED) {
    849      int64_t actualOffset;
    850      EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset));
    851 
    852      EXPECT_EQ(static_cast<uint64_t>(actualOffset), data.Length());
    853    }
    854  }
    855 };
    856 
    857 TEST_P(ParametrizedSeekCryptTest, DummyCipherStrategy_Seek) {
    858  DoSeekTest<DummyCipherStrategy>();
    859 }
    860 
    861 TEST_P(ParametrizedSeekCryptTest, NSSCipherStrategy_Seek) {
    862  DoSeekTest<NSSCipherStrategy>();
    863 }
    864 
    865 // The data size 244 has been calculated as 256 (block size) minus 8
    866 // (DummyCipherStrategy::BlockPrefixLength) minus 4
    867 // (DummyCipherStrategy::BasicBlockSize).
    868 // The data size 1012 has been calculated as 1024 (block size) minus 8
    869 // (DummyCipherStrategy::BlockPrefixLength) minus 4
    870 // (DummyCipherStrategy::BasicBlockSize).
    871 static_assert(DummyCipherStrategy::BlockPrefixLength == 8);
    872 static_assert(DummyCipherStrategy::BasicBlockSize == 4);
    873 
    874 // The data size 208 has been calculated as 256 (block size) minus 32
    875 // (NSSCipherStrategy::BlockPrefixLength) minus 16
    876 // (NSSCipherStrategy::BasicBlockSize).
    877 // The data size 976 has been calculated as 1024 (block size) minus 32
    878 // (NSSCipherStrategy::BlockPrefixLength) minus 16
    879 // (NSSCipherStrategy::BasicBlockSize).
    880 static_assert(NSSCipherStrategy::BlockPrefixLength == 32);
    881 static_assert(NSSCipherStrategy::BasicBlockSize == 16);
    882 
    883 INSTANTIATE_TEST_SUITE_P(
    884    DOM_Quota_EncryptedStream_Parametrized, ParametrizedCryptTest,
    885    testing::Combine(
    886        /* dataSize */ testing::Values(0u, 16u, 208u, 244u, 256u, 512u, 513u,
    887                                       976u, 1012u),
    888        /* writeChunkSize */
    889        testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
    890                        ChunkSize::DataSize),
    891        /* readChunkSize */
    892        testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
    893                        ChunkSize::DataSize),
    894        /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
    895        /* flushMode */
    896        testing::Values(FlushMode::Never, FlushMode::AfterEachChunk),
    897        /* closeOnEOF */
    898        testing::Values(true, false)),
    899    TestParamToString);
    900 
    901 INSTANTIATE_TEST_SUITE_P(
    902    DOM_IndexedDB_EncryptedStream_ParametrizedSeek, ParametrizedSeekCryptTest,
    903    testing::Combine(
    904        /* dataSize */ testing::Values(0u, 16u, 208u, 244u, 256u, 512u, 513u,
    905                                       976u, 1012u),
    906        /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
    907        /* seekOperations */
    908        testing::Values(/* NS_SEEK_SET only, single ops */
    909                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
    910                                             SeekOffset::PlusDataSize, NS_OK}},
    911                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
    912                                             SeekOffset::PlusHalfDataSize,
    913                                             NS_OK}},
    914                        /* NS_SEEK_SET only, multiple ops */
    915                        std::vector<SeekOp>{
    916                            {nsISeekableStream::NS_SEEK_SET,
    917                             SeekOffset::PlusHalfDataSize, NS_OK},
    918                            {nsISeekableStream::NS_SEEK_SET, SeekOffset::Zero,
    919                             NS_OK}},
    920                        /* NS_SEEK_CUR only, single ops */
    921                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
    922                                             SeekOffset::Zero, NS_OK}},
    923                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
    924                                             SeekOffset::PlusDataSize, NS_OK}},
    925                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
    926                                             SeekOffset::PlusHalfDataSize,
    927                                             NS_OK}},
    928                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
    929                                             SeekOffset::MinusOne,
    930                                             NS_ERROR_ILLEGAL_VALUE}},
    931                        /* NS_SEEK_END only, single ops */
    932                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
    933                                             SeekOffset::Zero, NS_OK}},
    934                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
    935                                             SeekOffset::MinusDataSize, NS_OK}},
    936                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
    937                                             SeekOffset::MinusDataSizeAndOne,
    938                                             NS_ERROR_ILLEGAL_VALUE}},
    939                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
    940                                             SeekOffset::MinusHalfDataSize,
    941                                             NS_OK}},
    942                        std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
    943                                             SeekOffset::PlusOne,
    944                                             NS_ERROR_ILLEGAL_VALUE}}),
    945        /* closeOnEOF */
    946        testing::Values(true, false)),
    947    SeekTestParamToString);