1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Indexed Database.
17 : *
18 : * The Initial Developer of the Original Code is The Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Jan Varga <Jan.Varga@gmail.com>
24 : * Jonas Sicking <jonas@sicking.cc>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "Key.h"
41 : #include "nsIStreamBufferAccess.h"
42 : #include "jsfriendapi.h"
43 : #include "nsAlgorithm.h"
44 : #include "nsContentUtils.h"
45 : #include "nsJSUtils.h"
46 : #include "xpcpublic.h"
47 :
48 : USING_INDEXEDDB_NAMESPACE
49 :
50 : /*
51 : Here's how we encode keys:
52 :
53 : Basic strategy is the following
54 :
55 : Numbers: 1 n n n n n n n n ("n"s are encoded 64bit float)
56 : Dates: 2 n n n n n n n n ("n"s are encoded 64bit float)
57 : Strings: 3 s s s ... 0 ("s"s are encoded unicode bytes)
58 : Arrays: 4 i i i ... 0 ("i"s are encoded array items)
59 :
60 :
61 : When encoding floats, 64bit IEEE 754 are almost sortable, except that
62 : positive sort lower than negative, and negative sort descending. So we use
63 : the following encoding:
64 :
65 : value < 0 ?
66 : (-to64bitInt(value)) :
67 : (to64bitInt(value) | 0x8000000000000000)
68 :
69 :
70 : When encoding strings, we use variable-size encoding per the following table
71 :
72 : Chars 0 - 7E are encoded as 0xxxxxxx with 1 added
73 : Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F subtracted
74 : Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000
75 :
76 : This ensures that the first byte is never encoded as 0, which means that the
77 : string terminator (per basic-stategy table) sorts before any character.
78 : The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
79 : the chance that the last character is 0. See below for why.
80 :
81 :
82 : When encoding Arrays, we use an additional trick. Rather than adding a byte
83 : containing the value '4' to indicate type, we instead add 4 to the next byte.
84 : This is usually the byte containing the type of the first item in the array.
85 : So simple examples are
86 :
87 : ["foo"] 7 s s s 0 0 // 7 is 3 + 4
88 : [1, 2] 5 n n n n n n n n 1 n n n n n n n n 0 // 5 is 1 + 4
89 :
90 : Whe do this iteratively if the first item in the array is also an array
91 :
92 : [["foo"]] 11 s s s 0 0 0
93 :
94 : However, to avoid overflow in the byte, we only do this 3 times. If the first
95 : item in an array is an array, and that array also has an array as first item,
96 : we simply write out the total value accumulated so far and then follow the
97 : "normal" rules.
98 :
99 : [[["foo"]]] 12 3 s s s 0 0 0 0
100 :
101 : There is another edge case that can happen though, which is that the array
102 : doesn't have a first item to which we can add 4 to the type. Instead the
103 : next byte would normally be the array terminator (per basic-strategy table)
104 : so we simply add the 4 there.
105 :
106 : [[]] 8 0 // 8 is 4 + 4 + 0
107 : [] 4 // 4 is 4 + 0
108 : [[], "foo"] 8 3 s s s 0 0 // 8 is 4 + 4 + 0
109 :
110 : Note that the max-3-times rule kicks in before we get a chance to add to the
111 : array terminator
112 :
113 : [[[]]] 12 0 0 0 // 12 is 4 + 4 + 4
114 :
115 : We could use a much higher number than 3 at no complexity or performance cost,
116 : however it seems unlikely that it'll make a practical difference, and the low
117 : limit makes testing eaiser.
118 :
119 :
120 : As a final optimization we do a post-encoding step which drops all 0s at the
121 : end of the encoded buffer.
122 :
123 : "foo" // 3 s s s
124 : 1 // 1 bf f0
125 : ["a", "b"] // 7 s 3 s
126 : [1, 2] // 5 bf f0 0 0 0 0 0 0 1 c0
127 : [[]] // 8
128 : */
129 :
130 : const int MaxArrayCollapse = 3;
131 :
132 : nsresult
133 11743 : Key::EncodeJSVal(JSContext* aCx, const jsval aVal, PRUint8 aTypeOffset)
134 : {
135 : PR_STATIC_ASSERT(eMaxType * MaxArrayCollapse < 256);
136 :
137 11743 : if (JSVAL_IS_STRING(aVal)) {
138 7524 : nsDependentJSString str;
139 3762 : if (!str.init(aCx, aVal)) {
140 0 : return NS_ERROR_OUT_OF_MEMORY;
141 : }
142 3762 : EncodeString(str, aTypeOffset);
143 3762 : return NS_OK;
144 : }
145 :
146 7981 : if (JSVAL_IS_INT(aVal)) {
147 1968 : EncodeNumber((double)JSVAL_TO_INT(aVal), eFloat + aTypeOffset);
148 1968 : return NS_OK;
149 : }
150 :
151 6013 : if (JSVAL_IS_DOUBLE(aVal)) {
152 704 : double d = JSVAL_TO_DOUBLE(aVal);
153 704 : if (DOUBLE_IS_NaN(d)) {
154 12 : return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
155 : }
156 692 : EncodeNumber(d, eFloat + aTypeOffset);
157 692 : return NS_OK;
158 : }
159 :
160 5309 : if (!JSVAL_IS_PRIMITIVE(aVal)) {
161 5280 : JSObject* obj = JSVAL_TO_OBJECT(aVal);
162 5280 : if (JS_IsArrayObject(aCx, obj)) {
163 4366 : aTypeOffset += eMaxType;
164 :
165 4366 : if (aTypeOffset == eMaxType * MaxArrayCollapse) {
166 638 : mBuffer.Append(aTypeOffset);
167 638 : aTypeOffset = 0;
168 : }
169 4366 : NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
170 : aTypeOffset < (eMaxType * MaxArrayCollapse),
171 : "Wrong typeoffset");
172 :
173 : uint32_t length;
174 4366 : if (!JS_GetArrayLength(aCx, obj, &length)) {
175 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
176 : }
177 :
178 8912 : for (uint32_t index = 0; index < length; index++) {
179 : jsval val;
180 4612 : if (!JS_GetElement(aCx, obj, index, &val)) {
181 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
182 : }
183 :
184 4612 : nsresult rv = EncodeJSVal(aCx, val, aTypeOffset);
185 4612 : NS_ENSURE_SUCCESS(rv, rv);
186 :
187 4546 : aTypeOffset = 0;
188 : }
189 :
190 4300 : mBuffer.Append(eTerminator + aTypeOffset);
191 :
192 4300 : return NS_OK;
193 : }
194 :
195 914 : if (JS_ObjectIsDate(aCx, obj)) {
196 846 : EncodeNumber(js_DateGetMsecSinceEpoch(aCx, obj), eDate + aTypeOffset);
197 846 : return NS_OK;
198 : }
199 : }
200 :
201 97 : return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
202 : }
203 :
204 : // static
205 : nsresult
206 2885 : Key::DecodeJSVal(const unsigned char*& aPos, const unsigned char* aEnd,
207 : JSContext* aCx, PRUint8 aTypeOffset, jsval* aVal)
208 : {
209 2885 : if (*aPos - aTypeOffset >= eArray) {
210 202 : JSObject* array = JS_NewArrayObject(aCx, 0, nsnull);
211 202 : if (!array) {
212 0 : NS_WARNING("Failed to make array!");
213 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
214 : }
215 :
216 202 : aTypeOffset += eMaxType;
217 :
218 202 : if (aTypeOffset == eMaxType * MaxArrayCollapse) {
219 32 : ++aPos;
220 32 : aTypeOffset = 0;
221 : }
222 :
223 202 : uint32_t index = 0;
224 612 : while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) {
225 : jsval val;
226 208 : nsresult rv = DecodeJSVal(aPos, aEnd, aCx, aTypeOffset, &val);
227 208 : NS_ENSURE_SUCCESS(rv, rv);
228 :
229 208 : aTypeOffset = 0;
230 :
231 208 : if (!JS_SetElement(aCx, array, index++, &val)) {
232 0 : NS_WARNING("Failed to set array element!");
233 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
234 : }
235 : }
236 :
237 202 : NS_ASSERTION(aPos >= aEnd || (*aPos % eMaxType) == eTerminator,
238 : "Should have found end-of-array marker");
239 202 : ++aPos;
240 :
241 202 : *aVal = OBJECT_TO_JSVAL(array);
242 : }
243 2683 : else if (*aPos - aTypeOffset == eString) {
244 1160 : nsString key;
245 580 : DecodeString(aPos, aEnd, key);
246 580 : if (!xpc::StringToJsval(aCx, key, aVal)) {
247 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
248 : }
249 : }
250 2103 : else if (*aPos - aTypeOffset == eDate) {
251 36 : double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
252 36 : JSObject* date = JS_NewDateObjectMsec(aCx, msec);
253 36 : if (!date) {
254 0 : NS_WARNING("Failed to make date!");
255 0 : return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
256 : }
257 :
258 36 : *aVal = OBJECT_TO_JSVAL(date);
259 : }
260 2067 : else if (*aPos - aTypeOffset == eFloat) {
261 2067 : *aVal = DOUBLE_TO_JSVAL(DecodeNumber(aPos, aEnd));
262 : }
263 : else {
264 0 : NS_NOTREACHED("Unknown key type!");
265 : }
266 :
267 2885 : return NS_OK;
268 : }
269 :
270 :
271 : #define ONE_BYTE_LIMIT 0x7E
272 : #define TWO_BYTE_LIMIT (0x3FFF+0x7F)
273 :
274 : #define ONE_BYTE_ADJUST 1
275 : #define TWO_BYTE_ADJUST (-0x7F)
276 : #define THREE_BYTE_SHIFT 6
277 :
278 : void
279 3762 : Key::EncodeString(const nsAString& aString, PRUint8 aTypeOffset)
280 : {
281 : // First measure how long the encoded string will be.
282 :
283 : // The +2 is for initial 3 and trailing 0. We'll compensate for multi-byte
284 : // chars below.
285 3762 : PRUint32 size = aString.Length() + 2;
286 :
287 3762 : const PRUnichar* start = aString.BeginReading();
288 3762 : const PRUnichar* end = aString.EndReading();
289 11128 : for (const PRUnichar* iter = start; iter < end; ++iter) {
290 7366 : if (*iter > ONE_BYTE_LIMIT) {
291 1426 : size += *iter > TWO_BYTE_LIMIT ? 2 : 1;
292 : }
293 : }
294 :
295 : // Allocate memory for the new size
296 3762 : PRUint32 oldLen = mBuffer.Length();
297 : char* buffer;
298 3762 : if (!mBuffer.GetMutableData(&buffer, oldLen + size)) {
299 0 : return;
300 : }
301 3762 : buffer += oldLen;
302 :
303 : // Write type marker
304 3762 : *(buffer++) = eString + aTypeOffset;
305 :
306 : // Encode string
307 11128 : for (const PRUnichar* iter = start; iter < end; ++iter) {
308 7366 : if (*iter <= ONE_BYTE_LIMIT) {
309 5940 : *(buffer++) = *iter + ONE_BYTE_ADJUST;
310 : }
311 1426 : else if (*iter <= TWO_BYTE_LIMIT) {
312 752 : PRUnichar c = PRUnichar(*iter) + TWO_BYTE_ADJUST + 0x8000;
313 752 : *(buffer++) = (char)(c >> 8);
314 752 : *(buffer++) = (char)(c & 0xFF);
315 : }
316 : else {
317 674 : PRUint32 c = (PRUint32(*iter) << THREE_BYTE_SHIFT) | 0x00C00000;
318 674 : *(buffer++) = (char)(c >> 16);
319 674 : *(buffer++) = (char)(c >> 8);
320 674 : *(buffer++) = (char)c;
321 : }
322 : }
323 :
324 : // Write end marker
325 3762 : *(buffer++) = eTerminator;
326 :
327 3762 : NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
328 : }
329 :
330 : // static
331 : void
332 580 : Key::DecodeString(const unsigned char*& aPos, const unsigned char* aEnd,
333 : nsString& aString)
334 : {
335 580 : NS_ASSERTION(*aPos % eMaxType == eString, "Don't call me!");
336 :
337 580 : const unsigned char* buffer = aPos + 1;
338 :
339 : // First measure how big the decoded string will be.
340 580 : PRUint32 size = 0;
341 : const unsigned char* iter;
342 4014 : for (iter = buffer; iter < aEnd && *iter != eTerminator; ++iter) {
343 3434 : if (*iter & 0x80) {
344 60 : iter += (*iter & 0x40) ? 2 : 1;
345 : }
346 3434 : ++size;
347 : }
348 :
349 : // Set end so that we don't have to check for null termination in the loop
350 : // below
351 580 : if (iter < aEnd) {
352 14 : aEnd = iter;
353 : }
354 :
355 : PRUnichar* out;
356 580 : if (size && !aString.GetMutableData(&out, size)) {
357 0 : return;
358 : }
359 :
360 4594 : for (iter = buffer; iter < aEnd;) {
361 3434 : if (!(*iter & 0x80)) {
362 3374 : *out = *(iter++) - ONE_BYTE_ADJUST;
363 : }
364 60 : else if (!(*iter & 0x40)) {
365 32 : PRUnichar c = (PRUnichar(*(iter++)) << 8);
366 32 : if (iter < aEnd) {
367 30 : c |= *(iter++);
368 : }
369 32 : *out = c - TWO_BYTE_ADJUST - 0x8000;
370 : }
371 : else {
372 28 : PRUint32 c = PRUint32(*(iter++)) << (16 - THREE_BYTE_SHIFT);
373 28 : if (iter < aEnd) {
374 20 : c |= PRUint32(*(iter++)) << (8 - THREE_BYTE_SHIFT);
375 : }
376 28 : if (iter < aEnd) {
377 20 : c |= *(iter++) >> THREE_BYTE_SHIFT;
378 : }
379 28 : *out = (PRUnichar)c;
380 : }
381 :
382 3434 : ++out;
383 : }
384 :
385 580 : NS_ASSERTION(!size || out == aString.EndReading(),
386 : "Should have written the whole string");
387 :
388 580 : aPos = iter + 1;
389 : }
390 :
391 : union Float64Union {
392 : double d;
393 : PRUint64 u;
394 : };
395 :
396 : void
397 4813 : Key::EncodeNumber(double aFloat, PRUint8 aType)
398 : {
399 : // Allocate memory for the new size
400 4813 : PRUint32 oldLen = mBuffer.Length();
401 : char* buffer;
402 4813 : if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) {
403 0 : return;
404 : }
405 4813 : buffer += oldLen;
406 :
407 4813 : *(buffer++) = aType;
408 :
409 : Float64Union pun;
410 4813 : pun.d = aFloat;
411 : PRUint64 number = pun.u & PR_UINT64(0x8000000000000000) ?
412 : -pun.u :
413 4813 : (pun.u | PR_UINT64(0x8000000000000000));
414 :
415 4813 : number = NS_SWAP64(number);
416 4813 : memcpy(buffer, &number, sizeof(number));
417 : }
418 :
419 : // static
420 : double
421 2236 : Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd)
422 : {
423 2236 : NS_ASSERTION(*aPos % eMaxType == eFloat ||
424 : *aPos % eMaxType == eDate, "Don't call me!");
425 :
426 2236 : ++aPos;
427 :
428 2236 : PRUint64 number = 0;
429 2236 : memcpy(&number, aPos, NS_MIN<size_t>(sizeof(number), aEnd - aPos));
430 2236 : number = NS_SWAP64(number);
431 :
432 2236 : aPos += sizeof(number);
433 :
434 : Float64Union pun;
435 : pun.u = number & PR_UINT64(0x8000000000000000) ?
436 : (number & ~PR_UINT64(0x8000000000000000)) :
437 2236 : -number;
438 :
439 2236 : return pun.d;
440 : }
|