tor-browser

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

packets.sys.mjs (11214B)


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