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/validator", [
      6   "mojo/public/js/bindings/codec",
      7 ], function(codec) {
      8 
      9   var validationError = {
     10     NONE: 'VALIDATION_ERROR_NONE',
     11     MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
     12     ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
     13     UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
     14     UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
     15     ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
     16     UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
     17     ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
     18     UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
     19     MESSAGE_HEADER_INVALID_FLAG_COMBINATION:
     20         'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION',
     21     MESSAGE_HEADER_MISSING_REQUEST_ID:
     22         'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID'
     23   };
     24 
     25   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
     26 
     27   function isStringClass(cls) {
     28     return cls === codec.String || cls === codec.NullableString;
     29   }
     30 
     31   function isHandleClass(cls) {
     32     return cls === codec.Handle || cls === codec.NullableHandle;
     33   }
     34 
     35   function isNullable(type) {
     36     return type === codec.NullableString || type === codec.NullableHandle ||
     37         type instanceof codec.NullableArrayOf ||
     38         type instanceof codec.NullablePointerTo;
     39   }
     40 
     41   function Validator(message) {
     42     this.message = message;
     43     this.offset = 0;
     44     this.handleIndex = 0;
     45   }
     46 
     47   Object.defineProperty(Validator.prototype, "offsetLimit", {
     48     get: function() { return this.message.buffer.byteLength; }
     49   });
     50 
     51   Object.defineProperty(Validator.prototype, "handleIndexLimit", {
     52     get: function() { return this.message.handles.length; }
     53   });
     54 
     55   // True if we can safely allocate a block of bytes from start to
     56   // to start + numBytes.
     57   Validator.prototype.isValidRange = function(start, numBytes) {
     58     // Only positive JavaScript integers that are less than 2^53
     59     // (Number.MAX_SAFE_INTEGER) can be represented exactly.
     60     if (start < this.offset || numBytes <= 0 ||
     61         !Number.isSafeInteger(start) ||
     62         !Number.isSafeInteger(numBytes))
     63       return false;
     64 
     65     var newOffset = start + numBytes;
     66     if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
     67       return false;
     68 
     69     return true;
     70   }
     71 
     72   Validator.prototype.claimRange = function(start, numBytes) {
     73     if (this.isValidRange(start, numBytes)) {
     74       this.offset = start + numBytes;
     75       return true;
     76     }
     77     return false;
     78   }
     79 
     80   Validator.prototype.claimHandle = function(index) {
     81     if (index === codec.kEncodedInvalidHandleValue)
     82       return true;
     83 
     84     if (index < this.handleIndex || index >= this.handleIndexLimit)
     85       return false;
     86 
     87     // This is safe because handle indices are uint32.
     88     this.handleIndex = index + 1;
     89     return true;
     90   }
     91 
     92   Validator.prototype.validateHandle = function(offset, nullable) {
     93     var index = this.message.buffer.getUint32(offset);
     94 
     95     if (index === codec.kEncodedInvalidHandleValue)
     96       return nullable ?
     97           validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
     98 
     99     if (!this.claimHandle(index))
    100       return validationError.ILLEGAL_HANDLE;
    101     return validationError.NONE;
    102   }
    103 
    104   Validator.prototype.validateStructHeader =
    105       function(offset, minNumBytes, minNumFields) {
    106     if (!codec.isAligned(offset))
    107       return validationError.MISALIGNED_OBJECT;
    108 
    109     if (!this.isValidRange(offset, codec.kStructHeaderSize))
    110       return validationError.ILLEGAL_MEMORY_RANGE;
    111 
    112     var numBytes = this.message.buffer.getUint32(offset);
    113     var numFields = this.message.buffer.getUint32(offset + 4);
    114 
    115     if (numBytes < minNumBytes || numFields < minNumFields)
    116       return validationError.UNEXPECTED_STRUCT_HEADER;
    117 
    118     if (!this.claimRange(offset, numBytes))
    119       return validationError.ILLEGAL_MEMORY_RANGE;
    120 
    121     return validationError.NONE;
    122   }
    123 
    124   Validator.prototype.validateMessageHeader = function() {
    125     var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
    126     if (err != validationError.NONE)
    127       return err;
    128 
    129     var numBytes = this.message.getHeaderNumBytes();
    130     var numFields = this.message.getHeaderNumFields();
    131 
    132     var validNumFieldsAndNumBytes =
    133         (numFields == 2 && numBytes == codec.kMessageHeaderSize) ||
    134         (numFields == 3 &&
    135          numBytes == codec.kMessageWithRequestIDHeaderSize) ||
    136         (numFields > 3 &&
    137          numBytes >= codec.kMessageWithRequestIDHeaderSize);
    138     if (!validNumFieldsAndNumBytes)
    139       return validationError.UNEXPECTED_STRUCT_HEADER;
    140 
    141     var expectsResponse = this.message.expectsResponse();
    142     var isResponse = this.message.isResponse();
    143 
    144     if (numFields == 2 && (expectsResponse || isResponse))
    145       return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
    146 
    147     if (isResponse && expectsResponse)
    148       return validationError.MESSAGE_HEADER_INVALID_FLAG_COMBINATION;
    149 
    150     return validationError.NONE;
    151   }
    152 
    153   // Returns the message.buffer relative offset this pointer "points to",
    154   // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
    155   // pointer's value is not valid.
    156   Validator.prototype.decodePointer = function(offset) {
    157     var pointerValue = this.message.buffer.getUint64(offset);
    158     if (pointerValue === 0)
    159       return NULL_MOJO_POINTER;
    160     var bufferOffset = offset + pointerValue;
    161     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
    162   }
    163 
    164   Validator.prototype.validateArrayPointer = function(
    165       offset, elementSize, expectedElementCount, elementType, nullable) {
    166     var arrayOffset = this.decodePointer(offset);
    167     if (arrayOffset === null)
    168       return validationError.ILLEGAL_POINTER;
    169 
    170     if (arrayOffset === NULL_MOJO_POINTER)
    171       return nullable ?
    172           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    173 
    174     return this.validateArray(
    175         arrayOffset, elementSize, expectedElementCount, elementType);
    176   }
    177 
    178   Validator.prototype.validateStructPointer = function(
    179         offset, structClass, nullable) {
    180     var structOffset = this.decodePointer(offset);
    181     if (structOffset === null)
    182       return validationError.ILLEGAL_POINTER;
    183 
    184     if (structOffset === NULL_MOJO_POINTER)
    185       return nullable ?
    186           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    187 
    188     return structClass.validate(this, structOffset);
    189   }
    190 
    191   Validator.prototype.validateStringPointer = function(offset, nullable) {
    192     return this.validateArrayPointer(
    193         offset, codec.Uint8.encodedSize, 0, codec.Uint8, nullable);
    194   }
    195 
    196   // Similar to Array_Data<T>::Validate()
    197   // mojo/public/cpp/bindings/lib/array_internal.h
    198 
    199   Validator.prototype.validateArray =
    200       function (offset, elementSize, expectedElementCount, elementType) {
    201     if (!codec.isAligned(offset))
    202       return validationError.MISALIGNED_OBJECT;
    203 
    204     if (!this.isValidRange(offset, codec.kArrayHeaderSize))
    205       return validationError.ILLEGAL_MEMORY_RANGE;
    206 
    207     var numBytes = this.message.buffer.getUint32(offset);
    208     var numElements = this.message.buffer.getUint32(offset + 4);
    209 
    210     // Note: this computation is "safe" because elementSize <= 8 and
    211     // numElements is a uint32.
    212     var elementsTotalSize = (elementType === codec.PackedBool) ?
    213         Math.ceil(numElements / 8) : (elementSize * numElements);
    214 
    215     if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
    216       return validationError.UNEXPECTED_ARRAY_HEADER;
    217 
    218     if (expectedElementCount != 0 && numElements != expectedElementCount)
    219       return validationError.UNEXPECTED_ARRAY_HEADER;
    220 
    221     if (!this.claimRange(offset, numBytes))
    222       return validationError.ILLEGAL_MEMORY_RANGE;
    223 
    224     // Validate the array's elements if they are pointers or handles.
    225 
    226     var elementsOffset = offset + codec.kArrayHeaderSize;
    227     var nullable = isNullable(elementType);
    228 
    229     if (isHandleClass(elementType))
    230       return this.validateHandleElements(elementsOffset, numElements, nullable);
    231     if (isStringClass(elementType))
    232       return this.validateArrayElements(
    233           elementsOffset, numElements, codec.Uint8, nullable)
    234     if (elementType instanceof codec.PointerTo)
    235       return this.validateStructElements(
    236           elementsOffset, numElements, elementType.cls, nullable);
    237     if (elementType instanceof codec.ArrayOf)
    238       return this.validateArrayElements(
    239           elementsOffset, numElements, elementType.cls, nullable);
    240 
    241     return validationError.NONE;
    242   }
    243 
    244   // Note: the |offset + i * elementSize| computation in the validateFooElements
    245   // methods below is "safe" because elementSize <= 8, offset and
    246   // numElements are uint32, and 0 <= i < numElements.
    247 
    248   Validator.prototype.validateHandleElements =
    249       function(offset, numElements, nullable) {
    250     var elementSize = codec.Handle.encodedSize;
    251     for (var i = 0; i < numElements; i++) {
    252       var elementOffset = offset + i * elementSize;
    253       var err = this.validateHandle(elementOffset, nullable);
    254       if (err != validationError.NONE)
    255         return err;
    256     }
    257     return validationError.NONE;
    258   }
    259 
    260   // The elementClass parameter is the element type of the element arrays.
    261   Validator.prototype.validateArrayElements =
    262       function(offset, numElements, elementClass, nullable) {
    263     var elementSize = codec.PointerTo.prototype.encodedSize;
    264     for (var i = 0; i < numElements; i++) {
    265       var elementOffset = offset + i * elementSize;
    266       var err = this.validateArrayPointer(
    267           elementOffset, elementClass.encodedSize, 0, elementClass, nullable);
    268       if (err != validationError.NONE)
    269         return err;
    270     }
    271     return validationError.NONE;
    272   }
    273 
    274   Validator.prototype.validateStructElements =
    275       function(offset, numElements, structClass, nullable) {
    276     var elementSize = codec.PointerTo.prototype.encodedSize;
    277     for (var i = 0; i < numElements; i++) {
    278       var elementOffset = offset + i * elementSize;
    279       var err =
    280           this.validateStructPointer(elementOffset, structClass, nullable);
    281       if (err != validationError.NONE)
    282         return err;
    283     }
    284     return validationError.NONE;
    285   }
    286 
    287   var exports = {};
    288   exports.validationError = validationError;
    289   exports.Validator = Validator;
    290   return exports;
    291 });
    292