tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

packets.js (12982B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /**
      8 * Packets contain read / write functionality for the different packet types
      9 * supported by the debugging protocol, so that a transport can focus on
     10 * delivery and queue management without worrying too much about the specific
     11 * packet types.
     12 *
     13 * They are intended to be "one use only", so a new packet should be
     14 * instantiated for each incoming or outgoing packet.
     15 *
     16 * A complete Packet type should expose at least the following:
     17 *   * read(stream, scriptableStream)
     18 *     Called when the input stream has data to read
     19 *   * write(stream)
     20 *     Called when the output stream is ready to write
     21 *   * get done()
     22 *     Returns true once the packet is done being read / written
     23 *   * destroy()
     24 *     Called to clean up at the end of use
     25 */
     26 
     27 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     28 const { dumpn, dumpv } = DevToolsUtils;
     29 const flags = require("resource://devtools/shared/flags.js");
     30 const StreamUtils = require("resource://devtools/shared/transport/stream-utils.js");
     31 
     32 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
     33  // eslint-disable-next-line no-shadow
     34  const unicodeConverter = Cc[
     35    "@mozilla.org/intl/scriptableunicodeconverter"
     36  ].createInstance(Ci.nsIScriptableUnicodeConverter);
     37  unicodeConverter.charset = "UTF-8";
     38  return unicodeConverter;
     39 });
     40 
     41 // The transport's previous check ensured the header length did not exceed 20
     42 // characters.  Here, we opt for the somewhat smaller, but still large limit of
     43 // 1 TiB.
     44 const PACKET_LENGTH_MAX = Math.pow(2, 40);
     45 
     46 /**
     47 * A generic Packet processing object (extended by two subtypes below).
     48 */
     49 class Packet {
     50  constructor(transport) {
     51    this._transport = transport;
     52    this._length = 0;
     53  }
     54  /**
     55   * Attempt to initialize a new Packet based on the incoming packet header we've
     56   * received so far.  We try each of the types in succession, trying JSON packets
     57   * first since they are much more common.
     58   *
     59   * @param {string} header
     60   *        The packet header string to attempt parsing.
     61   * @param {DebuggerTransport} transport
     62   *        The transport instance that will own the packet.
     63   * @return Packet
     64   *         The parsed packet of the matching type, or null if no types matched.
     65   */
     66  static fromHeader(header, transport) {
     67    return (
     68      JSONPacket.fromHeader(header, transport) ||
     69      BulkPacket.fromHeader(header, transport)
     70    );
     71  }
     72  get length() {
     73    return this._length;
     74  }
     75 
     76  set length(length) {
     77    if (length > PACKET_LENGTH_MAX) {
     78      throw Error(
     79        "Packet length " +
     80          length +
     81          " exceeds the max length of " +
     82          PACKET_LENGTH_MAX
     83      );
     84    }
     85    this._length = length;
     86  }
     87 
     88  destroy() {
     89    this._transport = null;
     90  }
     91 }
     92 
     93 exports.Packet = Packet;
     94 
     95 /**
     96 * With a JSON packet (the typical packet type sent via the transport), data is
     97 * transferred as a JSON packet serialized into a string, with the string length
     98 * prepended to the packet, followed by a colon ([length]:[packet]). The
     99 * contents of the JSON packet are specified in the Remote Debugging Protocol
    100 * specification.
    101 *
    102 */
    103 class JSONPacket extends Packet {
    104  /**
    105   * @param {DebuggerTransport} transport
    106   *        The transport instance that will own the packet.
    107   */
    108  constructor(transport) {
    109    super(transport);
    110 
    111    this._data = "";
    112    this._done = false;
    113  }
    114  /**
    115   * Attempt to initialize a new JSONPacket based on the incoming packet header
    116   * we've received so far.
    117   *
    118   * @param {string} header
    119   *        The packet header string to attempt parsing.
    120   * @param {DebuggerTransport} transport
    121   *        The transport instance that will own the packet.
    122   * @return {JSONPacket}
    123   *         The parsed packet, or null if it's not a match.
    124   */
    125  static fromHeader(header, transport) {
    126    const match = this.HEADER_PATTERN.exec(header);
    127 
    128    if (!match) {
    129      return null;
    130    }
    131 
    132    dumpv("Header matches JSON packet");
    133    const packet = new JSONPacket(transport);
    134    packet.length = +match[1];
    135    return packet;
    136  }
    137 
    138  static HEADER_PATTERN = /^(\d+):$/;
    139 
    140  /**
    141   * Gets the object (not the serialized string) being read or written.
    142   */
    143  get object() {
    144    return this._object;
    145  }
    146  /**
    147   * Sets the object to be sent when write() is called.
    148   */
    149  set object(object) {
    150    this._object = object;
    151    const data = JSON.stringify(object);
    152    this._data = unicodeConverter.ConvertFromUnicode(data);
    153    this.length = this._data.length;
    154  }
    155 
    156  read(stream, scriptableStream) {
    157    dumpv("Reading JSON packet");
    158 
    159    // Read in more packet data.
    160    this._readData(stream, scriptableStream);
    161 
    162    if (!this.done) {
    163      // Don't have a complete packet yet.
    164      return;
    165    }
    166 
    167    let json = this._data;
    168    try {
    169      json = unicodeConverter.ConvertToUnicode(json);
    170      this._object = JSON.parse(json);
    171    } catch (e) {
    172      const msg =
    173        "Error parsing incoming packet: " +
    174        json +
    175        " (" +
    176        e +
    177        " - " +
    178        e.stack +
    179        ")";
    180      console.error(msg);
    181      dumpn(msg);
    182      return;
    183    }
    184 
    185    this._transport._onJSONObjectReady(this._object);
    186  }
    187 
    188  _readData(stream, scriptableStream) {
    189    if (flags.wantVerbose) {
    190      dumpv(
    191        "Reading JSON data: _l: " +
    192          this.length +
    193          " dL: " +
    194          this._data.length +
    195          " sA: " +
    196          stream.available()
    197      );
    198    }
    199    const bytesToRead = Math.min(
    200      this.length - this._data.length,
    201      stream.available()
    202    );
    203    this._data += scriptableStream.readBytes(bytesToRead);
    204    this._done = this._data.length === this.length;
    205  }
    206 
    207  write(stream) {
    208    dumpv("Writing JSON packet");
    209 
    210    if (this._outgoing === undefined) {
    211      // Format the serialized packet to a buffer
    212      this._outgoing = this.length + ":" + this._data;
    213    }
    214 
    215    const written = stream.write(this._outgoing, this._outgoing.length);
    216    this._outgoing = this._outgoing.slice(written);
    217    this._done = !this._outgoing.length;
    218  }
    219 
    220  get done() {
    221    return this._done;
    222  }
    223 
    224  toString() {
    225    return JSON.stringify(this._object, null, 2);
    226  }
    227 }
    228 
    229 exports.JSONPacket = JSONPacket;
    230 
    231 /**
    232 * With a bulk packet, data is transferred by temporarily handing over the
    233 * transport's input or output stream to the application layer for writing data
    234 * directly.  This can be much faster for large data sets, and avoids various
    235 * stages of copies and data duplication inherent in the JSON packet type.  The
    236 * bulk packet looks like:
    237 *
    238 * bulk [actor] [type] [length]:[data]
    239 *
    240 * The interpretation of the data portion depends on the kind of actor and the
    241 * packet's type.  See the Remote Debugging Protocol Stream Transport spec for
    242 * more details.
    243 *
    244 */
    245 class BulkPacket extends Packet {
    246  /**
    247   * @param {DebuggerTransport} transport
    248   *        The transport instance that will own the packet.
    249   */
    250  constructor(transport) {
    251    super(transport);
    252 
    253    this._done = false;
    254    let _resolve;
    255    this._readyForWriting = new Promise(resolve => {
    256      _resolve = resolve;
    257    });
    258    this._readyForWriting.resolve = _resolve;
    259  }
    260 
    261  /**
    262   * Attempt to initialize a new BulkPacket based on the incoming packet header
    263   * we've received so far.
    264   *
    265   * @param {string} header
    266   *        The packet header string to attempt parsing.
    267   * @param {DebuggerTransport} transport
    268   *        The transport instance that will own the packet.
    269   * @return {BulkPacket}
    270   *         The parsed packet, or null if it's not a match.
    271   */
    272  static fromHeader = function (header, transport) {
    273    const match = this.HEADER_PATTERN.exec(header);
    274 
    275    if (!match) {
    276      return null;
    277    }
    278 
    279    dumpv("Header matches bulk packet");
    280    const packet = new BulkPacket(transport);
    281    packet.header = {
    282      actor: match[1],
    283      type: match[2],
    284      length: +match[3],
    285    };
    286    return packet;
    287  };
    288 
    289  static HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
    290 
    291  read(stream) {
    292    dumpv("Reading bulk packet, handing off input stream");
    293 
    294    // Temporarily pause monitoring of the input stream
    295    this._transport.pauseIncoming();
    296 
    297    new Promise(resolve => {
    298      this._transport._onBulkReadReady({
    299        actor: this.actor,
    300        type: this.type,
    301        length: this.length,
    302        copyTo: output => {
    303          dumpv("CT length: " + this.length);
    304          const copying = StreamUtils.copyStream(stream, output, this.length);
    305          resolve(copying);
    306          return copying;
    307        },
    308        copyToBuffer: outputBuffer => {
    309          if (outputBuffer.byteLength !== this.length) {
    310            throw new Error(
    311              `In copyToBuffer, the output buffer needs to have the same length as the data to read. ${outputBuffer.byteLength} !== ${this.length}`
    312            );
    313          }
    314          dumpv("CT length: " + this.length);
    315          const copying = StreamUtils.copyAsyncStreamToArrayBuffer(
    316            stream,
    317            outputBuffer
    318          );
    319          resolve(copying);
    320          return copying;
    321        },
    322        stream,
    323        done: resolve,
    324      });
    325      // Await the result of reading from the stream
    326    }).then(() => {
    327      dumpv("onReadDone called, ending bulk mode");
    328      this._done = true;
    329      this._transport.resumeIncoming();
    330    }, this._transport.close);
    331 
    332    // Ensure this is only done once
    333    this.read = () => {
    334      throw new Error("Tried to read() a BulkPacket's stream multiple times.");
    335    };
    336  }
    337 
    338  write(stream) {
    339    dumpv("Writing bulk packet");
    340 
    341    if (this._outgoingHeader === undefined) {
    342      dumpv("Serializing bulk packet header");
    343      // Format the serialized packet header to a buffer
    344      this._outgoingHeader =
    345        "bulk " + this.actor + " " + this.type + " " + this.length + ":";
    346    }
    347 
    348    // Write the header, or whatever's left of it to write.
    349    if (this._outgoingHeader.length) {
    350      dumpv("Writing bulk packet header");
    351      const written = stream.write(
    352        this._outgoingHeader,
    353        this._outgoingHeader.length
    354      );
    355      this._outgoingHeader = this._outgoingHeader.slice(written);
    356      return;
    357    }
    358 
    359    dumpv("Handing off output stream");
    360 
    361    // Temporarily pause the monitoring of the output stream
    362    this._transport.pauseOutgoing();
    363 
    364    new Promise(resolve => {
    365      this._readyForWriting.resolve({
    366        copyFrom: input => {
    367          dumpv("CF length: " + this.length);
    368          const copying = StreamUtils.copyStream(input, stream, this.length);
    369          resolve(copying);
    370          return copying;
    371        },
    372        copyFromBuffer: inputBuffer => {
    373          if (inputBuffer.byteLength !== this.length) {
    374            throw new Error(
    375              `In copyFromBuffer, the input buffer needs to have the same length as the data to write. ${inputBuffer.byteLength} !== ${this.length}`
    376            );
    377          }
    378          dumpv("CF length: " + this.length);
    379          const copying = StreamUtils.copyArrayBufferToAsyncStream(
    380            inputBuffer,
    381            stream
    382          );
    383          resolve(copying);
    384          return copying;
    385        },
    386        stream,
    387        done: resolve,
    388      });
    389      // Await the result of writing to the stream
    390    }).then(() => {
    391      dumpv("onWriteDone called, ending bulk mode");
    392      this._done = true;
    393      this._transport.resumeOutgoing();
    394    }, this._transport.close);
    395 
    396    // Ensure this is only done once
    397    this.write = () => {
    398      throw new Error("Tried to write() a BulkPacket's stream multiple times.");
    399    };
    400  }
    401 
    402  get streamReadyForWriting() {
    403    return this._readyForWriting;
    404  }
    405 
    406  get header() {
    407    return {
    408      actor: this.actor,
    409      type: this.type,
    410      length: this.length,
    411    };
    412  }
    413 
    414  set header(header) {
    415    this.actor = header.actor;
    416    this.type = header.type;
    417    this.length = header.length;
    418  }
    419 
    420  get done() {
    421    return this._done;
    422  }
    423 
    424  toString() {
    425    return "Bulk: " + JSON.stringify(this.header, null, 2);
    426  }
    427 }
    428 
    429 exports.BulkPacket = BulkPacket;
    430 
    431 /**
    432 * RawPacket is used to test the transport's error handling of malformed
    433 * packets, by writing data directly onto the stream.
    434 */
    435 class RawPacket extends Packet {
    436  /**
    437   * @param {DebuggerTransport} transport
    438   *        The transport instance that will own the packet.
    439   * @param {string} data
    440   *        The raw string to send out onto the stream.
    441   */
    442  constructor(transport, data) {
    443    super(transport);
    444    this._data = data;
    445    this.length = data.length;
    446    this._done = false;
    447  }
    448  read() {
    449    // This hasn't yet been needed for testing.
    450    throw Error("Not implmented.");
    451  }
    452  write(stream) {
    453    const written = stream.write(this._data, this._data.length);
    454    this._data = this._data.slice(written);
    455    this._done = !this._data.length;
    456  }
    457  get done() {
    458    return this._done;
    459  }
    460 }
    461 
    462 exports.RawPacket = RawPacket;