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     UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
     28   };
     29 
     30   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
     31 
     32   function isEnumClass(cls) {
     33     return cls instanceof codec.Enum;
     34   }
     35 
     36   function isStringClass(cls) {
     37     return cls === codec.String || cls === codec.NullableString;
     38   }
     39 
     40   function isHandleClass(cls) {
     41     return cls === codec.Handle || cls === codec.NullableHandle;
     42   }
     43 
     44   function isInterfaceClass(cls) {
     45     return cls instanceof codec.Interface;
     46   }
     47 
     48   function isInterfaceRequestClass(cls) {
     49     return cls === codec.InterfaceRequest ||
     50         cls === codec.NullableInterfaceRequest;
     51   }
     52 
     53   function isNullable(type) {
     54     return type === codec.NullableString || type === codec.NullableHandle ||
     55         type === codec.NullableInterface ||
     56         type === codec.NullableInterfaceRequest ||
     57         type instanceof codec.NullableArrayOf ||
     58         type instanceof codec.NullablePointerTo;
     59   }
     60 
     61   function Validator(message) {
     62     this.message = message;
     63     this.offset = 0;
     64     this.handleIndex = 0;
     65   }
     66 
     67   Object.defineProperty(Validator.prototype, "offsetLimit", {
     68     get: function() { return this.message.buffer.byteLength; }
     69   });
     70 
     71   Object.defineProperty(Validator.prototype, "handleIndexLimit", {
     72     get: function() { return this.message.handles.length; }
     73   });
     74 
     75   // True if we can safely allocate a block of bytes from start to
     76   // to start + numBytes.
     77   Validator.prototype.isValidRange = function(start, numBytes) {
     78     // Only positive JavaScript integers that are less than 2^53
     79     // (Number.MAX_SAFE_INTEGER) can be represented exactly.
     80     if (start < this.offset || numBytes <= 0 ||
     81         !Number.isSafeInteger(start) ||
     82         !Number.isSafeInteger(numBytes))
     83       return false;
     84 
     85     var newOffset = start + numBytes;
     86     if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
     87       return false;
     88 
     89     return true;
     90   };
     91 
     92   Validator.prototype.claimRange = function(start, numBytes) {
     93     if (this.isValidRange(start, numBytes)) {
     94       this.offset = start + numBytes;
     95       return true;
     96     }
     97     return false;
     98   };
     99 
    100   Validator.prototype.claimHandle = function(index) {
    101     if (index === codec.kEncodedInvalidHandleValue)
    102       return true;
    103 
    104     if (index < this.handleIndex || index >= this.handleIndexLimit)
    105       return false;
    106 
    107     // This is safe because handle indices are uint32.
    108     this.handleIndex = index + 1;
    109     return true;
    110   };
    111 
    112   Validator.prototype.validateEnum = function(offset, enumClass) {
    113     // Note: Assumes that enums are always 32 bits! But this matches
    114     // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
    115     var value = this.message.buffer.getInt32(offset);
    116     return enumClass.validate(value);
    117   }
    118 
    119   Validator.prototype.validateHandle = function(offset, nullable) {
    120     var index = this.message.buffer.getUint32(offset);
    121 
    122     if (index === codec.kEncodedInvalidHandleValue)
    123       return nullable ?
    124           validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
    125 
    126     if (!this.claimHandle(index))
    127       return validationError.ILLEGAL_HANDLE;
    128 
    129     return validationError.NONE;
    130   };
    131 
    132   Validator.prototype.validateInterface = function(offset, nullable) {
    133     return this.validateHandle(offset, nullable);
    134   };
    135 
    136   Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
    137     return this.validateHandle(offset, nullable);
    138   };
    139 
    140   Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
    141     if (!codec.isAligned(offset))
    142       return validationError.MISALIGNED_OBJECT;
    143 
    144     if (!this.isValidRange(offset, codec.kStructHeaderSize))
    145       return validationError.ILLEGAL_MEMORY_RANGE;
    146 
    147     var numBytes = this.message.buffer.getUint32(offset);
    148 
    149     if (numBytes < minNumBytes)
    150       return validationError.UNEXPECTED_STRUCT_HEADER;
    151 
    152     if (!this.claimRange(offset, numBytes))
    153       return validationError.ILLEGAL_MEMORY_RANGE;
    154 
    155     return validationError.NONE;
    156   };
    157 
    158   Validator.prototype.validateStructVersion = function(offset, versionSizes) {
    159     var numBytes = this.message.buffer.getUint32(offset);
    160     var version = this.message.buffer.getUint32(offset + 4);
    161 
    162     if (version <= versionSizes[versionSizes.length - 1].version) {
    163       // Scan in reverse order to optimize for more recent versionSizes.
    164       for (var i = versionSizes.length - 1; i >= 0; --i) {
    165         if (version >= versionSizes[i].version) {
    166           if (numBytes == versionSizes[i].numBytes)
    167             break;
    168           return validationError.UNEXPECTED_STRUCT_HEADER;
    169         }
    170       }
    171     } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
    172       return validationError.UNEXPECTED_STRUCT_HEADER;
    173     }
    174 
    175     return validationError.NONE;
    176   };
    177 
    178   Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
    179     var structVersion = this.message.buffer.getUint32(offset + 4);
    180     return fieldVersion <= structVersion;
    181   };
    182 
    183   Validator.prototype.validateMessageHeader = function() {
    184 
    185     var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
    186     if (err != validationError.NONE)
    187       return err;
    188 
    189     var numBytes = this.message.getHeaderNumBytes();
    190     var version = this.message.getHeaderVersion();
    191 
    192     var validVersionAndNumBytes =
    193         (version == 0 && numBytes == codec.kMessageHeaderSize) ||
    194         (version == 1 &&
    195          numBytes == codec.kMessageWithRequestIDHeaderSize) ||
    196         (version > 1 &&
    197          numBytes >= codec.kMessageWithRequestIDHeaderSize);
    198     if (!validVersionAndNumBytes)
    199       return validationError.UNEXPECTED_STRUCT_HEADER;
    200 
    201     var expectsResponse = this.message.expectsResponse();
    202     var isResponse = this.message.isResponse();
    203 
    204     if (version == 0 && (expectsResponse || isResponse))
    205       return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
    206 
    207     if (isResponse && expectsResponse)
    208       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
    209 
    210     return validationError.NONE;
    211   };
    212 
    213   Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
    214     if (this.message.isResponse() || this.message.expectsResponse()) {
    215       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
    216     }
    217     return validationError.NONE;
    218   };
    219 
    220   Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
    221     if (this.message.isResponse() || !this.message.expectsResponse()) {
    222       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
    223     }
    224     return validationError.NONE;
    225   };
    226 
    227   Validator.prototype.validateMessageIsResponse = function() {
    228     if (this.message.expectsResponse() || !this.message.isResponse()) {
    229       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
    230     }
    231     return validationError.NONE;
    232   };
    233 
    234   // Returns the message.buffer relative offset this pointer "points to",
    235   // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
    236   // pointer's value is not valid.
    237   Validator.prototype.decodePointer = function(offset) {
    238     var pointerValue = this.message.buffer.getUint64(offset);
    239     if (pointerValue === 0)
    240       return NULL_MOJO_POINTER;
    241     var bufferOffset = offset + pointerValue;
    242     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
    243   };
    244 
    245   Validator.prototype.decodeUnionSize = function(offset) {
    246     return this.message.buffer.getUint32(offset);
    247   };
    248 
    249   Validator.prototype.decodeUnionTag = function(offset) {
    250     return this.message.buffer.getUint32(offset + 4);
    251   };
    252 
    253   Validator.prototype.validateArrayPointer = function(
    254       offset, elementSize, elementType, nullable, expectedDimensionSizes,
    255       currentDimension) {
    256     var arrayOffset = this.decodePointer(offset);
    257     if (arrayOffset === null)
    258       return validationError.ILLEGAL_POINTER;
    259 
    260     if (arrayOffset === NULL_MOJO_POINTER)
    261       return nullable ?
    262           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    263 
    264     return this.validateArray(arrayOffset, elementSize, elementType,
    265                               expectedDimensionSizes, currentDimension);
    266   };
    267 
    268   Validator.prototype.validateStructPointer = function(
    269       offset, structClass, nullable) {
    270     var structOffset = this.decodePointer(offset);
    271     if (structOffset === null)
    272       return validationError.ILLEGAL_POINTER;
    273 
    274     if (structOffset === NULL_MOJO_POINTER)
    275       return nullable ?
    276           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    277 
    278     return structClass.validate(this, structOffset);
    279   };
    280 
    281   Validator.prototype.validateUnion = function(
    282       offset, unionClass, nullable) {
    283     var size = this.message.buffer.getUint32(offset);
    284     if (size == 0) {
    285       return nullable ?
    286           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
    287     }
    288 
    289     return unionClass.validate(this, offset);
    290   };
    291 
    292   Validator.prototype.validateNestedUnion = function(
    293       offset, unionClass, nullable) {
    294     var unionOffset = this.decodePointer(offset);
    295     if (unionOffset === null)
    296       return validationError.ILLEGAL_POINTER;
    297 
    298     if (unionOffset === NULL_MOJO_POINTER)
    299       return nullable ?
    300           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
    301 
    302     return this.validateUnion(unionOffset, unionClass, nullable);
    303   };
    304 
    305   // This method assumes that the array at arrayPointerOffset has
    306   // been validated.
    307 
    308   Validator.prototype.arrayLength = function(arrayPointerOffset) {
    309     var arrayOffset = this.decodePointer(arrayPointerOffset);
    310     return this.message.buffer.getUint32(arrayOffset + 4);
    311   };
    312 
    313   Validator.prototype.validateMapPointer = function(
    314       offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
    315     // Validate the implicit map struct:
    316     // struct {array<keyClass> keys; array<valueClass> values};
    317     var structOffset = this.decodePointer(offset);
    318     if (structOffset === null)
    319       return validationError.ILLEGAL_POINTER;
    320 
    321     if (structOffset === NULL_MOJO_POINTER)
    322       return mapIsNullable ?
    323           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
    324 
    325     var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
    326     var err = this.validateStructHeader(structOffset, mapEncodedSize);
    327     if (err !== validationError.NONE)
    328         return err;
    329 
    330     // Validate the keys array.
    331     var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
    332     err = this.validateArrayPointer(
    333         keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
    334     if (err !== validationError.NONE)
    335         return err;
    336 
    337     // Validate the values array.
    338     var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
    339     var valuesArrayDimensions = [0]; // Validate the actual length below.
    340     if (valueClass instanceof codec.ArrayOf)
    341       valuesArrayDimensions =
    342           valuesArrayDimensions.concat(valueClass.dimensions());
    343     var err = this.validateArrayPointer(valuesArrayPointerOffset,
    344                                         valueClass.encodedSize,
    345                                         valueClass,
    346                                         valueIsNullable,
    347                                         valuesArrayDimensions,
    348                                         0);
    349     if (err !== validationError.NONE)
    350         return err;
    351 
    352     // Validate the lengths of the keys and values arrays.
    353     var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
    354     var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
    355     if (keysArrayLength != valuesArrayLength)
    356       return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
    357 
    358     return validationError.NONE;
    359   };
    360 
    361   Validator.prototype.validateStringPointer = function(offset, nullable) {
    362     return this.validateArrayPointer(
    363         offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
    364   };
    365 
    366   // Similar to Array_Data<T>::Validate()
    367   // mojo/public/cpp/bindings/lib/array_internal.h
    368 
    369   Validator.prototype.validateArray =
    370       function (offset, elementSize, elementType, expectedDimensionSizes,
    371                 currentDimension) {
    372     if (!codec.isAligned(offset))
    373       return validationError.MISALIGNED_OBJECT;
    374 
    375     if (!this.isValidRange(offset, codec.kArrayHeaderSize))
    376       return validationError.ILLEGAL_MEMORY_RANGE;
    377 
    378     var numBytes = this.message.buffer.getUint32(offset);
    379     var numElements = this.message.buffer.getUint32(offset + 4);
    380 
    381     // Note: this computation is "safe" because elementSize <= 8 and
    382     // numElements is a uint32.
    383     var elementsTotalSize = (elementType === codec.PackedBool) ?
    384         Math.ceil(numElements / 8) : (elementSize * numElements);
    385 
    386     if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
    387       return validationError.UNEXPECTED_ARRAY_HEADER;
    388 
    389     if (expectedDimensionSizes[currentDimension] != 0 &&
    390         numElements != expectedDimensionSizes[currentDimension]) {
    391       return validationError.UNEXPECTED_ARRAY_HEADER;
    392     }
    393 
    394     if (!this.claimRange(offset, numBytes))
    395       return validationError.ILLEGAL_MEMORY_RANGE;
    396 
    397     // Validate the array's elements if they are pointers or handles.
    398 
    399     var elementsOffset = offset + codec.kArrayHeaderSize;
    400     var nullable = isNullable(elementType);
    401 
    402     if (isHandleClass(elementType))
    403       return this.validateHandleElements(elementsOffset, numElements, nullable);
    404     if (isInterfaceClass(elementType))
    405       return this.validateInterfaceElements(
    406           elementsOffset, numElements, nullable);
    407     if (isInterfaceRequestClass(elementType))
    408       return this.validateInterfaceRequestElements(
    409           elementsOffset, numElements, nullable);
    410     if (isStringClass(elementType))
    411       return this.validateArrayElements(
    412           elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
    413     if (elementType instanceof codec.PointerTo)
    414       return this.validateStructElements(
    415           elementsOffset, numElements, elementType.cls, nullable);
    416     if (elementType instanceof codec.ArrayOf)
    417       return this.validateArrayElements(
    418           elementsOffset, numElements, elementType.cls, nullable,
    419           expectedDimensionSizes, currentDimension + 1);
    420     if (isEnumClass(elementType))
    421       return this.validateEnumElements(elementsOffset, numElements,
    422                                        elementType.cls);
    423 
    424     return validationError.NONE;
    425   };
    426 
    427   // Note: the |offset + i * elementSize| computation in the validateFooElements
    428   // methods below is "safe" because elementSize <= 8, offset and
    429   // numElements are uint32, and 0 <= i < numElements.
    430 
    431   Validator.prototype.validateHandleElements =
    432       function(offset, numElements, nullable) {
    433     var elementSize = codec.Handle.encodedSize;
    434     for (var i = 0; i < numElements; i++) {
    435       var elementOffset = offset + i * elementSize;
    436       var err = this.validateHandle(elementOffset, nullable);
    437       if (err != validationError.NONE)
    438         return err;
    439     }
    440     return validationError.NONE;
    441   };
    442 
    443   Validator.prototype.validateInterfaceElements =
    444       function(offset, numElements, nullable) {
    445     var elementSize = codec.Interface.prototype.encodedSize;
    446     for (var i = 0; i < numElements; i++) {
    447       var elementOffset = offset + i * elementSize;
    448       var err = this.validateInterface(elementOffset, nullable);
    449       if (err != validationError.NONE)
    450         return err;
    451     }
    452     return validationError.NONE;
    453   };
    454 
    455   Validator.prototype.validateInterfaceRequestElements =
    456       function(offset, numElements, nullable) {
    457     var elementSize = codec.InterfaceRequest.encodedSize;
    458     for (var i = 0; i < numElements; i++) {
    459       var elementOffset = offset + i * elementSize;
    460       var err = this.validateInterfaceRequest(elementOffset, nullable);
    461       if (err != validationError.NONE)
    462         return err;
    463     }
    464     return validationError.NONE;
    465   };
    466 
    467   // The elementClass parameter is the element type of the element arrays.
    468   Validator.prototype.validateArrayElements =
    469       function(offset, numElements, elementClass, nullable,
    470                expectedDimensionSizes, currentDimension) {
    471     var elementSize = codec.PointerTo.prototype.encodedSize;
    472     for (var i = 0; i < numElements; i++) {
    473       var elementOffset = offset + i * elementSize;
    474       var err = this.validateArrayPointer(
    475           elementOffset, elementClass.encodedSize, elementClass, nullable,
    476           expectedDimensionSizes, currentDimension);
    477       if (err != validationError.NONE)
    478         return err;
    479     }
    480     return validationError.NONE;
    481   };
    482 
    483   Validator.prototype.validateStructElements =
    484       function(offset, numElements, structClass, nullable) {
    485     var elementSize = codec.PointerTo.prototype.encodedSize;
    486     for (var i = 0; i < numElements; i++) {
    487       var elementOffset = offset + i * elementSize;
    488       var err =
    489           this.validateStructPointer(elementOffset, structClass, nullable);
    490       if (err != validationError.NONE)
    491         return err;
    492     }
    493     return validationError.NONE;
    494   };
    495 
    496   Validator.prototype.validateEnumElements =
    497       function(offset, numElements, enumClass) {
    498     var elementSize = codec.Enum.prototype.encodedSize;
    499     for (var i = 0; i < numElements; i++) {
    500       var elementOffset = offset + i * elementSize;
    501       var err = this.validateEnum(elementOffset, enumClass);
    502       if (err != validationError.NONE)
    503         return err;
    504     }
    505     return validationError.NONE;
    506   };
    507 
    508   var exports = {};
    509   exports.validationError = validationError;
    510   exports.Validator = Validator;
    511   return exports;
    512 });
    513