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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 #include <stdint.h> // for UINT32_MAX, uintptr_t
12 #include "js/Array.h" // JS::NewArrayObject
13 #include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,NewArrayBuffer{,WithContents},GetArrayBufferLengthAndData}
15 #include "js/experimental/TypedData.h" // JS_IsArrayBufferViewObject, JS_GetObjectAsArrayBufferView
16 #include "js/MemoryFunctions.h"
17 #include "js/Object.h" // JS::GetBuiltinClass
18 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById
20 #include "jsfriendapi.h"
21 #include "mozilla/Casting.h"
22 #include "mozilla/CheckedInt.h"
23 #include "mozilla/EndianUtils.h"
24 #include "mozilla/FloatingPoint.h"
25 #include "mozilla/intl/Collator.h"
26 #include "mozilla/ResultExtensions.h"
27 #include "mozilla/ReverseIterator.h"
28 #include "mozilla/dom/indexedDB/IDBResult.h"
29 #include "mozilla/dom/indexedDB/Key.h"
30 #include "mozilla/dom/quota/QuotaCommon.h"
31 #include "mozilla/dom/quota/ResultExtensions.h"
32 #include "mozIStorageStatement.h"
33 #include "mozIStorageValueArray.h"
34 #include "nsJSUtils.h"
35 #include "nsTStringRepr.h"
36 #include "ReportInternalError.h"
37 #include "xpcpublic.h"
39 namespace mozilla::dom::indexedDB
{
42 // Implementation of the array branch of step 3 of
43 // https://w3c.github.io/IndexedDB/#convert-value-to-key
44 template <typename ArrayConversionPolicy
>
45 IDBResult
<Ok
, IDBSpecialValue::Invalid
> ConvertArrayValueToKey(
46 JSContext
* const aCx
, JS::Handle
<JSObject
*> aObject
,
47 ArrayConversionPolicy
&& aPolicy
) {
48 // 1. Let `len` be ? ToLength( ? Get(`input`, "length")).
50 if (!JS::GetArrayLength(aCx
, aObject
, &len
)) {
51 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
54 // 2. Add `input` to `seen`.
55 aPolicy
.AddToSeenSet(aCx
, aObject
);
57 // 3. Let `keys` be a new empty list.
58 aPolicy
.BeginSubkeyList();
60 // 4. Let `index` be 0.
63 // 5. While `index` is less than `len`:
65 JS::Rooted
<JS::PropertyKey
> indexId(aCx
);
66 if (!JS_IndexToId(aCx
, index
, &indexId
)) {
67 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
70 // 1. Let `hop` be ? HasOwnProperty(`input`, `index`).
72 if (!JS_HasOwnPropertyById(aCx
, aObject
, indexId
, &hop
)) {
73 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
76 // 2. If `hop` is false, return invalid.
78 return Err(IDBError(SpecialValues::Invalid
));
81 // 3. Let `entry` be ? Get(`input`, `index`).
82 JS::Rooted
<JS::Value
> entry(aCx
);
83 if (!JS_GetPropertyById(aCx
, aObject
, indexId
, &entry
)) {
84 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
87 // 4. Let `key` be the result of running the steps to convert a value to a
88 // key with arguments `entry` and `seen`.
89 // 5. ReturnIfAbrupt(`key`).
90 // 6. If `key` is invalid abort these steps and return invalid.
91 // 7. Append `key` to `keys`.
92 auto result
= aPolicy
.ConvertSubkey(aCx
, entry
, index
);
97 // 8. Increase `index` by 1.
101 // 6. Return a new array key with value `keys`.
102 aPolicy
.EndSubkeyList();
108 Here's how we encode keys:
110 Basic strategy is the following
112 Numbers: 0x10 n n n n n n n n ("n"s are encoded 64bit float)
113 Dates: 0x20 n n n n n n n n ("n"s are encoded 64bit float)
114 Strings: 0x30 s s s ... 0 ("s"s are encoded unicode bytes)
115 Binaries: 0x40 s s s ... 0 ("s"s are encoded unicode bytes)
116 Arrays: 0x50 i i i ... 0 ("i"s are encoded array items)
119 When encoding floats, 64bit IEEE 754 are almost sortable, except that
120 positive sort lower than negative, and negative sort descending. So we use
121 the following encoding:
124 (-to64bitInt(value)) :
125 (to64bitInt(value) | 0x8000000000000000)
128 When encoding strings, we use variable-size encoding per the following table
130 Chars 0 - 7E are encoded as 0xxxxxxx with 1 added
131 Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F
133 Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000
135 This ensures that the first byte is never encoded as 0, which means that the
136 string terminator (per basic-strategy table) sorts before any character.
137 The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
138 the chance that the last character is 0. See below for why.
140 When encoding binaries, the algorithm is the same to how strings are encoded.
141 Since each octet in binary is in the range of [0-255], it'll take 1 to 2
142 encoded unicode bytes.
144 When encoding Arrays, we use an additional trick. Rather than adding a byte
145 containing the value 0x50 to indicate type, we instead add 0x50 to the next
146 byte. This is usually the byte containing the type of the first item in the
147 array. So simple examples are
149 ["foo"] 0x80 s s s 0 0 // 0x80 is 0x30 + 0x50
150 [1, 2] 0x60 n n n n n n n n 1 n n n n n n n n 0 // 0x60 is 0x10 + 0x50
152 Whe do this iteratively if the first item in the array is also an array
154 [["foo"]] 0xA0 s s s 0 0 0
156 However, to avoid overflow in the byte, we only do this 3 times. If the first
157 item in an array is an array, and that array also has an array as first item,
158 we simply write out the total value accumulated so far and then follow the
161 [[["foo"]]] 0xF0 0x30 s s s 0 0 0 0
163 There is another edge case that can happen though, which is that the array
164 doesn't have a first item to which we can add 0x50 to the type. Instead the
165 next byte would normally be the array terminator (per basic-strategy table)
166 so we simply add the 0x50 there.
168 [[]] 0xA0 0 // 0xA0 is 0x50 + 0x50 + 0
169 [] 0x50 // 0x50 is 0x50 + 0
170 [[], "foo"] 0xA0 0x30 s s s 0 0 // 0xA0 is 0x50 + 0x50 + 0
172 Note that the max-3-times rule kicks in before we get a chance to add to the
175 [[[]]] 0xF0 0 0 0 // 0xF0 is 0x50 + 0x50 + 0x50
177 As a final optimization we do a post-encoding step which drops all 0s at the
178 end of the encoded buffer.
182 ["a", "b"] // 0x80 s 0 0x30 s
183 [1, 2] // 0x60 bf f0 0 0 0 0 0 0 0x10 c0
187 Result
<Ok
, nsresult
> Key::SetFromString(const nsAString
& aString
) {
189 auto result
= EncodeString(aString
, 0);
196 // |aPos| should point to the type indicator.
197 // The returned length doesn't include the type indicator
198 // or the terminator.
200 uint32_t Key::LengthOfEncodedBinary(const EncodedDataType
* aPos
,
201 const EncodedDataType
* aEnd
) {
202 MOZ_ASSERT(*aPos
% Key::eMaxType
== Key::eBinary
, "Don't call me!");
204 const auto* iter
= aPos
+ 1;
205 for (; iter
< aEnd
&& *iter
!= eTerminator
; ++iter
) {
208 // XXX if iter == aEnd now, we got a bad enconding, should we report that
209 // also in non-debug builds?
210 MOZ_ASSERT(iter
< aEnd
);
214 return iter
- aPos
- 1;
217 Result
<Key
, nsresult
> Key::ToLocaleAwareKey(const nsCString
& aLocale
) const {
224 if (IsFloat() || IsDate() || IsBinary()) {
225 res
.mBuffer
= mBuffer
;
229 auto* it
= BufferStart();
230 auto* const end
= BufferEnd();
232 // First we do a pass and see if there are any strings in this key. We only
233 // want to copy/decode when necessary.
234 bool canShareBuffers
= true;
236 const auto type
= *it
% eMaxType
;
237 if (type
== eTerminator
) {
239 } else if (type
== eFloat
|| type
== eDate
) {
241 it
+= std::min(sizeof(uint64_t), size_t(end
- it
));
242 } else if (type
== eBinary
) {
243 // skip all binary data
244 const auto binaryLength
= LengthOfEncodedBinary(it
, end
);
249 canShareBuffers
= false;
254 if (canShareBuffers
) {
255 MOZ_ASSERT(it
== end
);
256 res
.mBuffer
= mBuffer
;
260 if (!res
.mBuffer
.SetCapacity(mBuffer
.Length(), fallible
)) {
261 return Err(NS_ERROR_OUT_OF_MEMORY
);
264 // A string was found, so we need to copy the data we've read so far
265 auto* const start
= BufferStart();
268 MOZ_ALWAYS_TRUE(res
.mBuffer
.GetMutableData(&buffer
, it
- start
));
269 std::copy(start
, it
, buffer
);
272 // Now continue decoding
275 const size_t oldLen
= res
.mBuffer
.Length();
276 const auto type
= *it
% eMaxType
;
278 // Note: Do not modify |it| before calling |updateBufferAndIter|;
279 // |byteCount| doesn't include the type indicator
280 const auto updateBufferAndIter
= [&](size_t byteCount
) -> bool {
281 if (!res
.mBuffer
.GetMutableData(&buffer
, oldLen
+ 1 + byteCount
)) {
286 // should also copy the type indicator at the begining
287 std::copy_n(it
, byteCount
+ 1, buffer
);
288 it
+= (byteCount
+ 1);
292 if (type
== eTerminator
) {
293 // Copy array TypeID and terminator from raw key
294 if (!updateBufferAndIter(0)) {
295 return Err(NS_ERROR_OUT_OF_MEMORY
);
297 } else if (type
== eFloat
|| type
== eDate
) {
298 // Copy number from raw key
299 const size_t byteCount
= std::min(sizeof(uint64_t), size_t(end
- it
- 1));
301 if (!updateBufferAndIter(byteCount
)) {
302 return Err(NS_ERROR_OUT_OF_MEMORY
);
304 } else if (type
== eBinary
) {
305 // skip all binary data
306 const auto binaryLength
= LengthOfEncodedBinary(it
, end
);
308 if (!updateBufferAndIter(binaryLength
)) {
309 return Err(NS_ERROR_OUT_OF_MEMORY
);
312 // Decode string and reencode
313 const uint8_t typeOffset
= *it
- eString
;
314 MOZ_ASSERT((typeOffset
% eArray
== 0) && (typeOffset
/ eArray
<= 2));
316 auto str
= DecodeString(it
, end
);
317 auto result
= res
.EncodeLocaleString(str
, typeOffset
, aLocale
);
318 if (NS_WARN_IF(result
.isErr())) {
319 return result
.propagateErr();
327 class MOZ_STACK_CLASS
Key::ArrayValueEncoder final
{
329 ArrayValueEncoder(Key
& aKey
, const uint8_t aTypeOffset
,
330 const uint16_t aRecursionDepth
)
332 mTypeOffset(aTypeOffset
),
333 mRecursionDepth(aRecursionDepth
) {}
335 void AddToSeenSet(JSContext
* const aCx
, JS::Handle
<JSObject
*>) {
339 void BeginSubkeyList() {
340 mTypeOffset
+= Key::eMaxType
;
341 if (mTypeOffset
== eMaxType
* kMaxArrayCollapse
) {
342 mKey
.mBuffer
.Append(mTypeOffset
);
345 MOZ_ASSERT(mTypeOffset
% eMaxType
== 0,
346 "Current type offset must indicate beginning of array");
347 MOZ_ASSERT(mTypeOffset
< eMaxType
* kMaxArrayCollapse
);
350 IDBResult
<Ok
, IDBSpecialValue::Invalid
> ConvertSubkey(
351 JSContext
* const aCx
, JS::Handle
<JS::Value
> aEntry
,
352 const uint32_t aIndex
) {
354 mKey
.EncodeJSValInternal(aCx
, aEntry
, mTypeOffset
, mRecursionDepth
);
359 void EndSubkeyList() const { mKey
.mBuffer
.Append(eTerminator
+ mTypeOffset
); }
364 uint16_t mRecursionDepth
;
367 // Implements the following algorithm:
368 // https://w3c.github.io/IndexedDB/#convert-a-value-to-a-key
369 IDBResult
<Ok
, IDBSpecialValue::Invalid
> Key::EncodeJSValInternal(
370 JSContext
* const aCx
, JS::Handle
<JS::Value
> aVal
, uint8_t aTypeOffset
,
371 const uint16_t aRecursionDepth
) {
372 static_assert(eMaxType
* kMaxArrayCollapse
< 256, "Unable to encode jsvals.");
374 // 1. If `seen` was not given, let `seen` be a new empty set.
375 // 2. If `input` is in `seen` return invalid.
376 // Note: we replace this check with a simple recursion depth check.
377 if (NS_WARN_IF(aRecursionDepth
== kMaxRecursionDepth
)) {
378 return Err(IDBError(SpecialValues::Invalid
));
381 // 3. Jump to the appropriate step below:
382 // Note: some cases appear out of order to make the implementation more
383 // straightforward. This shouldn't affect observable behavior.
385 // If Type(`input`) is Number
386 if (aVal
.isNumber()) {
387 const auto number
= aVal
.toNumber();
389 // 1. If `input` is NaN then return invalid.
390 if (std::isnan(number
)) {
391 return Err(IDBError(SpecialValues::Invalid
));
394 // 2. Otherwise, return a new key with type `number` and value `input`.
395 return EncodeNumber(number
, eFloat
+ aTypeOffset
);
398 // If Type(`input`) is String
399 if (aVal
.isString()) {
400 // 1. Return a new key with type `string` and value `input`.
401 nsAutoJSString string
;
402 if (!string
.init(aCx
, aVal
)) {
403 IDB_REPORT_INTERNAL_ERR();
404 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
406 return EncodeString(string
, aTypeOffset
);
409 if (aVal
.isObject()) {
410 JS::Rooted
<JSObject
*> object(aCx
, &aVal
.toObject());
412 js::ESClass builtinClass
;
413 if (!JS::GetBuiltinClass(aCx
, object
, &builtinClass
)) {
414 IDB_REPORT_INTERNAL_ERR();
415 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
418 // If `input` is a Date (has a [[DateValue]] internal slot)
419 if (builtinClass
== js::ESClass::Date
) {
420 // 1. Let `ms` be the value of `input`’s [[DateValue]] internal slot.
422 if (!js::DateGetMsecSinceEpoch(aCx
, object
, &ms
)) {
423 IDB_REPORT_INTERNAL_ERR();
424 return Err(IDBException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
));
427 // 2. If `ms` is NaN then return invalid.
428 if (std::isnan(ms
)) {
429 return Err(IDBError(SpecialValues::Invalid
));
432 // 3. Otherwise, return a new key with type `date` and value `ms`.
433 return EncodeNumber(ms
, eDate
+ aTypeOffset
);
436 // If `input` is a buffer source type
437 if (JS::IsArrayBufferObject(object
) || JS_IsArrayBufferViewObject(object
)) {
438 const bool isViewObject
= JS_IsArrayBufferViewObject(object
);
439 return EncodeBinary(object
, isViewObject
, aTypeOffset
);
442 // If IsArray(`input`)
443 if (builtinClass
== js::ESClass::Array
) {
444 return ConvertArrayValueToKey(
445 aCx
, object
, ArrayValueEncoder
{*this, aTypeOffset
, aRecursionDepth
});
451 return Err(IDBError(SpecialValues::Invalid
));
455 nsresult
Key::DecodeJSValInternal(const EncodedDataType
*& aPos
,
456 const EncodedDataType
* aEnd
, JSContext
* aCx
,
458 JS::MutableHandle
<JS::Value
> aVal
,
459 uint16_t aRecursionDepth
) {
460 if (NS_WARN_IF(aRecursionDepth
== kMaxRecursionDepth
)) {
461 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
464 if (*aPos
- aTypeOffset
>= eArray
) {
465 JS::Rooted
<JSObject
*> array(aCx
, JS::NewArrayObject(aCx
, 0));
467 NS_WARNING("Failed to make array!");
468 IDB_REPORT_INTERNAL_ERR();
469 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
472 aTypeOffset
+= eMaxType
;
474 if (aTypeOffset
== eMaxType
* kMaxArrayCollapse
) {
480 JS::Rooted
<JS::Value
> val(aCx
);
481 while (aPos
< aEnd
&& *aPos
- aTypeOffset
!= eTerminator
) {
482 QM_TRY(MOZ_TO_RESULT(DecodeJSValInternal(aPos
, aEnd
, aCx
, aTypeOffset
,
483 &val
, aRecursionDepth
+ 1)));
487 if (!JS_DefineElement(aCx
, array
, index
++, val
, JSPROP_ENUMERATE
)) {
488 NS_WARNING("Failed to set array element!");
489 IDB_REPORT_INTERNAL_ERR();
490 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
494 NS_ASSERTION(aPos
>= aEnd
|| (*aPos
% eMaxType
) == eTerminator
,
495 "Should have found end-of-array marker");
498 aVal
.setObject(*array
);
499 } else if (*aPos
- aTypeOffset
== eString
) {
500 auto key
= DecodeString(aPos
, aEnd
);
501 if (!xpc::StringToJsval(aCx
, key
, aVal
)) {
502 IDB_REPORT_INTERNAL_ERR();
503 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
505 } else if (*aPos
- aTypeOffset
== eDate
) {
506 double msec
= static_cast<double>(DecodeNumber(aPos
, aEnd
));
507 JS::ClippedTime time
= JS::TimeClip(msec
);
508 MOZ_ASSERT(msec
== time
.toDouble(),
509 "encoding from a Date object not containing an invalid date "
510 "means we should always have clipped values");
511 JSObject
* date
= JS::NewDateObject(aCx
, time
);
513 IDB_WARNING("Failed to make date!");
514 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
517 aVal
.setObject(*date
);
518 } else if (*aPos
- aTypeOffset
== eFloat
) {
519 aVal
.setDouble(DecodeNumber(aPos
, aEnd
));
520 } else if (*aPos
- aTypeOffset
== eBinary
) {
521 JSObject
* binary
= DecodeBinary(aPos
, aEnd
, aCx
);
523 IDB_REPORT_INTERNAL_ERR();
524 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
527 aVal
.setObject(*binary
);
529 MOZ_ASSERT_UNREACHABLE("Unknown key type!");
535 #define ONE_BYTE_LIMIT 0x7E
536 #define TWO_BYTE_LIMIT (0x3FFF + 0x7F)
538 #define ONE_BYTE_ADJUST 1
539 #define TWO_BYTE_ADJUST (-0x7F)
540 #define THREE_BYTE_SHIFT 6
542 IDBResult
<Ok
, IDBSpecialValue::Invalid
> Key::EncodeJSVal(
543 JSContext
* aCx
, JS::Handle
<JS::Value
> aVal
, uint8_t aTypeOffset
) {
544 return EncodeJSValInternal(aCx
, aVal
, aTypeOffset
, 0);
547 Result
<Ok
, nsresult
> Key::EncodeString(const nsAString
& aString
,
548 uint8_t aTypeOffset
) {
549 return EncodeString(Span
{aString
}, aTypeOffset
);
552 template <typename T
>
553 Result
<Ok
, nsresult
> Key::EncodeString(const Span
<const T
> aInput
,
554 uint8_t aTypeOffset
) {
555 return EncodeAsString(aInput
, eString
+ aTypeOffset
);
558 // nsCString maximum length is limited by INT32_MAX.
559 // XXX: We probably want to enforce even shorter keys, though.
560 #define KEY_MAXIMUM_BUFFER_LENGTH \
561 ::mozilla::detail::nsTStringLengthStorage<char>::kMax
563 template <typename T
>
564 Result
<Ok
, nsresult
> Key::EncodeAsString(const Span
<const T
> aInput
,
566 // Please note that the input buffer can either be based on two-byte UTF-16
567 // values or on arbitrary single byte binary values. Only the first case
568 // needs to account for the TWO_BYTE_LIMIT of UTF-8.
569 // First we measure how long the encoded string will be.
571 // The 2 is for initial aType and trailing 0. We'll compensate for multi-byte
575 // We construct a range over the raw pointers here because this loop is
577 // XXX It might be good to encapsulate this in some function to make it less
578 // error-prone and more expressive.
579 const auto inputRange
= mozilla::detail::IteratorRange(
580 aInput
.Elements(), aInput
.Elements() + aInput
.Length());
582 size_t payloadSize
= aInput
.Length();
583 bool anyMultibyte
= false;
584 for (const T val
: inputRange
) {
585 if (val
> ONE_BYTE_LIMIT
) {
587 payloadSize
+= char16_t(val
) > TWO_BYTE_LIMIT
? 2 : 1;
588 if (payloadSize
> KEY_MAXIMUM_BUFFER_LENGTH
) {
589 return Err(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
596 // Now we allocate memory for the new size
597 size_t oldLen
= mBuffer
.Length();
600 if (size
> KEY_MAXIMUM_BUFFER_LENGTH
) {
601 return Err(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
605 if (!mBuffer
.GetMutableData(&buffer
, size
)) {
606 IDB_REPORT_INTERNAL_ERR();
607 return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
616 for (const auto val
: inputRange
) {
617 if (val
<= ONE_BYTE_LIMIT
) {
618 *(buffer
++) = val
+ ONE_BYTE_ADJUST
;
619 } else if (char16_t(val
) <= TWO_BYTE_LIMIT
) {
620 char16_t c
= char16_t(val
) + TWO_BYTE_ADJUST
+ 0x8000;
621 *(buffer
++) = (char)(c
>> 8);
622 *(buffer
++) = (char)(c
& 0xFF);
624 uint32_t c
= (uint32_t(val
) << THREE_BYTE_SHIFT
) | 0x00C00000;
625 *(buffer
++) = (char)(c
>> 16);
626 *(buffer
++) = (char)(c
>> 8);
627 *(buffer
++) = (char)c
;
631 // Optimization for the case where there are no multibyte characters.
632 // This is ca. 13 resp. 5.8 times faster than the non-optimized version in
633 // an -O2 build: https://quick-bench.com/q/v1oBpLGifs-3w_pkZG8alVSWVAw, for
634 // the T==uint8_t resp. T==char16_t cases (for the char16_t case, copying
635 // and then adjusting could even be slightly faster, but then we would need
636 // another case distinction here)
637 size_t inputLen
= std::distance(inputRange
.cbegin(), inputRange
.cend());
638 MOZ_ASSERT(inputLen
== payloadSize
);
639 std::transform(inputRange
.cbegin(), inputRange
.cend(), buffer
,
640 [](auto value
) { return value
+ ONE_BYTE_ADJUST
; });
645 *(buffer
++) = eTerminator
;
647 NS_ASSERTION(buffer
== mBuffer
.EndReading(), "Wrote wrong number of bytes");
652 Result
<Ok
, nsresult
> Key::EncodeLocaleString(const nsAString
& aString
,
654 const nsCString
& aLocale
) {
655 const int length
= aString
.Length();
660 auto collResult
= intl::Collator::TryCreate(aLocale
.get());
661 if (collResult
.isErr()) {
662 return Err(NS_ERROR_FAILURE
);
664 auto collator
= collResult
.unwrap();
665 MOZ_ASSERT(collator
);
667 AutoTArray
<uint8_t, 128> keyBuffer
;
668 MOZ_TRY(collator
->GetSortKey(Span
{aString
}, keyBuffer
)
669 .mapErr([](intl::ICUError icuError
) {
670 return icuError
== intl::ICUError::OutOfMemory
671 ? NS_ERROR_OUT_OF_MEMORY
675 size_t sortKeyLength
= keyBuffer
.Length();
676 return EncodeString(Span
{keyBuffer
}.AsConst().First(sortKeyLength
),
681 nsresult
Key::DecodeJSVal(const EncodedDataType
*& aPos
,
682 const EncodedDataType
* aEnd
, JSContext
* aCx
,
683 JS::MutableHandle
<JS::Value
> aVal
) {
684 return DecodeJSValInternal(aPos
, aEnd
, aCx
, 0, aVal
, 0);
688 template <typename T
>
689 uint32_t Key::CalcDecodedStringySize(
690 const EncodedDataType
* const aBegin
, const EncodedDataType
* const aEnd
,
691 const EncodedDataType
** aOutEncodedSectionEnd
) {
692 static_assert(sizeof(T
) <= 2,
693 "Only implemented for 1 and 2 byte decoded types");
694 uint32_t decodedSize
= 0;
696 for (; iter
< aEnd
&& *iter
!= eTerminator
; ++iter
) {
698 iter
+= (sizeof(T
) > 1 && (*iter
& 0x40)) ? 2 : 1;
702 *aOutEncodedSectionEnd
= std::min(aEnd
, iter
);
707 template <typename T
>
708 void Key::DecodeAsStringy(const EncodedDataType
* const aEncodedSectionBegin
,
709 const EncodedDataType
* const aEncodedSectionEnd
,
710 const uint32_t aDecodedLength
, T
* const aOut
) {
711 static_assert(sizeof(T
) <= 2,
712 "Only implemented for 1 and 2 byte decoded types");
713 T
* decodedPos
= aOut
;
714 for (const EncodedDataType
* iter
= aEncodedSectionBegin
;
715 iter
< aEncodedSectionEnd
;) {
716 if (!(*iter
& 0x80)) {
717 *decodedPos
= *(iter
++) - ONE_BYTE_ADJUST
;
718 } else if (sizeof(T
) == 1 || !(*iter
& 0x40)) {
719 auto c
= static_cast<uint16_t>(*(iter
++)) << 8;
720 if (iter
< aEncodedSectionEnd
) {
723 *decodedPos
= static_cast<T
>(c
- TWO_BYTE_ADJUST
- 0x8000);
724 } else if (sizeof(T
) > 1) {
725 auto c
= static_cast<uint32_t>(*(iter
++)) << (16 - THREE_BYTE_SHIFT
);
726 if (iter
< aEncodedSectionEnd
) {
727 c
|= static_cast<uint32_t>(*(iter
++)) << (8 - THREE_BYTE_SHIFT
);
729 if (iter
< aEncodedSectionEnd
) {
730 c
|= *(iter
++) >> THREE_BYTE_SHIFT
;
732 *decodedPos
= static_cast<T
>(c
);
737 MOZ_ASSERT(static_cast<uint32_t>(decodedPos
- aOut
) == aDecodedLength
,
738 "Should have written the whole decoded area");
742 template <Key::EncodedDataType TypeMask
, typename T
, typename AcquireBuffer
,
743 typename AcquireEmpty
>
744 void Key::DecodeStringy(const EncodedDataType
*& aPos
,
745 const EncodedDataType
* aEnd
,
746 const AcquireBuffer
& acquireBuffer
,
747 const AcquireEmpty
& acquireEmpty
) {
748 NS_ASSERTION(*aPos
% eMaxType
== TypeMask
, "Don't call me!");
750 // First measure how big the decoded stringy data will be.
751 const EncodedDataType
* const encodedSectionBegin
= aPos
+ 1;
752 const EncodedDataType
* encodedSectionEnd
;
753 // decodedLength does not include the terminating 0 (in case of a string)
754 const uint32_t decodedLength
=
755 CalcDecodedStringySize
<T
>(encodedSectionBegin
, aEnd
, &encodedSectionEnd
);
756 aPos
= encodedSectionEnd
+ 1;
758 if (!decodedLength
) {
764 if (!acquireBuffer(&out
, decodedLength
)) {
768 DecodeAsStringy(encodedSectionBegin
, encodedSectionEnd
, decodedLength
, out
);
772 nsAutoString
Key::DecodeString(const EncodedDataType
*& aPos
,
773 const EncodedDataType
* const aEnd
) {
775 DecodeStringy
<eString
, char16_t
>(
777 [&res
](char16_t
** out
, uint32_t decodedLength
) {
778 return 0 != res
.GetMutableData(out
, decodedLength
);
784 Result
<Ok
, nsresult
> Key::EncodeNumber(double aFloat
, uint8_t aType
) {
785 // Allocate memory for the new size
786 size_t oldLen
= mBuffer
.Length();
787 size_t newLen
= oldLen
+ 1 + sizeof(double);
788 if (newLen
> KEY_MAXIMUM_BUFFER_LENGTH
) {
789 return Err(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
793 if (!mBuffer
.GetMutableData(&buffer
, newLen
)) {
794 return Err(NS_ERROR_DOM_INDEXEDDB_KEY_ERR
);
800 uint64_t bits
= BitwiseCast
<uint64_t>(aFloat
);
801 // Note: The subtraction from 0 below is necessary to fix
802 // MSVC build warning C4146 (negating an unsigned value).
803 const uint64_t signbit
= FloatingPoint
<double>::kSignBit
;
804 uint64_t number
= bits
& signbit
? (0 - bits
) : (bits
| signbit
);
806 mozilla::BigEndian::writeUint64(buffer
, number
);
811 double Key::DecodeNumber(const EncodedDataType
*& aPos
,
812 const EncodedDataType
* aEnd
) {
813 NS_ASSERTION(*aPos
% eMaxType
== eFloat
|| *aPos
% eMaxType
== eDate
,
819 memcpy(&number
, aPos
, std::min
<size_t>(sizeof(number
), aEnd
- aPos
));
820 number
= mozilla::NativeEndian::swapFromBigEndian(number
);
822 aPos
+= sizeof(number
);
824 // Note: The subtraction from 0 below is necessary to fix
825 // MSVC build warning C4146 (negating an unsigned value).
826 const uint64_t signbit
= FloatingPoint
<double>::kSignBit
;
827 uint64_t bits
= number
& signbit
? (number
& ~signbit
) : (0 - number
);
829 return BitwiseCast
<double>(bits
);
832 Result
<Ok
, nsresult
> Key::EncodeBinary(JSObject
* aObject
, bool aIsViewObject
,
833 uint8_t aTypeOffset
) {
837 // We must use JS::GetObjectAsArrayBuffer()/JS_GetObjectAsArrayBufferView()
838 // instead of js::GetArrayBufferLengthAndData(). The object might be wrapped,
839 // the former will handle the wrapped case, the later won't.
842 JS_GetObjectAsArrayBufferView(aObject
, &bufferLength
, &unused
, &bufferData
);
844 JS::GetObjectAsArrayBuffer(aObject
, &bufferLength
, &bufferData
);
847 return EncodeAsString(Span
{bufferData
, bufferLength
}.AsConst(),
848 eBinary
+ aTypeOffset
);
852 JSObject
* Key::DecodeBinary(const EncodedDataType
*& aPos
,
853 const EncodedDataType
* aEnd
, JSContext
* aCx
) {
854 JS::Rooted
<JSObject
*> rv(aCx
);
855 DecodeStringy
<eBinary
, uint8_t>(
857 [&rv
, aCx
](uint8_t** out
, uint32_t decodedSize
) {
858 UniquePtr
<void, JS::FreePolicy
> ptr
{JS_malloc(aCx
, decodedSize
)};
859 if (NS_WARN_IF(!ptr
)) {
865 *out
= static_cast<uint8_t*>(ptr
.get());
866 rv
= JS::NewArrayBufferWithContents(aCx
, decodedSize
, std::move(ptr
));
867 if (NS_WARN_IF(!rv
)) {
873 [&rv
, aCx
] { rv
= JS::NewArrayBuffer(aCx
, 0); });
877 nsresult
Key::BindToStatement(mozIStorageStatement
* aStatement
,
878 const nsACString
& aParamName
) const {
881 rv
= aStatement
->BindNullByName(aParamName
);
883 rv
= aStatement
->BindBlobByName(
884 aParamName
, reinterpret_cast<const uint8_t*>(mBuffer
.get()),
888 return NS_SUCCEEDED(rv
) ? NS_OK
: NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
891 nsresult
Key::SetFromStatement(mozIStorageStatement
* aStatement
,
893 return SetFromSource(aStatement
, aIndex
);
896 nsresult
Key::SetFromValueArray(mozIStorageValueArray
* aValues
,
898 return SetFromSource(aValues
, aIndex
);
901 IDBResult
<Ok
, IDBSpecialValue::Invalid
> Key::SetFromJSVal(
902 JSContext
* aCx
, JS::Handle
<JS::Value
> aVal
) {
905 if (aVal
.isNull() || aVal
.isUndefined()) {
910 auto result
= EncodeJSVal(aCx
, aVal
, 0);
911 if (result
.isErr()) {
919 nsresult
Key::ToJSVal(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aVal
) const {
925 const EncodedDataType
* pos
= BufferStart();
926 nsresult rv
= DecodeJSVal(pos
, BufferEnd(), aCx
, aVal
);
927 if (NS_WARN_IF(NS_FAILED(rv
))) {
931 MOZ_ASSERT(pos
>= BufferEnd());
936 nsresult
Key::ToJSVal(JSContext
* aCx
, JS::Heap
<JS::Value
>& aVal
) const {
937 JS::Rooted
<JS::Value
> value(aCx
);
938 nsresult rv
= ToJSVal(aCx
, &value
);
939 if (NS_SUCCEEDED(rv
)) {
945 IDBResult
<Ok
, IDBSpecialValue::Invalid
> Key::AppendItem(
946 JSContext
* aCx
, bool aFirstOfArray
, JS::Handle
<JS::Value
> aVal
) {
947 auto result
= EncodeJSVal(aCx
, aVal
, aFirstOfArray
? eMaxType
: 0);
948 if (result
.isErr()) {
954 template <typename T
>
955 nsresult
Key::SetFromSource(T
* aSource
, uint32_t aIndex
) {
957 uint32_t dataLength
= 0;
959 nsresult rv
= aSource
->GetSharedBlob(aIndex
, &dataLength
, &data
);
960 if (NS_WARN_IF(NS_FAILED(rv
))) {
961 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
964 mBuffer
.Assign(reinterpret_cast<const char*>(data
), dataLength
);
969 } // namespace mozilla::dom::indexedDB