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.
5 define("mojo/public/js/bindings/validator", [
6 "mojo/public/js/bindings/codec",
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'
25 var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
27 function isStringClass(cls) {
28 return cls === codec.String || cls === codec.NullableString;
31 function isHandleClass(cls) {
32 return cls === codec.Handle || cls === codec.NullableHandle;
35 function isNullable(type) {
36 return type === codec.NullableString || type === codec.NullableHandle ||
37 type instanceof codec.NullableArrayOf ||
38 type instanceof codec.NullablePointerTo;
41 function Validator(message) {
42 this.message = message;
47 Object.defineProperty(Validator.prototype, "offsetLimit", {
48 get: function() { return this.message.buffer.byteLength; }
51 Object.defineProperty(Validator.prototype, "handleIndexLimit", {
52 get: function() { return this.message.handles.length; }
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))
65 var newOffset = start + numBytes;
66 if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
72 Validator.prototype.claimRange = function(start, numBytes) {
73 if (this.isValidRange(start, numBytes)) {
74 this.offset = start + numBytes;
80 Validator.prototype.claimHandle = function(index) {
81 if (index === codec.kEncodedInvalidHandleValue)
84 if (index < this.handleIndex || index >= this.handleIndexLimit)
87 // This is safe because handle indices are uint32.
88 this.handleIndex = index + 1;
92 Validator.prototype.validateHandle = function(offset, nullable) {
93 var index = this.message.buffer.getUint32(offset);
95 if (index === codec.kEncodedInvalidHandleValue)
97 validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
99 if (!this.claimHandle(index))
100 return validationError.ILLEGAL_HANDLE;
101 return validationError.NONE;
104 Validator.prototype.validateStructHeader =
105 function(offset, minNumBytes, minNumFields) {
106 if (!codec.isAligned(offset))
107 return validationError.MISALIGNED_OBJECT;
109 if (!this.isValidRange(offset, codec.kStructHeaderSize))
110 return validationError.ILLEGAL_MEMORY_RANGE;
112 var numBytes = this.message.buffer.getUint32(offset);
113 var numFields = this.message.buffer.getUint32(offset + 4);
115 if (numBytes < minNumBytes || numFields < minNumFields)
116 return validationError.UNEXPECTED_STRUCT_HEADER;
118 if (!this.claimRange(offset, numBytes))
119 return validationError.ILLEGAL_MEMORY_RANGE;
121 return validationError.NONE;
124 Validator.prototype.validateMessageHeader = function() {
125 var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
126 if (err != validationError.NONE)
129 var numBytes = this.message.getHeaderNumBytes();
130 var numFields = this.message.getHeaderNumFields();
132 var validNumFieldsAndNumBytes =
133 (numFields == 2 && numBytes == codec.kMessageHeaderSize) ||
135 numBytes == codec.kMessageWithRequestIDHeaderSize) ||
137 numBytes >= codec.kMessageWithRequestIDHeaderSize);
138 if (!validNumFieldsAndNumBytes)
139 return validationError.UNEXPECTED_STRUCT_HEADER;
141 var expectsResponse = this.message.expectsResponse();
142 var isResponse = this.message.isResponse();
144 if (numFields == 2 && (expectsResponse || isResponse))
145 return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
147 if (isResponse && expectsResponse)
148 return validationError.MESSAGE_HEADER_INVALID_FLAG_COMBINATION;
150 return validationError.NONE;
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;
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;
170 if (arrayOffset === NULL_MOJO_POINTER)
172 validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
174 return this.validateArray(
175 arrayOffset, elementSize, expectedElementCount, elementType);
178 Validator.prototype.validateStructPointer = function(
179 offset, structClass, nullable) {
180 var structOffset = this.decodePointer(offset);
181 if (structOffset === null)
182 return validationError.ILLEGAL_POINTER;
184 if (structOffset === NULL_MOJO_POINTER)
186 validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
188 return structClass.validate(this, structOffset);
191 Validator.prototype.validateStringPointer = function(offset, nullable) {
192 return this.validateArrayPointer(
193 offset, codec.Uint8.encodedSize, 0, codec.Uint8, nullable);
196 // Similar to Array_Data<T>::Validate()
197 // mojo/public/cpp/bindings/lib/array_internal.h
199 Validator.prototype.validateArray =
200 function (offset, elementSize, expectedElementCount, elementType) {
201 if (!codec.isAligned(offset))
202 return validationError.MISALIGNED_OBJECT;
204 if (!this.isValidRange(offset, codec.kArrayHeaderSize))
205 return validationError.ILLEGAL_MEMORY_RANGE;
207 var numBytes = this.message.buffer.getUint32(offset);
208 var numElements = this.message.buffer.getUint32(offset + 4);
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);
215 if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
216 return validationError.UNEXPECTED_ARRAY_HEADER;
218 if (expectedElementCount != 0 && numElements != expectedElementCount)
219 return validationError.UNEXPECTED_ARRAY_HEADER;
221 if (!this.claimRange(offset, numBytes))
222 return validationError.ILLEGAL_MEMORY_RANGE;
224 // Validate the array's elements if they are pointers or handles.
226 var elementsOffset = offset + codec.kArrayHeaderSize;
227 var nullable = isNullable(elementType);
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);
241 return validationError.NONE;
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.
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)
257 return validationError.NONE;
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)
271 return validationError.NONE;
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;
280 this.validateStructPointer(elementOffset, structClass, nullable);
281 if (err != validationError.NONE)
284 return validationError.NONE;
288 exports.validationError = validationError;
289 exports.Validator = Validator;