Home | History | Annotate | Download | only in bindings
      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 
      5 define("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