• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview A buffer implementation that can decode data for protobufs.
3 */
4
5goog.module('protobuf.binary.BufferDecoder');
6
7const ByteString = goog.require('protobuf.ByteString');
8const functions = goog.require('goog.functions');
9const {POLYFILL_TEXT_ENCODING, checkCriticalPositionIndex, checkCriticalState, checkState} = goog.require('protobuf.internal.checks');
10const {byteStringFromUint8ArrayUnsafe} = goog.require('protobuf.byteStringInternal');
11const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
12const {decode} = goog.require('protobuf.binary.textencoding');
13
14/**
15 * Returns a valid utf-8 decoder function based on TextDecoder if available or
16 * a polyfill.
17 * Some of the environments we run in do not have TextDecoder defined.
18 * TextDecoder is faster than our polyfill so we prefer it over the polyfill.
19 * @return {function(!DataView): string}
20 */
21function getStringDecoderFunction() {
22  if (goog.global['TextDecoder']) {
23    const textDecoder = new goog.global['TextDecoder']('utf-8', {fatal: true});
24    return bytes => textDecoder.decode(bytes);
25  }
26  if (POLYFILL_TEXT_ENCODING) {
27    return decode;
28  } else {
29    throw new Error(
30        'TextDecoder is missing. ' +
31        'Enable protobuf.defines.POLYFILL_TEXT_ENCODING.');
32  }
33}
34
35/** @type {function(): function(!DataView): string} */
36const stringDecoderFunction =
37    functions.cacheReturnValue(() => getStringDecoderFunction());
38
39/** @type {function(): !DataView} */
40const emptyDataView =
41    functions.cacheReturnValue(() => new DataView(new ArrayBuffer(0)));
42
43class BufferDecoder {
44  /**
45   * @param {!Array<!BufferDecoder>} bufferDecoders
46   * @return {!BufferDecoder}
47   */
48  static merge(bufferDecoders) {
49    const uint8Arrays = bufferDecoders.map(b => b.asUint8Array());
50    const bytesArray = concatenateByteArrays(uint8Arrays);
51    return BufferDecoder.fromArrayBuffer(bytesArray.buffer);
52  }
53
54  /**
55   * @param {!ArrayBuffer} arrayBuffer
56   * @return {!BufferDecoder}
57   */
58  static fromArrayBuffer(arrayBuffer) {
59    return new BufferDecoder(
60        new DataView(arrayBuffer), 0, arrayBuffer.byteLength);
61  }
62
63  /**
64   * @param {!DataView} dataView
65   * @param {number} startIndex
66   * @param {number} length
67   * @private
68   */
69  constructor(dataView, startIndex, length) {
70    /** @private @const {!DataView} */
71    this.dataView_ = dataView;
72    /** @private @const {number} */
73    this.startIndex_ = startIndex;
74    /** @private @const {number} */
75    this.endIndex_ = startIndex + length;
76    /** @private {number} */
77    this.cursor_ = startIndex;
78  }
79
80  /**
81   * Returns the start index of the underlying buffer.
82   * @return {number}
83   */
84  startIndex() {
85    return this.startIndex_;
86  }
87
88  /**
89   * Returns the end index of the underlying buffer.
90   * @return {number}
91   */
92  endIndex() {
93    return this.endIndex_;
94  }
95
96  /**
97   * Returns the length of the underlying buffer.
98   * @return {number}
99   */
100  length() {
101    return this.endIndex_ - this.startIndex_;
102  }
103
104  /**
105   * Returns the start position of the next data, i.e. end position of the last
106   * read data + 1.
107   * @return {number}
108   */
109  cursor() {
110    return this.cursor_;
111  }
112
113  /**
114   * Sets the cursor to the specified position.
115   * @param {number} position
116   */
117  setCursor(position) {
118    this.cursor_ = position;
119  }
120
121  /**
122   * Returns if there is more data to read after the current cursor position.
123   * @return {boolean}
124   */
125  hasNext() {
126    return this.cursor_ < this.endIndex_;
127  }
128
129  /**
130   * Returns a float32 from a given index
131   * @param {number} index
132   * @return {number}
133   */
134  getFloat32(index) {
135    this.cursor_ = index + 4;
136    return this.dataView_.getFloat32(index, true);
137  }
138
139  /**
140   * Returns a float64 from a given index
141   * @param {number} index
142   * @return {number}
143   */
144  getFloat64(index) {
145    this.cursor_ = index + 8;
146    return this.dataView_.getFloat64(index, true);
147  }
148
149  /**
150   * Returns an int32 from a given index
151   * @param {number} index
152   * @return {number}
153   */
154  getInt32(index) {
155    this.cursor_ = index + 4;
156    return this.dataView_.getInt32(index, true);
157  }
158
159  /**
160   * Returns a uint32 from a given index
161   * @param {number} index
162   * @return {number}
163   */
164  getUint32(index) {
165    this.cursor_ = index + 4;
166    return this.dataView_.getUint32(index, true);
167  }
168
169  /**
170   * Returns two JS numbers each representing 32 bits of a 64 bit number. Also
171   * sets the cursor to the start of the next block of data.
172   * @param {number} index
173   * @return {{lowBits: number, highBits: number}}
174   */
175  getVarint(index) {
176    this.cursor_ = index;
177    let lowBits = 0;
178    let highBits = 0;
179
180    for (let shift = 0; shift < 28; shift += 7) {
181      const b = this.dataView_.getUint8(this.cursor_++);
182      lowBits |= (b & 0x7F) << shift;
183      if ((b & 0x80) === 0) {
184        return {lowBits, highBits};
185      }
186    }
187
188    const middleByte = this.dataView_.getUint8(this.cursor_++);
189
190    // last four bits of the first 32 bit number
191    lowBits |= (middleByte & 0x0F) << 28;
192
193    // 3 upper bits are part of the next 32 bit number
194    highBits = (middleByte & 0x70) >> 4;
195
196    if ((middleByte & 0x80) === 0) {
197      return {lowBits, highBits};
198    }
199
200
201    for (let shift = 3; shift <= 31; shift += 7) {
202      const b = this.dataView_.getUint8(this.cursor_++);
203      highBits |= (b & 0x7F) << shift;
204      if ((b & 0x80) === 0) {
205        return {lowBits, highBits};
206      }
207    }
208
209    checkCriticalState(false, 'Data is longer than 10 bytes');
210
211    return {lowBits, highBits};
212  }
213
214  /**
215   * Returns an unsigned int32 number at the current cursor position. The upper
216   * bits are discarded if the varint is longer than 32 bits. Also sets the
217   * cursor to the start of the next block of data.
218   * @return {number}
219   */
220  getUnsignedVarint32() {
221    let b = this.dataView_.getUint8(this.cursor_++);
222    let result = b & 0x7F;
223    if ((b & 0x80) === 0) {
224      return result;
225    }
226
227    b = this.dataView_.getUint8(this.cursor_++);
228    result |= (b & 0x7F) << 7;
229    if ((b & 0x80) === 0) {
230      return result;
231    }
232
233    b = this.dataView_.getUint8(this.cursor_++);
234    result |= (b & 0x7F) << 14;
235    if ((b & 0x80) === 0) {
236      return result;
237    }
238
239    b = this.dataView_.getUint8(this.cursor_++);
240    result |= (b & 0x7F) << 21;
241    if ((b & 0x80) === 0) {
242      return result;
243    }
244
245    // Extract only last 4 bits
246    b = this.dataView_.getUint8(this.cursor_++);
247    result |= (b & 0x0F) << 28;
248
249    for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) {
250      b = this.dataView_.getUint8(this.cursor_++);
251    }
252
253    checkCriticalState((b & 0x80) === 0, 'Data is longer than 10 bytes');
254
255    // Result can be have 32 bits, convert it to unsigned
256    return result >>> 0;
257  }
258
259  /**
260   * Returns an unsigned int32 number at the specified index. The upper bits are
261   * discarded if the varint is longer than 32 bits. Also sets the cursor to the
262   * start of the next block of data.
263   * @param {number} index
264   * @return {number}
265   */
266  getUnsignedVarint32At(index) {
267    this.cursor_ = index;
268    return this.getUnsignedVarint32();
269  }
270
271  /**
272   * Seeks forward by the given amount.
273   * @param {number} skipAmount
274   * @package
275   */
276  skip(skipAmount) {
277    this.cursor_ += skipAmount;
278    checkCriticalPositionIndex(this.cursor_, this.endIndex_);
279  }
280
281  /**
282   * Skips over a varint from the current cursor position.
283   * @package
284   */
285  skipVarint() {
286    const startIndex = this.cursor_;
287    while (this.dataView_.getUint8(this.cursor_++) & 0x80) {
288    }
289    checkCriticalPositionIndex(this.cursor_, startIndex + 10);
290  }
291
292  /**
293   * @param {number} startIndex
294   * @param {number} length
295   * @return {!BufferDecoder}
296   */
297  subBufferDecoder(startIndex, length) {
298    checkState(
299        startIndex >= this.startIndex(),
300        `Current start: ${this.startIndex()}, subBufferDecoder start: ${
301            startIndex}`);
302    checkState(length >= 0, `Length: ${length}`);
303    checkState(
304        startIndex + length <= this.endIndex(),
305        `Current end: ${this.endIndex()}, subBufferDecoder start: ${
306            startIndex}, subBufferDecoder length: ${length}`);
307    return new BufferDecoder(this.dataView_, startIndex, length);
308  }
309
310  /**
311   * Returns the buffer as a string.
312   * @return {string}
313   */
314  asString() {
315    // TODO: Remove this check when we no longer need to support IE
316    const stringDataView = this.length() === 0 ?
317        emptyDataView() :
318        new DataView(this.dataView_.buffer, this.startIndex_, this.length());
319    return stringDecoderFunction()(stringDataView);
320  }
321
322  /**
323   * Returns the buffer as a ByteString.
324   * @return {!ByteString}
325   */
326  asByteString() {
327    return byteStringFromUint8ArrayUnsafe(this.asUint8Array());
328  }
329
330  /**
331   * Returns the DataView as an Uint8Array. DO NOT MODIFY or expose the
332   * underlying buffer.
333   *
334   * @package
335   * @return {!Uint8Array}
336   */
337  asUint8Array() {
338    return new Uint8Array(
339        this.dataView_.buffer, this.startIndex_, this.length());
340  }
341}
342
343exports = BufferDecoder;
344