forked from xdebug/vscode-php-debug
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdbgp.ts
More file actions
125 lines (116 loc) · 5.4 KB
/
Copy pathdbgp.ts
File metadata and controls
125 lines (116 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import * as net from 'net';
import {EventEmitter} from 'events';
import * as iconv from 'iconv-lite';
import {DOMParser} from 'xmldom';
/** The encoding all XDebug messages are encoded with */
export const ENCODING = 'iso-8859-1';
/** The two states the connection switches between */
enum ParsingState {DataLength, Response};
/** Wraps the NodeJS Socket and calls handleResponse() whenever a full response arrives */
export class DbgpConnection extends EventEmitter {
private _socket: net.Socket;
private _parsingState: ParsingState;
private _chunksDataLength: number;
private _chunks: Buffer[];
private _dataLength: number;
constructor(socket: net.Socket) {
super();
this._socket = socket;
this._parsingState = ParsingState.DataLength;
this._chunksDataLength = 0;
this._chunks = [];
socket.on('data', (data: Buffer) => this._handleDataChunk(data));
socket.on('error', (error: Error) => this.emit('error', error));
socket.on('close', () => this.emit('close'));
}
private _handleDataChunk(data: Buffer) {
// Anatomy of packets: [data length] [NULL] [xml] [NULL]
// are we waiting for the data length or for the response?
if (this._parsingState === ParsingState.DataLength) {
// does data contain a NULL byte?
const nullByteIndex = data.indexOf(0);
if (nullByteIndex !== -1) {
// YES -> we received the data length and are ready to receive the response
const lastPiece = data.slice(0, nullByteIndex);
this._chunks.push(lastPiece);
this._chunksDataLength += lastPiece.length;
this._dataLength = parseInt(iconv.decode(Buffer.concat(this._chunks, this._chunksDataLength), ENCODING));
// reset buffered chunks
this._chunks = [];
this._chunksDataLength = 0;
// switch to response parsing state
this._parsingState = ParsingState.Response;
// if data contains more info (except the NULL byte)
if (data.length > nullByteIndex + 1) {
// handle the rest of the packet as part of the response
const rest = data.slice(nullByteIndex + 1);
this._handleDataChunk(rest);
}
} else {
// NO -> this is only part of the data length. We wait for the next data event
this._chunks.push(data);
this._chunksDataLength += data.length;
}
} else if (this._parsingState === ParsingState.Response) {
// does the new data together with the buffered data add up to the data length?
if (this._chunksDataLength + data.length >= this._dataLength) {
// YES -> we received the whole response
// append the last piece of the response
const lastResponsePiece = data.slice(0, this._dataLength - this._chunksDataLength);
this._chunks.push(lastResponsePiece);
this._chunksDataLength += data.length;
const response = Buffer.concat(this._chunks, this._chunksDataLength);
// call response handler
const xml = iconv.decode(response, ENCODING);
const parser = new DOMParser({
errorHandler: {
warning: warning => {
this.emit('warning', warning);
},
error: error => {
this.emit('error', error instanceof Error ? error : new Error(error));
},
fatalError: error => {
this.emit('error', error instanceof Error ? error : new Error(error));
}
}
});
const document = parser.parseFromString(xml, 'application/xml');
this.emit('message', document);
// reset buffer
this._chunks = [];
this._chunksDataLength = 0;
// switch to data length parsing state
this._parsingState = ParsingState.DataLength;
// if data contains more info (except the NULL byte)
if (data.length > lastResponsePiece.length + 1) {
// handle the rest of the packet (after the NULL byte) as data length
const rest = data.slice(lastResponsePiece.length + 1);
this._handleDataChunk(rest);
}
} else {
// NO -> this is not the whole response yet. We buffer it and wait for the next data event.
this._chunks.push(data);
this._chunksDataLength += data.length;
}
}
}
public write(command: Buffer): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this._socket.writable) {
this._socket.write(command, () => {
resolve();
});
} else {
reject(new Error('socket not writable'));
}
});
}
/** closes the underlying socket */
public close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this._socket.once('close', resolve);
this._socket.end();
});
}
}