123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- /* 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/. */
- "use strict";
- /**
- * Packets contain read / write functionality for the different packet types
- * supported by the debugging protocol, so that a transport can focus on
- * delivery and queue management without worrying too much about the specific
- * packet types.
- *
- * They are intended to be "one use only", so a new packet should be
- * instantiated for each incoming or outgoing packet.
- *
- * A complete Packet type should expose at least the following:
- * * read(stream, scriptableStream)
- * Called when the input stream has data to read
- * * write(stream)
- * Called when the output stream is ready to write
- * * get done()
- * Returns true once the packet is done being read / written
- * * destroy()
- * Called to clean up at the end of use
- */
- const { Cc, Ci, Cu } = require("chrome");
- const DevToolsUtils = require("devtools/shared/DevToolsUtils");
- const { dumpn, dumpv } = DevToolsUtils;
- const flags = require("devtools/shared/flags");
- const StreamUtils = require("devtools/shared/transport/stream-utils");
- const promise = require("promise");
- const defer = require("devtools/shared/defer");
- DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
- const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- unicodeConverter.charset = "UTF-8";
- return unicodeConverter;
- });
- // The transport's previous check ensured the header length did not exceed 20
- // characters. Here, we opt for the somewhat smaller, but still large limit of
- // 1 TiB.
- const PACKET_LENGTH_MAX = Math.pow(2, 40);
- /**
- * A generic Packet processing object (extended by two subtypes below).
- */
- function Packet(transport) {
- this._transport = transport;
- this._length = 0;
- }
- /**
- * Attempt to initialize a new Packet based on the incoming packet header we've
- * received so far. We try each of the types in succession, trying JSON packets
- * first since they are much more common.
- * @param header string
- * The packet header string to attempt parsing.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- * @return Packet
- * The parsed packet of the matching type, or null if no types matched.
- */
- Packet.fromHeader = function (header, transport) {
- return JSONPacket.fromHeader(header, transport) ||
- BulkPacket.fromHeader(header, transport);
- };
- Packet.prototype = {
- get length() {
- return this._length;
- },
- set length(length) {
- if (length > PACKET_LENGTH_MAX) {
- throw Error("Packet length " + length + " exceeds the max length of " +
- PACKET_LENGTH_MAX);
- }
- this._length = length;
- },
- destroy: function () {
- this._transport = null;
- }
- };
- exports.Packet = Packet;
- /**
- * With a JSON packet (the typical packet type sent via the transport), data is
- * transferred as a JSON packet serialized into a string, with the string length
- * prepended to the packet, followed by a colon ([length]:[packet]). The
- * contents of the JSON packet are specified in the Remote Debugging Protocol
- * specification.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- */
- function JSONPacket(transport) {
- Packet.call(this, transport);
- this._data = "";
- this._done = false;
- }
- /**
- * Attempt to initialize a new JSONPacket based on the incoming packet header
- * we've received so far.
- * @param header string
- * The packet header string to attempt parsing.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- * @return JSONPacket
- * The parsed packet, or null if it's not a match.
- */
- JSONPacket.fromHeader = function (header, transport) {
- let match = this.HEADER_PATTERN.exec(header);
- if (!match) {
- return null;
- }
- dumpv("Header matches JSON packet");
- let packet = new JSONPacket(transport);
- packet.length = +match[1];
- return packet;
- };
- JSONPacket.HEADER_PATTERN = /^(\d+):$/;
- JSONPacket.prototype = Object.create(Packet.prototype);
- Object.defineProperty(JSONPacket.prototype, "object", {
- /**
- * Gets the object (not the serialized string) being read or written.
- */
- get: function () { return this._object; },
- /**
- * Sets the object to be sent when write() is called.
- */
- set: function (object) {
- this._object = object;
- let data = JSON.stringify(object);
- this._data = unicodeConverter.ConvertFromUnicode(data);
- this.length = this._data.length;
- }
- });
- JSONPacket.prototype.read = function (stream, scriptableStream) {
- dumpv("Reading JSON packet");
- // Read in more packet data.
- this._readData(stream, scriptableStream);
- if (!this.done) {
- // Don't have a complete packet yet.
- return;
- }
- let json = this._data;
- try {
- json = unicodeConverter.ConvertToUnicode(json);
- this._object = JSON.parse(json);
- } catch (e) {
- let msg = "Error parsing incoming packet: " + json + " (" + e +
- " - " + e.stack + ")";
- console.error(msg);
- dumpn(msg);
- return;
- }
- this._transport._onJSONObjectReady(this._object);
- };
- JSONPacket.prototype._readData = function (stream, scriptableStream) {
- if (flags.wantVerbose) {
- dumpv("Reading JSON data: _l: " + this.length + " dL: " +
- this._data.length + " sA: " + stream.available());
- }
- let bytesToRead = Math.min(this.length - this._data.length,
- stream.available());
- this._data += scriptableStream.readBytes(bytesToRead);
- this._done = this._data.length === this.length;
- };
- JSONPacket.prototype.write = function (stream) {
- dumpv("Writing JSON packet");
- if (this._outgoing === undefined) {
- // Format the serialized packet to a buffer
- this._outgoing = this.length + ":" + this._data;
- }
- let written = stream.write(this._outgoing, this._outgoing.length);
- this._outgoing = this._outgoing.slice(written);
- this._done = !this._outgoing.length;
- };
- Object.defineProperty(JSONPacket.prototype, "done", {
- get: function () { return this._done; }
- });
- JSONPacket.prototype.toString = function () {
- return JSON.stringify(this._object, null, 2);
- };
- exports.JSONPacket = JSONPacket;
- /**
- * With a bulk packet, data is transferred by temporarily handing over the
- * transport's input or output stream to the application layer for writing data
- * directly. This can be much faster for large data sets, and avoids various
- * stages of copies and data duplication inherent in the JSON packet type. The
- * bulk packet looks like:
- *
- * bulk [actor] [type] [length]:[data]
- *
- * The interpretation of the data portion depends on the kind of actor and the
- * packet's type. See the Remote Debugging Protocol Stream Transport spec for
- * more details.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- */
- function BulkPacket(transport) {
- Packet.call(this, transport);
- this._done = false;
- this._readyForWriting = defer();
- }
- /**
- * Attempt to initialize a new BulkPacket based on the incoming packet header
- * we've received so far.
- * @param header string
- * The packet header string to attempt parsing.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- * @return BulkPacket
- * The parsed packet, or null if it's not a match.
- */
- BulkPacket.fromHeader = function (header, transport) {
- let match = this.HEADER_PATTERN.exec(header);
- if (!match) {
- return null;
- }
- dumpv("Header matches bulk packet");
- let packet = new BulkPacket(transport);
- packet.header = {
- actor: match[1],
- type: match[2],
- length: +match[3]
- };
- return packet;
- };
- BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
- BulkPacket.prototype = Object.create(Packet.prototype);
- BulkPacket.prototype.read = function (stream) {
- dumpv("Reading bulk packet, handing off input stream");
- // Temporarily pause monitoring of the input stream
- this._transport.pauseIncoming();
- let deferred = defer();
- this._transport._onBulkReadReady({
- actor: this.actor,
- type: this.type,
- length: this.length,
- copyTo: (output) => {
- dumpv("CT length: " + this.length);
- let copying = StreamUtils.copyStream(stream, output, this.length);
- deferred.resolve(copying);
- return copying;
- },
- stream: stream,
- done: deferred
- });
- // Await the result of reading from the stream
- deferred.promise.then(() => {
- dumpv("onReadDone called, ending bulk mode");
- this._done = true;
- this._transport.resumeIncoming();
- }, this._transport.close);
- // Ensure this is only done once
- this.read = () => {
- throw new Error("Tried to read() a BulkPacket's stream multiple times.");
- };
- };
- BulkPacket.prototype.write = function (stream) {
- dumpv("Writing bulk packet");
- if (this._outgoingHeader === undefined) {
- dumpv("Serializing bulk packet header");
- // Format the serialized packet header to a buffer
- this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
- this.length + ":";
- }
- // Write the header, or whatever's left of it to write.
- if (this._outgoingHeader.length) {
- dumpv("Writing bulk packet header");
- let written = stream.write(this._outgoingHeader,
- this._outgoingHeader.length);
- this._outgoingHeader = this._outgoingHeader.slice(written);
- return;
- }
- dumpv("Handing off output stream");
- // Temporarily pause the monitoring of the output stream
- this._transport.pauseOutgoing();
- let deferred = defer();
- this._readyForWriting.resolve({
- copyFrom: (input) => {
- dumpv("CF length: " + this.length);
- let copying = StreamUtils.copyStream(input, stream, this.length);
- deferred.resolve(copying);
- return copying;
- },
- stream: stream,
- done: deferred
- });
- // Await the result of writing to the stream
- deferred.promise.then(() => {
- dumpv("onWriteDone called, ending bulk mode");
- this._done = true;
- this._transport.resumeOutgoing();
- }, this._transport.close);
- // Ensure this is only done once
- this.write = () => {
- throw new Error("Tried to write() a BulkPacket's stream multiple times.");
- };
- };
- Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
- get: function () {
- return this._readyForWriting.promise;
- }
- });
- Object.defineProperty(BulkPacket.prototype, "header", {
- get: function () {
- return {
- actor: this.actor,
- type: this.type,
- length: this.length
- };
- },
- set: function (header) {
- this.actor = header.actor;
- this.type = header.type;
- this.length = header.length;
- },
- });
- Object.defineProperty(BulkPacket.prototype, "done", {
- get: function () { return this._done; },
- });
- BulkPacket.prototype.toString = function () {
- return "Bulk: " + JSON.stringify(this.header, null, 2);
- };
- exports.BulkPacket = BulkPacket;
- /**
- * RawPacket is used to test the transport's error handling of malformed
- * packets, by writing data directly onto the stream.
- * @param transport DebuggerTransport
- * The transport instance that will own the packet.
- * @param data string
- * The raw string to send out onto the stream.
- */
- function RawPacket(transport, data) {
- Packet.call(this, transport);
- this._data = data;
- this.length = data.length;
- this._done = false;
- }
- RawPacket.prototype = Object.create(Packet.prototype);
- RawPacket.prototype.read = function (stream) {
- // This hasn't yet been needed for testing.
- throw Error("Not implmented.");
- };
- RawPacket.prototype.write = function (stream) {
- let written = stream.write(this._data, this._data.length);
- this._data = this._data.slice(written);
- this._done = !this._data.length;
- };
- Object.defineProperty(RawPacket.prototype, "done", {
- get: function () { return this._done; }
- });
- exports.RawPacket = RawPacket;
|