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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 | 1×
1×
25×
25×
25×
1×
1×
1×
7×
7×
7×
10×
10×
8×
10×
10×
1×
1×
9×
9×
3×
6×
6×
6×
3×
6×
7×
1×
25×
25×
23×
23×
23×
1×
22×
1×
21×
1×
20×
20×
2×
18×
18×
20×
19×
19×
19×
1×
18×
1×
17×
2×
15×
15×
16×
15×
15×
2×
2×
13×
13×
1×
12×
1×
11×
1×
10×
10×
1×
9×
9×
12×
11×
11×
1×
10×
1×
9×
9×
10×
1×
4×
4×
4×
4×
3×
3×
1×
4×
| var EventEmitter = require('events').EventEmitter
, util = require('util')
, Message = require('./Message')
/**
* Handles serializing and de-serializing RELP messages
* Each connection should have a parser
*
* @constructor
*/
var Parser = function () {
Parser.super_.call(this)
this.buffer = new Buffer(0)
this.currentMessage = null
}
util.inherits(Parser, EventEmitter)
module.exports = Parser
/**
* Adds to the existing buffer and consumes it
*
* @param buffer
*/
Parser.prototype.consume = function (buffer) {
var self = this
this.buffer = Buffer.concat([this.buffer, buffer])
/**
* Buffer consumer that tries to be nice to the event loop
*/
var consume = function () {
var result
if (self.currentMessage === null) {
self.currentMessage = new Message()
}
try {
result = self.deserialize(self.buffer, self.currentMessage)
} catch (e) {
/**
* Error event that contains a caught exception
*
* @event Parser#error
* @type {Error}
*/
self.emit('error', e)
return
}
self.buffer = self.buffer.slice(result.position)
if (result.complete === false) {
return
}
var emitMessage = self.currentMessage
self.currentMessage = null
if (self.buffer.length > 0) {
process.nextTick(consume)
}
/**
* Message event that contains a parsed Message
*
* @event Parser#message
* @type {Message}
*/
self.emit('message', emitMessage)
}
process.nextTick(consume)
}
/**
* Attempts to consume an entire message from the buffer
* If the buffer does not contain an entire message then `message` will be partial
*
* @param {String} buffer The buffer to consume serialized messages from
* @param {Message} message The message to put deserialized parts onto
*
* @returns {{complete: boolean, position: number}} An object describing the state of the message being parsed and the
* position in the buffer we got to
*/
Parser.prototype.deserialize = function (buffer, message) {
/*
NOTE: Node doesn't provide a good way to read bytes until reaching a specific character, outside of ReadLine.
For most of this we read utf pessimistically until we get to the message body, since that is the only time we
know the full size of what we need to read
TODO: In node 10 we are going to have some issues with the backwards compat breaks in how Buffer works
*/
var parsedPosition = 0,
nextToken = 0,
header = ''
if (message.transactionId === void 0) {
header = buffer.toString('utf8', 0, 10)
nextToken = header.indexOf(' ', parsedPosition)
if (nextToken === -1 && header.length > 9) {
throw new Error('Expected transaction id, got something longer than 9 characters')
} else if (nextToken === 0) {
throw new Error('Expected transaction id, got a space instead')
} else if (nextToken < 0) {
return { complete: false, position: parsedPosition }
}
var transactionId = parseInt(header.slice(parsedPosition, nextToken))
if (isNaN(transactionId)) {
throw new Error('Expected transaction id to be a number, got something else')
}
message.transactionId = transactionId
parsedPosition = nextToken + 1
}
if (message.command === void 0) {
header = buffer.toString('utf8', parsedPosition, parsedPosition + 33)
nextToken = header.indexOf(' ')
if (nextToken === -1 && header.length > 32) {
throw new Error('Expected command, got something longer than 32 characters')
} else if (nextToken === 0) {
throw new Error('Expected command, got a space instead')
} else if (nextToken < 0) {
return { complete: false, position: parsedPosition }
}
message.command = header.slice(0, nextToken)
parsedPosition += nextToken + 1
}
if (message.bodyLength === void 0) {
header = buffer.toString('utf8', parsedPosition, parsedPosition + 10)
//No space after body length if it is 0
if (header[0] === '0') {
message.bodyLength = 0
parsedPosition++
} else {
nextToken = header.indexOf(' ')
if (nextToken > 10 || (nextToken === -1 && header.length > 9)) {
throw new Error('Expected bodyLength, got something longer than 9 characters')
} else if (nextToken === 0) {
throw new Error('Expected bodyLength, got a space instead')
} else if (nextToken < 0) {
return { complete: false, position: parsedPosition }
}
var bodyLength = parseInt(header.slice(0, nextToken))
if (isNaN(bodyLength)) {
throw new Error('Expected bodyLength to be a number, got something else')
}
message.bodyLength = bodyLength
parsedPosition += nextToken + 1
}
}
if (message.body === void 0) {
var start = parsedPosition + message.bodyLength
if ((buffer.length - parsedPosition) < message.bodyLength + 1) {
return { complete: false, position: parsedPosition }
} else if (buffer.toString('utf8', start, start + 1) !== '\n') {
//TODO: Figure out why this is happening, maybe not recieved it all yet?
throw new Error('Expected ending newline, got something else')
}
message.body = buffer.toString('utf8', parsedPosition, parsedPosition + message.bodyLength)
parsedPosition = parsedPosition + message.bodyLength + 1
}
return { complete: true, position: parsedPosition }
}
/**
* Serializes a RELP message for transmission over the wire
*
* @param {Message} message The RELP message to serialize
*
* @returns {string} The serialized string, ready to transmit
*/
Parser.prototype.serialize = function (message) {
//TODO: should probably guard this a bit, id is a number, bodyLength is a number, things aren't too long etc
var buffer = []
buffer.push(message.transactionId)
buffer.push(message.command)
if (message.body) {
buffer.push(Buffer.byteLength(message.body))
buffer.push(message.body)
} else {
buffer.push('0')
}
return buffer.join(' ') + '\n'
}
|