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