Home | History | Annotate | Download | only in js
      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/validator", [
      6   "mojo/public/js/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_FLAGS:
     20         'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
     21     MESSAGE_HEADER_MISSING_REQUEST_ID:
     22         'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
     23     DIFFERENT_SIZED_ARRAYS_IN_MAP:
     24         'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
     25     INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
     26     UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
     27   };
     28 
     29   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
     30 
     31   function isStringClass(cls) {
     32     return cls === codec.String || cls === codec.NullableString;
     33   }
     34 
     35   function isHandleClass(cls) {
     36     return cls === codec.Handle || cls === codec.NullableHandle;
     37   }
     38 
     39   function isInterfaceClass(cls) {
     40     return cls === codec.Interface || cls === codec.NullableInterface;
     41   }
     42 
     43   function isNullable(type) {
     44     return type === codec.NullableString || type === codec.NullableHandle ||
     45         type === codec.NullableInterface ||
     46         type instanceof codec.NullableArrayOf ||
     47         type instanceof codec.NullablePointerTo;
     48   }
     49 
     50   function Validator(message) {
     51     this.message = message;
     52     this.offset = 0;
     53     this.handleIndex = 0;
     54   }
     55 
     56   Object.defineProperty(Validator.prototype, "offsetLimit", {
     57     get: function() { return this.message.buffer.byteLength; }
     58   });
     59 
     60   Object.defineProperty(Validator.prototype, "handleIndexLimit", {
     61     get: function() { return this.message.handles.length; }
     62   });
     63 
     64   // True if we can safely allocate a block of bytes from start to
     65   // to start + numBytes.
     66   Validator.prototype.isValidRange = function(start, numBytes) {
     67     // Only positive JavaScript integers that are less than 2^53
     68     // (Number.MAX_SAFE_INTEGER) can be represented exactly.
     69     if (start < this.offset || numBytes <= 0 ||
     70         !Number.isSafeInteger(start) ||
     71         !Number.isSafeInteger(numBytes))
     72       return false;
     73 
     74     var newOffset = start + numBytes;
     75     if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
     76       return false;
     77 
     78     return true;
     79   }
     80 
     81   Validator.prototype.claimRange = function(start, numBytes) {
     82     if (this.isValidRange(start, numBytes)) {
     83       this.offset = start + numBytes;
     84       return true;
     85     }
     86     return false;
     87   }
     88 
     89   Validator.prototype.claimHandle = function(index) {
     90     if (index === codec.kEncodedInvalidHandleValue)
     91       return true;
     92 
     93     if (index < this.handleIndex || index >= this.handleIndexLimit)
     94       return false;
     95 
     96     // This is safe because handle indices are uint32.
     97     this.handleIndex = index + 1;
     98     return true;
     99   }
    100 
    101   Validator.prototype.validateHandle = function(offset, nullable) {
    102     var index = this.message.buffer.getUint32(offset);
    103 
    104     if (index === codec.kEncodedInvalidHandleValue)
    105       return nullable ?
    106           validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
    107 
    108     if (!this.claimHandle(index))
    109       return validationError.ILLEGAL_HANDLE;
    110     return validationError.NONE;
    111   }
    112 
    113   Validator.prototype.validateInterface = function(offset, nullable) {
    114     return this.validateHandle(offset, nullable);
    115   }
    116 
    117   Validator.prototype.validateStructHeader =
    118       function(offset, minNumBytes, minVersion) {
    119     if (!codec.isAligned(offset))
    120       return validationError.MISALIGNED_OBJECT;
    121 
    122     if (!this.isValidRange(offset, codec.kStructHeaderSize))
    123       return validationError.ILLEGAL_MEMORY_RANGE;
    124 
    125     var numBytes = this.message.buffer.getUint32(offset);
    126     var version = this.message.buffer.getUint32(offset + 4);
    127 
    128     // Backward compatibility is not yet supported.
    129     if (numBytes < minNumBytes || version < minVersion)
    130       return validationError.UNEXPECTED_STRUCT_HEADER;
    131 
    132     if (!this.claimRange(offset, numBytes))
    133       return validationError.ILLEGAL_MEMORY_RANGE;
    134 
    135     return validationError.NONE;
    136   }
    137 
    138   Validator.prototype.validateMessageHeader = function() {
    139     var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 0);
    140     if (err != validationError.NONE)
    141       return err;
    142 
    143     var numBytes = this.message.getHeaderNumBytes();
    144     var version = this.message.getHeaderVersion();
    145 
    146     var validVersionAndNumBytes =
    147         (version == 0 && numBytes == codec.kMessageHeaderSize) ||
    148         (version == 1 &&
    149          numBytes == codec.kMessageWithRequestIDHeaderSize) ||
    150         (version > 1 &&
    151          numBytes >= codec.kMessageWithRequestIDHeaderSize);
    152     if (!validVersionAndNumBytes)
    153       return validationError.UNEXPECTED_STRUCT_HEADER;
    154 
    155     var expectsResponse = this.message.expectsResponse();
    156     var isResponse = this.message.isResponse();
    157 
    158     if (version == 0 && (expectsResponse || isResponse))
    159       return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
    160 
    161     if (isResponse && expectsResponse)
    162       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
    163 
    164     return validationError.NONE;
    165   }
    166 
    167   // Returns the message.buffer relative offset this pointer "points to",
    168   // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
    169   // pointer's value is not valid.
    170   Validator.prototype.decodePointer = function(offset) {
    171     var pointerValue = this.message.buffer.getUint64(offset);
    172     if (pointerValue === 0)
    173       return NULL_MOJO_POINTER;
    174     var bufferOffset = offset + pointerValue;
    175     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
    176   }
    177 
    178   Validator.prototype.decodeUnionSize = function(offset) {
    179     return this.message.buffer.getUint32(offset);
    180   };
    181 
    182   Validator.prototype.decodeUnionTag = function(offset) {
    183     return this.message.buffer.getUint32(offset + 4);
    184   };
    185 
    186   Validator.prototype.validateArrayPointer = function(
    187       offset, elementSize, elementType, nullable, expectedDimensionSizes,
    188       currentDimension) {
    189     var arrayOffset = this.decodePointer(offset);
    190     if (arrayOffset === null)
    191       return validationError.ILLEGAL_POINTER;
    192 
    193     if (arrayOffset === NULL_MOJO_POINTER)
    194       return nullable ?
    195           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    196 
    197     return this.validateArray(arrayOffset, elementSize, elementType,
    198                               expectedDimensionSizes, currentDimension);
    199   }
    200 
    201   Validator.prototype.validateStructPointer = function(
    202       offset, structClass, nullable) {
    203     var structOffset = this.decodePointer(offset);
    204     if (structOffset === null)
    205       return validationError.ILLEGAL_POINTER;
    206 
    207     if (structOffset === NULL_MOJO_POINTER)
    208       return nullable ?
    209           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    210 
    211     return structClass.validate(this, structOffset);
    212   }
    213 
    214   Validator.prototype.validateUnion = function(
    215       offset, unionClass, nullable) {
    216     var size = this.message.buffer.getUint32(offset);
    217     if (size == 0) {
    218       return nullable ?
    219           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
    220     }
    221 
    222     return unionClass.validate(this, offset);
    223   }
    224 
    225   Validator.prototype.validateNestedUnion = function(
    226       offset, unionClass, nullable) {
    227     var unionOffset = this.decodePointer(offset);
    228     if (unionOffset === null)
    229       return validationError.ILLEGAL_POINTER;
    230 
    231     if (unionOffset === NULL_MOJO_POINTER)
    232       return nullable ?
    233           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
    234 
    235     return this.validateUnion(unionOffset, unionClass, nullable);
    236   }
    237 
    238   // This method assumes that the array at arrayPointerOffset has
    239   // been validated.
    240 
    241   Validator.prototype.arrayLength = function(arrayPointerOffset) {
    242     var arrayOffset = this.decodePointer(arrayPointerOffset);
    243     return this.message.buffer.getUint32(arrayOffset + 4);
    244   }
    245 
    246   Validator.prototype.validateMapPointer = function(
    247       offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
    248     // Validate the implicit map struct:
    249     // struct {array<keyClass> keys; array<valueClass> values};
    250     var structOffset = this.decodePointer(offset);
    251     if (structOffset === null)
    252       return validationError.ILLEGAL_POINTER;
    253 
    254     if (structOffset === NULL_MOJO_POINTER)
    255       return mapIsNullable ?
    256           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    257 
    258     var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
    259     var err = this.validateStructHeader(structOffset, mapEncodedSize, 0);
    260     if (err !== validationError.NONE)
    261         return err;
    262 
    263     // Validate the keys array.
    264     var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
    265     err = this.validateArrayPointer(
    266         keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
    267     if (err !== validationError.NONE)
    268         return err;
    269 
    270     // Validate the values array.
    271     var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
    272     var valuesArrayDimensions = [0]; // Validate the actual length below.
    273     if (valueClass instanceof codec.ArrayOf)
    274       valuesArrayDimensions =
    275           valuesArrayDimensions.concat(valueClass.dimensions());
    276     var err = this.validateArrayPointer(valuesArrayPointerOffset,
    277                                         valueClass.encodedSize,
    278                                         valueClass,
    279                                         valueIsNullable,
    280                                         valuesArrayDimensions,
    281                                         0);
    282     if (err !== validationError.NONE)
    283         return err;
    284 
    285     // Validate the lengths of the keys and values arrays.
    286     var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
    287     var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
    288     if (keysArrayLength != valuesArrayLength)
    289       return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
    290 
    291     return validationError.NONE;
    292   }
    293 
    294   Validator.prototype.validateStringPointer = function(offset, nullable) {
    295     return this.validateArrayPointer(
    296         offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
    297   }
    298 
    299   // Similar to Array_Data<T>::Validate()
    300   // mojo/public/cpp/bindings/lib/array_internal.h
    301 
    302   Validator.prototype.validateArray =
    303       function (offset, elementSize, elementType, expectedDimensionSizes,
    304                 currentDimension) {
    305     if (!codec.isAligned(offset))
    306       return validationError.MISALIGNED_OBJECT;
    307 
    308     if (!this.isValidRange(offset, codec.kArrayHeaderSize))
    309       return validationError.ILLEGAL_MEMORY_RANGE;
    310 
    311     var numBytes = this.message.buffer.getUint32(offset);
    312     var numElements = this.message.buffer.getUint32(offset + 4);
    313 
    314     // Note: this computation is "safe" because elementSize <= 8 and
    315     // numElements is a uint32.
    316     var elementsTotalSize = (elementType === codec.PackedBool) ?
    317         Math.ceil(numElements / 8) : (elementSize * numElements);
    318 
    319     if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
    320       return validationError.UNEXPECTED_ARRAY_HEADER;
    321 
    322     if (expectedDimensionSizes[currentDimension] != 0 &&
    323         numElements != expectedDimensionSizes[currentDimension]) {
    324       return validationError.UNEXPECTED_ARRAY_HEADER;
    325     }
    326 
    327     if (!this.claimRange(offset, numBytes))
    328       return validationError.ILLEGAL_MEMORY_RANGE;
    329 
    330     // Validate the array's elements if they are pointers or handles.
    331 
    332     var elementsOffset = offset + codec.kArrayHeaderSize;
    333     var nullable = isNullable(elementType);
    334 
    335     if (isHandleClass(elementType))
    336       return this.validateHandleElements(elementsOffset, numElements, nullable);
    337     if (isInterfaceClass(elementType))
    338       return this.validateInterfaceElements(
    339           elementsOffset, numElements, nullable);
    340     if (isStringClass(elementType))
    341       return this.validateArrayElements(
    342           elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
    343     if (elementType instanceof codec.PointerTo)
    344       return this.validateStructElements(
    345           elementsOffset, numElements, elementType.cls, nullable);
    346     if (elementType instanceof codec.ArrayOf)
    347       return this.validateArrayElements(
    348           elementsOffset, numElements, elementType.cls, nullable,
    349           expectedDimensionSizes, currentDimension + 1);
    350 
    351     return validationError.NONE;
    352   }
    353 
    354   // Note: the |offset + i * elementSize| computation in the validateFooElements
    355   // methods below is "safe" because elementSize <= 8, offset and
    356   // numElements are uint32, and 0 <= i < numElements.
    357 
    358   Validator.prototype.validateHandleElements =
    359       function(offset, numElements, nullable) {
    360     var elementSize = codec.Handle.encodedSize;
    361     for (var i = 0; i < numElements; i++) {
    362       var elementOffset = offset + i * elementSize;
    363       var err = this.validateHandle(elementOffset, nullable);
    364       if (err != validationError.NONE)
    365         return err;
    366     }
    367     return validationError.NONE;
    368   }
    369 
    370   Validator.prototype.validateInterfaceElements =
    371       function(offset, numElements, nullable) {
    372     var elementSize = codec.Interface.encodedSize;
    373     for (var i = 0; i < numElements; i++) {
    374       var elementOffset = offset + i * elementSize;
    375       var err = this.validateInterface(elementOffset, nullable);
    376       if (err != validationError.NONE)
    377         return err;
    378     }
    379     return validationError.NONE;
    380   }
    381 
    382   // The elementClass parameter is the element type of the element arrays.
    383   Validator.prototype.validateArrayElements =
    384       function(offset, numElements, elementClass, nullable,
    385                expectedDimensionSizes, currentDimension) {
    386     var elementSize = codec.PointerTo.prototype.encodedSize;
    387     for (var i = 0; i < numElements; i++) {
    388       var elementOffset = offset + i * elementSize;
    389       var err = this.validateArrayPointer(
    390           elementOffset, elementClass.encodedSize, elementClass, nullable,
    391           expectedDimensionSizes, currentDimension);
    392       if (err != validationError.NONE)
    393         return err;
    394     }
    395     return validationError.NONE;
    396   }
    397 
    398   Validator.prototype.validateStructElements =
    399       function(offset, numElements, structClass, nullable) {
    400     var elementSize = codec.PointerTo.prototype.encodedSize;
    401     for (var i = 0; i < numElements; i++) {
    402       var elementOffset = offset + i * elementSize;
    403       var err =
    404           this.validateStructPointer(elementOffset, structClass, nullable);
    405       if (err != validationError.NONE)
    406         return err;
    407     }
    408     return validationError.NONE;
    409   }
    410 
    411   var exports = {};
    412   exports.validationError = validationError;
    413   exports.Validator = Validator;
    414   return exports;
    415 });
    416