Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | |
michael@0 | 6 | /** |
michael@0 | 7 | * This object contains helpers buffering incoming data & deconstructing it |
michael@0 | 8 | * into parcels as well as buffering outgoing data & constructing parcels. |
michael@0 | 9 | * For that it maintains two buffers and corresponding uint8 views, indexes. |
michael@0 | 10 | * |
michael@0 | 11 | * The incoming buffer is a circular buffer where we store incoming data. |
michael@0 | 12 | * As soon as a complete parcel is received, it is processed right away, so |
michael@0 | 13 | * the buffer only needs to be large enough to hold one parcel. |
michael@0 | 14 | * |
michael@0 | 15 | * The outgoing buffer is to prepare outgoing parcels. The index is reset |
michael@0 | 16 | * every time a parcel is sent. |
michael@0 | 17 | */ |
michael@0 | 18 | |
michael@0 | 19 | let Buf = { |
michael@0 | 20 | INT32_MAX: 2147483647, |
michael@0 | 21 | UINT8_SIZE: 1, |
michael@0 | 22 | UINT16_SIZE: 2, |
michael@0 | 23 | UINT32_SIZE: 4, |
michael@0 | 24 | PARCEL_SIZE_SIZE: 4, |
michael@0 | 25 | PDU_HEX_OCTET_SIZE: 4, |
michael@0 | 26 | |
michael@0 | 27 | incomingBufferLength: 1024, |
michael@0 | 28 | incomingBuffer: null, |
michael@0 | 29 | incomingBytes: null, |
michael@0 | 30 | incomingWriteIndex: 0, |
michael@0 | 31 | incomingReadIndex: 0, |
michael@0 | 32 | readIncoming: 0, |
michael@0 | 33 | readAvailable: 0, |
michael@0 | 34 | currentParcelSize: 0, |
michael@0 | 35 | |
michael@0 | 36 | outgoingBufferLength: 1024, |
michael@0 | 37 | outgoingBuffer: null, |
michael@0 | 38 | outgoingBytes: null, |
michael@0 | 39 | outgoingIndex: 0, |
michael@0 | 40 | outgoingBufferCalSizeQueue: null, |
michael@0 | 41 | |
michael@0 | 42 | _init: function() { |
michael@0 | 43 | this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength); |
michael@0 | 44 | this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength); |
michael@0 | 45 | |
michael@0 | 46 | this.incomingBytes = new Uint8Array(this.incomingBuffer); |
michael@0 | 47 | this.outgoingBytes = new Uint8Array(this.outgoingBuffer); |
michael@0 | 48 | |
michael@0 | 49 | // Track where incoming data is read from and written to. |
michael@0 | 50 | this.incomingWriteIndex = 0; |
michael@0 | 51 | this.incomingReadIndex = 0; |
michael@0 | 52 | |
michael@0 | 53 | // Leave room for the parcel size for outgoing parcels. |
michael@0 | 54 | this.outgoingIndex = this.PARCEL_SIZE_SIZE; |
michael@0 | 55 | |
michael@0 | 56 | // How many bytes we've read for this parcel so far. |
michael@0 | 57 | this.readIncoming = 0; |
michael@0 | 58 | |
michael@0 | 59 | // How many bytes available as parcel data. |
michael@0 | 60 | this.readAvailable = 0; |
michael@0 | 61 | |
michael@0 | 62 | // Size of the incoming parcel. If this is zero, we're expecting a new |
michael@0 | 63 | // parcel. |
michael@0 | 64 | this.currentParcelSize = 0; |
michael@0 | 65 | |
michael@0 | 66 | // Queue for storing outgoing override points |
michael@0 | 67 | this.outgoingBufferCalSizeQueue = []; |
michael@0 | 68 | }, |
michael@0 | 69 | |
michael@0 | 70 | /** |
michael@0 | 71 | * Mark current outgoingIndex as start point for calculation length of data |
michael@0 | 72 | * written to outgoingBuffer. |
michael@0 | 73 | * Mark can be nested for here uses queue to remember marks. |
michael@0 | 74 | * |
michael@0 | 75 | * @param writeFunction |
michael@0 | 76 | * Function to write data length into outgoingBuffer, this function is |
michael@0 | 77 | * also used to allocate buffer for data length. |
michael@0 | 78 | * Raw data size(in Uint8) is provided as parameter calling writeFunction. |
michael@0 | 79 | * If raw data size is not in proper unit for writing, user can adjust |
michael@0 | 80 | * the length value in writeFunction before writing. |
michael@0 | 81 | **/ |
michael@0 | 82 | startCalOutgoingSize: function(writeFunction) { |
michael@0 | 83 | let sizeInfo = {index: this.outgoingIndex, |
michael@0 | 84 | write: writeFunction}; |
michael@0 | 85 | |
michael@0 | 86 | // Allocate buffer for data lemgtj. |
michael@0 | 87 | writeFunction.call(0); |
michael@0 | 88 | |
michael@0 | 89 | // Get size of data length buffer for it is not counted into data size. |
michael@0 | 90 | sizeInfo.size = this.outgoingIndex - sizeInfo.index; |
michael@0 | 91 | |
michael@0 | 92 | // Enqueue size calculation information. |
michael@0 | 93 | this.outgoingBufferCalSizeQueue.push(sizeInfo); |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | /** |
michael@0 | 97 | * Calculate data length since last mark, and write it into mark position. |
michael@0 | 98 | **/ |
michael@0 | 99 | stopCalOutgoingSize: function() { |
michael@0 | 100 | let sizeInfo = this.outgoingBufferCalSizeQueue.pop(); |
michael@0 | 101 | |
michael@0 | 102 | // Remember current outgoingIndex. |
michael@0 | 103 | let currentOutgoingIndex = this.outgoingIndex; |
michael@0 | 104 | // Calculate data length, in uint8. |
michael@0 | 105 | let writeSize = this.outgoingIndex - sizeInfo.index - sizeInfo.size; |
michael@0 | 106 | |
michael@0 | 107 | // Write data length to mark, use same function for allocating buffer to make |
michael@0 | 108 | // sure there is no buffer overloading. |
michael@0 | 109 | this.outgoingIndex = sizeInfo.index; |
michael@0 | 110 | sizeInfo.write(writeSize); |
michael@0 | 111 | |
michael@0 | 112 | // Restore outgoingIndex. |
michael@0 | 113 | this.outgoingIndex = currentOutgoingIndex; |
michael@0 | 114 | }, |
michael@0 | 115 | |
michael@0 | 116 | /** |
michael@0 | 117 | * Grow the incoming buffer. |
michael@0 | 118 | * |
michael@0 | 119 | * @param min_size |
michael@0 | 120 | * Minimum new size. The actual new size will be the the smallest |
michael@0 | 121 | * power of 2 that's larger than this number. |
michael@0 | 122 | */ |
michael@0 | 123 | growIncomingBuffer: function(min_size) { |
michael@0 | 124 | if (DEBUG) { |
michael@0 | 125 | debug("Current buffer of " + this.incomingBufferLength + |
michael@0 | 126 | " can't handle incoming " + min_size + " bytes."); |
michael@0 | 127 | } |
michael@0 | 128 | let oldBytes = this.incomingBytes; |
michael@0 | 129 | this.incomingBufferLength = |
michael@0 | 130 | 2 << Math.floor(Math.log(min_size)/Math.log(2)); |
michael@0 | 131 | if (DEBUG) debug("New incoming buffer size: " + this.incomingBufferLength); |
michael@0 | 132 | this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength); |
michael@0 | 133 | this.incomingBytes = new Uint8Array(this.incomingBuffer); |
michael@0 | 134 | if (this.incomingReadIndex <= this.incomingWriteIndex) { |
michael@0 | 135 | // Read and write index are in natural order, so we can just copy |
michael@0 | 136 | // the old buffer over to the bigger one without having to worry |
michael@0 | 137 | // about the indexes. |
michael@0 | 138 | this.incomingBytes.set(oldBytes, 0); |
michael@0 | 139 | } else { |
michael@0 | 140 | // The write index has wrapped around but the read index hasn't yet. |
michael@0 | 141 | // Write whatever the read index has left to read until it would |
michael@0 | 142 | // circle around to the beginning of the new buffer, and the rest |
michael@0 | 143 | // behind that. |
michael@0 | 144 | let head = oldBytes.subarray(this.incomingReadIndex); |
michael@0 | 145 | let tail = oldBytes.subarray(0, this.incomingReadIndex); |
michael@0 | 146 | this.incomingBytes.set(head, 0); |
michael@0 | 147 | this.incomingBytes.set(tail, head.length); |
michael@0 | 148 | this.incomingReadIndex = 0; |
michael@0 | 149 | this.incomingWriteIndex += head.length; |
michael@0 | 150 | } |
michael@0 | 151 | if (DEBUG) { |
michael@0 | 152 | debug("New incoming buffer size is " + this.incomingBufferLength); |
michael@0 | 153 | } |
michael@0 | 154 | }, |
michael@0 | 155 | |
michael@0 | 156 | /** |
michael@0 | 157 | * Grow the outgoing buffer. |
michael@0 | 158 | * |
michael@0 | 159 | * @param min_size |
michael@0 | 160 | * Minimum new size. The actual new size will be the the smallest |
michael@0 | 161 | * power of 2 that's larger than this number. |
michael@0 | 162 | */ |
michael@0 | 163 | growOutgoingBuffer: function(min_size) { |
michael@0 | 164 | if (DEBUG) { |
michael@0 | 165 | debug("Current buffer of " + this.outgoingBufferLength + |
michael@0 | 166 | " is too small."); |
michael@0 | 167 | } |
michael@0 | 168 | let oldBytes = this.outgoingBytes; |
michael@0 | 169 | this.outgoingBufferLength = |
michael@0 | 170 | 2 << Math.floor(Math.log(min_size)/Math.log(2)); |
michael@0 | 171 | this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength); |
michael@0 | 172 | this.outgoingBytes = new Uint8Array(this.outgoingBuffer); |
michael@0 | 173 | this.outgoingBytes.set(oldBytes, 0); |
michael@0 | 174 | if (DEBUG) { |
michael@0 | 175 | debug("New outgoing buffer size is " + this.outgoingBufferLength); |
michael@0 | 176 | } |
michael@0 | 177 | }, |
michael@0 | 178 | |
michael@0 | 179 | /** |
michael@0 | 180 | * Functions for reading data from the incoming buffer. |
michael@0 | 181 | * |
michael@0 | 182 | * These are all little endian, apart from readParcelSize(); |
michael@0 | 183 | */ |
michael@0 | 184 | |
michael@0 | 185 | /** |
michael@0 | 186 | * Ensure position specified is readable. |
michael@0 | 187 | * |
michael@0 | 188 | * @param index |
michael@0 | 189 | * Data position in incoming parcel, valid from 0 to |
michael@0 | 190 | * currentParcelSize. |
michael@0 | 191 | */ |
michael@0 | 192 | ensureIncomingAvailable: function(index) { |
michael@0 | 193 | if (index >= this.currentParcelSize) { |
michael@0 | 194 | throw new Error("Trying to read data beyond the parcel end!"); |
michael@0 | 195 | } else if (index < 0) { |
michael@0 | 196 | throw new Error("Trying to read data before the parcel begin!"); |
michael@0 | 197 | } |
michael@0 | 198 | }, |
michael@0 | 199 | |
michael@0 | 200 | /** |
michael@0 | 201 | * Seek in current incoming parcel. |
michael@0 | 202 | * |
michael@0 | 203 | * @param offset |
michael@0 | 204 | * Seek offset in relative to current position. |
michael@0 | 205 | */ |
michael@0 | 206 | seekIncoming: function(offset) { |
michael@0 | 207 | // Translate to 0..currentParcelSize |
michael@0 | 208 | let cur = this.currentParcelSize - this.readAvailable; |
michael@0 | 209 | |
michael@0 | 210 | let newIndex = cur + offset; |
michael@0 | 211 | this.ensureIncomingAvailable(newIndex); |
michael@0 | 212 | |
michael@0 | 213 | // ... incomingReadIndex -->| |
michael@0 | 214 | // 0 new cur currentParcelSize |
michael@0 | 215 | // |================|=======|====================| |
michael@0 | 216 | // |<-- cur -->|<- readAvailable ->| |
michael@0 | 217 | // |<-- newIndex -->|<-- new readAvailable -->| |
michael@0 | 218 | this.readAvailable = this.currentParcelSize - newIndex; |
michael@0 | 219 | |
michael@0 | 220 | // Translate back: |
michael@0 | 221 | if (this.incomingReadIndex < cur) { |
michael@0 | 222 | // The incomingReadIndex is wrapped. |
michael@0 | 223 | newIndex += this.incomingBufferLength; |
michael@0 | 224 | } |
michael@0 | 225 | newIndex += (this.incomingReadIndex - cur); |
michael@0 | 226 | newIndex %= this.incomingBufferLength; |
michael@0 | 227 | this.incomingReadIndex = newIndex; |
michael@0 | 228 | }, |
michael@0 | 229 | |
michael@0 | 230 | readUint8Unchecked: function() { |
michael@0 | 231 | let value = this.incomingBytes[this.incomingReadIndex]; |
michael@0 | 232 | this.incomingReadIndex = (this.incomingReadIndex + 1) % |
michael@0 | 233 | this.incomingBufferLength; |
michael@0 | 234 | return value; |
michael@0 | 235 | }, |
michael@0 | 236 | |
michael@0 | 237 | readUint8: function() { |
michael@0 | 238 | // Translate to 0..currentParcelSize |
michael@0 | 239 | let cur = this.currentParcelSize - this.readAvailable; |
michael@0 | 240 | this.ensureIncomingAvailable(cur); |
michael@0 | 241 | |
michael@0 | 242 | this.readAvailable--; |
michael@0 | 243 | return this.readUint8Unchecked(); |
michael@0 | 244 | }, |
michael@0 | 245 | |
michael@0 | 246 | readUint8Array: function(length) { |
michael@0 | 247 | // Translate to 0..currentParcelSize |
michael@0 | 248 | let last = this.currentParcelSize - this.readAvailable; |
michael@0 | 249 | last += (length - 1); |
michael@0 | 250 | this.ensureIncomingAvailable(last); |
michael@0 | 251 | |
michael@0 | 252 | let array = new Uint8Array(length); |
michael@0 | 253 | for (let i = 0; i < length; i++) { |
michael@0 | 254 | array[i] = this.readUint8Unchecked(); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | this.readAvailable -= length; |
michael@0 | 258 | return array; |
michael@0 | 259 | }, |
michael@0 | 260 | |
michael@0 | 261 | readUint16: function() { |
michael@0 | 262 | return this.readUint8() | this.readUint8() << 8; |
michael@0 | 263 | }, |
michael@0 | 264 | |
michael@0 | 265 | readInt32: function() { |
michael@0 | 266 | return this.readUint8() | this.readUint8() << 8 | |
michael@0 | 267 | this.readUint8() << 16 | this.readUint8() << 24; |
michael@0 | 268 | }, |
michael@0 | 269 | |
michael@0 | 270 | readInt32List: function() { |
michael@0 | 271 | let length = this.readInt32(); |
michael@0 | 272 | let ints = []; |
michael@0 | 273 | for (let i = 0; i < length; i++) { |
michael@0 | 274 | ints.push(this.readInt32()); |
michael@0 | 275 | } |
michael@0 | 276 | return ints; |
michael@0 | 277 | }, |
michael@0 | 278 | |
michael@0 | 279 | readString: function() { |
michael@0 | 280 | let string_len = this.readInt32(); |
michael@0 | 281 | if (string_len < 0 || string_len >= this.INT32_MAX) { |
michael@0 | 282 | return null; |
michael@0 | 283 | } |
michael@0 | 284 | let s = ""; |
michael@0 | 285 | for (let i = 0; i < string_len; i++) { |
michael@0 | 286 | s += String.fromCharCode(this.readUint16()); |
michael@0 | 287 | } |
michael@0 | 288 | // Strings are \0\0 delimited, but that isn't part of the length. And |
michael@0 | 289 | // if the string length is even, the delimiter is two characters wide. |
michael@0 | 290 | // It's insane, I know. |
michael@0 | 291 | this.readStringDelimiter(string_len); |
michael@0 | 292 | return s; |
michael@0 | 293 | }, |
michael@0 | 294 | |
michael@0 | 295 | readStringList: function() { |
michael@0 | 296 | let num_strings = this.readInt32(); |
michael@0 | 297 | let strings = []; |
michael@0 | 298 | for (let i = 0; i < num_strings; i++) { |
michael@0 | 299 | strings.push(this.readString()); |
michael@0 | 300 | } |
michael@0 | 301 | return strings; |
michael@0 | 302 | }, |
michael@0 | 303 | |
michael@0 | 304 | readStringDelimiter: function(length) { |
michael@0 | 305 | let delimiter = this.readUint16(); |
michael@0 | 306 | if (!(length & 1)) { |
michael@0 | 307 | delimiter |= this.readUint16(); |
michael@0 | 308 | } |
michael@0 | 309 | if (DEBUG) { |
michael@0 | 310 | if (delimiter !== 0) { |
michael@0 | 311 | debug("Something's wrong, found string delimiter: " + delimiter); |
michael@0 | 312 | } |
michael@0 | 313 | } |
michael@0 | 314 | }, |
michael@0 | 315 | |
michael@0 | 316 | readParcelSize: function() { |
michael@0 | 317 | return this.readUint8Unchecked() << 24 | |
michael@0 | 318 | this.readUint8Unchecked() << 16 | |
michael@0 | 319 | this.readUint8Unchecked() << 8 | |
michael@0 | 320 | this.readUint8Unchecked(); |
michael@0 | 321 | }, |
michael@0 | 322 | |
michael@0 | 323 | /** |
michael@0 | 324 | * Functions for writing data to the outgoing buffer. |
michael@0 | 325 | */ |
michael@0 | 326 | |
michael@0 | 327 | /** |
michael@0 | 328 | * Ensure position specified is writable. |
michael@0 | 329 | * |
michael@0 | 330 | * @param index |
michael@0 | 331 | * Data position in outgoing parcel, valid from 0 to |
michael@0 | 332 | * outgoingBufferLength. |
michael@0 | 333 | */ |
michael@0 | 334 | ensureOutgoingAvailable: function(index) { |
michael@0 | 335 | if (index >= this.outgoingBufferLength) { |
michael@0 | 336 | this.growOutgoingBuffer(index + 1); |
michael@0 | 337 | } |
michael@0 | 338 | }, |
michael@0 | 339 | |
michael@0 | 340 | writeUint8: function(value) { |
michael@0 | 341 | this.ensureOutgoingAvailable(this.outgoingIndex); |
michael@0 | 342 | |
michael@0 | 343 | this.outgoingBytes[this.outgoingIndex] = value; |
michael@0 | 344 | this.outgoingIndex++; |
michael@0 | 345 | }, |
michael@0 | 346 | |
michael@0 | 347 | writeUint16: function(value) { |
michael@0 | 348 | this.writeUint8(value & 0xff); |
michael@0 | 349 | this.writeUint8((value >> 8) & 0xff); |
michael@0 | 350 | }, |
michael@0 | 351 | |
michael@0 | 352 | writeInt32: function(value) { |
michael@0 | 353 | this.writeUint8(value & 0xff); |
michael@0 | 354 | this.writeUint8((value >> 8) & 0xff); |
michael@0 | 355 | this.writeUint8((value >> 16) & 0xff); |
michael@0 | 356 | this.writeUint8((value >> 24) & 0xff); |
michael@0 | 357 | }, |
michael@0 | 358 | |
michael@0 | 359 | writeString: function(value) { |
michael@0 | 360 | if (value == null) { |
michael@0 | 361 | this.writeInt32(-1); |
michael@0 | 362 | return; |
michael@0 | 363 | } |
michael@0 | 364 | this.writeInt32(value.length); |
michael@0 | 365 | for (let i = 0; i < value.length; i++) { |
michael@0 | 366 | this.writeUint16(value.charCodeAt(i)); |
michael@0 | 367 | } |
michael@0 | 368 | // Strings are \0\0 delimited, but that isn't part of the length. And |
michael@0 | 369 | // if the string length is even, the delimiter is two characters wide. |
michael@0 | 370 | // It's insane, I know. |
michael@0 | 371 | this.writeStringDelimiter(value.length); |
michael@0 | 372 | }, |
michael@0 | 373 | |
michael@0 | 374 | writeStringList: function(strings) { |
michael@0 | 375 | this.writeInt32(strings.length); |
michael@0 | 376 | for (let i = 0; i < strings.length; i++) { |
michael@0 | 377 | this.writeString(strings[i]); |
michael@0 | 378 | } |
michael@0 | 379 | }, |
michael@0 | 380 | |
michael@0 | 381 | writeStringDelimiter: function(length) { |
michael@0 | 382 | this.writeUint16(0); |
michael@0 | 383 | if (!(length & 1)) { |
michael@0 | 384 | this.writeUint16(0); |
michael@0 | 385 | } |
michael@0 | 386 | }, |
michael@0 | 387 | |
michael@0 | 388 | writeParcelSize: function(value) { |
michael@0 | 389 | /** |
michael@0 | 390 | * Parcel size will always be the first thing in the parcel byte |
michael@0 | 391 | * array, but the last thing written. Store the current index off |
michael@0 | 392 | * to a temporary to be reset after we write the size. |
michael@0 | 393 | */ |
michael@0 | 394 | let currentIndex = this.outgoingIndex; |
michael@0 | 395 | this.outgoingIndex = 0; |
michael@0 | 396 | this.writeUint8((value >> 24) & 0xff); |
michael@0 | 397 | this.writeUint8((value >> 16) & 0xff); |
michael@0 | 398 | this.writeUint8((value >> 8) & 0xff); |
michael@0 | 399 | this.writeUint8(value & 0xff); |
michael@0 | 400 | this.outgoingIndex = currentIndex; |
michael@0 | 401 | }, |
michael@0 | 402 | |
michael@0 | 403 | copyIncomingToOutgoing: function(length) { |
michael@0 | 404 | if (!length || (length < 0)) { |
michael@0 | 405 | return; |
michael@0 | 406 | } |
michael@0 | 407 | |
michael@0 | 408 | let translatedReadIndexEnd = |
michael@0 | 409 | this.currentParcelSize - this.readAvailable + length - 1; |
michael@0 | 410 | this.ensureIncomingAvailable(translatedReadIndexEnd); |
michael@0 | 411 | |
michael@0 | 412 | let translatedWriteIndexEnd = this.outgoingIndex + length - 1; |
michael@0 | 413 | this.ensureOutgoingAvailable(translatedWriteIndexEnd); |
michael@0 | 414 | |
michael@0 | 415 | let newIncomingReadIndex = this.incomingReadIndex + length; |
michael@0 | 416 | if (newIncomingReadIndex < this.incomingBufferLength) { |
michael@0 | 417 | // Reading won't cause wrapping, go ahead with builtin copy. |
michael@0 | 418 | this.outgoingBytes |
michael@0 | 419 | .set(this.incomingBytes.subarray(this.incomingReadIndex, |
michael@0 | 420 | newIncomingReadIndex), |
michael@0 | 421 | this.outgoingIndex); |
michael@0 | 422 | } else { |
michael@0 | 423 | // Not so lucky. |
michael@0 | 424 | newIncomingReadIndex %= this.incomingBufferLength; |
michael@0 | 425 | this.outgoingBytes |
michael@0 | 426 | .set(this.incomingBytes.subarray(this.incomingReadIndex, |
michael@0 | 427 | this.incomingBufferLength), |
michael@0 | 428 | this.outgoingIndex); |
michael@0 | 429 | if (newIncomingReadIndex) { |
michael@0 | 430 | let firstPartLength = this.incomingBufferLength - this.incomingReadIndex; |
michael@0 | 431 | this.outgoingBytes.set(this.incomingBytes.subarray(0, newIncomingReadIndex), |
michael@0 | 432 | this.outgoingIndex + firstPartLength); |
michael@0 | 433 | } |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | this.incomingReadIndex = newIncomingReadIndex; |
michael@0 | 437 | this.readAvailable -= length; |
michael@0 | 438 | this.outgoingIndex += length; |
michael@0 | 439 | }, |
michael@0 | 440 | |
michael@0 | 441 | /** |
michael@0 | 442 | * Parcel management |
michael@0 | 443 | */ |
michael@0 | 444 | |
michael@0 | 445 | /** |
michael@0 | 446 | * Write incoming data to the circular buffer. |
michael@0 | 447 | * |
michael@0 | 448 | * @param incoming |
michael@0 | 449 | * Uint8Array containing the incoming data. |
michael@0 | 450 | */ |
michael@0 | 451 | writeToIncoming: function(incoming) { |
michael@0 | 452 | // We don't have to worry about the head catching the tail since |
michael@0 | 453 | // we process any backlog in parcels immediately, before writing |
michael@0 | 454 | // new data to the buffer. So the only edge case we need to handle |
michael@0 | 455 | // is when the incoming data is larger than the buffer size. |
michael@0 | 456 | let minMustAvailableSize = incoming.length + this.readIncoming; |
michael@0 | 457 | if (minMustAvailableSize > this.incomingBufferLength) { |
michael@0 | 458 | this.growIncomingBuffer(minMustAvailableSize); |
michael@0 | 459 | } |
michael@0 | 460 | |
michael@0 | 461 | // We can let the typed arrays do the copying if the incoming data won't |
michael@0 | 462 | // wrap around the edges of the circular buffer. |
michael@0 | 463 | let remaining = this.incomingBufferLength - this.incomingWriteIndex; |
michael@0 | 464 | if (remaining >= incoming.length) { |
michael@0 | 465 | this.incomingBytes.set(incoming, this.incomingWriteIndex); |
michael@0 | 466 | } else { |
michael@0 | 467 | // The incoming data would wrap around it. |
michael@0 | 468 | let head = incoming.subarray(0, remaining); |
michael@0 | 469 | let tail = incoming.subarray(remaining); |
michael@0 | 470 | this.incomingBytes.set(head, this.incomingWriteIndex); |
michael@0 | 471 | this.incomingBytes.set(tail, 0); |
michael@0 | 472 | } |
michael@0 | 473 | this.incomingWriteIndex = (this.incomingWriteIndex + incoming.length) % |
michael@0 | 474 | this.incomingBufferLength; |
michael@0 | 475 | }, |
michael@0 | 476 | |
michael@0 | 477 | /** |
michael@0 | 478 | * Process incoming data. |
michael@0 | 479 | * |
michael@0 | 480 | * @param incoming |
michael@0 | 481 | * Uint8Array containing the incoming data. |
michael@0 | 482 | */ |
michael@0 | 483 | processIncoming: function(incoming) { |
michael@0 | 484 | if (DEBUG) { |
michael@0 | 485 | debug("Received " + incoming.length + " bytes."); |
michael@0 | 486 | debug("Already read " + this.readIncoming); |
michael@0 | 487 | } |
michael@0 | 488 | |
michael@0 | 489 | this.writeToIncoming(incoming); |
michael@0 | 490 | this.readIncoming += incoming.length; |
michael@0 | 491 | while (true) { |
michael@0 | 492 | if (!this.currentParcelSize) { |
michael@0 | 493 | // We're expecting a new parcel. |
michael@0 | 494 | if (this.readIncoming < this.PARCEL_SIZE_SIZE) { |
michael@0 | 495 | // We don't know how big the next parcel is going to be, need more |
michael@0 | 496 | // data. |
michael@0 | 497 | if (DEBUG) debug("Next parcel size unknown, going to sleep."); |
michael@0 | 498 | return; |
michael@0 | 499 | } |
michael@0 | 500 | this.currentParcelSize = this.readParcelSize(); |
michael@0 | 501 | if (DEBUG) { |
michael@0 | 502 | debug("New incoming parcel of size " + this.currentParcelSize); |
michael@0 | 503 | } |
michael@0 | 504 | // The size itself is not included in the size. |
michael@0 | 505 | this.readIncoming -= this.PARCEL_SIZE_SIZE; |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | if (this.readIncoming < this.currentParcelSize) { |
michael@0 | 509 | // We haven't read enough yet in order to be able to process a parcel. |
michael@0 | 510 | if (DEBUG) debug("Read " + this.readIncoming + ", but parcel size is " |
michael@0 | 511 | + this.currentParcelSize + ". Going to sleep."); |
michael@0 | 512 | return; |
michael@0 | 513 | } |
michael@0 | 514 | |
michael@0 | 515 | // Alright, we have enough data to process at least one whole parcel. |
michael@0 | 516 | // Let's do that. |
michael@0 | 517 | let expectedAfterIndex = (this.incomingReadIndex + this.currentParcelSize) |
michael@0 | 518 | % this.incomingBufferLength; |
michael@0 | 519 | |
michael@0 | 520 | if (DEBUG) { |
michael@0 | 521 | let parcel; |
michael@0 | 522 | if (expectedAfterIndex < this.incomingReadIndex) { |
michael@0 | 523 | let head = this.incomingBytes.subarray(this.incomingReadIndex); |
michael@0 | 524 | let tail = this.incomingBytes.subarray(0, expectedAfterIndex); |
michael@0 | 525 | parcel = Array.slice(head).concat(Array.slice(tail)); |
michael@0 | 526 | } else { |
michael@0 | 527 | parcel = Array.slice(this.incomingBytes.subarray( |
michael@0 | 528 | this.incomingReadIndex, expectedAfterIndex)); |
michael@0 | 529 | } |
michael@0 | 530 | debug("Parcel (size " + this.currentParcelSize + "): " + parcel); |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | if (DEBUG) debug("We have at least one complete parcel."); |
michael@0 | 534 | try { |
michael@0 | 535 | this.readAvailable = this.currentParcelSize; |
michael@0 | 536 | this.processParcel(); |
michael@0 | 537 | } catch (ex) { |
michael@0 | 538 | if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack); |
michael@0 | 539 | } |
michael@0 | 540 | |
michael@0 | 541 | // Ensure that the whole parcel was consumed. |
michael@0 | 542 | if (this.incomingReadIndex != expectedAfterIndex) { |
michael@0 | 543 | if (DEBUG) { |
michael@0 | 544 | debug("Parcel handler didn't consume whole parcel, " + |
michael@0 | 545 | Math.abs(expectedAfterIndex - this.incomingReadIndex) + |
michael@0 | 546 | " bytes left over"); |
michael@0 | 547 | } |
michael@0 | 548 | this.incomingReadIndex = expectedAfterIndex; |
michael@0 | 549 | } |
michael@0 | 550 | this.readIncoming -= this.currentParcelSize; |
michael@0 | 551 | this.readAvailable = 0; |
michael@0 | 552 | this.currentParcelSize = 0; |
michael@0 | 553 | } |
michael@0 | 554 | }, |
michael@0 | 555 | |
michael@0 | 556 | /** |
michael@0 | 557 | * Communicate with the IPC thread. |
michael@0 | 558 | */ |
michael@0 | 559 | sendParcel: function() { |
michael@0 | 560 | // Compute the size of the parcel and write it to the front of the parcel |
michael@0 | 561 | // where we left room for it. Note that he parcel size does not include |
michael@0 | 562 | // the size itself. |
michael@0 | 563 | let parcelSize = this.outgoingIndex - this.PARCEL_SIZE_SIZE; |
michael@0 | 564 | this.writeParcelSize(parcelSize); |
michael@0 | 565 | |
michael@0 | 566 | // This assumes that postRILMessage will make a copy of the ArrayBufferView |
michael@0 | 567 | // right away! |
michael@0 | 568 | let parcel = this.outgoingBytes.subarray(0, this.outgoingIndex); |
michael@0 | 569 | if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel)); |
michael@0 | 570 | this.onSendParcel(parcel); |
michael@0 | 571 | this.outgoingIndex = this.PARCEL_SIZE_SIZE; |
michael@0 | 572 | }, |
michael@0 | 573 | |
michael@0 | 574 | getCurrentParcelSize: function() { |
michael@0 | 575 | return this.currentParcelSize; |
michael@0 | 576 | }, |
michael@0 | 577 | |
michael@0 | 578 | getReadAvailable: function() { |
michael@0 | 579 | return this.readAvailable; |
michael@0 | 580 | } |
michael@0 | 581 | |
michael@0 | 582 | /** |
michael@0 | 583 | * Process one parcel. |
michael@0 | 584 | * |
michael@0 | 585 | * |processParcel| is an implementation provided incoming parcel processing |
michael@0 | 586 | * function invoked when we have received a complete parcel. Implementation |
michael@0 | 587 | * may call multiple read functions to extract data from the incoming buffer. |
michael@0 | 588 | */ |
michael@0 | 589 | //processParcel: function() { |
michael@0 | 590 | // let something = this.readInt32(); |
michael@0 | 591 | // ... |
michael@0 | 592 | //}, |
michael@0 | 593 | |
michael@0 | 594 | /** |
michael@0 | 595 | * Write raw data out to underlying channel. |
michael@0 | 596 | * |
michael@0 | 597 | * |onSendParcel| is an implementation provided stream output function |
michael@0 | 598 | * invoked when we're really going to write something out. We assume the |
michael@0 | 599 | * data are completely copied to some output buffer in this call and may |
michael@0 | 600 | * be destroyed when it's done. |
michael@0 | 601 | * |
michael@0 | 602 | * @param parcel |
michael@0 | 603 | * An array of numeric octet data. |
michael@0 | 604 | */ |
michael@0 | 605 | //onSendParcel: function(parcel) { |
michael@0 | 606 | // ... |
michael@0 | 607 | //} |
michael@0 | 608 | }; |
michael@0 | 609 | |
michael@0 | 610 | module.exports = { Buf: Buf }; |