Source code

Revision control

Other Tools

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/. */
6
7
#include "Key.h"
8
9
#include <algorithm>
10
#include <stdint.h> // for UINT32_MAX, uintptr_t
11
#include "IndexedDatabaseManager.h"
12
#include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,NewArrayBuffer{,WithContents},GetArrayBufferLengthAndData}
13
#include "js/Date.h"
14
#include "js/MemoryFunctions.h"
15
#include "js/Value.h"
16
#include "jsfriendapi.h"
17
#include "mozilla/Casting.h"
18
#include "mozilla/CheckedInt.h"
19
#include "mozilla/EndianUtils.h"
20
#include "mozilla/FloatingPoint.h"
21
#include "mozIStorageStatement.h"
22
#include "mozIStorageValueArray.h"
23
#include "nsAlgorithm.h"
24
#include "nsJSUtils.h"
25
#include "ReportInternalError.h"
26
#include "unicode/ucol.h"
27
#include "xpcpublic.h"
28
29
namespace mozilla {
30
namespace dom {
31
namespace indexedDB {
32
33
/*
34
Here's how we encode keys:
35
36
Basic strategy is the following
37
38
Numbers: 0x10 n n n n n n n n ("n"s are encoded 64bit float)
39
Dates: 0x20 n n n n n n n n ("n"s are encoded 64bit float)
40
Strings: 0x30 s s s ... 0 ("s"s are encoded unicode bytes)
41
Binaries: 0x40 s s s ... 0 ("s"s are encoded unicode bytes)
42
Arrays: 0x50 i i i ... 0 ("i"s are encoded array items)
43
44
45
When encoding floats, 64bit IEEE 754 are almost sortable, except that
46
positive sort lower than negative, and negative sort descending. So we use
47
the following encoding:
48
49
value < 0 ?
50
(-to64bitInt(value)) :
51
(to64bitInt(value) | 0x8000000000000000)
52
53
54
When encoding strings, we use variable-size encoding per the following table
55
56
Chars 0 - 7E are encoded as 0xxxxxxx with 1 added
57
Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F
58
subtracted
59
Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000
60
61
This ensures that the first byte is never encoded as 0, which means that the
62
string terminator (per basic-strategy table) sorts before any character.
63
The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
64
the chance that the last character is 0. See below for why.
65
66
When encoding binaries, the algorithm is the same to how strings are encoded.
67
Since each octet in binary is in the range of [0-255], it'll take 1 to 2
68
encoded unicode bytes.
69
70
When encoding Arrays, we use an additional trick. Rather than adding a byte
71
containing the value 0x50 to indicate type, we instead add 0x50 to the next
72
byte. This is usually the byte containing the type of the first item in the
73
array. So simple examples are
74
75
["foo"] 0x80 s s s 0 0 // 0x80 is 0x30 + 0x50
76
[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
77
78
Whe do this iteratively if the first item in the array is also an array
79
80
[["foo"]] 0xA0 s s s 0 0 0
81
82
However, to avoid overflow in the byte, we only do this 3 times. If the first
83
item in an array is an array, and that array also has an array as first item,
84
we simply write out the total value accumulated so far and then follow the
85
"normal" rules.
86
87
[[["foo"]]] 0xF0 0x30 s s s 0 0 0 0
88
89
There is another edge case that can happen though, which is that the array
90
doesn't have a first item to which we can add 0x50 to the type. Instead the
91
next byte would normally be the array terminator (per basic-strategy table)
92
so we simply add the 0x50 there.
93
94
[[]] 0xA0 0 // 0xA0 is 0x50 + 0x50 + 0
95
[] 0x50 // 0x50 is 0x50 + 0
96
[[], "foo"] 0xA0 0x30 s s s 0 0 // 0xA0 is 0x50 + 0x50 + 0
97
98
Note that the max-3-times rule kicks in before we get a chance to add to the
99
array terminator
100
101
[[[]]] 0xF0 0 0 0 // 0xF0 is 0x50 + 0x50 + 0x50
102
103
As a final optimization we do a post-encoding step which drops all 0s at the
104
end of the encoded buffer.
105
106
"foo" // 0x30 s s s
107
1 // 0x10 bf f0
108
["a", "b"] // 0x80 s 0 0x30 s
109
[1, 2] // 0x60 bf f0 0 0 0 0 0 0 0x10 c0
110
[[]] // 0x80
111
*/
112
113
IDBResult<void, IDBSpecialValue::Invalid> Key::SetFromString(
114
const nsAString& aString, ErrorResult& aRv) {
115
mBuffer.Truncate();
116
auto result = EncodeString(aString, 0, aRv);
117
if (result.Is(Ok, aRv)) {
118
TrimBuffer();
119
}
120
return result;
121
}
122
123
// |aPos| should point to the type indicator.
124
// The returned length doesn't include the type indicator
125
// or the terminator.
126
// static
127
uint32_t Key::LengthOfEncodedBinary(const EncodedDataType* aPos,
128
const EncodedDataType* aEnd) {
129
MOZ_ASSERT(*aPos % Key::eMaxType == Key::eBinary, "Don't call me!");
130
const EncodedDataType* encodedSectionEnd;
131
return CalcDecodedStringySize<uint8_t>(aPos + 1, aEnd, &encodedSectionEnd);
132
}
133
134
IDBResult<void, IDBSpecialValue::Invalid> Key::ToLocaleAwareKey(
135
Key& aTarget, const nsCString& aLocale, ErrorResult& aRv) const {
136
if (IsUnset()) {
137
aTarget.Unset();
138
return Ok();
139
}
140
141
if (IsFloat() || IsDate() || IsBinary()) {
142
aTarget.mBuffer = mBuffer;
143
return Ok();
144
}
145
146
aTarget.mBuffer.Truncate();
147
148
auto* it = BufferStart();
149
auto* const end = BufferEnd();
150
151
// First we do a pass and see if there are any strings in this key. We only
152
// want to copy/decode when necessary.
153
bool canShareBuffers = true;
154
while (it < end) {
155
const auto type = *it % eMaxType;
156
if (type == eTerminator) {
157
it++;
158
} else if (type == eFloat || type == eDate) {
159
it++;
160
it += std::min(sizeof(uint64_t), size_t(end - it));
161
} else if (type == eBinary) {
162
// skip all binary data
163
const auto binaryLength = LengthOfEncodedBinary(it, end);
164
it++;
165
it += binaryLength;
166
} else {
167
// We have a string!
168
canShareBuffers = false;
169
break;
170
}
171
}
172
173
if (canShareBuffers) {
174
MOZ_ASSERT(it == end);
175
aTarget.mBuffer = mBuffer;
176
return Ok();
177
}
178
179
aTarget.mBuffer.SetCapacity(mBuffer.Length());
180
181
// A string was found, so we need to copy the data we've read so far
182
auto* const start = BufferStart();
183
if (it > start) {
184
char* buffer;
185
if (!aTarget.mBuffer.GetMutableData(&buffer, it - start)) {
186
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
187
return Exception;
188
}
189
190
std::copy(start, it, buffer);
191
}
192
193
// Now continue decoding
194
while (it < end) {
195
char* buffer;
196
const uint32_t oldLen = aTarget.mBuffer.Length();
197
const auto type = *it % eMaxType;
198
199
// Note: Do not modify |it| before calling |updateBufferAndIter|;
200
// |byteCount| doesn't include the type indicator
201
const auto updateBufferAndIter = [&](size_t byteCount) -> bool {
202
if (!aTarget.mBuffer.GetMutableData(&buffer, oldLen + 1 + byteCount)) {
203
return false;
204
}
205
buffer += oldLen;
206
207
// should also copy the type indicator at the begining
208
std::copy_n(it, byteCount + 1, buffer);
209
it += (byteCount + 1);
210
return true;
211
};
212
213
if (type == eTerminator) {
214
// Copy array TypeID and terminator from raw key
215
if (!updateBufferAndIter(0)) {
216
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
217
return Exception;
218
}
219
} else if (type == eFloat || type == eDate) {
220
// Copy number from raw key
221
const size_t byteCount = std::min(sizeof(uint64_t), size_t(end - it - 1));
222
223
if (!updateBufferAndIter(byteCount)) {
224
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
225
return Exception;
226
}
227
} else if (type == eBinary) {
228
// skip all binary data
229
const auto binaryLength = LengthOfEncodedBinary(it, end);
230
231
if (!updateBufferAndIter(binaryLength)) {
232
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
233
return Exception;
234
}
235
} else {
236
// Decode string and reencode
237
const uint8_t typeOffset = *it - eString;
238
MOZ_ASSERT((typeOffset % eArray == 0) && (typeOffset / eArray <= 2));
239
240
nsDependentString str;
241
DecodeString(it, end, str);
242
auto result = aTarget.EncodeLocaleString(str, typeOffset, aLocale, aRv);
243
if (NS_WARN_IF(!result.Is(Ok, aRv))) {
244
return result;
245
}
246
}
247
}
248
aTarget.TrimBuffer();
249
return Ok();
250
}
251
252
class MOZ_STACK_CLASS Key::ArrayValueEncoder final {
253
public:
254
ArrayValueEncoder(Key& aKey, const uint8_t aTypeOffset,
255
const uint16_t aRecursionDepth)
256
: mKey(aKey),
257
mTypeOffset(aTypeOffset),
258
mRecursionDepth(aRecursionDepth) {}
259
260
void AddToSeenSet(JSContext* const aCx, JS::HandleObject) {
261
++mRecursionDepth;
262
}
263
264
void BeginSubkeyList() {
265
mTypeOffset += Key::eMaxType;
266
if (mTypeOffset == eMaxType * kMaxArrayCollapse) {
267
mKey.mBuffer.Append(mTypeOffset);
268
mTypeOffset = 0;
269
}
270
MOZ_ASSERT(mTypeOffset % eMaxType == 0,
271
"Current type offset must indicate beginning of array");
272
MOZ_ASSERT(mTypeOffset < eMaxType * kMaxArrayCollapse);
273
}
274
275
IDBResult<void, IDBSpecialValue::Invalid> ConvertSubkey(
276
JSContext* const aCx, JS::HandleValue aEntry, const uint32_t aIndex,
277
ErrorResult& aRv) {
278
const auto result = mKey.EncodeJSValInternal(aCx, aEntry, mTypeOffset,
279
mRecursionDepth, aRv);
280
mTypeOffset = 0;
281
return result;
282
}
283
284
void EndSubkeyList() const { mKey.mBuffer.Append(eTerminator + mTypeOffset); }
285
286
private:
287
Key& mKey;
288
uint8_t mTypeOffset;
289
uint16_t mRecursionDepth;
290
};
291
292
// Implements the following algorithm:
294
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeJSValInternal(
295
JSContext* const aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
296
const uint16_t aRecursionDepth, ErrorResult& aRv) {
297
static_assert(eMaxType * kMaxArrayCollapse < 256, "Unable to encode jsvals.");
298
299
// 1. If `seen` was not given, let `seen` be a new empty set.
300
// 2. If `input` is in `seen` return invalid.
301
// Note: we replace this check with a simple recursion depth check.
302
if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
303
return Invalid;
304
}
305
306
// 3. Jump to the appropriate step below:
307
// Note: some cases appear out of order to make the implementation more
308
// straightforward. This shouldn't affect observable behavior.
309
310
// If Type(`input`) is Number
311
if (aVal.isNumber()) {
312
const auto number = aVal.toNumber();
313
314
// 1. If `input` is NaN then return invalid.
315
if (mozilla::IsNaN(number)) {
316
return Invalid;
317
}
318
319
// 2. Otherwise, return a new key with type `number` and value `input`.
320
EncodeNumber(number, eFloat + aTypeOffset);
321
return Ok();
322
}
323
324
// If Type(`input`) is String
325
if (aVal.isString()) {
326
// 1. Return a new key with type `string` and value `input`.
327
nsAutoJSString string;
328
if (!string.init(aCx, aVal)) {
329
IDB_REPORT_INTERNAL_ERR();
330
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
331
return Exception;
332
}
333
return EncodeString(string, aTypeOffset, aRv);
334
}
335
336
if (aVal.isObject()) {
337
JS::RootedObject object(aCx, &aVal.toObject());
338
339
js::ESClass builtinClass;
340
if (!js::GetBuiltinClass(aCx, object, &builtinClass)) {
341
IDB_REPORT_INTERNAL_ERR();
342
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
343
return Exception;
344
}
345
346
// If `input` is a Date (has a [[DateValue]] internal slot)
347
if (builtinClass == js::ESClass::Date) {
348
// 1. Let `ms` be the value of `input`’s [[DateValue]] internal slot.
349
double ms;
350
if (!js::DateGetMsecSinceEpoch(aCx, object, &ms)) {
351
IDB_REPORT_INTERNAL_ERR();
352
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
353
return Exception;
354
}
355
356
// 2. If `ms` is NaN then return invalid.
357
if (mozilla::IsNaN(ms)) {
358
return Invalid;
359
}
360
361
// 3. Otherwise, return a new key with type `date` and value `ms`.
362
EncodeNumber(ms, eDate + aTypeOffset);
363
return Ok();
364
}
365
366
// If `input` is a buffer source type
367
if (JS::IsArrayBufferObject(object) || JS_IsArrayBufferViewObject(object)) {
368
const bool isViewObject = JS_IsArrayBufferViewObject(object);
369
return EncodeBinary(object, isViewObject, aTypeOffset, aRv);
370
}
371
372
// If IsArray(`input`)
373
if (builtinClass == js::ESClass::Array) {
374
ArrayValueEncoder encoder(*this, aTypeOffset, aRecursionDepth);
375
return ConvertArrayValueToKey(aCx, object, encoder, aRv);
376
}
377
}
378
379
// Otherwise
380
// Return invalid.
381
return Invalid;
382
}
383
384
// static
385
nsresult Key::DecodeJSValInternal(const EncodedDataType*& aPos,
386
const EncodedDataType* aEnd, JSContext* aCx,
387
uint8_t aTypeOffset,
388
JS::MutableHandle<JS::Value> aVal,
389
uint16_t aRecursionDepth) {
390
if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
391
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
392
}
393
394
if (*aPos - aTypeOffset >= eArray) {
395
JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
396
if (!array) {
397
NS_WARNING("Failed to make array!");
398
IDB_REPORT_INTERNAL_ERR();
399
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
400
}
401
402
aTypeOffset += eMaxType;
403
404
if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
405
++aPos;
406
aTypeOffset = 0;
407
}
408
409
uint32_t index = 0;
410
JS::Rooted<JS::Value> val(aCx);
411
while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) {
412
nsresult rv = DecodeJSValInternal(aPos, aEnd, aCx, aTypeOffset, &val,
413
aRecursionDepth + 1);
414
NS_ENSURE_SUCCESS(rv, rv);
415
416
aTypeOffset = 0;
417
418
if (!JS_DefineElement(aCx, array, index++, val, JSPROP_ENUMERATE)) {
419
NS_WARNING("Failed to set array element!");
420
IDB_REPORT_INTERNAL_ERR();
421
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
422
}
423
}
424
425
NS_ASSERTION(aPos >= aEnd || (*aPos % eMaxType) == eTerminator,
426
"Should have found end-of-array marker");
427
++aPos;
428
429
aVal.setObject(*array);
430
} else if (*aPos - aTypeOffset == eString) {
431
nsString key;
432
DecodeString(aPos, aEnd, key);
433
if (!xpc::StringToJsval(aCx, key, aVal)) {
434
IDB_REPORT_INTERNAL_ERR();
435
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
436
}
437
} else if (*aPos - aTypeOffset == eDate) {
438
double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
439
JS::ClippedTime time = JS::TimeClip(msec);
440
MOZ_ASSERT(msec == time.toDouble(),
441
"encoding from a Date object not containing an invalid date "
442
"means we should always have clipped values");
443
JSObject* date = JS::NewDateObject(aCx, time);
444
if (!date) {
445
IDB_WARNING("Failed to make date!");
446
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
447
}
448
449
aVal.setObject(*date);
450
} else if (*aPos - aTypeOffset == eFloat) {
451
aVal.setDouble(DecodeNumber(aPos, aEnd));
452
} else if (*aPos - aTypeOffset == eBinary) {
453
JSObject* binary = DecodeBinary(aPos, aEnd, aCx);
454
if (!binary) {
455
IDB_REPORT_INTERNAL_ERR();
456
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
457
}
458
459
aVal.setObject(*binary);
460
} else {
461
MOZ_ASSERT_UNREACHABLE("Unknown key type!");
462
}
463
464
return NS_OK;
465
}
466
467
#define ONE_BYTE_LIMIT 0x7E
468
#define TWO_BYTE_LIMIT (0x3FFF + 0x7F)
469
470
#define ONE_BYTE_ADJUST 1
471
#define TWO_BYTE_ADJUST (-0x7F)
472
#define THREE_BYTE_SHIFT 6
473
474
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeJSVal(
475
JSContext* aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
476
ErrorResult& aRv) {
477
return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0, aRv);
478
}
479
480
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeString(
481
const nsAString& aString, uint8_t aTypeOffset, ErrorResult& aRv) {
482
const char16_t* start = aString.BeginReading();
483
const char16_t* end = aString.EndReading();
484
return EncodeString(start, end, aTypeOffset, aRv);
485
}
486
487
template <typename T>
488
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeString(const T* aStart,
489
const T* aEnd,
490
uint8_t aTypeOffset,
491
ErrorResult& aRv) {
492
return EncodeAsString(aStart, aEnd, eString + aTypeOffset, aRv);
493
}
494
495
template <typename T>
496
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeAsString(
497
const T* aStart, const T* aEnd, uint8_t aType, ErrorResult& aRv) {
498
// First measure how long the encoded string will be.
499
if (NS_WARN_IF(aStart > aEnd || UINT32_MAX - 2 < uintptr_t(aEnd - aStart))) {
500
IDB_REPORT_INTERNAL_ERR();
501
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
502
return Exception;
503
}
504
505
// The +2 is for initial aType and trailing 0. We'll compensate for multi-byte
506
// chars below.
507
uint32_t checkedSize = aEnd - aStart;
508
CheckedUint32 size = checkedSize;
509
size += 2;
510
511
MOZ_ASSERT(size.isValid());
512
513
const T* start = aStart;
514
const T* end = aEnd;
515
for (const T* iter = start; iter < end; ++iter) {
516
if (*iter > ONE_BYTE_LIMIT) {
517
size += char16_t(*iter) > TWO_BYTE_LIMIT ? 2 : 1;
518
if (!size.isValid()) {
519
IDB_REPORT_INTERNAL_ERR();
520
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
521
return Exception;
522
}
523
}
524
}
525
526
// Allocate memory for the new size
527
uint32_t oldLen = mBuffer.Length();
528
size += oldLen;
529
530
if (!size.isValid()) {
531
IDB_REPORT_INTERNAL_ERR();
532
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
533
return Exception;
534
}
535
536
char* buffer;
537
if (!mBuffer.GetMutableData(&buffer, size.value())) {
538
IDB_REPORT_INTERNAL_ERR();
539
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
540
return Exception;
541
}
542
buffer += oldLen;
543
544
// Write type marker
545
*(buffer++) = aType;
546
547
// Encode string
548
for (const T* iter = start; iter < end; ++iter) {
549
if (*iter <= ONE_BYTE_LIMIT) {
550
*(buffer++) = *iter + ONE_BYTE_ADJUST;
551
} else if (char16_t(*iter) <= TWO_BYTE_LIMIT) {
552
char16_t c = char16_t(*iter) + TWO_BYTE_ADJUST + 0x8000;
553
*(buffer++) = (char)(c >> 8);
554
*(buffer++) = (char)(c & 0xFF);
555
} else {
556
uint32_t c = (uint32_t(*iter) << THREE_BYTE_SHIFT) | 0x00C00000;
557
*(buffer++) = (char)(c >> 16);
558
*(buffer++) = (char)(c >> 8);
559
*(buffer++) = (char)c;
560
}
561
}
562
563
// Write end marker
564
*(buffer++) = eTerminator;
565
566
NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
567
568
return indexedDB::Ok();
569
}
570
571
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeLocaleString(
572
const nsDependentString& aString, uint8_t aTypeOffset,
573
const nsCString& aLocale, ErrorResult& aRv) {
574
const int length = aString.Length();
575
if (length == 0) {
576
return Ok();
577
}
578
const UChar* ustr = reinterpret_cast<const UChar*>(aString.BeginReading());
579
580
UErrorCode uerror = U_ZERO_ERROR;
581
UCollator* collator = ucol_open(aLocale.get(), &uerror);
582
if (NS_WARN_IF(U_FAILURE(uerror))) {
583
aRv.Throw(NS_ERROR_FAILURE);
584
return Exception;
585
}
586
MOZ_ASSERT(collator);
587
588
AutoTArray<uint8_t, 128> keyBuffer;
589
int32_t sortKeyLength = ucol_getSortKey(
590
collator, ustr, length, keyBuffer.Elements(), keyBuffer.Length());
591
if (sortKeyLength > (int32_t)keyBuffer.Length()) {
592
keyBuffer.SetLength(sortKeyLength);
593
sortKeyLength = ucol_getSortKey(collator, ustr, length,
594
keyBuffer.Elements(), sortKeyLength);
595
}
596
597
ucol_close(collator);
598
if (NS_WARN_IF(sortKeyLength == 0)) {
599
aRv.Throw(NS_ERROR_FAILURE);
600
return Exception;
601
}
602
603
return EncodeString(keyBuffer.Elements(),
604
keyBuffer.Elements() + sortKeyLength, aTypeOffset, aRv);
605
}
606
607
// static
608
nsresult Key::DecodeJSVal(const EncodedDataType*& aPos,
609
const EncodedDataType* aEnd, JSContext* aCx,
610
JS::MutableHandle<JS::Value> aVal) {
611
return DecodeJSValInternal(aPos, aEnd, aCx, 0, aVal, 0);
612
}
613
614
// static
615
template <typename T>
616
uint32_t Key::CalcDecodedStringySize(
617
const EncodedDataType* const aBegin, const EncodedDataType* const aEnd,
618
const EncodedDataType** aOutEncodedSectionEnd) {
619
static_assert(sizeof(T) <= 2,
620
"Only implemented for 1 and 2 byte decoded types");
621
uint32_t decodedSize = 0;
622
auto* iter = aBegin;
623
for (; iter < aEnd && *iter != eTerminator; ++iter) {
624
if (*iter & 0x80) {
625
iter += (sizeof(T) > 1 && (*iter & 0x40)) ? 2 : 1;
626
}
627
++decodedSize;
628
}
629
*aOutEncodedSectionEnd = std::min(aEnd, iter);
630
return decodedSize;
631
}
632
633
// static
634
template <typename T>
635
void Key::DecodeAsStringy(const EncodedDataType* const aEncodedSectionBegin,
636
const EncodedDataType* const aEncodedSectionEnd,
637
const uint32_t aDecodedLength, T* const aOut) {
638
static_assert(sizeof(T) <= 2,
639
"Only implemented for 1 and 2 byte decoded types");
640
T* decodedPos = aOut;
641
for (const EncodedDataType* iter = aEncodedSectionBegin;
642
iter < aEncodedSectionEnd;) {
643
if (!(*iter & 0x80)) {
644
*decodedPos = *(iter++) - ONE_BYTE_ADJUST;
645
} else if (sizeof(T) == 1 || !(*iter & 0x40)) {
646
auto c = static_cast<uint16_t>(*(iter++)) << 8;
647
if (iter < aEncodedSectionEnd) {
648
c |= *(iter++);
649
}
650
*decodedPos = static_cast<T>(c - TWO_BYTE_ADJUST - 0x8000);
651
} else if (sizeof(T) > 1) {
652
auto c = static_cast<uint32_t>(*(iter++)) << (16 - THREE_BYTE_SHIFT);
653
if (iter < aEncodedSectionEnd) {
654
c |= static_cast<uint32_t>(*(iter++)) << (8 - THREE_BYTE_SHIFT);
655
}
656
if (iter < aEncodedSectionEnd) {
657
c |= *(iter++) >> THREE_BYTE_SHIFT;
658
}
659
*decodedPos = static_cast<T>(c);
660
}
661
++decodedPos;
662
}
663
664
MOZ_ASSERT(static_cast<uint32_t>(decodedPos - aOut) == aDecodedLength,
665
"Should have written the whole decoded area");
666
}
667
668
// static
669
template <Key::EncodedDataType TypeMask, typename T, typename AcquireBuffer,
670
typename AcquireEmpty>
671
void Key::DecodeStringy(const EncodedDataType*& aPos,
672
const EncodedDataType* aEnd,
673
const AcquireBuffer& acquireBuffer,
674
const AcquireEmpty& acquireEmpty) {
675
NS_ASSERTION(*aPos % eMaxType == TypeMask, "Don't call me!");
676
677
// First measure how big the decoded stringy data will be.
678
const EncodedDataType* const encodedSectionBegin = aPos + 1;
679
const EncodedDataType* encodedSectionEnd;
680
// decodedLength does not include the terminating 0 (in case of a string)
681
const uint32_t decodedLength =
682
CalcDecodedStringySize<T>(encodedSectionBegin, aEnd, &encodedSectionEnd);
683
aPos = encodedSectionEnd + 1;
684
685
if (!decodedLength) {
686
acquireEmpty();
687
return;
688
}
689
690
T* out;
691
if (!acquireBuffer(&out, decodedLength)) {
692
return;
693
}
694
695
DecodeAsStringy(encodedSectionBegin, encodedSectionEnd, decodedLength, out);
696
}
697
698
// static
699
void Key::DecodeString(const EncodedDataType*& aPos,
700
const EncodedDataType* const aEnd, nsString& aString) {
701
MOZ_ASSERT(aString.IsEmpty(), "aString should be empty on call!");
702
703
DecodeStringy<eString, char16_t>(
704
aPos, aEnd,
705
[&aString](char16_t** out, uint32_t decodedLength) {
706
return 0 != aString.GetMutableData(out, decodedLength);
707
},
708
[] {});
709
}
710
711
void Key::EncodeNumber(double aFloat, uint8_t aType) {
712
// Allocate memory for the new size
713
uint32_t oldLen = mBuffer.Length();
714
char* buffer;
715
if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) {
716
return;
717
}
718
buffer += oldLen;
719
720
*(buffer++) = aType;
721
722
uint64_t bits = BitwiseCast<uint64_t>(aFloat);
723
// Note: The subtraction from 0 below is necessary to fix
724
// MSVC build warning C4146 (negating an unsigned value).
725
const uint64_t signbit = FloatingPoint<double>::kSignBit;
726
uint64_t number = bits & signbit ? (0 - bits) : (bits | signbit);
727
728
mozilla::BigEndian::writeUint64(buffer, number);
729
}
730
731
// static
732
double Key::DecodeNumber(const EncodedDataType*& aPos,
733
const EncodedDataType* aEnd) {
734
NS_ASSERTION(*aPos % eMaxType == eFloat || *aPos % eMaxType == eDate,
735
"Don't call me!");
736
737
++aPos;
738
739
uint64_t number = 0;
740
memcpy(&number, aPos, std::min<size_t>(sizeof(number), aEnd - aPos));
741
number = mozilla::NativeEndian::swapFromBigEndian(number);
742
743
aPos += sizeof(number);
744
745
// Note: The subtraction from 0 below is necessary to fix
746
// MSVC build warning C4146 (negating an unsigned value).
747
const uint64_t signbit = FloatingPoint<double>::kSignBit;
748
uint64_t bits = number & signbit ? (number & ~signbit) : (0 - number);
749
750
return BitwiseCast<double>(bits);
751
}
752
753
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeBinary(JSObject* aObject,
754
bool aIsViewObject,
755
uint8_t aTypeOffset,
756
ErrorResult& aRv) {
757
uint8_t* bufferData;
758
uint32_t bufferLength;
759
bool unused;
760
761
if (aIsViewObject) {
762
// We must use JS_GetObjectAsArrayBufferView() instead of
763
// js::GetArrayBufferLengthAndData(). Because we check ArrayBufferView
764
// via JS_IsArrayBufferViewObject(), the object might be wrapped,
765
// the former will handle the wrapped case, the later won't.
766
JS_GetObjectAsArrayBufferView(aObject, &bufferLength, &unused, &bufferData);
767
768
} else {
769
JS::GetArrayBufferLengthAndData(aObject, &bufferLength, &unused,
770
&bufferData);
771
}
772
773
return EncodeAsString(bufferData, bufferData + bufferLength,
774
eBinary + aTypeOffset, aRv);
775
}
776
777
// static
778
JSObject* Key::DecodeBinary(const EncodedDataType*& aPos,
779
const EncodedDataType* aEnd, JSContext* aCx) {
780
JS::RootedObject rv(aCx);
781
DecodeStringy<eBinary, uint8_t>(
782
aPos, aEnd,
783
[&rv, aCx](uint8_t** out, uint32_t decodedSize) {
784
*out = static_cast<uint8_t*>(JS_malloc(aCx, decodedSize));
785
if (NS_WARN_IF(!*out)) {
786
rv = nullptr;
787
return false;
788
}
789
rv = JS::NewArrayBufferWithContents(aCx, decodedSize, *out);
790
return true;
791
},
792
[&rv, aCx] { rv = JS::NewArrayBuffer(aCx, 0); });
793
return rv;
794
}
795
796
nsresult Key::BindToStatement(mozIStorageStatement* aStatement,
797
const nsACString& aParamName) const {
798
nsresult rv;
799
if (IsUnset()) {
800
rv = aStatement->BindNullByName(aParamName);
801
} else {
802
rv = aStatement->BindBlobByName(
803
aParamName, reinterpret_cast<const uint8_t*>(mBuffer.get()),
804
mBuffer.Length());
805
}
806
807
return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
808
}
809
810
nsresult Key::SetFromStatement(mozIStorageStatement* aStatement,
811
uint32_t aIndex) {
812
return SetFromSource(aStatement, aIndex);
813
}
814
815
nsresult Key::SetFromValueArray(mozIStorageValueArray* aValues,
816
uint32_t aIndex) {
817
return SetFromSource(aValues, aIndex);
818
}
819
820
IDBResult<void, IDBSpecialValue::Invalid> Key::SetFromJSVal(
821
JSContext* aCx, JS::Handle<JS::Value> aVal, ErrorResult& aRv) {
822
mBuffer.Truncate();
823
824
if (aVal.isNull() || aVal.isUndefined()) {
825
Unset();
826
return Ok();
827
}
828
829
auto result = EncodeJSVal(aCx, aVal, 0, aRv);
830
if (!result.Is(Ok, aRv)) {
831
Unset();
832
return result;
833
}
834
TrimBuffer();
835
return Ok();
836
}
837
838
nsresult Key::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) const {
839
if (IsUnset()) {
840
aVal.setUndefined();
841
return NS_OK;
842
}
843
844
const EncodedDataType* pos = BufferStart();
845
nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, aVal);
846
if (NS_WARN_IF(NS_FAILED(rv))) {
847
return rv;
848
}
849
850
MOZ_ASSERT(pos >= BufferEnd());
851
852
return NS_OK;
853
}
854
855
nsresult Key::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aVal) const {
856
JS::Rooted<JS::Value> value(aCx);
857
nsresult rv = ToJSVal(aCx, &value);
858
if (NS_SUCCEEDED(rv)) {
859
aVal = value;
860
}
861
return rv;
862
}
863
864
IDBResult<void, IDBSpecialValue::Invalid> Key::AppendItem(
865
JSContext* aCx, bool aFirstOfArray, JS::Handle<JS::Value> aVal,
866
ErrorResult& aRv) {
867
auto result = EncodeJSVal(aCx, aVal, aFirstOfArray ? eMaxType : 0, aRv);
868
if (!result.Is(Ok, aRv)) {
869
Unset();
870
}
871
return result;
872
}
873
874
template <typename T>
875
nsresult Key::SetFromSource(T* aSource, uint32_t aIndex) {
876
const uint8_t* data;
877
uint32_t dataLength = 0;
878
879
nsresult rv = aSource->GetSharedBlob(aIndex, &dataLength, &data);
880
if (NS_WARN_IF(NS_FAILED(rv))) {
881
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
882
}
883
884
mBuffer.Assign(reinterpret_cast<const char*>(data), dataLength);
885
886
return NS_OK;
887
}
888
889
} // namespace indexedDB
890
} // namespace dom
891
} // namespace mozilla