• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5define("mojo/public/js/bindings/codec", [
6  "mojo/public/js/bindings/unicode"
7], function(unicode) {
8
9  var kErrorUnsigned = "Passing negative value to unsigned";
10
11  // Memory -------------------------------------------------------------------
12
13  var kAlignment = 8;
14  var kHighWordMultiplier = 0x100000000;
15  var kHostIsLittleEndian = (function () {
16    var endianArrayBuffer = new ArrayBuffer(2);
17    var endianUint8Array = new Uint8Array(endianArrayBuffer);
18    var endianUint16Array = new Uint16Array(endianArrayBuffer);
19    endianUint16Array[0] = 1;
20    return endianUint8Array[0] == 1;
21  })();
22
23  function align(size) {
24    return size + (kAlignment - (size % kAlignment)) % kAlignment;
25  }
26
27  function getInt64(dataView, byteOffset, value) {
28    var lo, hi;
29    if (kHostIsLittleEndian) {
30      lo = dataView.getUint32(byteOffset, kHostIsLittleEndian);
31      hi = dataView.getInt32(byteOffset + 4, kHostIsLittleEndian);
32    } else {
33      hi = dataView.getInt32(byteOffset, kHostIsLittleEndian);
34      lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian);
35    }
36    return lo + hi * kHighWordMultiplier;
37  }
38
39  function getUint64(dataView, byteOffset, value) {
40    var lo, hi;
41    if (kHostIsLittleEndian) {
42      lo = dataView.getUint32(byteOffset, kHostIsLittleEndian);
43      hi = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian);
44    } else {
45      hi = dataView.getUint32(byteOffset, kHostIsLittleEndian);
46      lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian);
47    }
48    return lo + hi * kHighWordMultiplier;
49  }
50
51  function setInt64(dataView, byteOffset, value) {
52    var hi = Math.floor(value / kHighWordMultiplier);
53    if (kHostIsLittleEndian) {
54      dataView.setInt32(byteOffset, value, kHostIsLittleEndian);
55      dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian);
56    } else {
57      dataView.setInt32(byteOffset, hi, kHostIsLittleEndian);
58      dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian);
59    }
60  }
61
62  function setUint64(dataView, byteOffset, value) {
63    var hi = (value / kHighWordMultiplier) | 0;
64    if (kHostIsLittleEndian) {
65      dataView.setInt32(byteOffset, value, kHostIsLittleEndian);
66      dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian);
67    } else {
68      dataView.setInt32(byteOffset, hi, kHostIsLittleEndian);
69      dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian);
70    }
71  }
72
73  function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
74    (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
75  }
76
77  // Buffer -------------------------------------------------------------------
78
79  function Buffer(sizeOrArrayBuffer) {
80    if (sizeOrArrayBuffer instanceof ArrayBuffer) {
81      this.arrayBuffer = sizeOrArrayBuffer;
82    } else {
83      this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
84    };
85
86    this.dataView = new DataView(this.arrayBuffer);
87    this.next = 0;
88  }
89
90  Buffer.prototype.alloc = function(size) {
91    var pointer = this.next;
92    this.next += size;
93    if (this.next > this.arrayBuffer.byteLength) {
94      var newSize = (1.5 * (this.arrayBuffer.byteLength + size)) | 0;
95      this.grow(newSize);
96    }
97    return pointer;
98  };
99
100  Buffer.prototype.grow = function(size) {
101    var newArrayBuffer = new ArrayBuffer(size);
102    copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
103    this.arrayBuffer = newArrayBuffer;
104    this.dataView = new DataView(this.arrayBuffer);
105  };
106
107  Buffer.prototype.trim = function() {
108    this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
109    this.dataView = new DataView(this.arrayBuffer);
110  };
111
112  // Constants ----------------------------------------------------------------
113
114  var kArrayHeaderSize = 8;
115  var kStructHeaderSize = 8;
116  var kMessageHeaderSize = 16;
117  var kMessageWithRequestIDHeaderSize = 24;
118
119  // Decoder ------------------------------------------------------------------
120
121  function Decoder(buffer, handles, base) {
122    this.buffer = buffer;
123    this.handles = handles;
124    this.base = base;
125    this.next = base;
126  }
127
128  Decoder.prototype.skip = function(offset) {
129    this.next += offset;
130  };
131
132  Decoder.prototype.readInt8 = function() {
133    var result = this.buffer.dataView.getInt8(this.next, kHostIsLittleEndian);
134    this.next += 1;
135    return result;
136  };
137
138  Decoder.prototype.readUint8 = function() {
139    var result = this.buffer.dataView.getUint8(this.next, kHostIsLittleEndian);
140    this.next += 1;
141    return result;
142  };
143
144  Decoder.prototype.readInt16 = function() {
145    var result = this.buffer.dataView.getInt16(this.next, kHostIsLittleEndian);
146    this.next += 2;
147    return result;
148  };
149
150  Decoder.prototype.readUint16 = function() {
151    var result = this.buffer.dataView.getUint16(this.next, kHostIsLittleEndian);
152    this.next += 2;
153    return result;
154  };
155
156  Decoder.prototype.readInt32 = function() {
157    var result = this.buffer.dataView.getInt32(this.next, kHostIsLittleEndian);
158    this.next += 4;
159    return result;
160  };
161
162  Decoder.prototype.readUint32 = function() {
163    var result = this.buffer.dataView.getUint32(this.next, kHostIsLittleEndian);
164    this.next += 4;
165    return result;
166  };
167
168  Decoder.prototype.readInt64 = function() {
169    var result = getInt64(this.buffer.dataView, this.next, kHostIsLittleEndian);
170    this.next += 8;
171    return result;
172  };
173
174  Decoder.prototype.readUint64 = function() {
175    var result = getUint64(
176        this.buffer.dataView, this.next, kHostIsLittleEndian);
177    this.next += 8;
178    return result;
179  };
180
181  Decoder.prototype.readFloat = function() {
182    var result = this.buffer.dataView.getFloat32(
183        this.next, kHostIsLittleEndian);
184    this.next += 4;
185    return result;
186  };
187
188  Decoder.prototype.readDouble = function() {
189    var result = this.buffer.dataView.getFloat64(
190        this.next, kHostIsLittleEndian);
191    this.next += 8;
192    return result;
193  };
194
195  Decoder.prototype.decodePointer = function() {
196    // TODO(abarth): To correctly decode a pointer, we need to know the real
197    // base address of the array buffer.
198    var offsetPointer = this.next;
199    var offset = this.readUint64();
200    if (!offset)
201      return 0;
202    return offsetPointer + offset;
203  };
204
205  Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
206    return new Decoder(this.buffer, this.handles, pointer);
207  };
208
209  Decoder.prototype.decodeHandle = function() {
210    return this.handles[this.readUint32()];
211  };
212
213  Decoder.prototype.decodeString = function() {
214    var numberOfBytes = this.readUint32();
215    var numberOfElements = this.readUint32();
216    var base = this.next;
217    this.next += numberOfElements;
218    return unicode.decodeUtf8String(
219        new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
220  };
221
222  Decoder.prototype.decodeArray = function(cls) {
223    var numberOfBytes = this.readUint32();
224    var numberOfElements = this.readUint32();
225    var val = new Array(numberOfElements);
226    for (var i = 0; i < numberOfElements; ++i) {
227      val[i] = cls.decode(this);
228    }
229    return val;
230  };
231
232  Decoder.prototype.decodeStruct = function(cls) {
233    return cls.decode(this);
234  };
235
236  Decoder.prototype.decodeStructPointer = function(cls) {
237    var pointer = this.decodePointer();
238    if (!pointer) {
239      return null;
240    }
241    return cls.decode(this.decodeAndCreateDecoder(pointer));
242  };
243
244  Decoder.prototype.decodeArrayPointer = function(cls) {
245    var pointer = this.decodePointer();
246    if (!pointer) {
247      return null;
248    }
249    return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
250  };
251
252  Decoder.prototype.decodeStringPointer = function() {
253    var pointer = this.decodePointer();
254    if (!pointer) {
255      return null;
256    }
257    return this.decodeAndCreateDecoder(pointer).decodeString();
258  };
259
260  // Encoder ------------------------------------------------------------------
261
262  function Encoder(buffer, handles, base) {
263    this.buffer = buffer;
264    this.handles = handles;
265    this.base = base;
266    this.next = base;
267  }
268
269  Encoder.prototype.skip = function(offset) {
270    this.next += offset;
271  };
272
273  Encoder.prototype.writeInt8 = function(val) {
274    // NOTE: Endianness doesn't come into play for single bytes.
275    this.buffer.dataView.setInt8(this.next, val);
276    this.next += 1;
277  };
278
279  Encoder.prototype.writeUint8 = function(val) {
280    if (val < 0) {
281      throw new Error(kErrorUnsigned);
282    }
283    // NOTE: Endianness doesn't come into play for single bytes.
284    this.buffer.dataView.setUint8(this.next, val);
285    this.next += 1;
286  };
287
288  Encoder.prototype.writeInt16 = function(val) {
289    this.buffer.dataView.setInt16(this.next, val, kHostIsLittleEndian);
290    this.next += 2;
291  };
292
293  Encoder.prototype.writeUint16 = function(val) {
294    if (val < 0) {
295      throw new Error(kErrorUnsigned);
296    }
297    this.buffer.dataView.setUint16(this.next, val, kHostIsLittleEndian);
298    this.next += 2;
299  };
300
301  Encoder.prototype.writeInt32 = function(val) {
302    this.buffer.dataView.setInt32(this.next, val, kHostIsLittleEndian);
303    this.next += 4;
304  };
305
306  Encoder.prototype.writeUint32 = function(val) {
307    if (val < 0) {
308      throw new Error(kErrorUnsigned);
309    }
310    this.buffer.dataView.setUint32(this.next, val, kHostIsLittleEndian);
311    this.next += 4;
312  };
313
314  Encoder.prototype.writeInt64 = function(val) {
315    setInt64(this.buffer.dataView, this.next, val);
316    this.next += 8;
317  };
318
319  Encoder.prototype.writeUint64 = function(val) {
320    if (val < 0) {
321      throw new Error(kErrorUnsigned);
322    }
323    setUint64(this.buffer.dataView, this.next, val);
324    this.next += 8;
325  };
326
327  Encoder.prototype.writeFloat = function(val) {
328    this.buffer.dataView.setFloat32(this.next, val, kHostIsLittleEndian);
329    this.next += 4;
330  };
331
332  Encoder.prototype.writeDouble = function(val) {
333    this.buffer.dataView.setFloat64(this.next, val, kHostIsLittleEndian);
334    this.next += 8;
335  };
336
337  Encoder.prototype.encodePointer = function(pointer) {
338    if (!pointer)
339      return this.writeUint64(0);
340    // TODO(abarth): To correctly encode a pointer, we need to know the real
341    // base address of the array buffer.
342    var offset = pointer - this.next;
343    this.writeUint64(offset);
344  };
345
346  Encoder.prototype.createAndEncodeEncoder = function(size) {
347    var pointer = this.buffer.alloc(align(size));
348    this.encodePointer(pointer);
349    return new Encoder(this.buffer, this.handles, pointer);
350  };
351
352  Encoder.prototype.encodeHandle = function(handle) {
353    this.handles.push(handle);
354    this.writeUint32(this.handles.length - 1);
355  };
356
357  Encoder.prototype.encodeString = function(val) {
358    var base = this.next + kArrayHeaderSize;
359    var numberOfElements = unicode.encodeUtf8String(
360        val, new Uint8Array(this.buffer.arrayBuffer, base));
361    var numberOfBytes = kArrayHeaderSize + numberOfElements;
362    this.writeUint32(numberOfBytes);
363    this.writeUint32(numberOfElements);
364    this.next += numberOfElements;
365  };
366
367  Encoder.prototype.encodeArray = function(cls, val) {
368    var numberOfElements = val.length;
369    var numberOfBytes = kArrayHeaderSize + cls.encodedSize * numberOfElements;
370    this.writeUint32(numberOfBytes);
371    this.writeUint32(numberOfElements);
372    for (var i = 0; i < numberOfElements; ++i) {
373      cls.encode(this, val[i]);
374    }
375  };
376
377  Encoder.prototype.encodeStruct = function(cls, val) {
378    return cls.encode(this, val);
379  };
380
381  Encoder.prototype.encodeStructPointer = function(cls, val) {
382    if (!val) {
383      this.encodePointer(val);
384      return;
385    }
386    var encoder = this.createAndEncodeEncoder(cls.encodedSize);
387    cls.encode(encoder, val);
388  };
389
390  Encoder.prototype.encodeArrayPointer = function(cls, val) {
391    if (!val) {
392      this.encodePointer(val);
393      return;
394    }
395    var encodedSize = kArrayHeaderSize + cls.encodedSize * val.length;
396    var encoder = this.createAndEncodeEncoder(encodedSize);
397    encoder.encodeArray(cls, val);
398  };
399
400  Encoder.prototype.encodeStringPointer = function(val) {
401    if (!val) {
402      this.encodePointer(val);
403      return;
404    }
405    var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
406    var encoder = this.createAndEncodeEncoder(encodedSize);
407    encoder.encodeString(val);
408  };
409
410  // Message ------------------------------------------------------------------
411
412  var kMessageExpectsResponse = 1 << 0;
413  var kMessageIsResponse      = 1 << 1;
414
415  // Skip over num_bytes, num_fields, and message_name.
416  var kFlagsOffset = 4 + 4 + 4;
417
418  // Skip over num_bytes, num_fields, message_name, and flags.
419  var kRequestIDOffset = 4 + 4 + 4 + 4;
420
421  function Message(buffer, handles) {
422    this.buffer = buffer;
423    this.handles = handles;
424  }
425
426  Message.prototype.setRequestID = function(requestID) {
427    // TODO(darin): Verify that space was reserved for this field!
428    setUint64(this.buffer.dataView, kRequestIDOffset, requestID);
429  };
430
431  Message.prototype.getFlags = function() {
432    return this.buffer.dataView.getUint32(kFlagsOffset, kHostIsLittleEndian);
433  };
434
435  // MessageBuilder -----------------------------------------------------------
436
437  function MessageBuilder(messageName, payloadSize) {
438    // Currently, we don't compute the payload size correctly ahead of time.
439    // Instead, we resize the buffer at the end.
440    var numberOfBytes = kMessageHeaderSize + payloadSize;
441    this.buffer = new Buffer(numberOfBytes);
442    this.handles = [];
443    var encoder = this.createEncoder(kMessageHeaderSize);
444    encoder.writeUint32(kMessageHeaderSize);
445    encoder.writeUint32(2);  // num_fields.
446    encoder.writeUint32(messageName);
447    encoder.writeUint32(0);  // flags.
448  }
449
450  MessageBuilder.prototype.createEncoder = function(size) {
451    var pointer = this.buffer.alloc(size);
452    return new Encoder(this.buffer, this.handles, pointer);
453  };
454
455  MessageBuilder.prototype.encodeStruct = function(cls, val) {
456    cls.encode(this.createEncoder(cls.encodedSize), val);
457  };
458
459  MessageBuilder.prototype.finish = function() {
460    // TODO(abarth): Rather than resizing the buffer at the end, we could
461    // compute the size we need ahead of time, like we do in C++.
462    this.buffer.trim();
463    var message = new Message(this.buffer, this.handles);
464    this.buffer = null;
465    this.handles = null;
466    this.encoder = null;
467    return message;
468  };
469
470  // MessageWithRequestIDBuilder -----------------------------------------------
471
472  function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
473                                       requestID) {
474    // Currently, we don't compute the payload size correctly ahead of time.
475    // Instead, we resize the buffer at the end.
476    var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
477    this.buffer = new Buffer(numberOfBytes);
478    this.handles = [];
479    var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
480    encoder.writeUint32(kMessageWithRequestIDHeaderSize);
481    encoder.writeUint32(3);  // num_fields.
482    encoder.writeUint32(messageName);
483    encoder.writeUint32(flags);
484    encoder.writeUint64(requestID);
485  }
486
487  MessageWithRequestIDBuilder.prototype =
488      Object.create(MessageBuilder.prototype);
489
490  MessageWithRequestIDBuilder.prototype.constructor =
491      MessageWithRequestIDBuilder;
492
493  // MessageReader ------------------------------------------------------------
494
495  function MessageReader(message) {
496    this.decoder = new Decoder(message.buffer, message.handles, 0);
497    var messageHeaderSize = this.decoder.readUint32();
498    this.payloadSize =
499        message.buffer.arrayBuffer.byteLength - messageHeaderSize;
500    var numFields = this.decoder.readUint32();
501    this.messageName = this.decoder.readUint32();
502    this.flags = this.decoder.readUint32();
503    if (numFields >= 3)
504      this.requestID = this.decoder.readUint64();
505    this.decoder.skip(messageHeaderSize - this.decoder.next);
506  }
507
508  MessageReader.prototype.decodeStruct = function(cls) {
509    return cls.decode(this.decoder);
510  };
511
512  // Built-in types -----------------------------------------------------------
513
514  function Int8() {
515  }
516
517  Int8.encodedSize = 1;
518
519  Int8.decode = function(decoder) {
520    return decoder.readInt8();
521  };
522
523  Int8.encode = function(encoder, val) {
524    encoder.writeInt8(val);
525  };
526
527  Uint8.encode = function(encoder, val) {
528    encoder.writeUint8(val);
529  };
530
531  function Uint8() {
532  }
533
534  Uint8.encodedSize = 1;
535
536  Uint8.decode = function(decoder) {
537    return decoder.readUint8();
538  };
539
540  Uint8.encode = function(encoder, val) {
541    encoder.writeUint8(val);
542  };
543
544  function Int16() {
545  }
546
547  Int16.encodedSize = 2;
548
549  Int16.decode = function(decoder) {
550    return decoder.readInt16();
551  };
552
553  Int16.encode = function(encoder, val) {
554    encoder.writeInt16(val);
555  };
556
557  function Uint16() {
558  }
559
560  Uint16.encodedSize = 2;
561
562  Uint16.decode = function(decoder) {
563    return decoder.readUint16();
564  };
565
566  Uint16.encode = function(encoder, val) {
567    encoder.writeUint16(val);
568  };
569
570  function Int32() {
571  }
572
573  Int32.encodedSize = 4;
574
575  Int32.decode = function(decoder) {
576    return decoder.readInt32();
577  };
578
579  Int32.encode = function(encoder, val) {
580    encoder.writeInt32(val);
581  };
582
583  function Uint32() {
584  }
585
586  Uint32.encodedSize = 4;
587
588  Uint32.decode = function(decoder) {
589    return decoder.readUint32();
590  };
591
592  Uint32.encode = function(encoder, val) {
593    encoder.writeUint32(val);
594  };
595
596  function Int64() {
597  }
598
599  Int64.encodedSize = 8;
600
601  Int64.decode = function(decoder) {
602    return decoder.readInt64();
603  };
604
605  Int64.encode = function(encoder, val) {
606    encoder.writeInt64(val);
607  };
608
609  function Uint64() {
610  }
611
612  Uint64.encodedSize = 8;
613
614  Uint64.decode = function(decoder) {
615    return decoder.readUint64();
616  };
617
618  Uint64.encode = function(encoder, val) {
619    encoder.writeUint64(val);
620  };
621
622  function String() {
623  };
624
625  String.encodedSize = 8;
626
627  String.decode = function(decoder) {
628    return decoder.decodeStringPointer();
629  };
630
631  String.encode = function(encoder, val) {
632    encoder.encodeStringPointer(val);
633  };
634
635
636  function Float() {
637  }
638
639  Float.encodedSize = 4;
640
641  Float.decode = function(decoder) {
642    return decoder.readFloat();
643  };
644
645  Float.encode = function(encoder, val) {
646    encoder.writeFloat(val);
647  };
648
649  function Double() {
650  }
651
652  Double.encodedSize = 8;
653
654  Double.decode = function(decoder) {
655    return decoder.readDouble();
656  };
657
658  Double.encode = function(encoder, val) {
659    encoder.writeDouble(val);
660  };
661
662  function PointerTo(cls) {
663    this.cls = cls;
664  }
665
666  PointerTo.prototype.encodedSize = 8;
667
668  PointerTo.prototype.decode = function(decoder) {
669    var pointer = decoder.decodePointer();
670    if (!pointer) {
671      return null;
672    }
673    return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
674  };
675
676  PointerTo.prototype.encode = function(encoder, val) {
677    if (!val) {
678      encoder.encodePointer(val);
679      return;
680    }
681    var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
682    this.cls.encode(objectEncoder, val);
683  };
684
685  function ArrayOf(cls) {
686    this.cls = cls;
687  }
688
689  ArrayOf.prototype.encodedSize = 8;
690
691  ArrayOf.prototype.decode = function(decoder) {
692    return decoder.decodeArrayPointer(this.cls);
693  };
694
695  ArrayOf.prototype.encode = function(encoder, val) {
696    encoder.encodeArrayPointer(this.cls, val);
697  };
698
699  function Handle() {
700  }
701
702  Handle.encodedSize = 4;
703
704  Handle.decode = function(decoder) {
705    return decoder.decodeHandle();
706  };
707
708  Handle.encode = function(encoder, val) {
709    encoder.encodeHandle(val);
710  };
711
712  var exports = {};
713  exports.align = align;
714  exports.Buffer = Buffer;
715  exports.Message = Message;
716  exports.MessageBuilder = MessageBuilder;
717  exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
718  exports.MessageReader = MessageReader;
719  exports.kArrayHeaderSize = kArrayHeaderSize;
720  exports.kStructHeaderSize = kStructHeaderSize;
721  exports.kMessageHeaderSize = kMessageHeaderSize;
722  exports.kMessageExpectsResponse = kMessageExpectsResponse;
723  exports.kMessageIsResponse = kMessageIsResponse;
724  exports.Int8 = Int8;
725  exports.Uint8 = Uint8;
726  exports.Int16 = Int16;
727  exports.Uint16 = Uint16;
728  exports.Int32 = Int32;
729  exports.Uint32 = Uint32;
730  exports.Int64 = Int64;
731  exports.Uint64 = Uint64;
732  exports.Float = Float;
733  exports.Double = Double;
734  exports.String = String;
735  exports.PointerTo = PointerTo;
736  exports.ArrayOf = ArrayOf;
737  exports.Handle = Handle;
738  return exports;
739});
740