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