packets.js 12 KB

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