diff options
Diffstat (limited to '')
-rw-r--r-- | dom/indexedDB/ActorsParentCommon.cpp | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/dom/indexedDB/ActorsParentCommon.cpp b/dom/indexedDB/ActorsParentCommon.cpp new file mode 100644 index 0000000000..52e35c66f5 --- /dev/null +++ b/dom/indexedDB/ActorsParentCommon.cpp @@ -0,0 +1,739 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ActorsParentCommon.h" + +// local includes +#include "DatabaseFileInfo.h" +#include "DatabaseFileManager.h" +#include "IndexedDatabase.h" // for StructuredCloneFile... +#include "IndexedDatabaseInlines.h" +#include "IndexedDatabaseManager.h" +#include "IndexedDBCipherKeyManager.h" +#include "IndexedDBCommon.h" +#include "ReportInternalError.h" + +// global includes +#include <stdlib.h> +#include <string.h> +#include <algorithm> +#include <numeric> +#include <type_traits> +#include "MainThreadUtils.h" +#include "SafeRefPtr.h" +#include "js/RootingAPI.h" +#include "js/StructuredClone.h" +#include "mozIStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozIStorageValueArray.h" +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/JSObjectHolder.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryScalarEnums.h" +#include "mozilla/dom/quota/DecryptingInputStream_impl.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/ScopedLogExtraInfo.h" +#include "mozilla/fallible.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/mozalloc.h" +#include "nsCOMPtr.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIInputStream.h" +#include "nsIPrincipal.h" +#include "nsIXPConnect.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsStringFlags.h" +#include "nsXULAppAPI.h" +#include "snappy/snappy.h" + +class nsIFile; + +namespace mozilla::dom::indexedDB { + +static_assert(SNAPPY_VERSION == 0x010109); + +using mozilla::ipc::IsOnBackgroundThread; + +const nsLiteralString kJournalDirectoryName = u"journals"_ns; + +namespace { + +constexpr StructuredCloneFileBase::FileType ToStructuredCloneFileType( + const char16_t aTag) { + switch (aTag) { + case char16_t('-'): + return StructuredCloneFileBase::eMutableFile; + + case char16_t('.'): + return StructuredCloneFileBase::eStructuredClone; + + case char16_t('/'): + return StructuredCloneFileBase::eWasmBytecode; + + case char16_t('\\'): + return StructuredCloneFileBase::eWasmCompiled; + + default: + return StructuredCloneFileBase::eBlob; + } +} + +int32_t ToInteger(const nsAString& aStr, nsresult* const aRv) { + return aStr.ToInteger(aRv); +} + +Result<StructuredCloneFileParent, nsresult> DeserializeStructuredCloneFile( + const DatabaseFileManager& aFileManager, + const nsDependentSubstring& aText) { + MOZ_ASSERT(!aText.IsEmpty()); + + const StructuredCloneFileBase::FileType type = + ToStructuredCloneFileType(aText.First()); + + QM_TRY_INSPECT(const auto& id, + MOZ_TO_RESULT_GET_TYPED( + int32_t, ToInteger, + type == StructuredCloneFileBase::eBlob + ? aText + : static_cast<const nsAString&>(Substring(aText, 1)))); + + SafeRefPtr<DatabaseFileInfo> fileInfo = aFileManager.GetFileInfo(id); + MOZ_ASSERT(fileInfo); + // XXX In bug 1432133, for some reasons DatabaseFileInfo object cannot be + // got. This is just a short-term fix, and we are working on finding the real + // cause in bug 1519859. + QM_TRY(OkIf((bool)fileInfo), Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)); + + return StructuredCloneFileParent{type, std::move(fileInfo)}; +} + +// This class helps to create only 1 sandbox. +class SandboxHolder final { + public: + NS_INLINE_DECL_REFCOUNTING(SandboxHolder) + + private: + friend JSObject* mozilla::dom::indexedDB::GetSandbox(JSContext* aCx); + + ~SandboxHolder() = default; + + static SandboxHolder* GetOrCreate() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + static StaticRefPtr<SandboxHolder> sHolder; + if (!sHolder) { + sHolder = new SandboxHolder(); + ClearOnShutdown(&sHolder); + } + return sHolder; + } + + JSObject* GetSandboxInternal(JSContext* aCx) { + if (!mSandbox) { + nsIXPConnect* const xpc = nsContentUtils::XPConnect(); + MOZ_ASSERT(xpc, "This should never be null!"); + + // Let's use a null principal. + const nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + + JS::Rooted<JSObject*> sandbox(aCx); + QM_TRY( + MOZ_TO_RESULT(xpc->CreateSandbox(aCx, principal, sandbox.address())), + nullptr); + + mSandbox = new JSObjectHolder(aCx, sandbox); + } + + return mSandbox->GetJSObject(); + } + + RefPtr<JSObjectHolder> mSandbox; +}; + +uint32_t CompressedByteCountForNumber(uint64_t aNumber) { + // All bytes have 7 bits available. + uint32_t count = 1; + while ((aNumber >>= 7)) { + count++; + } + + return count; +} + +uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) { + MOZ_ASSERT(aIndexId); + MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId), + "Overflow!"); + + return CompressedByteCountForNumber(uint64_t(aIndexId * 2)); +} + +void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) { + MOZ_ASSERT(aIterator); + MOZ_ASSERT(*aIterator); + + uint8_t*& buffer = *aIterator; + +#ifdef DEBUG + const uint8_t* const bufferStart = buffer; + const uint64_t originalNumber = aNumber; +#endif + + while (true) { + uint64_t shiftedNumber = aNumber >> 7; + if (shiftedNumber) { + *buffer++ = uint8_t(0x80 | (aNumber & 0x7f)); + aNumber = shiftedNumber; + } else { + *buffer++ = uint8_t(aNumber); + break; + } + } + + MOZ_ASSERT(buffer > bufferStart); + MOZ_ASSERT(uint32_t(buffer - bufferStart) == + CompressedByteCountForNumber(originalNumber)); +} + +void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique, + uint8_t** aIterator) { + MOZ_ASSERT(aIndexId); + MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId), + "Overflow!"); + MOZ_ASSERT(aIterator); + MOZ_ASSERT(*aIterator); + + const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0)); + WriteCompressedNumber(indexId, aIterator); +} + +// aOutIndexValues is an output parameter, since its storage is reused. +nsresult ReadCompressedIndexDataValuesFromBlob( + const Span<const uint8_t> aBlobData, + nsTArray<IndexDataValue>* aOutIndexValues) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(!aBlobData.IsEmpty()); + MOZ_ASSERT(aOutIndexValues); + MOZ_ASSERT(aOutIndexValues->IsEmpty()); + + AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM); + + // XXX Is this check still necessary with a Span? Or should it rather be moved + // to the caller? + QM_TRY(OkIf(uintptr_t(aBlobData.Elements()) <= + UINTPTR_MAX - aBlobData.LengthBytes()), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + for (auto remainder = aBlobData; !remainder.IsEmpty();) { + QM_TRY_INSPECT((const auto& [indexId, unique, remainderAfterIndexId]), + ReadCompressedIndexId(remainder)); + + QM_TRY(OkIf(!remainderAfterIndexId.IsEmpty()), NS_ERROR_FILE_CORRUPTED, + IDB_REPORT_INTERNAL_ERR_LAMBDA); + + // Read key buffer length. + QM_TRY_INSPECT( + (const auto& [keyBufferLength, remainderAfterKeyBufferLength]), + ReadCompressedNumber(remainderAfterIndexId)); + + QM_TRY(OkIf(!remainderAfterKeyBufferLength.IsEmpty()), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + QM_TRY(OkIf(keyBufferLength <= uint64_t(UINT32_MAX)), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + QM_TRY(OkIf(keyBufferLength <= remainderAfterKeyBufferLength.Length()), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + const auto [keyBuffer, remainderAfterKeyBuffer] = + remainderAfterKeyBufferLength.SplitAt(keyBufferLength); + auto idv = + IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}}; + + // Read sort key buffer length. + QM_TRY_INSPECT( + (const auto& [sortKeyBufferLength, remainderAfterSortKeyBufferLength]), + ReadCompressedNumber(remainderAfterKeyBuffer)); + + remainder = remainderAfterSortKeyBufferLength; + if (sortKeyBufferLength > 0) { + QM_TRY(OkIf(!remainder.IsEmpty()), NS_ERROR_FILE_CORRUPTED, + IDB_REPORT_INTERNAL_ERR_LAMBDA); + + QM_TRY(OkIf(sortKeyBufferLength <= uint64_t(UINT32_MAX)), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + QM_TRY(OkIf(sortKeyBufferLength <= remainder.Length()), + NS_ERROR_FILE_CORRUPTED, IDB_REPORT_INTERNAL_ERR_LAMBDA); + + const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] = + remainder.SplitAt(sortKeyBufferLength); + idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}}; + remainder = remainderAfterSortKeyBuffer; + } + + QM_TRY(OkIf(aOutIndexValues->AppendElement(std::move(idv), fallible)), + NS_ERROR_OUT_OF_MEMORY, IDB_REPORT_INTERNAL_ERR_LAMBDA); + } + aOutIndexValues->Sort(); + + return NS_OK; +} + +// aOutIndexValues is an output parameter, since its storage is reused. +template <typename T> +nsresult ReadCompressedIndexDataValuesFromSource( + T& aSource, uint32_t aColumnIndex, + nsTArray<IndexDataValue>* aOutIndexValues) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(aOutIndexValues); + MOZ_ASSERT(aOutIndexValues->IsEmpty()); + + QM_TRY_INSPECT( + const int32_t& columnType, + MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aColumnIndex)); + + switch (columnType) { + case mozIStorageStatement::VALUE_TYPE_NULL: + return NS_OK; + + case mozIStorageStatement::VALUE_TYPE_BLOB: { + // XXX ToResultInvoke does not support multiple output parameters yet, so + // we also can't use QM_TRY_UNWRAP/QM_TRY_INSPECT here. + const uint8_t* blobData; + uint32_t blobDataLength; + QM_TRY(MOZ_TO_RESULT( + aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData))); + + QM_TRY(OkIf(blobDataLength), NS_ERROR_FILE_CORRUPTED, + IDB_REPORT_INTERNAL_ERR_LAMBDA); + + QM_TRY(MOZ_TO_RESULT(ReadCompressedIndexDataValuesFromBlob( + Span(blobData, blobDataLength), aOutIndexValues))); + + return NS_OK; + } + + default: + return NS_ERROR_FILE_CORRUPTED; + } +} + +Result<StructuredCloneReadInfoParent, nsresult> +GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData, + uint32_t aBlobDataLength, + const DatabaseFileManager& aFileManager, + const nsAString& aFileIds) { + MOZ_ASSERT(!IsOnBackgroundThread()); + + AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromBlob", DOM); + + const char* const compressed = reinterpret_cast<const char*>(aBlobData); + const size_t compressedLength = size_t(aBlobDataLength); + + size_t uncompressedLength; + QM_TRY(OkIf(snappy::GetUncompressedLength(compressed, compressedLength, + &uncompressedLength)), + Err(NS_ERROR_FILE_CORRUPTED)); + + // `data` (JSStructuredCloneData) currently uses 4k buffer internally. + // For performance reasons, it's better to align `uncompressed` with that. + AutoTArray<uint8_t, 4096> uncompressed; + QM_TRY(OkIf(uncompressed.SetLength(uncompressedLength, fallible)), + Err(NS_ERROR_OUT_OF_MEMORY)); + + char* const uncompressedBuffer = + reinterpret_cast<char*>(uncompressed.Elements()); + + QM_TRY(OkIf(snappy::RawUncompress(compressed, compressedLength, + uncompressedBuffer)), + Err(NS_ERROR_FILE_CORRUPTED)); + + JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess); + QM_TRY(OkIf(data.AppendBytes(uncompressedBuffer, uncompressed.Length())), + Err(NS_ERROR_OUT_OF_MEMORY)); + + nsTArray<StructuredCloneFileParent> files; + if (!aFileIds.IsVoid()) { + QM_TRY_UNWRAP(files, + DeserializeStructuredCloneFiles(aFileManager, aFileIds)); + } + + return StructuredCloneReadInfoParent{std::move(data), std::move(files), + false}; +} + +Result<StructuredCloneReadInfoParent, nsresult> +GetStructuredCloneReadInfoFromExternalBlob( + uint64_t aIntData, const DatabaseFileManager& aFileManager, + const nsAString& aFileIds) { + MOZ_ASSERT(!IsOnBackgroundThread()); + + AUTO_PROFILER_LABEL("GetStructuredCloneReadInfoFromExternalBlob", DOM); + + nsTArray<StructuredCloneFileParent> files; + if (!aFileIds.IsVoid()) { + QM_TRY_UNWRAP(files, + DeserializeStructuredCloneFiles(aFileManager, aFileIds)); + } + + // Higher and lower 32 bits described + // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork. + const uint32_t index = uint32_t(aIntData & UINT32_MAX); + + QM_TRY(OkIf(index < files.Length()), Err(NS_ERROR_UNEXPECTED), + [](const auto&) { MOZ_ASSERT(false, "Bad index value!"); }); + + if (StaticPrefs::dom_indexedDB_preprocessing()) { + return StructuredCloneReadInfoParent{ + JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess}, + std::move(files), true}; + } + + // XXX Why can there be multiple files, but we use only a single one here? + const StructuredCloneFileParent& file = files[index]; + MOZ_ASSERT(file.Type() == StructuredCloneFileBase::eStructuredClone); + + Maybe<CipherKey> maybeKey; + + if (aFileManager.IsInPrivateBrowsingMode()) { + nsCString fileKeyId; + fileKeyId.AppendInt(file.FileInfo().Id()); + + maybeKey = aFileManager.MutableCipherKeyManagerRef().Get(fileKeyId); + } + + auto data = JSStructuredCloneData{JS::StructuredCloneScope::DifferentProcess}; + + { + const nsCOMPtr<nsIFile> nativeFile = file.FileInfo().GetFileForFileInfo(); + QM_TRY(OkIf(nativeFile), Err(NS_ERROR_FAILURE)); + + QM_TRY_INSPECT( + const auto& fileInputStream, + NS_NewLocalFileInputStream(nativeFile) + .andThen([maybeKey](auto fileInputStream) + -> Result<nsCOMPtr<nsIInputStream>, nsresult> { + if (maybeKey) { + return nsCOMPtr<nsIInputStream>{MakeRefPtr< + quota::DecryptingInputStream<IndexedDBCipherStrategy>>( + WrapNotNull(std::move(fileInputStream)), + kEncryptedStreamBlockSize, *maybeKey)}; + } + + return fileInputStream; + })); + + QM_TRY(MOZ_TO_RESULT( + SnappyUncompressStructuredCloneData(*fileInputStream, data))); + } + + return StructuredCloneReadInfoParent{std::move(data), std::move(files), + false}; +} + +template <typename T> +Result<StructuredCloneReadInfoParent, nsresult> +GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex, + uint32_t aFileIdsIndex, + const DatabaseFileManager& aFileManager) { + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(aSource); + + QM_TRY_INSPECT( + const int32_t& columnType, + MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetTypeOfIndex, aDataIndex)); + + QM_TRY_INSPECT(const bool& isNull, MOZ_TO_RESULT_INVOKE_MEMBER( + aSource, GetIsNull, aFileIdsIndex)); + + QM_TRY_INSPECT(const nsString& fileIds, ([aSource, aFileIdsIndex, isNull] { + return isNull ? Result<nsString, nsresult>{VoidString()} + : MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( + nsString, aSource, GetString, + aFileIdsIndex); + }())); + + switch (columnType) { + case mozIStorageStatement::VALUE_TYPE_INTEGER: { + QM_TRY_INSPECT( + const int64_t& intData, + MOZ_TO_RESULT_INVOKE_MEMBER(aSource, GetInt64, aDataIndex)); + + uint64_t uintData; + memcpy(&uintData, &intData, sizeof(uint64_t)); + + return GetStructuredCloneReadInfoFromExternalBlob(uintData, aFileManager, + fileIds); + } + + case mozIStorageStatement::VALUE_TYPE_BLOB: { + const uint8_t* blobData; + uint32_t blobDataLength; + QM_TRY(MOZ_TO_RESULT( + aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData))); + + return GetStructuredCloneReadInfoFromBlob(blobData, blobDataLength, + aFileManager, fileIds); + } + + default: + return Err(NS_ERROR_FILE_CORRUPTED); + } +} + +} // namespace + +IndexDataValue::IndexDataValue() : mIndexId(0), mUnique(false) { + MOZ_COUNT_CTOR(IndexDataValue); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +IndexDataValue::IndexDataValue(IndexDataValue&& aOther) + : mIndexId(aOther.mIndexId), + mPosition(std::move(aOther.mPosition)), + mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)), + mUnique(aOther.mUnique) { + MOZ_ASSERT(!aOther.mPosition.IsUnset()); + + MOZ_COUNT_CTOR(IndexDataValue); +} +#endif + +IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique, + const Key& aPosition) + : mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) { + MOZ_ASSERT(!aPosition.IsUnset()); + + MOZ_COUNT_CTOR(IndexDataValue); +} + +IndexDataValue::IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique, + const Key& aPosition, + const Key& aLocaleAwarePosition) + : mIndexId(aIndexId), + mPosition(aPosition), + mLocaleAwarePosition(aLocaleAwarePosition), + mUnique(aUnique) { + MOZ_ASSERT(!aPosition.IsUnset()); + + MOZ_COUNT_CTOR(IndexDataValue); +} + +bool IndexDataValue::operator==(const IndexDataValue& aOther) const { + if (mIndexId != aOther.mIndexId) { + return false; + } + if (mLocaleAwarePosition.IsUnset()) { + return mPosition == aOther.mPosition; + } + return mLocaleAwarePosition == aOther.mLocaleAwarePosition; +} + +bool IndexDataValue::operator<(const IndexDataValue& aOther) const { + if (mIndexId == aOther.mIndexId) { + if (mLocaleAwarePosition.IsUnset()) { + return mPosition < aOther.mPosition; + } + return mLocaleAwarePosition < aOther.mLocaleAwarePosition; + } + + return mIndexId < aOther.mIndexId; +} + +JSObject* GetSandbox(JSContext* aCx) { + SandboxHolder* holder = SandboxHolder::GetOrCreate(); + return holder->GetSandboxInternal(aCx); +} + +Result<std::pair<UniqueFreePtr<uint8_t>, uint32_t>, nsresult> +MakeCompressedIndexDataValues(const nsTArray<IndexDataValue>& aIndexValues) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + + AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM); + + const uint32_t arrayLength = aIndexValues.Length(); + if (!arrayLength) { + return std::pair{UniqueFreePtr<uint8_t>{}, 0u}; + } + + // First calculate the size of the final buffer. + const auto blobDataLength = std::accumulate( + aIndexValues.cbegin(), aIndexValues.cend(), CheckedUint32(0), + [](CheckedUint32 sum, const IndexDataValue& info) { + const nsCString& keyBuffer = info.mPosition.GetBuffer(); + const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer(); + const uint32_t keyBufferLength = keyBuffer.Length(); + const uint32_t sortKeyBufferLength = sortKeyBuffer.Length(); + + MOZ_ASSERT(!keyBuffer.IsEmpty()); + + return sum + CompressedByteCountForIndexId(info.mIndexId) + + CompressedByteCountForNumber(keyBufferLength) + + CompressedByteCountForNumber(sortKeyBufferLength) + + keyBufferLength + sortKeyBufferLength; + }); + + QM_TRY(OkIf(blobDataLength.isValid()), + Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), + IDB_REPORT_INTERNAL_ERR_LAMBDA); + + UniqueFreePtr<uint8_t> blobData( + static_cast<uint8_t*>(malloc(blobDataLength.value()))); + QM_TRY(OkIf(static_cast<bool>(blobData)), Err(NS_ERROR_OUT_OF_MEMORY), + IDB_REPORT_INTERNAL_ERR_LAMBDA); + + uint8_t* blobDataIter = blobData.get(); + + for (const IndexDataValue& info : aIndexValues) { + const nsCString& keyBuffer = info.mPosition.GetBuffer(); + const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer(); + const uint32_t keyBufferLength = keyBuffer.Length(); + const uint32_t sortKeyBufferLength = sortKeyBuffer.Length(); + + WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter); + WriteCompressedNumber(keyBufferLength, &blobDataIter); + + memcpy(blobDataIter, keyBuffer.get(), keyBufferLength); + blobDataIter += keyBufferLength; + + WriteCompressedNumber(sortKeyBufferLength, &blobDataIter); + + memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength); + blobDataIter += sortKeyBufferLength; + } + + MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength.value()); + + return std::pair{std::move(blobData), blobDataLength.value()}; +} + +nsresult ReadCompressedIndexDataValues( + mozIStorageStatement& aStatement, uint32_t aColumnIndex, + nsTArray<IndexDataValue>& aOutIndexValues) { + return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex, + &aOutIndexValues); +} + +template <typename T> +Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues( + T& aValues, uint32_t aColumnIndex) { + return MOZ_TO_RESULT_INVOKE_TYPED(IndexDataValuesAutoArray, + &ReadCompressedIndexDataValuesFromSource<T>, + aValues, aColumnIndex); +} + +template Result<IndexDataValuesAutoArray, nsresult> +ReadCompressedIndexDataValues<mozIStorageValueArray>(mozIStorageValueArray&, + uint32_t); + +template Result<IndexDataValuesAutoArray, nsresult> +ReadCompressedIndexDataValues<mozIStorageStatement>(mozIStorageStatement&, + uint32_t); + +Result<std::tuple<IndexOrObjectStoreId, bool, Span<const uint8_t>>, nsresult> +ReadCompressedIndexId(const Span<const uint8_t> aData) { + QM_TRY_INSPECT((const auto& [indexId, remainder]), + ReadCompressedNumber(aData)); + + MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!"); + + return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1, + remainder}; +} + +Result<std::pair<uint64_t, mozilla::Span<const uint8_t>>, nsresult> +ReadCompressedNumber(const Span<const uint8_t> aSpan) { + uint8_t shiftCounter = 0; + uint64_t result = 0; + + const auto end = aSpan.cend(); + + const auto newPos = + std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) { + MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!"); + + result += (uint64_t(byte & 0x7f) << shiftCounter); + shiftCounter += 7; + + return !(byte & 0x80); + }); + + QM_TRY(OkIf(newPos != end), Err(NS_ERROR_FILE_CORRUPTED), [](const auto&) { + MOZ_ASSERT(false); + IDB_REPORT_INTERNAL_ERR(); + }); + + return std::pair{result, Span{newPos + 1, end}}; +} + +Result<StructuredCloneReadInfoParent, nsresult> +GetStructuredCloneReadInfoFromValueArray( + mozIStorageValueArray* aValues, uint32_t aDataIndex, uint32_t aFileIdsIndex, + const DatabaseFileManager& aFileManager) { + return GetStructuredCloneReadInfoFromSource(aValues, aDataIndex, + aFileIdsIndex, aFileManager); +} + +Result<StructuredCloneReadInfoParent, nsresult> +GetStructuredCloneReadInfoFromStatement( + mozIStorageStatement* aStatement, uint32_t aDataIndex, + uint32_t aFileIdsIndex, const DatabaseFileManager& aFileManager) { + return GetStructuredCloneReadInfoFromSource(aStatement, aDataIndex, + aFileIdsIndex, aFileManager); +} + +Result<nsTArray<StructuredCloneFileParent>, nsresult> +DeserializeStructuredCloneFiles(const DatabaseFileManager& aFileManager, + const nsAString& aText) { + MOZ_ASSERT(!IsOnBackgroundThread()); + + nsTArray<StructuredCloneFileParent> result; + for (const auto& token : + nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ') + .ToRange()) { + MOZ_ASSERT(!token.IsEmpty()); + + QM_TRY_UNWRAP(auto structuredCloneFile, + DeserializeStructuredCloneFile(aFileManager, token)); + + result.EmplaceBack(std::move(structuredCloneFile)); + } + + return result; +} + +nsresult ExecuteSimpleSQLSequence(mozIStorageConnection& aConnection, + Span<const nsLiteralCString> aSQLCommands) { + for (const auto& aSQLCommand : aSQLCommands) { + const auto extraInfo = quota::ScopedLogExtraInfo{ + quota::ScopedLogExtraInfo::kTagQuery, aSQLCommand}; + + QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(aSQLCommand))); + } + + return NS_OK; +} + +} // namespace mozilla::dom::indexedDB |