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