• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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