[WebAuthN] Import a JS CBOR coder
[WebKit-https.git] / LayoutTests / http / wpt / webauthn / resources / cbor.js
1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 (function(global, undefined) { "use strict";
26 var POW_2_24 = 5.960464477539063e-8,
27     POW_2_32 = 4294967296,
28     POW_2_53 = 9007199254740992;
29
30 function encode(value) {
31   var data = new ArrayBuffer(256);
32   var dataView = new DataView(data);
33   var lastLength;
34   var offset = 0;
35
36   function prepareWrite(length) {
37     var newByteLength = data.byteLength;
38     var requiredLength = offset + length;
39     while (newByteLength < requiredLength)
40       newByteLength <<= 1;
41     if (newByteLength !== data.byteLength) {
42       var oldDataView = dataView;
43       data = new ArrayBuffer(newByteLength);
44       dataView = new DataView(data);
45       var uint32count = (offset + 3) >> 2;
46       for (var i = 0; i < uint32count; ++i)
47         dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
48     }
49
50     lastLength = length;
51     return dataView;
52   }
53   function commitWrite() {
54     offset += lastLength;
55   }
56   function writeFloat64(value) {
57     commitWrite(prepareWrite(8).setFloat64(offset, value));
58   }
59   function writeUint8(value) {
60     commitWrite(prepareWrite(1).setUint8(offset, value));
61   }
62   function writeUint8Array(value) {
63     var dataView = prepareWrite(value.length);
64     for (var i = 0; i < value.length; ++i)
65       dataView.setUint8(offset + i, value[i]);
66     commitWrite();
67   }
68   function writeUint16(value) {
69     commitWrite(prepareWrite(2).setUint16(offset, value));
70   }
71   function writeUint32(value) {
72     commitWrite(prepareWrite(4).setUint32(offset, value));
73   }
74   function writeUint64(value) {
75     var low = value % POW_2_32;
76     var high = (value - low) / POW_2_32;
77     var dataView = prepareWrite(8);
78     dataView.setUint32(offset, high);
79     dataView.setUint32(offset + 4, low);
80     commitWrite();
81   }
82   function writeTypeAndLength(type, length) {
83     if (length < 24) {
84       writeUint8(type << 5 | length);
85     } else if (length < 0x100) {
86       writeUint8(type << 5 | 24);
87       writeUint8(length);
88     } else if (length < 0x10000) {
89       writeUint8(type << 5 | 25);
90       writeUint16(length);
91     } else if (length < 0x100000000) {
92       writeUint8(type << 5 | 26);
93       writeUint32(length);
94     } else {
95       writeUint8(type << 5 | 27);
96       writeUint64(length);
97     }
98   }
99
100   function encodeItem(value) {
101     var i;
102
103     if (value === false)
104       return writeUint8(0xf4);
105     if (value === true)
106       return writeUint8(0xf5);
107     if (value === null)
108       return writeUint8(0xf6);
109     if (value === undefined)
110       return writeUint8(0xf7);
111
112     switch (typeof value) {
113       case "number":
114         if (Math.floor(value) === value) {
115           if (0 <= value && value <= POW_2_53)
116             return writeTypeAndLength(0, value);
117           if (-POW_2_53 <= value && value < 0)
118             return writeTypeAndLength(1, -(value + 1));
119         }
120         writeUint8(0xfb);
121         return writeFloat64(value);
122
123       case "string":
124         var utf8data = [];
125         for (i = 0; i < value.length; ++i) {
126           var charCode = value.charCodeAt(i);
127           if (charCode < 0x80) {
128             utf8data.push(charCode);
129           } else if (charCode < 0x800) {
130             utf8data.push(0xc0 | charCode >> 6);
131             utf8data.push(0x80 | charCode & 0x3f);
132           } else if (charCode < 0xd800) {
133             utf8data.push(0xe0 | charCode >> 12);
134             utf8data.push(0x80 | (charCode >> 6)  & 0x3f);
135             utf8data.push(0x80 | charCode & 0x3f);
136           } else {
137             charCode = (charCode & 0x3ff) << 10;
138             charCode |= value.charCodeAt(++i) & 0x3ff;
139             charCode += 0x10000;
140
141             utf8data.push(0xf0 | charCode >> 18);
142             utf8data.push(0x80 | (charCode >> 12)  & 0x3f);
143             utf8data.push(0x80 | (charCode >> 6)  & 0x3f);
144             utf8data.push(0x80 | charCode & 0x3f);
145           }
146         }
147
148         writeTypeAndLength(3, utf8data.length);
149         return writeUint8Array(utf8data);
150
151       default:
152         var length;
153         if (Array.isArray(value)) {
154           length = value.length;
155           writeTypeAndLength(4, length);
156           for (i = 0; i < length; ++i)
157             encodeItem(value[i]);
158         } else if (value instanceof Uint8Array) {
159           writeTypeAndLength(2, value.length);
160           writeUint8Array(value);
161         } else {
162           var keys = Object.keys(value);
163           length = keys.length;
164           writeTypeAndLength(5, length);
165           for (i = 0; i < length; ++i) {
166             var key = keys[i];
167             encodeItem(key);
168             encodeItem(value[key]);
169           }
170         }
171     }
172   }
173
174   encodeItem(value);
175
176   if ("slice" in data)
177     return data.slice(0, offset);
178
179   var ret = new ArrayBuffer(offset);
180   var retView = new DataView(ret);
181   for (var i = 0; i < offset; ++i)
182     retView.setUint8(i, dataView.getUint8(i));
183   return ret;
184 }
185
186 function decode(data, tagger, simpleValue) {
187   var dataView = new DataView(data);
188   var offset = 0;
189
190   if (typeof tagger !== "function")
191     tagger = function(value) { return value; };
192   if (typeof simpleValue !== "function")
193     simpleValue = function() { return undefined; };
194
195   function commitRead(length, value) {
196     offset += length;
197     return value;
198   }
199   function readArrayBuffer(length) {
200     return commitRead(length, new Uint8Array(data, offset, length));
201   }
202   function readFloat16() {
203     var tempArrayBuffer = new ArrayBuffer(4);
204     var tempDataView = new DataView(tempArrayBuffer);
205     var value = readUint16();
206
207     var sign = value & 0x8000;
208     var exponent = value & 0x7c00;
209     var fraction = value & 0x03ff;
210
211     if (exponent === 0x7c00)
212       exponent = 0xff << 10;
213     else if (exponent !== 0)
214       exponent += (127 - 15) << 10;
215     else if (fraction !== 0)
216       return (sign ? -1 : 1) * fraction * POW_2_24;
217
218     tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
219     return tempDataView.getFloat32(0);
220   }
221   function readFloat32() {
222     return commitRead(4, dataView.getFloat32(offset));
223   }
224   function readFloat64() {
225     return commitRead(8, dataView.getFloat64(offset));
226   }
227   function readUint8() {
228     return commitRead(1, dataView.getUint8(offset));
229   }
230   function readUint16() {
231     return commitRead(2, dataView.getUint16(offset));
232   }
233   function readUint32() {
234     return commitRead(4, dataView.getUint32(offset));
235   }
236   function readUint64() {
237     return readUint32() * POW_2_32 + readUint32();
238   }
239   function readBreak() {
240     if (dataView.getUint8(offset) !== 0xff)
241       return false;
242     offset += 1;
243     return true;
244   }
245   function readLength(additionalInformation) {
246     if (additionalInformation < 24)
247       return additionalInformation;
248     if (additionalInformation === 24)
249       return readUint8();
250     if (additionalInformation === 25)
251       return readUint16();
252     if (additionalInformation === 26)
253       return readUint32();
254     if (additionalInformation === 27)
255       return readUint64();
256     if (additionalInformation === 31)
257       return -1;
258     throw "Invalid length encoding";
259   }
260   function readIndefiniteStringLength(majorType) {
261     var initialByte = readUint8();
262     if (initialByte === 0xff)
263       return -1;
264     var length = readLength(initialByte & 0x1f);
265     if (length < 0 || (initialByte >> 5) !== majorType)
266       throw "Invalid indefinite length element";
267     return length;
268   }
269
270   function appendUtf16Data(utf16data, length) {
271     for (var i = 0; i < length; ++i) {
272       var value = readUint8();
273       if (value & 0x80) {
274         if (value < 0xe0) {
275           value = (value & 0x1f) <<  6
276                 | (readUint8() & 0x3f);
277           length -= 1;
278         } else if (value < 0xf0) {
279           value = (value & 0x0f) << 12
280                 | (readUint8() & 0x3f) << 6
281                 | (readUint8() & 0x3f);
282           length -= 2;
283         } else {
284           value = (value & 0x0f) << 18
285                 | (readUint8() & 0x3f) << 12
286                 | (readUint8() & 0x3f) << 6
287                 | (readUint8() & 0x3f);
288           length -= 3;
289         }
290       }
291
292       if (value < 0x10000) {
293         utf16data.push(value);
294       } else {
295         value -= 0x10000;
296         utf16data.push(0xd800 | (value >> 10));
297         utf16data.push(0xdc00 | (value & 0x3ff));
298       }
299     }
300   }
301
302   function decodeItem() {
303     var initialByte = readUint8();
304     var majorType = initialByte >> 5;
305     var additionalInformation = initialByte & 0x1f;
306     var i;
307     var length;
308
309     if (majorType === 7) {
310       switch (additionalInformation) {
311         case 25:
312           return readFloat16();
313         case 26:
314           return readFloat32();
315         case 27:
316           return readFloat64();
317       }
318     }
319
320     length = readLength(additionalInformation);
321     if (length < 0 && (majorType < 2 || 6 < majorType))
322       throw "Invalid length";
323
324     switch (majorType) {
325       case 0:
326         return length;
327       case 1:
328         return -1 - length;
329       case 2:
330         if (length < 0) {
331           var elements = [];
332           var fullArrayLength = 0;
333           while ((length = readIndefiniteStringLength(majorType)) >= 0) {
334             fullArrayLength += length;
335             elements.push(readArrayBuffer(length));
336           }
337           var fullArray = new Uint8Array(fullArrayLength);
338           var fullArrayOffset = 0;
339           for (i = 0; i < elements.length; ++i) {
340             fullArray.set(elements[i], fullArrayOffset);
341             fullArrayOffset += elements[i].length;
342           }
343           return fullArray;
344         }
345         return readArrayBuffer(length);
346       case 3:
347         var utf16data = [];
348         if (length < 0) {
349           while ((length = readIndefiniteStringLength(majorType)) >= 0)
350             appendUtf16Data(utf16data, length);
351         } else
352           appendUtf16Data(utf16data, length);
353         return String.fromCharCode.apply(null, utf16data);
354       case 4:
355         var retArray;
356         if (length < 0) {
357           retArray = [];
358           while (!readBreak())
359             retArray.push(decodeItem());
360         } else {
361           retArray = new Array(length);
362           for (i = 0; i < length; ++i)
363             retArray[i] = decodeItem();
364         }
365         return retArray;
366       case 5:
367         var retObject = {};
368         for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
369           var key = decodeItem();
370           retObject[key] = decodeItem();
371         }
372         return retObject;
373       case 6:
374         return tagger(decodeItem(), length);
375       case 7:
376         switch (length) {
377           case 20:
378             return false;
379           case 21:
380             return true;
381           case 22:
382             return null;
383           case 23:
384             return undefined;
385           default:
386             return simpleValue(length);
387         }
388     }
389   }
390
391   var ret = decodeItem();
392   if (offset !== data.byteLength)
393     throw "Remaining bytes";
394   return ret;
395 }
396
397 var obj = { encode: encode, decode: decode };
398
399 if (typeof define === "function" && define.amd)
400   define("cbor/cbor", obj);
401 else if (typeof module !== "undefined" && module.exports)
402   module.exports = obj;
403 else if (!global.CBOR)
404   global.CBOR = obj;
405
406 })(this);