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