/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ const INT32_MAX = 2147483647; const UINT8_SIZE = 1; const UINT16_SIZE = 2; const UINT32_SIZE = 4; const PARCEL_SIZE_SIZE = UINT32_SIZE; /** * This object contains helpers buffering incoming data & deconstructing it * into parcels as well as buffering outgoing data & constructing parcels. * For that it maintains two buffers and corresponding uint8 views, indexes. * * The incoming buffer is a circular buffer where we store incoming data. * As soon as a complete parcel is received, it is processed right away, so * the buffer only needs to be large enough to hold one parcel. * * The outgoing buffer is to prepare outgoing parcels. The index is reset * every time a parcel is sent. */ let Buf = { mIncomingBufferLength: 1024, mIncomingBuffer: null, mIncomingBytes: null, mIncomingWriteIndex: 0, mIncomingReadIndex: 0, mReadIncoming: 0, mReadAvailable: 0, mCurrentParcelSize: 0, mOutgoingBufferLength: 1024, mOutgoingBuffer: null, mOutgoingBytes: null, mOutgoingIndex: 0, mOutgoingBufferCalSizeQueue: null, mToken: 0, mTokenRequestMap: null, init: function init() { this.mIncomingBuffer = new ArrayBuffer(this.mIncomingBufferLength); this.mOutgoingBuffer = new ArrayBuffer(this.mOutgoingBufferLength); this.mIncomingBytes = new Uint8Array(this.mIncomingBuffer); this.mOutgoingBytes = new Uint8Array(this.mOutgoingBuffer); // Track where incoming data is read from and written to. this.mIncomingWriteIndex = 0; this.mIncomingReadIndex = 0; // Leave room for the parcel size for outgoing parcels. this.mOutgoingIndex = PARCEL_SIZE_SIZE; // How many bytes we've read for this parcel so far. this.mReadIncoming = 0; // How many bytes available as parcel data. this.mReadAvailable = 0; // Size of the incoming parcel. If this is zero, we're expecting a new // parcel. this.mCurrentParcelSize = 0; // This gets incremented each time we send out a parcel. this.mToken = 1; // Maps tokens we send out with requests to the request type, so that // when we get a response parcel back, we know what request it was for. this.mTokenRequestMap = {}; // Queue for storing outgoing override points this.mOutgoingBufferCalSizeQueue = []; }, /** * Mark current mOutgoingIndex as start point for calculation length of data * written to mOutgoingBuffer. * Mark can be nested for here uses queue to remember marks. * * @param writeFunction * Function to write data length into mOutgoingBuffer, this function is * also used to allocate buffer for data length. * Raw data size(in Uint8) is provided as parameter calling writeFunction. * If raw data size is not in proper unit for writing, user can adjust * the length value in writeFunction before writing. **/ startCalOutgoingSize: function startCalOutgoingSize(writeFunction) { let sizeInfo = {index: this.mOutgoingIndex, write: writeFunction}; // Allocate buffer for data lemgtj. writeFunction.call(0); // Get size of data length buffer for it is not counted into data size. sizeInfo.size = this.mOutgoingIndex - sizeInfo.index; // Enqueue size calculation information. this.mOutgoingBufferCalSizeQueue.push(sizeInfo); }, /** * Calculate data length since last mark, and write it into mark position. **/ stopCalOutgoingSize: function stopCalOutgoingSize() { let sizeInfo = this.mOutgoingBufferCalSizeQueue.pop(); // Remember current mOutgoingIndex. let currentOutgoingIndex = this.mOutgoingIndex; // Calculate data length, in uint8. let writeSize = this.mOutgoingIndex - sizeInfo.index - sizeInfo.size; // Write data length to mark, use same function for allocating buffer to make // sure there is no buffer overloading. this.mOutgoingIndex = sizeInfo.index; sizeInfo.write(writeSize); // Restore mOutgoingIndex. this.mOutgoingIndex = currentOutgoingIndex; }, /** * Grow the incoming buffer. * * @param min_size * Minimum new size. The actual new size will be the the smallest * power of 2 that's larger than this number. */ growIncomingBuffer: function growIncomingBuffer(min_size) { if (DEBUG) { debug("Current buffer of " + this.mIncomingBufferLength + " can't handle incoming " + min_size + " bytes."); } let oldBytes = this.mIncomingBytes; this.mIncomingBufferLength = 2 << Math.floor(Math.log(min_size)/Math.log(2)); if (DEBUG) debug("New incoming buffer size: " + this.mIncomingBufferLength); this.mIncomingBuffer = new ArrayBuffer(this.mIncomingBufferLength); this.mIncomingBytes = new Uint8Array(this.mIncomingBuffer); if (this.mIncomingReadIndex <= this.mIncomingWriteIndex) { // Read and write index are in natural order, so we can just copy // the old buffer over to the bigger one without having to worry // about the indexes. this.mIncomingBytes.set(oldBytes, 0); } else { // The write index has wrapped around but the read index hasn't yet. // Write whatever the read index has left to read until it would // circle around to the beginning of the new buffer, and the rest // behind that. let head = oldBytes.subarray(this.mIncomingReadIndex); let tail = oldBytes.subarray(0, this.mIncomingReadIndex); this.mIncomingBytes.set(head, 0); this.mIncomingBytes.set(tail, head.length); this.mIncomingReadIndex = 0; this.mIncomingWriteIndex += head.length; } if (DEBUG) { debug("New incoming buffer size is " + this.mIncomingBufferLength); } }, /** * Grow the outgoing buffer. * * @param min_size * Minimum new size. The actual new size will be the the smallest * power of 2 that's larger than this number. */ growOutgoingBuffer: function growOutgoingBuffer(min_size) { if (DEBUG) { debug("Current buffer of " + this.mOutgoingBufferLength + " is too small."); } let oldBytes = this.mOutgoingBytes; this.mOutgoingBufferLength = 2 << Math.floor(Math.log(min_size)/Math.log(2)); this.mOutgoingBuffer = new ArrayBuffer(this.mOutgoingBufferLength); this.mOutgoingBytes = new Uint8Array(this.mOutgoingBuffer); this.mOutgoingBytes.set(oldBytes, 0); if (DEBUG) { debug("New outgoing buffer size is " + this.mOutgoingBufferLength); } }, /** * Functions for reading data from the incoming buffer. * * These are all little endian, apart from readParcelSize(); */ /** * Ensure position specified is readable. * * @param index * Data position in incoming parcel, valid from 0 to * mCurrentParcelSize. */ ensureIncomingAvailable: function ensureIncomingAvailable(index) { if (index >= this.mCurrentParcelSize) { throw new Error("Trying to read data beyond the parcel end!"); } else if (index < 0) { throw new Error("Trying to read data before the parcel begin!"); } }, /** * Seek in current incoming parcel. * * @param offset * Seek offset in relative to current position. */ seekIncoming: function seekIncoming(offset) { // Translate to 0..mCurrentParcelSize let cur = this.mCurrentParcelSize - this.mReadAvailable; let newIndex = cur + offset; this.ensureIncomingAvailable(newIndex); // ... mIncomingReadIndex -->| // 0 new cur mCurrentParcelSize // |================|=======|====================| // |<-- cur -->|<- mReadAvailable ->| // |<-- newIndex -->|<-- new mReadAvailable -->| this.mReadAvailable = this.mCurrentParcelSize - newIndex; // Translate back: if (this.mIncomingReadIndex < cur) { // The mIncomingReadIndex is wrapped. newIndex += this.mIncomingBufferLength; } newIndex += (this.mIncomingReadIndex - cur); newIndex %= this.mIncomingBufferLength; this.mIncomingReadIndex = newIndex; }, readUint8Unchecked: function readUint8Unchecked() { let value = this.mIncomingBytes[this.mIncomingReadIndex]; this.mIncomingReadIndex = (this.mIncomingReadIndex + 1) % this.mIncomingBufferLength; return value; }, readUint8: function readUint8() { // Translate to 0..mCurrentParcelSize let cur = this.mCurrentParcelSize - this.mReadAvailable; this.ensureIncomingAvailable(cur); this.mReadAvailable--; return this.readUint8Unchecked(); }, readUint8Array: function readUint8Array(length) { // Translate to 0..mCurrentParcelSize let last = this.mCurrentParcelSize - this.mReadAvailable; last += (length - 1); this.ensureIncomingAvailable(last); let array = new Uint8Array(length); for (let i = 0; i < length; i++) { array[i] = this.readUint8Unchecked(); } this.mReadAvailable -= length; return array; }, readUint16: function readUint16() { return this.readUint8() | this.readUint8() << 8; }, readUint32: function readUint32() { return this.readUint8() | this.readUint8() << 8 | this.readUint8() << 16 | this.readUint8() << 24; }, readUint32List: function readUint32List() { let length = this.readUint32(); let ints = []; for (let i = 0; i < length; i++) { ints.push(this.readUint32()); } return ints; }, readString: function readString() { let string_len = this.readUint32(); if (string_len < 0 || string_len >= INT32_MAX) { return null; } let s = ""; for (let i = 0; i < string_len; i++) { s += String.fromCharCode(this.readUint16()); } // Strings are \0\0 delimited, but that isn't part of the length. And // if the string length is even, the delimiter is two characters wide. // It's insane, I know. this.readStringDelimiter(string_len); return s; }, readStringList: function readStringList() { let num_strings = this.readUint32(); let strings = []; for (let i = 0; i < num_strings; i++) { strings.push(this.readString()); } return strings; }, readStringDelimiter: function readStringDelimiter(length) { let delimiter = this.readUint16(); if (!(length & 1)) { delimiter |= this.readUint16(); } if (DEBUG) { if (delimiter !== 0) { debug("Something's wrong, found string delimiter: " + delimiter); } } }, readParcelSize: function readParcelSize() { return this.readUint8Unchecked() << 24 | this.readUint8Unchecked() << 16 | this.readUint8Unchecked() << 8 | this.readUint8Unchecked(); }, /** * Functions for writing data to the outgoing buffer. */ /** * Ensure position specified is writable. * * @param index * Data position in outgoing parcel, valid from 0 to * mOutgoingBufferLength. */ ensureOutgoingAvailable: function ensureOutgoingAvailable(index) { if (index >= this.mOutgoingBufferLength) { this.growOutgoingBuffer(index + 1); } }, writeUint8: function writeUint8(value) { this.ensureOutgoingAvailable(this.mOutgoingIndex); this.mOutgoingBytes[this.mOutgoingIndex] = value; this.mOutgoingIndex++; }, writeUint16: function writeUint16(value) { this.writeUint8(value & 0xff); this.writeUint8((value >> 8) & 0xff); }, writeUint32: function writeUint32(value) { this.writeUint8(value & 0xff); this.writeUint8((value >> 8) & 0xff); this.writeUint8((value >> 16) & 0xff); this.writeUint8((value >> 24) & 0xff); }, writeString: function writeString(value) { if (value == null) { this.writeUint32(-1); return; } this.writeUint32(value.length); for (let i = 0; i < value.length; i++) { this.writeUint16(value.charCodeAt(i)); } // Strings are \0\0 delimited, but that isn't part of the length. And // if the string length is even, the delimiter is two characters wide. // It's insane, I know. this.writeStringDelimiter(value.length); }, writeStringList: function writeStringList(strings) { this.writeUint32(strings.length); for (let i = 0; i < strings.length; i++) { this.writeString(strings[i]); } }, writeStringDelimiter: function writeStringDelimiter(length) { this.writeUint16(0); if (!(length & 1)) { this.writeUint16(0); } }, writeParcelSize: function writeParcelSize(value) { /** * Parcel size will always be the first thing in the parcel byte * array, but the last thing written. Store the current index off * to a temporary to be reset after we write the size. */ let currentIndex = this.mOutgoingIndex; this.mOutgoingIndex = 0; this.writeUint8((value >> 24) & 0xff); this.writeUint8((value >> 16) & 0xff); this.writeUint8((value >> 8) & 0xff); this.writeUint8(value & 0xff); this.mOutgoingIndex = currentIndex; }, copyIncomingToOutgoing: function copyIncomingToOutgoing(length) { if (!length || (length < 0)) { return; } let translatedReadIndexEnd = this.mCurrentParcelSize - this.mReadAvailable + length - 1; this.ensureIncomingAvailable(translatedReadIndexEnd); let translatedWriteIndexEnd = this.mOutgoingIndex + length - 1; this.ensureOutgoingAvailable(translatedWriteIndexEnd); let newIncomingReadIndex = this.mIncomingReadIndex + length; if (newIncomingReadIndex < this.mIncomingBufferLength) { // Reading won't cause wrapping, go ahead with builtin copy. this.mOutgoingBytes .set(this.mIncomingBytes.subarray(this.mIncomingReadIndex, newIncomingReadIndex), this.mOutgoingIndex); } else { // Not so lucky. newIncomingReadIndex %= this.mIncomingBufferLength; this.mOutgoingBytes .set(this.mIncomingBytes.subarray(this.mIncomingReadIndex, this.mIncomingBufferLength), this.mOutgoingIndex); if (newIncomingReadIndex) { let firstPartLength = this.mIncomingBufferLength - this.mIncomingReadIndex; this.mOutgoingBytes.set(this.mIncomingBytes.subarray(0, newIncomingReadIndex), this.mOutgoingIndex + firstPartLength); } } this.mIncomingReadIndex = newIncomingReadIndex; this.mReadAvailable -= length; this.mOutgoingIndex += length; }, /** * Parcel management */ /** * Write incoming data to the circular buffer. * * @param incoming * Uint8Array containing the incoming data. */ writeToIncoming: function writeToIncoming(incoming) { // We don't have to worry about the head catching the tail since // we process any backlog in parcels immediately, before writing // new data to the buffer. So the only edge case we need to handle // is when the incoming data is larger than the buffer size. let minMustAvailableSize = incoming.length + this.mReadIncoming; if (minMustAvailableSize > this.mIncomingBufferLength) { this.growIncomingBuffer(minMustAvailableSize); } // We can let the typed arrays do the copying if the incoming data won't // wrap around the edges of the circular buffer. let remaining = this.mIncomingBufferLength - this.mIncomingWriteIndex; if (remaining >= incoming.length) { this.mIncomingBytes.set(incoming, this.mIncomingWriteIndex); } else { // The incoming data would wrap around it. let head = incoming.subarray(0, remaining); let tail = incoming.subarray(remaining); this.mIncomingBytes.set(head, this.mIncomingWriteIndex); this.mIncomingBytes.set(tail, 0); } this.mIncomingWriteIndex = (this.mIncomingWriteIndex + incoming.length) % this.mIncomingBufferLength; }, /** * Process incoming data. * * @param incoming * Uint8Array containing the incoming data. */ processIncoming: function processIncoming(incoming) { if (DEBUG) { debug("Received " + incoming.length + " bytes."); debug("Already read " + this.mReadIncoming); } this.writeToIncoming(incoming); this.mReadIncoming += incoming.length; while (true) { if (!this.mCurrentParcelSize) { // We're expecting a new parcel. if (this.mReadIncoming < PARCEL_SIZE_SIZE) { // We don't know how big the next parcel is going to be, need more // data. if (DEBUG) debug("Next parcel size unknown, going to sleep."); return; } this.mCurrentParcelSize = this.readParcelSize(); if (DEBUG) { debug("New incoming parcel of size " + this.mCurrentParcelSize); } // The size itself is not included in the size. this.mReadIncoming -= PARCEL_SIZE_SIZE; } if (this.mReadIncoming < this.mCurrentParcelSize) { // We haven't read enough yet in order to be able to process a parcel. if (DEBUG) debug("Read " + this.mReadIncoming + ", but parcel size is " + this.mCurrentParcelSize + ". Going to sleep."); return; } // Alright, we have enough data to process at least one whole parcel. // Let's do that. let expectedAfterIndex = (this.mIncomingReadIndex + this.mCurrentParcelSize) % this.mIncomingBufferLength; if (DEBUG) { let parcel; if (expectedAfterIndex < this.mIncomingReadIndex) { let head = this.mIncomingBytes.subarray(this.mIncomingReadIndex); let tail = this.mIncomingBytes.subarray(0, expectedAfterIndex); parcel = Array.slice(head).concat(Array.slice(tail)); } else { parcel = Array.slice(this.mIncomingBytes.subarray( this.mIncomingReadIndex, expectedAfterIndex)); } debug("Parcel (size " + this.mCurrentParcelSize + "): " + parcel); } if (DEBUG) debug("We have at least one complete parcel."); try { this.mReadAvailable = this.mCurrentParcelSize; this.processParcel(); } catch (ex) { if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack); } // Ensure that the whole parcel was consumed. if (this.mIncomingReadIndex != expectedAfterIndex) { if (DEBUG) { debug("Parcel handler didn't consume whole parcel, " + Math.abs(expectedAfterIndex - this.mIncomingReadIndex) + " bytes left over"); } this.mIncomingReadIndex = expectedAfterIndex; } this.mReadIncoming -= this.mCurrentParcelSize; this.mReadAvailable = 0; this.mCurrentParcelSize = 0; } }, /** * Process one parcel. */ processParcel: function processParcel() { let response_type = this.readUint32(); let request_type, options; if (response_type == RESPONSE_TYPE_SOLICITED) { let token = this.readUint32(); let error = this.readUint32(); options = this.mTokenRequestMap[token]; if (!options) { if (DEBUG) { debug("Suspicious uninvited request found: " + token + ". Ignored!"); } return; } delete this.mTokenRequestMap[token]; request_type = options.rilRequestType; options.rilRequestError = error; if (DEBUG) { debug("Solicited response for request type " + request_type + ", token " + token + ", error " + error); } } else if (response_type == RESPONSE_TYPE_UNSOLICITED) { request_type = this.readUint32(); if (DEBUG) debug("Unsolicited response for request type " + request_type); } else { if (DEBUG) debug("Unknown response type: " + response_type); return; } RIL.handleParcel(request_type, this.mReadAvailable, options); }, /** * Start a new outgoing parcel. * * @param type * Integer specifying the request type. * @param options [optional] * Object containing information about the request, e.g. the * original main thread message object that led to the RIL request. */ newParcel: function newParcel(type, options) { if (DEBUG) debug("New outgoing parcel of type " + type); // We're going to leave room for the parcel size at the beginning. this.mOutgoingIndex = PARCEL_SIZE_SIZE; this.writeUint32(type); this.writeUint32(this.mToken); if (!options) { options = {}; } options.rilRequestType = type; options.rilRequestError = null; this.mTokenRequestMap[this.mToken] = options; this.mToken++; return this.mToken; }, /** * Communicate with the RIL IPC thread. */ sendParcel: function sendParcel() { // Compute the size of the parcel and write it to the front of the parcel // where we left room for it. Note that he parcel size does not include // the size itself. let parcelSize = this.mOutgoingIndex - PARCEL_SIZE_SIZE; this.writeParcelSize(parcelSize); // This assumes that postRILMessage will make a copy of the ArrayBufferView // right away! let parcel = this.mOutgoingBytes.subarray(0, this.mOutgoingIndex); if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel)); this.onSendParcel(parcel); this.mOutgoingIndex = PARCEL_SIZE_SIZE; }, simpleRequest: function simpleRequest(type, options) { this.newParcel(type, options); this.sendParcel(); }, getCurrentParcelSize: function getCurrentParcelSize() { return this.mCurrentParcelSize; }, getReadAvailable: function getReadAvailable() { return this.mReadAvailable; } /** * Write raw data out to underlying channel. * * |onSendParcel| is an implementation provided stream output function * invoked when we're really going to write something out. We assume the * data are completely copied to some output buffer in this call and may * be destroyed when it's done. * * @param parcel * An array of numeric octet data. */ //onSendParcel: function onSendParcel(parcel) { // ... //} }; module.exports = { Buf: Buf };