Skip to content

Commit 779dd04

Browse files
committed
WIP: New API, more features, new protocol abstraction (crashes)
1 parent 8cbfe2f commit 779dd04

File tree

5 files changed

+764
-383
lines changed

5 files changed

+764
-383
lines changed

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,9 @@
4444
"type": "php",
4545
"label": "PHP XDebug",
4646
"enableBreakpointsFor": {
47-
"languageIds": [
48-
"php"
49-
]
47+
"languageIds": ["php"]
5048
},
51-
"program": "./out/mockDebug.js",
49+
"program": "./out/phpDebug.js",
5250
"runtime": "node",
5351
"configurationAttributes": {
5452
"launch": {

src/dbgp.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
import * as net from 'net';
3+
import {EventEmitter} from 'events';
4+
import * as iconv from 'iconv-lite';
5+
import {DOMParser} from 'xmldom';
6+
7+
/** The encoding all XDebug messages are encoded with */
8+
const ENCODING = 'iso-8859-1';
9+
10+
function parseResponse(response: Buffer): XMLDocument {
11+
const xml = iconv.decode(response, ENCODING);
12+
const parser = new DOMParser();
13+
const document = parser.parseFromString(xml, 'application/xml');
14+
return document;
15+
}
16+
17+
/** The two states the connection switches between */
18+
enum ParsingState {DataLength, Response};
19+
20+
/** Wraps the NodeJS Socket and calls handleResponse() whenever a full response arrives */
21+
export abstract class DbgpConnection {
22+
23+
private _socket: net.Socket;
24+
private _parsingState: ParsingState;
25+
private _dataLengthBuffer: Buffer;
26+
private _responseBuffer: Buffer;
27+
private _dataLength: number;
28+
29+
constructor(socket: net.Socket) {
30+
this._socket = socket;
31+
this._parsingState = ParsingState.DataLength;
32+
this._dataLengthBuffer = new Buffer(0);
33+
this._responseBuffer = new Buffer(0);
34+
// Anatomy of packets: [data length] [NULL] [xml] [NULL]
35+
const handleDataChunk = (data: Buffer) => {
36+
// are we waiting for the data length or for the response?
37+
if (this._parsingState === ParsingState.DataLength) {
38+
// does data contain a NULL byte?
39+
const nullByteIndex = data.indexOf(0);
40+
if (nullByteIndex !== -1) {
41+
// YES -> we received the data length and are ready to receive the response
42+
this._dataLength = parseInt(iconv.decode(data.slice(0, nullByteIndex), ENCODING));
43+
// reset buffer
44+
this._dataLengthBuffer = new Buffer(0);
45+
// switch to response parsing state
46+
this._parsingState = ParsingState.Response;
47+
// if data contains more info (except the NULL byte)
48+
if (data.length > nullByteIndex + 1) {
49+
// handle the rest of the packet as part of the response
50+
const rest = data.slice(nullByteIndex + 1);
51+
handleDataChunk(rest);
52+
}
53+
} else {
54+
// NO -> this is only part of the data length. We buffer it and wait for the next data event
55+
this._dataLengthBuffer = Buffer.concat([this._dataLengthBuffer, data], this._dataLengthBuffer.length + data.length);
56+
}
57+
} else if (this._parsingState === ParsingState.Response) {
58+
// does the new data together with the buffered data add up to the data length?
59+
if (this._responseBuffer.length + data.length >= this._dataLength) {
60+
// YES -> we received the whole response
61+
const lastResponsePiece = data.slice(0, this._dataLength - this._responseBuffer.length);
62+
// append the last piece of the response
63+
const response = Buffer.concat([this._responseBuffer, lastResponsePiece], this._dataLength);
64+
// call response handler
65+
this.handleResponse(parseResponse(response));
66+
// reset buffer
67+
this._responseBuffer = new Buffer(0);
68+
// switch to data length parsing state
69+
this._parsingState = ParsingState.DataLength;
70+
// if data contains more info (except the NULL byte)
71+
if (data.length > lastResponsePiece.length + 1) {
72+
// handle the rest of the packet (after the NULL byte) as data length
73+
const rest = data.slice(lastResponsePiece.length + 1);
74+
handleDataChunk(rest);
75+
}
76+
} else {
77+
// NO -> this is not the whole response yet. We buffer it and wait for the next data event.
78+
this._responseBuffer = Buffer.concat([this._responseBuffer, data], this._responseBuffer.length + data.length);
79+
}
80+
}
81+
};
82+
socket.on('data', handleDataChunk);
83+
}
84+
85+
protected abstract handleResponse(response: XMLDocument): void;
86+
87+
public write(command: Buffer): void {
88+
this._socket.write(command);
89+
}
90+
91+
/** closes the underlying socket */
92+
public close(): Promise<void> {
93+
return new Promise<void>((resolve, reject) => {
94+
this._socket.once('close', resolve);
95+
this._socket.end();
96+
});
97+
}
98+
}

0 commit comments

Comments
 (0)