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 { StreamUtils } = ChromeUtils.import(
28 "chrome://marionette/content/stream-utils.js"
31 const unicodeConverter = Cc[
32 "@mozilla.org/intl/scriptableunicodeconverter"
33 ].createInstance(Ci.nsIScriptableUnicodeConverter);
34 unicodeConverter.charset = "UTF-8";
36 const defer = function() {
38 promise: new Promise((resolve, reject) => {
39 deferred.resolve = resolve;
40 deferred.reject = reject;
46 this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"];
48 // The transport's previous check ensured the header length did not
49 // exceed 20 characters. Here, we opt for the somewhat smaller, but still
50 // large limit of 1 TiB.
51 const PACKET_LENGTH_MAX = Math.pow(2, 40);
54 * A generic Packet processing object (extended by two subtypes below).
58 function Packet(transport) {
59 this._transport = transport;
64 * Attempt to initialize a new Packet based on the incoming packet header
65 * we've received so far. We try each of the types in succession, trying
66 * JSON packets first since they are much more common.
68 * @param {string} header
69 * Packet header string to attempt parsing.
70 * @param {DebuggerTransport} transport
71 * Transport instance that will own the packet.
74 * Parsed packet of the matching type, or null if no types matched.
76 Packet.fromHeader = function(header, transport) {
78 JSONPacket.fromHeader(header, transport) ||
79 BulkPacket.fromHeader(header, transport)
89 if (length > PACKET_LENGTH_MAX) {
93 " exceeds the max length of " +
97 this._length = length;
101 this._transport = null;
106 * With a JSON packet (the typical packet type sent via the transport),
107 * data is transferred as a JSON packet serialized into a string,
108 * with the string length prepended to the packet, followed by a colon
109 * ([length]:[packet]). The contents of the JSON packet are specified in
110 * the Remote Debugging Protocol specification.
112 * @param {DebuggerTransport} transport
113 * Transport instance that will own the packet.
115 function JSONPacket(transport) {
116 Packet.call(this, transport);
122 * Attempt to initialize a new JSONPacket based on the incoming packet
123 * header we've received so far.
125 * @param {string} header
126 * Packet header string to attempt parsing.
127 * @param {DebuggerTransport} transport
128 * Transport instance that will own the packet.
130 * @return {JSONPacket}
131 * Parsed packet, or null if it's not a match.
133 JSONPacket.fromHeader = function(header, transport) {
134 let match = this.HEADER_PATTERN.exec(header);
140 let packet = new JSONPacket(transport);
141 packet.length = +match[1];
145 JSONPacket.HEADER_PATTERN = /^(\d+):$/;
147 JSONPacket.prototype = Object.create(Packet.prototype);
149 Object.defineProperty(JSONPacket.prototype, "object", {
151 * Gets the object (not the serialized string) being read or written.
158 * Sets the object to be sent when write() is called.
161 this._object = object;
162 let data = JSON.stringify(object);
163 this._data = unicodeConverter.ConvertFromUnicode(data);
164 this.length = this._data.length;
168 JSONPacket.prototype.read = function(stream, scriptableStream) {
169 // Read in more packet data.
170 this._readData(stream, scriptableStream);
173 // Don't have a complete packet yet.
177 let json = this._data;
179 json = unicodeConverter.ConvertToUnicode(json);
180 this._object = JSON.parse(json);
183 "Error parsing incoming packet: " +
195 this._transport._onJSONObjectReady(this._object);
198 JSONPacket.prototype._readData = function(stream, scriptableStream) {
199 let bytesToRead = Math.min(
200 this.length - this._data.length,
203 this._data += scriptableStream.readBytes(bytesToRead);
204 this._done = this._data.length === this.length;
207 JSONPacket.prototype.write = function(stream) {
208 if (this._outgoing === undefined) {
209 // Format the serialized packet to a buffer
210 this._outgoing = this.length + ":" + this._data;
213 let written = stream.write(this._outgoing, this._outgoing.length);
214 this._outgoing = this._outgoing.slice(written);
215 this._done = !this._outgoing.length;
218 Object.defineProperty(JSONPacket.prototype, "done", {
224 JSONPacket.prototype.toString = function() {
225 return JSON.stringify(this._object, null, 2);
229 * With a bulk packet, data is transferred by temporarily handing over
230 * the transport's input or output stream to the application layer for
231 * writing data directly. This can be much faster for large data sets,
232 * and avoids various stages of copies and data duplication inherent in
233 * the JSON packet type. The bulk packet looks like:
235 * bulk [actor] [type] [length]:[data]
237 * The interpretation of the data portion depends on the kind of actor and
238 * the packet's type. See the Remote Debugging Protocol Stream Transport
239 * spec for more details.
241 * @param {DebuggerTransport} transport
242 * Transport instance that will own the packet.
244 function BulkPacket(transport) {
245 Packet.call(this, transport);
247 this._readyForWriting = defer();
251 * Attempt to initialize a new BulkPacket based on the incoming packet
252 * header we've received so far.
254 * @param {string} header
255 * Packet header string to attempt parsing.
256 * @param {DebuggerTransport} transport
257 * Transport instance that will own the packet.
259 * @return {BulkPacket}
260 * Parsed packet, or null if it's not a match.
262 BulkPacket.fromHeader = function(header, transport) {
263 let match = this.HEADER_PATTERN.exec(header);
269 let packet = new BulkPacket(transport);
278 BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
280 BulkPacket.prototype = Object.create(Packet.prototype);
282 BulkPacket.prototype.read = function(stream) {
283 // Temporarily pause monitoring of the input stream
284 this._transport.pauseIncoming();
286 let deferred = defer();
288 this._transport._onBulkReadReady({
293 let copying = StreamUtils.copyStream(stream, output, this.length);
294 deferred.resolve(copying);
301 // Await the result of reading from the stream
302 deferred.promise.then(() => {
304 this._transport.resumeIncoming();
305 }, this._transport.close);
307 // Ensure this is only done once
309 throw new Error("Tried to read() a BulkPacket's stream multiple times.");
313 BulkPacket.prototype.write = function(stream) {
314 if (this._outgoingHeader === undefined) {
315 // Format the serialized packet header to a buffer
316 this._outgoingHeader =
317 "bulk " + this.actor + " " + this.type + " " + this.length + ":";
320 // Write the header, or whatever's left of it to write.
321 if (this._outgoingHeader.length) {
322 let written = stream.write(
323 this._outgoingHeader,
324 this._outgoingHeader.length
326 this._outgoingHeader = this._outgoingHeader.slice(written);
330 // Temporarily pause the monitoring of the output stream
331 this._transport.pauseOutgoing();
333 let deferred = defer();
335 this._readyForWriting.resolve({
337 let copying = StreamUtils.copyStream(input, stream, this.length);
338 deferred.resolve(copying);
345 // Await the result of writing to the stream
346 deferred.promise.then(() => {
348 this._transport.resumeOutgoing();
349 }, this._transport.close);
351 // Ensure this is only done once
353 throw new Error("Tried to write() a BulkPacket's stream multiple times.");
357 Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
359 return this._readyForWriting.promise;
363 Object.defineProperty(BulkPacket.prototype, "header", {
373 this.actor = header.actor;
374 this.type = header.type;
375 this.length = header.length;
379 Object.defineProperty(BulkPacket.prototype, "done", {
385 BulkPacket.prototype.toString = function() {
386 return "Bulk: " + JSON.stringify(this.header, null, 2);
390 * RawPacket is used to test the transport's error handling of malformed
391 * packets, by writing data directly onto the stream.
392 * @param transport DebuggerTransport
393 * The transport instance that will own the packet.
395 * The raw string to send out onto the stream.
397 function RawPacket(transport, data) {
398 Packet.call(this, transport);
400 this.length = data.length;
404 RawPacket.prototype = Object.create(Packet.prototype);
406 RawPacket.prototype.read = function() {
407 // this has not yet been needed for testing
408 throw new Error("Not implemented");
411 RawPacket.prototype.write = function(stream) {
412 let written = stream.write(this._data, this._data.length);
413 this._data = this._data.slice(written);
414 this._done = !this._data.length;
417 Object.defineProperty(RawPacket.prototype, "done", {