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/. */
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
13 * They are intended to be "one use only", so a new packet should be
14 * instantiated for each incoming or outgoing packet.
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
20 * Called when the output stream is ready to write
22 * Returns true once the packet is done being read / written
24 * Called to clean up at the end of use
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");
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
;
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
44 const PACKET_LENGTH_MAX
= Math
.pow(2, 40);
47 * A generic Packet processing object (extended by two subtypes below).
49 function Packet(transport
) {
50 this._transport
= transport
;
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 * @param header string
59 * The packet header string to attempt parsing.
60 * @param transport DebuggerTransport
61 * The transport instance that will own the packet.
63 * The parsed packet of the matching type, or null if no types matched.
65 Packet
.fromHeader = function (header
, transport
) {
67 JSONPacket
.fromHeader(header
, transport
) ||
68 BulkPacket
.fromHeader(header
, transport
)
78 if (length
> PACKET_LENGTH_MAX
) {
82 " exceeds the max length of " +
86 this._length
= length
;
90 this._transport
= null;
94 exports
.Packet
= Packet
;
97 * With a JSON packet (the typical packet type sent via the transport), data is
98 * transferred as a JSON packet serialized into a string, with the string length
99 * prepended to the packet, followed by a colon ([length]:[packet]). The
100 * contents of the JSON packet are specified in the Remote Debugging Protocol
102 * @param transport DebuggerTransport
103 * The transport instance that will own the packet.
105 function JSONPacket(transport
) {
106 Packet
.call(this, transport
);
112 * Attempt to initialize a new JSONPacket based on the incoming packet header
113 * we've received so far.
114 * @param header string
115 * The packet header string to attempt parsing.
116 * @param transport DebuggerTransport
117 * The transport instance that will own the packet.
119 * The parsed packet, or null if it's not a match.
121 JSONPacket
.fromHeader = function (header
, transport
) {
122 const match
= this.HEADER_PATTERN
.exec(header
);
128 dumpv("Header matches JSON packet");
129 const packet
= new JSONPacket(transport
);
130 packet
.length
= +match
[1];
134 JSONPacket
.HEADER_PATTERN
= /^(\d+):$/;
136 JSONPacket
.prototype = Object
.create(Packet
.prototype);
138 Object
.defineProperty(JSONPacket
.prototype, "object", {
140 * Gets the object (not the serialized string) being read or written.
147 * Sets the object to be sent when write() is called.
150 this._object
= object
;
151 const data
= JSON
.stringify(object
);
152 this._data
= unicodeConverter
.ConvertFromUnicode(data
);
153 this.length
= this._data
.length
;
157 JSONPacket
.prototype.read = function (stream
, scriptableStream
) {
158 dumpv("Reading JSON packet");
160 // Read in more packet data.
161 this._readData(stream
, scriptableStream
);
164 // Don't have a complete packet yet.
168 let json
= this._data
;
170 json
= unicodeConverter
.ConvertToUnicode(json
);
171 this._object
= JSON
.parse(json
);
174 "Error parsing incoming packet: " +
186 this._transport
._onJSONObjectReady(this._object
);
189 JSONPacket
.prototype._readData = function (stream
, scriptableStream
) {
190 if (flags
.wantVerbose
) {
192 "Reading JSON data: _l: " +
200 const bytesToRead
= Math
.min(
201 this.length
- this._data
.length
,
204 this._data
+= scriptableStream
.readBytes(bytesToRead
);
205 this._done
= this._data
.length
=== this.length
;
208 JSONPacket
.prototype.write = function (stream
) {
209 dumpv("Writing JSON packet");
211 if (this._outgoing
=== undefined) {
212 // Format the serialized packet to a buffer
213 this._outgoing
= this.length
+ ":" + this._data
;
216 const written
= stream
.write(this._outgoing
, this._outgoing
.length
);
217 this._outgoing
= this._outgoing
.slice(written
);
218 this._done
= !this._outgoing
.length
;
221 Object
.defineProperty(JSONPacket
.prototype, "done", {
227 JSONPacket
.prototype.toString = function () {
228 return JSON
.stringify(this._object
, null, 2);
231 exports
.JSONPacket
= JSONPacket
;
234 * With a bulk packet, data is transferred by temporarily handing over the
235 * transport's input or output stream to the application layer for writing data
236 * directly. This can be much faster for large data sets, and avoids various
237 * stages of copies and data duplication inherent in the JSON packet type. The
238 * bulk packet looks like:
240 * bulk [actor] [type] [length]:[data]
242 * The interpretation of the data portion depends on the kind of actor and the
243 * packet's type. See the Remote Debugging Protocol Stream Transport spec for
245 * @param transport DebuggerTransport
246 * The transport instance that will own the packet.
248 function BulkPacket(transport
) {
249 Packet
.call(this, transport
);
252 this._readyForWriting
= new Promise(resolve
=> {
255 this._readyForWriting
.resolve
= _resolve
;
259 * Attempt to initialize a new BulkPacket based on the incoming packet header
260 * we've received so far.
261 * @param header string
262 * The packet header string to attempt parsing.
263 * @param transport DebuggerTransport
264 * The transport instance that will own the packet.
266 * The parsed packet, or null if it's not a match.
268 BulkPacket
.fromHeader = function (header
, transport
) {
269 const match
= this.HEADER_PATTERN
.exec(header
);
275 dumpv("Header matches bulk packet");
276 const packet
= new BulkPacket(transport
);
285 BulkPacket
.HEADER_PATTERN
= /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
287 BulkPacket
.prototype = Object
.create(Packet
.prototype);
289 BulkPacket
.prototype.read = function (stream
) {
290 dumpv("Reading bulk packet, handing off input stream");
292 // Temporarily pause monitoring of the input stream
293 this._transport
.pauseIncoming();
295 new Promise(resolve
=> {
296 this._transport
._onBulkReadReady({
301 dumpv("CT length: " + this.length
);
302 const copying
= StreamUtils
.copyStream(stream
, output
, this.length
);
309 // Await the result of reading from the stream
311 dumpv("onReadDone called, ending bulk mode");
313 this._transport
.resumeIncoming();
314 }, this._transport
.close
);
316 // Ensure this is only done once
318 throw new Error("Tried to read() a BulkPacket's stream multiple times.");
322 BulkPacket
.prototype.write = function (stream
) {
323 dumpv("Writing bulk packet");
325 if (this._outgoingHeader
=== undefined) {
326 dumpv("Serializing bulk packet header");
327 // Format the serialized packet header to a buffer
328 this._outgoingHeader
=
329 "bulk " + this.actor
+ " " + this.type
+ " " + this.length
+ ":";
332 // Write the header, or whatever's left of it to write.
333 if (this._outgoingHeader
.length
) {
334 dumpv("Writing bulk packet header");
335 const written
= stream
.write(
336 this._outgoingHeader
,
337 this._outgoingHeader
.length
339 this._outgoingHeader
= this._outgoingHeader
.slice(written
);
343 dumpv("Handing off output stream");
345 // Temporarily pause the monitoring of the output stream
346 this._transport
.pauseOutgoing();
348 new Promise(resolve
=> {
349 this._readyForWriting
.resolve({
351 dumpv("CF length: " + this.length
);
352 const copying
= StreamUtils
.copyStream(input
, stream
, this.length
);
359 // Await the result of writing to the stream
361 dumpv("onWriteDone called, ending bulk mode");
363 this._transport
.resumeOutgoing();
364 }, this._transport
.close
);
366 // Ensure this is only done once
368 throw new Error("Tried to write() a BulkPacket's stream multiple times.");
372 Object
.defineProperty(BulkPacket
.prototype, "streamReadyForWriting", {
374 return this._readyForWriting
;
378 Object
.defineProperty(BulkPacket
.prototype, "header", {
388 this.actor
= header
.actor
;
389 this.type
= header
.type
;
390 this.length
= header
.length
;
394 Object
.defineProperty(BulkPacket
.prototype, "done", {
400 BulkPacket
.prototype.toString = function () {
401 return "Bulk: " + JSON
.stringify(this.header
, null, 2);
404 exports
.BulkPacket
= BulkPacket
;
407 * RawPacket is used to test the transport's error handling of malformed
408 * packets, by writing data directly onto the stream.
409 * @param transport DebuggerTransport
410 * The transport instance that will own the packet.
412 * The raw string to send out onto the stream.
414 function RawPacket(transport
, data
) {
415 Packet
.call(this, transport
);
417 this.length
= data
.length
;
421 RawPacket
.prototype = Object
.create(Packet
.prototype);
423 RawPacket
.prototype.read = function () {
424 // This hasn't yet been needed for testing.
425 throw Error("Not implmented.");
428 RawPacket
.prototype.write = function (stream
) {
429 const written
= stream
.write(this._data
, this._data
.length
);
430 this._data
= this._data
.slice(written
);
431 this._done
= !this._data
.length
;
434 Object
.defineProperty(RawPacket
.prototype, "done", {
440 exports
.RawPacket
= RawPacket
;