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