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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
11 ChromeUtils.defineLazyGetter(lazy, "unicodeConverter", () => {
12 const unicodeConverter = Cc[
13 "@mozilla.org/intl/scriptableunicodeconverter"
14 ].createInstance(Ci.nsIScriptableUnicodeConverter);
15 unicodeConverter.charset = "UTF-8";
17 return unicodeConverter;
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
26 * They are intended to be "one use only", so a new packet should be
27 * instantiated for each incoming or outgoing packet.
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
33 * Called when the output stream is ready to write
35 * Returns true once the packet is done being read / written
37 * Called to clean up at the end of use
40 const defer = function () {
42 promise: new Promise((resolve, reject) => {
43 deferred.resolve = resolve;
44 deferred.reject = reject;
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);
56 * A generic Packet processing object (extended by two subtypes below).
60 export function Packet(transport) {
61 this._transport = transport;
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.
70 * @param {string} header
71 * Packet header string to attempt parsing.
72 * @param {DebuggerTransport} transport
73 * Transport instance that will own the packet.
76 * Parsed packet of the matching type, or null if no types matched.
78 Packet.fromHeader = function (header, transport) {
80 JSONPacket.fromHeader(header, transport) ||
81 BulkPacket.fromHeader(header, transport)
91 if (length > PACKET_LENGTH_MAX) {
95 " exceeds the max length of " +
99 this._length = length;
103 this._transport = null;
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.
114 * @param {DebuggerTransport} transport
115 * Transport instance that will own the packet.
117 export function JSONPacket(transport) {
118 Packet.call(this, transport);
124 * Attempt to initialize a new JSONPacket based on the incoming packet
125 * header we've received so far.
127 * @param {string} header
128 * Packet header string to attempt parsing.
129 * @param {DebuggerTransport} transport
130 * Transport instance that will own the packet.
132 * @returns {JSONPacket}
133 * Parsed packet, or null if it's not a match.
135 JSONPacket.fromHeader = function (header, transport) {
136 let match = this.HEADER_PATTERN.exec(header);
142 let packet = new JSONPacket(transport);
143 packet.length = +match[1];
147 JSONPacket.HEADER_PATTERN = /^(\d+):$/;
149 JSONPacket.prototype = Object.create(Packet.prototype);
151 Object.defineProperty(JSONPacket.prototype, "object", {
153 * Gets the object (not the serialized string) being read or written.
160 * Sets the object to be sent when write() is called.
163 this._object = object;
164 let data = JSON.stringify(object);
165 this._data = lazy.unicodeConverter.ConvertFromUnicode(data);
166 this.length = this._data.length;
170 JSONPacket.prototype.read = function (stream, scriptableStream) {
171 // Read in more packet data.
172 this._readData(stream, scriptableStream);
175 // Don't have a complete packet yet.
179 let json = this._data;
181 json = lazy.unicodeConverter.ConvertToUnicode(json);
182 this._object = JSON.parse(json);
185 "Error parsing incoming packet: " +
197 this._transport._onJSONObjectReady(this._object);
200 JSONPacket.prototype._readData = function (stream, scriptableStream) {
201 let bytesToRead = Math.min(
202 this.length - this._data.length,
205 this._data += scriptableStream.readBytes(bytesToRead);
206 this._done = this._data.length === this.length;
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;
215 let written = stream.write(this._outgoing, this._outgoing.length);
216 this._outgoing = this._outgoing.slice(written);
217 this._done = !this._outgoing.length;
220 Object.defineProperty(JSONPacket.prototype, "done", {
226 JSONPacket.prototype.toString = function () {
227 return JSON.stringify(this._object, null, 2);
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:
237 * bulk [actor] [type] [length]:[data]
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.
243 * @param {DebuggerTransport} transport
244 * Transport instance that will own the packet.
246 export function BulkPacket(transport) {
247 Packet.call(this, transport);
249 this._readyForWriting = defer();
253 * Attempt to initialize a new BulkPacket based on the incoming packet
254 * header we've received so far.
256 * @param {string} header
257 * Packet header string to attempt parsing.
258 * @param {DebuggerTransport} transport
259 * Transport instance that will own the packet.
261 * @returns {BulkPacket}
262 * Parsed packet, or null if it's not a match.
264 BulkPacket.fromHeader = function (header, transport) {
265 let match = this.HEADER_PATTERN.exec(header);
271 let packet = new BulkPacket(transport);
280 BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
282 BulkPacket.prototype = Object.create(Packet.prototype);
284 BulkPacket.prototype.read = function (stream) {
285 // Temporarily pause monitoring of the input stream
286 this._transport.pauseIncoming();
288 let deferred = defer();
290 this._transport._onBulkReadReady({
295 let copying = lazy.StreamUtils.copyStream(stream, output, this.length);
296 deferred.resolve(copying);
303 // Await the result of reading from the stream
304 deferred.promise.then(() => {
306 this._transport.resumeIncoming();
307 }, this._transport.close);
309 // Ensure this is only done once
311 throw new Error("Tried to read() a BulkPacket's stream multiple times.");
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 + ":";
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
328 this._outgoingHeader = this._outgoingHeader.slice(written);
332 // Temporarily pause the monitoring of the output stream
333 this._transport.pauseOutgoing();
335 let deferred = defer();
337 this._readyForWriting.resolve({
339 let copying = lazy.StreamUtils.copyStream(input, stream, this.length);
340 deferred.resolve(copying);
347 // Await the result of writing to the stream
348 deferred.promise.then(() => {
350 this._transport.resumeOutgoing();
351 }, this._transport.close);
353 // Ensure this is only done once
355 throw new Error("Tried to write() a BulkPacket's stream multiple times.");
359 Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
361 return this._readyForWriting.promise;
365 Object.defineProperty(BulkPacket.prototype, "header", {
375 this.actor = header.actor;
376 this.type = header.type;
377 this.length = header.length;
381 Object.defineProperty(BulkPacket.prototype, "done", {
387 BulkPacket.prototype.toString = function () {
388 return "Bulk: " + JSON.stringify(this.header, null, 2);
392 * RawPacket is used to test the transport's error handling of malformed
393 * packets, by writing data directly onto the stream.
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.
400 export function RawPacket(transport, data) {
401 Packet.call(this, transport);
403 this.length = data.length;
407 RawPacket.prototype = Object.create(Packet.prototype);
409 RawPacket.prototype.read = function () {
410 // this has not yet been needed for testing
411 throw new Error("Not implemented");
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;
420 Object.defineProperty(RawPacket.prototype, "done", {