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
126 lines (119 loc) · 5.36 KB
/
Copy pathdbgp.ts
File metadata and controls
126 lines (119 loc) · 5.36 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
126
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()
})
}
}