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/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