]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.5.2/debugger/duk_debug_proxy.js
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / jaegertracing / opentelemetry-cpp / third_party / prometheus-cpp / 3rdparty / civetweb / src / third_party / duktape-1.5.2 / debugger / duk_debug_proxy.js
1 /*
2 * JSON debug proxy written in DukLuv
3 *
4 * This single file JSON debug proxy implementation is an alternative to the
5 * Node.js-based proxy in duk_debug.js. DukLuv is a much smaller dependency
6 * than Node.js so embedding DukLuv in a debug client is easier.
7 */
8
9 'use strict';
10
11 // XXX: Code assumes uv.write() will write fully. This is not necessarily
12 // true; should add support for partial writes (or at least failing when
13 // a partial write occurs).
14
15 var log = new Duktape.Logger('Proxy'); // default logger
16 //log.l = 0; // enable debug and trace logging
17
18 /*
19 * Config
20 */
21
22 var serverHost = '0.0.0.0';
23 var serverPort = 9093;
24 var targetHost = '127.0.0.1';
25 var targetPort = 9091;
26 var singleConnection = false;
27 var readableNumberValue = false;
28 var lenientJsonParse = false;
29 var jxParse = false;
30 var metadataFile = null;
31 var metadata = {};
32 var TORTURE = false; // for manual testing of binary/json parsing robustness
33
34 /*
35 * Detect missing 'var' declarations
36 */
37
38 // Prevent new bindings on global object. This detects missing 'var'
39 // declarations, e.g. "x = 123;" in a function without declaring it.
40 var global = new Function('return this;')();
41 log.debug('Preventing extensions on global object');
42 log.debug('Global is extensible:', Object.isExtensible(global));
43 Object.preventExtensions(global);
44 log.debug('Global is extensible:', Object.isExtensible(global));
45
46 /*
47 * Misc helpers
48 */
49
50 function plainBufferCopy(typedarray) {
51 // This is still pretty awkward in Duktape 1.4.x.
52 // Argument may be a "slice" and we want a copy of the slice
53 // (not the full underlying buffer).
54
55 var u8 = new Uint8Array(typedarray.length);
56 u8.set(typedarray); // make a copy, ensuring there's no slice offset
57 return Duktape.Buffer(u8); // get underlying plain buffer
58 }
59
60 function isObject(x) {
61 // Note that typeof null === 'object'.
62 return (typeof x === 'object' && x !== null);
63 }
64
65 function readFully(filename, cb) {
66 uv.fs_open(metadataFile, 'r', 0, function (handle, err) {
67 var fileOff = 0;
68 var data = new Uint8Array(256);
69 var dataOff = 0;
70
71 if (err) {
72 return cb(null, err);
73 }
74 function readCb(buf, err) {
75 var res;
76 var newData;
77
78 log.debug('Read callback:', buf.length, err);
79 if (err) {
80 uv.fs_close(handle);
81 return cb(null, err);
82 }
83 if (buf.length == 0) {
84 uv.fs_close(handle);
85 res = new Uint8Array(dataOff);
86 res.set(data.subarray(0, dataOff));
87 res = Duktape.Buffer(res); // plain buffer
88 log.debug('Read', res.length, 'bytes from', filename);
89 return cb(res, null);
90 }
91 while (data.length - dataOff < buf.length) {
92 log.debug('Resize file read buffer:', data.length, '->', data.length * 2);
93 newData = new Uint8Array(data.length * 2);
94 newData.set(data);
95 data = newData;
96 }
97 data.set(new Uint8Array(buf), dataOff);
98 dataOff += buf.length;
99 fileOff += buf.length;
100 uv.fs_read(handle, 4096, fileOff, readCb);
101 }
102 uv.fs_read(handle, 4096, fileOff, readCb);
103 });
104 }
105
106 /*
107 * JSON proxy server
108 *
109 * Accepts an incoming JSON proxy client and connects to a debug target,
110 * tying the two connections together. Supports both a single connection
111 * and a persistent mode.
112 */
113
114 function JsonProxyServer(host, port) {
115 this.name = 'JsonProxyServer';
116 this.handle = uv.new_tcp();
117 uv.tcp_bind(this.handle, host, port);
118 uv.listen(this.handle, 128, this.onConnection.bind(this));
119 }
120
121 JsonProxyServer.prototype.onConnection = function onConnection(err) {
122 if (err) {
123 log.error('JSON proxy onConnection error:', err);
124 return;
125 }
126 log.info('JSON proxy client connected'); // XXX: it'd be nice to log remote peer host:port
127
128 var jsonSock = new JsonConnHandler(this);
129 var targSock = new TargetConnHandler(this);
130 jsonSock.targetHandler = targSock;
131 targSock.jsonHandler = jsonSock;
132 uv.accept(this.handle, jsonSock.handle);
133
134 log.info('Connecting to debug target at', targetHost + ':' + targetPort);
135 jsonSock.writeJson({ notify: '_TargetConnecting', args: [ targetHost, targetPort ] });
136 uv.tcp_connect(targSock.handle, targetHost, targetPort, targSock.onConnect.bind(targSock));
137
138 if (singleConnection) {
139 log.info('Single connection mode, stop listening for more connections');
140 uv.shutdown(this.handle);
141 uv.read_stop(this.handle); // unnecessary but just in case
142 uv.close(this.handle);
143 this.handle = null;
144 }
145 };
146
147 JsonProxyServer.prototype.onProxyClientDisconnected = function onProxyClientDisconnected() {
148 // When this is invoked the proxy connection and the target connection
149 // have both been closed.
150 if (singleConnection) {
151 log.info('Proxy connection finished (single connection mode: we should be exiting now)');
152 } else {
153 log.info('Proxy connection finished (persistent mode: wait for more connections)');
154 }
155 };
156
157 /*
158 * JSON connection handler
159 */
160
161 function JsonConnHandler(server) {
162 var i, n;
163
164 this.name = 'JsonConnHandler';
165 this.server = server;
166 this.handle = uv.new_tcp();
167 this.incoming = new Uint8Array(4096);
168 this.incomingOffset = 0;
169 this.targetHandler = null;
170
171 this.commandNumberLookup = {};
172 if (metadata && metadata.target_commands) {
173 for (i = 0, n = metadata.target_commands.length; i < n; i++) {
174 this.commandNumberLookup[metadata.target_commands[i]] = i;
175 }
176 }
177 }
178
179 JsonConnHandler.prototype.finish = function finish(msg) {
180 var args;
181
182 if (!this.handle) {
183 log.info('JsonConnHandler already disconnected, ignore finish()');
184 return;
185 }
186 log.info('JsonConnHandler finished:', msg);
187 try {
188 args = msg ? [ msg ] : void 0;
189 this.writeJson({ notify: '_Disconnecting', args: args });
190 } catch (e) {
191 log.info('Failed to write _Disconnecting notify, ignoring:', e);
192 }
193 uv.shutdown(this.handle);
194 uv.read_stop(this.handle);
195 uv.close(this.handle);
196 this.handle = null;
197
198 this.targetHandler.finish(msg); // disconnect target too (if not already disconnected)
199
200 this.server.onProxyClientDisconnected();
201 };
202
203 JsonConnHandler.prototype.onRead = function onRead(err, data) {
204 var newIncoming;
205 var msg;
206 var errmsg;
207 var tmpBuf;
208
209 log.trace('Received data from JSON socket, err:', err, 'data length:', data ? data.length : 'null');
210
211 if (err) {
212 errmsg = 'Error reading data from JSON debug client: ' + err;
213 this.finish(errmsg);
214 return;
215 }
216 if (data) {
217 // Feed the data one byte at a time when torture testing.
218 if (TORTURE && data.length > 1) {
219 for (var i = 0; i < data.length; i++) {
220 tmpBuf = Duktape.Buffer(1);
221 tmpBuf[0] = data[i];
222 this.onRead(null, tmpBuf);
223 }
224 return;
225 }
226
227 // Receive data into 'incoming', resizing as necessary.
228 while (data.length > this.incoming.length - this.incomingOffset) {
229 newIncoming = new Uint8Array(this.incoming.length * 1.3 + 16);
230 newIncoming.set(this.incoming);
231 this.incoming = newIncoming;
232 log.debug('Resize incoming JSON buffer to ' + this.incoming.length);
233 }
234 this.incoming.set(new Uint8Array(data), this.incomingOffset);
235 this.incomingOffset += data.length;
236
237 // Trial parse JSON message(s).
238 while (true) {
239 msg = this.trialParseJsonMessage();
240 if (!msg) {
241 break;
242 }
243 try {
244 this.dispatchJsonMessage(msg);
245 } catch (e) {
246 errmsg = 'JSON message dispatch failed: ' + e;
247 this.writeJson({ notify: '_Error', args: [ errmsg ] });
248 if (lenientJsonParse) {
249 log.warn('JSON message dispatch failed (lenient mode, ignoring):', e);
250 } else {
251 log.warn('JSON message dispatch failed (dropping connection):', e);
252 this.finish(errmsg);
253 }
254 }
255 }
256 } else {
257 this.finish('JSON proxy client disconnected');
258 }
259 };
260
261 JsonConnHandler.prototype.writeJson = function writeJson(msg) {
262 log.info('PROXY --> CLIENT:', JSON.stringify(msg));
263 if (this.handle) {
264 uv.write(this.handle, JSON.stringify(msg) + '\n');
265 }
266 };
267
268 JsonConnHandler.prototype.handleDebugMessage = function handleDebugMessage(dvalues) {
269 var msg = {};
270 var idx = 0;
271 var cmd;
272
273 if (dvalues.length <= 0) {
274 throw new Error('invalid dvalues list: length <= 0');
275 }
276 var x = dvalues[idx++];
277 if (!isObject(x)) {
278 throw new Error('invalid initial dvalue: ' + Duktape.enc('jx', dvalues));
279 }
280 if (x.type === 'req') {
281 cmd = dvalues[idx++];
282 if (typeof cmd !== 'number') {
283 throw new Error('invalid command: ' + Duktape.enc('jx', cmd));
284 }
285 msg.request = this.determineCommandName(cmd) || true;
286 msg.command = cmd;
287 } else if (x.type === 'rep') {
288 msg.reply = true;
289 } else if (x.type === 'err') {
290 msg.error = true;
291 } else if (x.type === 'nfy') {
292 cmd = dvalues[idx++];
293 if (typeof cmd !== 'number') {
294 throw new Error('invalid command: ' + Duktape.enc('jx', cmd));
295 }
296 msg.notify = this.determineCommandName(cmd) || true;
297 msg.command = cmd;
298 } else {
299 throw new Error('invalid initial dvalue: ' + Duktape.enc('jx', dvalues));
300 }
301
302 for (; idx < dvalues.length - 1; idx++) {
303 if (!msg.args) {
304 msg.args = [];
305 }
306 msg.args.push(dvalues[idx]);
307 }
308
309 if (!isObject(dvalues[idx]) || dvalues[idx].type !== 'eom') {
310 throw new Error('invalid final dvalue: ' + Duktape.enc('jx', dvalues));
311 }
312
313 this.writeJson(msg);
314 };
315
316 JsonConnHandler.prototype.determineCommandName = function determineCommandName(cmd) {
317 if (!(metadata && metadata.client_commands)) {
318 return;
319 }
320 return metadata.client_commands[cmd];
321 };
322
323 JsonConnHandler.prototype.trialParseJsonMessage = function trialParseJsonMessage() {
324 var buf = this.incoming;
325 var avail = this.incomingOffset;
326 var i;
327 var msg, str, errmsg;
328
329 for (i = 0; i < avail; i++) {
330 if (buf[i] == 0x0a) {
331 str = String(plainBufferCopy(buf.subarray(0, i)));
332 try {
333 if (jxParse) {
334 msg = Duktape.dec('jx', str);
335 } else {
336 msg = JSON.parse(str);
337 }
338 } catch (e) {
339 // In lenient mode if JSON parse fails just send back an _Error
340 // and ignore the line (useful for initial development).
341 //
342 // In non-lenient mode drop the connection here; if the failed line
343 // was a request the client is expecting a reply/error message back
344 // (otherwise it may go out of sync) but we can't send a synthetic
345 // one (as we can't parse the request).
346 errmsg = 'JSON parse failed for: ' + JSON.stringify(str) + ': ' + e;
347 this.writeJson({ notify: '_Error', args: [ errmsg ] });
348 if (lenientJsonParse) {
349 log.warn('JSON parse failed (lenient mode, ignoring):', e);
350 } else {
351 log.warn('JSON parse failed (dropping connection):', e);
352 this.finish(errmsg);
353 }
354 }
355
356 this.incoming.set(this.incoming.subarray(i + 1));
357 this.incomingOffset -= i + 1;
358 return msg;
359 }
360 }
361 };
362
363 JsonConnHandler.prototype.dispatchJsonMessage = function dispatchJsonMessage(msg) {
364 var cmd;
365 var dvalues = [];
366 var i, n;
367
368 log.info('PROXY <-- CLIENT:', JSON.stringify(msg));
369
370 // Parse message type, determine initial marker for binary message.
371 if (msg.request) {
372 cmd = this.determineCommandNumber(msg.request, msg.command);
373 dvalues.push(new Uint8Array([ 0x01 ]));
374 dvalues.push(this.encodeJsonDvalue(cmd));
375 } else if (msg.reply) {
376 dvalues.push(new Uint8Array([ 0x02 ]));
377 } else if (msg.notify) {
378 cmd = this.determineCommandNumber(msg.notify, msg.command);
379 dvalues.push(new Uint8Array([ 0x04 ]));
380 dvalues.push(this.encodeJsonDvalue(cmd));
381 } else if (msg.error) {
382 dvalues.push(new Uint8Array([ 0x03 ]));
383 } else {
384 throw new Error('invalid input JSON message: ' + JSON.stringify(msg));
385 }
386
387 // Encode arguments into dvalues.
388 for (i = 0, n = (msg.args ? msg.args.length : 0); i < n; i++) {
389 dvalues.push(this.encodeJsonDvalue(msg.args[i]));
390 }
391
392 // Add an EOM, and write out the dvalues to the debug target.
393 dvalues.push(new Uint8Array([ 0x00 ]));
394 for (i = 0, n = dvalues.length; i < n; i++) {
395 this.targetHandler.writeBinary(dvalues[i]);
396 }
397 };
398
399 JsonConnHandler.prototype.determineCommandNumber = function determineCommandNumber(name, val) {
400 var res;
401
402 if (typeof name === 'string') {
403 res = this.commandNumberLookup[name];
404 if (!res) {
405 log.info('Unknown command name: ' + name + ', command number: ' + val);
406 }
407 } else if (typeof name === 'number') {
408 res = name;
409 } else if (name !== true) {
410 throw new Error('invalid command name (must be string, number, or "true"): ' + name);
411 }
412 if (typeof res === 'undefined' && typeof val === 'undefined') {
413 throw new Error('cannot determine command number from name: ' + name);
414 }
415 if (typeof val !== 'number' && typeof val !== 'undefined') {
416 throw new Error('invalid command number: ' + val);
417 }
418 res = res || val;
419 return res;
420 };
421
422 JsonConnHandler.prototype.writeDebugStringToBuffer = function writeDebugStringToBuffer(v, buf, off) {
423 var i, n;
424
425 for (i = 0, n = v.length; i < n; i++) {
426 buf[off + i] = v.charCodeAt(i) & 0xff; // truncate higher bits
427 }
428 };
429
430 JsonConnHandler.prototype.encodeJsonDvalue = function encodeJsonDvalue(v) {
431 var buf, dec, len, dv;
432
433 if (isObject(v)) {
434 if (v.type === 'eom') {
435 return new Uint8Array([ 0x00 ]);
436 } else if (v.type === 'req') {
437 return new Uint8Array([ 0x01 ]);
438 } else if (v.type === 'rep') {
439 return new Uint8Array([ 0x02 ]);
440 } else if (v.type === 'err') {
441 return new Uint8Array([ 0x03 ]);
442 } else if (v.type === 'nfy') {
443 return new Uint8Array([ 0x04 ]);
444 } else if (v.type === 'unused') {
445 return new Uint8Array([ 0x15 ]);
446 } else if (v.type === 'undefined') {
447 return new Uint8Array([ 0x16 ]);
448 } else if (v.type === 'number') {
449 dec = Duktape.dec('hex', v.data);
450 len = dec.length;
451 if (len !== 8) {
452 throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v));
453 }
454 buf = new Uint8Array(1 + len);
455 buf[0] = 0x1a;
456 buf.set(new Uint8Array(dec), 1);
457 return buf;
458 } else if (v.type === 'buffer') {
459 dec = Duktape.dec('hex', v.data);
460 len = dec.length;
461 if (len <= 0xffff) {
462 buf = new Uint8Array(3 + len);
463 buf[0] = 0x14;
464 buf[1] = (len >> 8) & 0xff;
465 buf[2] = (len >> 0) & 0xff;
466 buf.set(new Uint8Arrau(dec), 3);
467 return buf;
468 } else {
469 buf = new Uint8Array(5 + len);
470 buf[0] = 0x13;
471 buf[1] = (len >> 24) & 0xff;
472 buf[2] = (len >> 16) & 0xff;
473 buf[3] = (len >> 8) & 0xff;
474 buf[4] = (len >> 0) & 0xff;
475 buf.set(new Uint8Array(dec), 5);
476 return buf;
477 }
478 } else if (v.type === 'object') {
479 dec = Duktape.dec('hex', v.pointer);
480 len = dec.length;
481 buf = new Uint8Array(3 + len);
482 buf[0] = 0x1b;
483 buf[1] = v.class;
484 buf[2] = len;
485 buf.set(new Uint8Array(dec), 3);
486 return buf;
487 } else if (v.type === 'pointer') {
488 dec = Duktape.dec('hex', v.pointer);
489 len = dec.length;
490 buf = new Uint8Array(2 + len);
491 buf[0] = 0x1c;
492 buf[1] = len;
493 buf.set(new Uint8Array(dec), 2);
494 return buf;
495 } else if (v.type === 'lightfunc') {
496 dec = Duktape.dec('hex', v.pointer);
497 len = dec.length;
498 buf = new Uint8Array(4 + len);
499 buf[0] = 0x1d;
500 buf[1] = (v.flags >> 8) & 0xff;
501 buf[2] = v.flags & 0xff;
502 buf[3] = len;
503 buf.set(new Uint8Array(dec), 4);
504 return buf;
505 } else if (v.type === 'heapptr') {
506 dec = Duktape.dec('hex', v.pointer);
507 len = dec.length;
508 buf = new Uint8Array(2 + len);
509 buf[0] = 0x1e;
510 buf[1] = len;
511 buf.set(new Uint8Array(dec), 2);
512 return buf;
513 }
514 } else if (v === null) {
515 return new Uint8Array([ 0x17 ]);
516 } else if (typeof v === 'boolean') {
517 return new Uint8Array([ v ? 0x18 : 0x19 ]);
518 } else if (typeof v === 'number') {
519 if (Math.floor(v) === v && /* whole */
520 (v !== 0 || 1 / v > 0) && /* not negative zero */
521 v >= -0x80000000 && v <= 0x7fffffff) {
522 // Represented signed 32-bit integers as plain integers.
523 // Debugger code expects this for all fields that are not
524 // duk_tval representations (e.g. command numbers and such).
525 if (v >= 0x00 && v <= 0x3f) {
526 return new Uint8Array([ 0x80 + v ]);
527 } else if (v >= 0x0000 && v <= 0x3fff) {
528 return new Uint8Array([ 0xc0 + (v >> 8), v & 0xff ]);
529 } else if (v >= -0x80000000 && v <= 0x7fffffff) {
530 return new Uint8Array([ 0x10,
531 (v >> 24) & 0xff,
532 (v >> 16) & 0xff,
533 (v >> 8) & 0xff,
534 (v >> 0) & 0xff ]);
535 } else {
536 throw new Error('internal error when encoding integer to dvalue: ' + v);
537 }
538 } else {
539 // Represent non-integers as IEEE double dvalues.
540 buf = new Uint8Array(1 + 8);
541 buf[0] = 0x1a;
542 new DataView(buf).setFloat64(1, v, false);
543 return buf;
544 }
545 } else if (typeof v === 'string') {
546 if (v.length < 0 || v.length > 0xffffffff) {
547 // Not possible in practice.
548 throw new TypeError('cannot convert to dvalue, invalid string length: ' + v.length);
549 }
550 if (v.length <= 0x1f) {
551 buf = new Uint8Array(1 + v.length);
552 buf[0] = 0x60 + v.length;
553 this.writeDebugStringToBuffer(v, buf, 1);
554 return buf;
555 } else if (v.length <= 0xffff) {
556 buf = new Uint8Array(3 + v.length);
557 buf[0] = 0x12;
558 buf[1] = (v.length >> 8) & 0xff;
559 buf[2] = (v.length >> 0) & 0xff;
560 this.writeDebugStringToBuffer(v, buf, 3);
561 return buf;
562 } else {
563 buf = new Uint8Array(5 + v.length);
564 buf[0] = 0x11;
565 buf[1] = (v.length >> 24) & 0xff;
566 buf[2] = (v.length >> 16) & 0xff;
567 buf[3] = (v.length >> 8) & 0xff;
568 buf[4] = (v.length >> 0) & 0xff;
569 this.writeDebugStringToBuffer(v, buf, 5);
570 return buf;
571 }
572 }
573
574 throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v));
575 };
576
577 /*
578 * Target binary connection handler
579 */
580
581 function TargetConnHandler(server) {
582 this.name = 'TargetConnHandler';
583 this.server = server;
584 this.handle = uv.new_tcp();
585 this.jsonHandler = null;
586 this.incoming = new Uint8Array(4096);
587 this.incomingOffset = 0;
588 this.dvalues = [];
589 }
590
591 TargetConnHandler.prototype.finish = function finish(msg) {
592 if (!this.handle) {
593 log.info('TargetConnHandler already disconnected, ignore finish()');
594 return;
595 }
596 log.info('TargetConnHandler finished:', msg);
597
598 this.jsonHandler.writeJson({ notify: '_TargetDisconnected' });
599
600 // XXX: write a notify to target?
601
602 uv.shutdown(this.handle);
603 uv.read_stop(this.handle);
604 uv.close(this.handle);
605 this.handle = null;
606
607 this.jsonHandler.finish(msg); // disconnect JSON client too (if not already disconnected)
608 };
609
610 TargetConnHandler.prototype.onConnect = function onConnect(err) {
611 var errmsg;
612
613 if (err) {
614 errmsg = 'Failed to connect to target: ' + err;
615 log.warn(errmsg);
616 this.jsonHandler.writeJson({ notify: '_Error', args: [ String(err) ] });
617 this.finish(errmsg);
618 return;
619 }
620
621 // Once we're connected to the target, start read both binary and JSON
622 // input. We don't want to read JSON input before this so that we can
623 // always translate incoming messages to dvalues and write them out
624 // without queueing. Any pending JSON messages will be queued by the
625 // OS instead.
626
627 log.info('Connected to debug target at', targetHost + ':' + targetPort);
628 uv.read_start(this.jsonHandler.handle, this.jsonHandler.onRead.bind(this.jsonHandler));
629 uv.read_start(this.handle, this.onRead.bind(this));
630 };
631
632 TargetConnHandler.prototype.writeBinary = function writeBinary(buf) {
633 var plain = plainBufferCopy(buf);
634 log.info('PROXY --> TARGET:', Duktape.enc('jx', plain));
635 if (this.handle) {
636 uv.write(this.handle, plain);
637 }
638 };
639
640 TargetConnHandler.prototype.onRead = function onRead(err, data) {
641 var res;
642 var errmsg;
643 var tmpBuf;
644 var newIncoming;
645
646 log.trace('Received data from target socket, err:', err, 'data length:', data ? data.length : 'null');
647
648 if (err) {
649 errmsg = 'Error reading data from debug target: ' + err;
650 this.finish(errmsg);
651 return;
652 }
653
654 if (data) {
655 // Feed the data one byte at a time when torture testing.
656 if (TORTURE && data.length > 1) {
657 for (var i = 0; i < data.length; i++) {
658 tmpBuf = Duktape.Buffer(1);
659 tmpBuf[0] = data[i];
660 this.onRead(null, tmpBuf);
661 }
662 return;
663 }
664
665 // Receive data into 'incoming', resizing as necessary.
666 while (data.length > this.incoming.length - this.incomingOffset) {
667 newIncoming = new Uint8Array(this.incoming.length * 1.3 + 16);
668 newIncoming.set(this.incoming);
669 this.incoming = newIncoming;
670 log.debug('Resize incoming binary buffer to ' + this.incoming.length);
671 }
672 this.incoming.set(new Uint8Array(data), this.incomingOffset);
673 this.incomingOffset += data.length;
674
675 // Trial parse handshake unless done.
676 if (!this.handshake) {
677 this.trialParseHandshake();
678 }
679
680 // Trial parse dvalue(s) and debug messages.
681 if (this.handshake) {
682 for (;;) {
683 res = this.trialParseDvalue();
684 if (!res) {
685 break;
686 }
687 log.trace('Got dvalue:', Duktape.enc('jx', res.dvalue));
688 this.dvalues.push(res.dvalue);
689 if (isObject(res.dvalue) && res.dvalue.type === 'eom') {
690 try {
691 this.jsonHandler.handleDebugMessage(this.dvalues);
692 this.dvalues = [];
693 } catch (e) {
694 errmsg = 'JSON message handling failed: ' + e;
695 this.jsonHandler.writeJson({ notify: '_Error', args: [ errmsg ] });
696 if (lenientJsonParse) {
697 log.warn('JSON message handling failed (lenient mode, ignoring):', e);
698 } else {
699 log.warn('JSON message handling failed (dropping connection):', e);
700 this.finish(errmsg);
701 }
702 }
703 }
704 }
705 }
706 } else {
707 log.info('Target disconnected');
708 this.finish('Target disconnected');
709 }
710 };
711
712 TargetConnHandler.prototype.trialParseHandshake = function trialParseHandshake() {
713 var buf = this.incoming;
714 var avail = this.incomingOffset;
715 var i;
716 var msg;
717 var m;
718 var protocolVersion;
719
720 for (i = 0; i < avail; i++) {
721 if (buf[i] == 0x0a) {
722 msg = String(plainBufferCopy(buf.subarray(0, i)));
723 this.incoming.set(this.incoming.subarray(i + 1));
724 this.incomingOffset -= i + 1;
725
726 // Generic handshake format: only relies on initial version field.
727 m = /^(\d+) (.*)$/.exec(msg) || {};
728 protocolVersion = +m[1];
729 this.handshake = {
730 line: msg,
731 protocolVersion: protocolVersion,
732 text: m[2]
733 };
734
735 // More detailed v1 handshake line.
736 if (protocolVersion === 1) {
737 m = /^(\d+) (\d+) (.*?) (.*?) (.*)$/.exec(msg) || {};
738 this.handshake.dukVersion = m[1];
739 this.handshake.dukGitDescribe = m[2];
740 this.handshake.targetString = m[3];
741 }
742
743 this.jsonHandler.writeJson({ notify: '_TargetConnected', args: [ msg ] });
744
745 log.info('Target handshake: ' + JSON.stringify(this.handshake));
746 return;
747 }
748 }
749 };
750
751 TargetConnHandler.prototype.bufferToDebugString = function bufferToDebugString(buf) {
752 return String.fromCharCode.apply(null, buf);
753 };
754
755 TargetConnHandler.prototype.trialParseDvalue = function trialParseDvalue() {
756 var _this = this;
757 var buf = this.incoming;
758 var avail = this.incomingOffset;
759 var v;
760 var gotValue = false; // explicit flag for e.g. v === undefined
761 var dv = new DataView(buf);
762 var tmp;
763 var x;
764 var len;
765
766 function consume(n) {
767 log.info('PROXY <-- TARGET:', Duktape.enc('jx', _this.incoming.subarray(0, n)));
768 _this.incoming.set(_this.incoming.subarray(n));
769 _this.incomingOffset -= n;
770 }
771
772 x = buf[0];
773 if (avail <= 0) {
774 ;
775 } else if (x >= 0xc0) {
776 // 0xc0...0xff: integers 0-16383
777 if (avail >= 2) {
778 v = ((x - 0xc0) << 8) + buf[1];
779 consume(2);
780 }
781 } else if (x >= 0x80) {
782 // 0x80...0xbf: integers 0-63
783 v = x - 0x80;
784 consume(1);
785 } else if (x >= 0x60) {
786 // 0x60...0x7f: strings with length 0-31
787 len = x - 0x60;
788 if (avail >= 1 + len) {
789 v = new Uint8Array(len);
790 v.set(buf.subarray(1, 1 + len));
791 v = this.bufferToDebugString(v);
792 consume(1 + len);
793 }
794 } else {
795 switch (x) {
796 case 0x00: consume(1); v = { type: 'eom' }; break;
797 case 0x01: consume(1); v = { type: 'req' }; break;
798 case 0x02: consume(1); v = { type: 'rep' }; break;
799 case 0x03: consume(1); v = { type: 'err' }; break;
800 case 0x04: consume(1); v = { type: 'nfy' }; break;
801 case 0x10: // 4-byte signed integer
802 if (avail >= 5) {
803 v = dv.getInt32(1, false);
804 consume(5);
805 }
806 break;
807 case 0x11: // 4-byte string
808 if (avail >= 5) {
809 len = dv.getUint32(1, false);
810 if (avail >= 5 + len) {
811 v = new Uint8Array(len);
812 v.set(buf.subarray(5, 5 + len));
813 v = this.bufferToDebugString(v);
814 consume(5 + len);
815 }
816 }
817 break;
818 case 0x12: // 2-byte string
819 if (avail >= 3) {
820 len = dv.getUint16(1, false);
821 if (avail >= 3 + len) {
822 v = new Uint8Array(len);
823 v.set(buf.subarray(3, 3 + len));
824 v = this.bufferToDebugString(v);
825 consume(3 + len);
826 }
827 }
828 break;
829 case 0x13: // 4-byte buffer
830 if (avail >= 5) {
831 len = dv.getUint32(1, false);
832 if (avail >= 5 + len) {
833 v = new Uint8Array(len);
834 v.set(buf.subarray(5, 5 + len));
835 v = { type: 'buffer', data: Duktape.enc('hex', Duktape.Buffer(v)) };
836 consume(5 + len);
837 }
838 }
839 break;
840 case 0x14: // 2-byte buffer
841 if (avail >= 3) {
842 len = dv.getUint16(1, false);
843 if (avail >= 3 + len) {
844 v = new Uint8Array(len);
845 v.set(buf.subarray(3, 3 + len));
846 v = { type: 'buffer', data: Duktape.enc('hex', Duktape.Buffer(v)) };
847 consume(3 + len);
848 }
849 }
850 break;
851 case 0x15: // unused/none
852 v = { type: 'unused' };
853 consume(1);
854 break;
855 case 0x16: // undefined
856 v = { type: 'undefined' };
857 gotValue = true; // indicate 'v' is actually set
858 consume(1);
859 break;
860 case 0x17: // null
861 v = null;
862 gotValue = true; // indicate 'v' is actually set
863 consume(1);
864 break;
865 case 0x18: // true
866 v = true;
867 consume(1);
868 break;
869 case 0x19: // false
870 v = false;
871 consume(1);
872 break;
873 case 0x1a: // number (IEEE double), big endian
874 if (avail >= 9) {
875 tmp = new Uint8Array(8);
876 tmp.set(buf.subarray(1, 9));
877 v = { type: 'number', data: Duktape.enc('hex', Duktape.Buffer(tmp)) };
878 if (readableNumberValue) {
879 // The value key should not be used programmatically,
880 // it is just there to make the dumps more readable.
881 v.value = new DataView(tmp.buffer).getFloat64(0, false);
882 }
883 consume(9);
884 }
885 break;
886 case 0x1b: // object
887 if (avail >= 3) {
888 len = buf[2];
889 if (avail >= 3 + len) {
890 v = new Uint8Array(len);
891 v.set(buf.subarray(3, 3 + len));
892 v = { type: 'object', 'class': buf[1], pointer: Duktape.enc('hex', Duktape.Buffer(v)) };
893 consume(3 + len);
894 }
895 }
896 break;
897 case 0x1c: // pointer
898 if (avail >= 2) {
899 len = buf[1];
900 if (avail >= 2 + len) {
901 v = new Uint8Array(len);
902 v.set(buf.subarray(2, 2 + len));
903 v = { type: 'pointer', pointer: Duktape.enc('hex', Duktape.Buffer(v)) };
904 consume(2 + len);
905 }
906 }
907 break;
908 case 0x1d: // lightfunc
909 if (avail >= 4) {
910 len = buf[3];
911 if (avail >= 4 + len) {
912 v = new Uint8Array(len);
913 v.set(buf.subarray(4, 4 + len));
914 v = { type: 'lightfunc', flags: dv.getUint16(1, false), pointer: Duktape.enc('hex', Duktape.Buffer(v)) };
915 consume(4 + len);
916 }
917 }
918 break;
919 case 0x1e: // heapptr
920 if (avail >= 2) {
921 len = buf[1];
922 if (avail >= 2 + len) {
923 v = new Uint8Array(len);
924 v.set(buf.subarray(2, 2 + len));
925 v = { type: 'heapptr', pointer: Duktape.enc('hex', Duktape.Buffer(v)) };
926 consume(2 + len);
927 }
928 }
929 break;
930 default:
931 throw new Error('failed parse initial byte: ' + buf[0]);
932 }
933 }
934
935 if (typeof v !== 'undefined' || gotValue) {
936 return { dvalue: v };
937 }
938 };
939
940 /*
941 * Main
942 */
943
944 function main() {
945 var argv = typeof uv.argv === 'function' ? uv.argv() : [];
946 var i;
947 for (i = 2; i < argv.length; i++) { // skip dukluv and script name
948 if (argv[i] == '--help') {
949 print('Usage: dukluv ' + argv[1] + ' [option]+');
950 print('');
951 print(' --server-host HOST JSON proxy server listen address');
952 print(' --server-port PORT JSON proxy server listen port');
953 print(' --target-host HOST Debug target address');
954 print(' --target-port PORT Debug target port');
955 print(' --metadata FILE Proxy metadata file (usually named duk_debug_meta.json)');
956 print(' --log-level LEVEL Set log level, default is 2; 0=trace, 1=debug, 2=info, 3=warn, etc');
957 print(' --single Run a single proxy connection and exit (default: persist for multiple connections)');
958 print(' --readable-numbers Add a non-programmatic "value" key for IEEE doubles help readability');
959 print(' --lenient Ignore (with warning) invalid JSON without dropping connection');
960 print(' --jx-parse Parse JSON proxy input with JX, useful when testing manually');
961 print('');
962 return; // don't register any sockets/timers etc to exit
963 } else if (argv[i] == '--single') {
964 singleConnection = true;
965 continue;
966 } else if (argv[i] == '--readable-numbers') {
967 readableNumberValue = true;
968 continue;
969 } else if (argv[i] == '--lenient') {
970 lenientJsonParse = true;
971 continue;
972 } else if (argv[i] == '--jx-parse') {
973 jxParse = true;
974 continue;
975 }
976 if (i >= argv.length - 1) {
977 throw new Error('missing option value for ' + argv[i]);
978 }
979 if (argv[i] == '--server-host') {
980 serverHost = argv[i + 1];
981 i++;
982 } else if (argv[i] == '--server-port') {
983 serverPort = Math.floor(+argv[i + 1]);
984 i++;
985 } else if (argv[i] == '--target-host') {
986 targetHost = argv[i + 1];
987 i++;
988 } else if (argv[i] == '--target-port') {
989 targetPort = Math.floor(+argv[i + 1]);
990 i++;
991 } else if (argv[i] == '--metadata') {
992 metadataFile = argv[i + 1];
993 i++;
994 } else if (argv[i] == '--log-level') {
995 log.l = Math.floor(+argv[i + 1]);
996 i++;
997 } else {
998 throw new Error('invalid option ' + argv[i]);
999 }
1000 }
1001
1002 function runServer() {
1003 var serverSocket = new JsonProxyServer(serverHost, serverPort);
1004 var connMode = singleConnection ? 'single connection mode' : 'persistent connection mode';
1005 log.info('Listening for incoming JSON debug connection on ' + serverHost + ':' + serverPort +
1006 ', target is ' + targetHost + ':' + targetPort + ', ' + connMode);
1007 }
1008
1009 if (metadataFile) {
1010 log.info('Read proxy metadata from', metadataFile);
1011 readFully(metadataFile, function (data, err) {
1012 if (err) {
1013 log.error('Failed to load metadata:', err);
1014 throw err;
1015 }
1016 try {
1017 metadata = JSON.parse(String(data));
1018 } catch (e) {
1019 log.error('Failed to parse JSON metadata from ' + metadataFile + ': ' + e);
1020 throw e;
1021 }
1022 runServer();
1023 });
1024 } else {
1025 runServer();
1026 }
1027 }
1028
1029 main();