Skip to content

Commit 85297bc

Browse files
authored
feat: Add slip decoder to parser-slip-encoder (#2196)
- Add option to specify slip escape codes - Add optional start and start escape
1 parent 015bc17 commit 85297bc

File tree

4 files changed

+402
-74
lines changed

4 files changed

+402
-74
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const { Transform } = require('stream')
2+
3+
/**
4+
* A transform stream that decodes slip encoded data.
5+
* @extends Transform
6+
* @summary Runs in O(n) time, stripping out slip encoding and emitting decoded data. Optionally,
7+
* custom slip escape and delimiters can be provided.
8+
* @example
9+
// Receive slip encoded data from a serialport and log decoded data
10+
const SerialPort = require('serialport')
11+
const { SlipDecoder } = require('@serialport/parser-slip-encoder')
12+
const port = new SerialPort('/dev/tty-usbserial1')
13+
const parser = port.pipe(new SlipDecoder())
14+
parser.on('data', console.log)
15+
*/
16+
class SlipDecoder extends Transform {
17+
constructor(options = {}) {
18+
super(options)
19+
20+
const opts = {
21+
START: undefined,
22+
ESC: 0xdb,
23+
END: 0xc0,
24+
25+
ESC_START: undefined,
26+
ESC_END: 0xdc,
27+
ESC_ESC: 0xdd,
28+
29+
...options,
30+
}
31+
this.opts = opts
32+
33+
this.buffer = Buffer.alloc(0)
34+
this.escape = false
35+
this.start = false
36+
}
37+
38+
_transform(chunk, encoding, cb) {
39+
for (let ndx = 0; ndx < chunk.length; ndx++) {
40+
let byte = chunk[ndx]
41+
42+
if (byte === this.opts.START) {
43+
this.start = true
44+
continue
45+
} else if (undefined == this.opts.START) {
46+
this.start = true
47+
}
48+
49+
if (this.escape) {
50+
if (byte === this.opts.ESC_START) {
51+
byte = this.opts.START
52+
} else if (byte === this.opts.ESC_ESC) {
53+
byte = this.opts.ESC
54+
} else if (byte === this.opts.ESC_END) {
55+
byte = this.opts.END
56+
} else {
57+
this.escape = false
58+
this.push(this.buffer)
59+
this.buffer = Buffer.alloc(0)
60+
}
61+
} else {
62+
if (byte === this.opts.ESC) {
63+
this.escape = true
64+
continue
65+
}
66+
67+
if (byte === this.opts.END) {
68+
this.push(this.buffer)
69+
this.buffer = Buffer.alloc(0)
70+
71+
this.escape = false
72+
this.start = false
73+
continue
74+
}
75+
}
76+
77+
this.escape = false
78+
79+
if (true === this.start) {
80+
this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])])
81+
}
82+
}
83+
84+
cb()
85+
}
86+
87+
_flush(cb) {
88+
this.push(this.buffer)
89+
this.buffer = Buffer.alloc(0)
90+
cb()
91+
}
92+
}
93+
94+
module.exports = SlipDecoder
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const { Transform } = require('stream')
2+
3+
/**
4+
* A transform stream that emits SLIP-encoded data for each incoming packet.
5+
* @extends Transform
6+
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each
7+
* received packet and escaping characters, according to RFC 1055. Adds another
8+
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as
9+
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer").
10+
* Optionally, custom slip escape and delimiters can be provided.
11+
* @example
12+
// Read lines from a text file, then SLIP-encode each and send them to a serial port
13+
const SerialPort = require('serialport')
14+
const { SlipEncoder } = require('@serialport/parser-slip-encoder')
15+
const Readline = require('parser-readline')
16+
const fileReader = require('fs').createReadStream('/tmp/some-file.txt');
17+
const port = new SerialPort('/dev/tty-usbserial1')
18+
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' }));
19+
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false }));
20+
encoder.pipe(port);
21+
*/
22+
class SlipEncoder extends Transform {
23+
constructor(options = {}) {
24+
super(options)
25+
26+
const opts = {
27+
START: undefined,
28+
ESC: 0xdb,
29+
END: 0xc0,
30+
31+
ESC_START: undefined,
32+
ESC_END: 0xdc,
33+
ESC_ESC: 0xdd,
34+
35+
...options,
36+
}
37+
this.opts = opts
38+
39+
if (options.bluetoothQuirk) {
40+
this._bluetoothQuirk = true
41+
}
42+
}
43+
44+
_transform(chunk, encoding, cb) {
45+
const chunkLength = chunk.length
46+
47+
if (this._bluetoothQuirk && chunkLength === 0) {
48+
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like
49+
// lots of 0xC0s together.
50+
return cb()
51+
}
52+
53+
// Allocate memory for the worst-case scenario: all bytes are escaped,
54+
// plus start and end separators.
55+
const encoded = Buffer.alloc(chunkLength * 2 + 2)
56+
let j = 0
57+
58+
if (this._bluetoothQuirk == true) {
59+
encoded[j++] = this.opts.END
60+
}
61+
62+
if (this.opts.START !== undefined) {
63+
encoded[j++] = this.opts.START
64+
}
65+
66+
for (let i = 0; i < chunkLength; i++) {
67+
let byte = chunk[i]
68+
69+
if (byte === this.opts.START) {
70+
encoded[j++] = this.opts.ESC
71+
byte = this.opts.ESC_START
72+
} else if (byte === this.opts.END) {
73+
encoded[j++] = this.opts.ESC
74+
byte = this.opts.ESC_END
75+
} else if (byte === this.opts.ESC) {
76+
encoded[j++] = this.opts.ESC
77+
byte = this.opts.ESC_ESC
78+
}
79+
80+
encoded[j++] = byte
81+
}
82+
83+
encoded[j++] = this.opts.END
84+
85+
cb(null, encoded.slice(0, j))
86+
}
87+
}
88+
89+
module.exports = SlipEncoder
Lines changed: 3 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,4 @@
1-
const { Transform } = require('stream')
2-
3-
const END = 0xc0
4-
const ESC = 0xdb
5-
const ESC_END = 0xdc
6-
const ESC_ESC = 0xdd
7-
8-
/**
9-
* A transform stream that emits SLIP-encoded data for each incoming packet.
10-
* @extends Transform
11-
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each
12-
* received packet and escaping characters, according to RFC 1055. Adds another
13-
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as
14-
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer").
15-
* Runs in O(n) time.
16-
* @example
17-
// Read lines from a text file, then SLIP-encode each and send them to a serial port
18-
const SerialPort = require('serialport')
19-
const SlipEncoder = require('@serialport/parser-slip-encoder')
20-
const Readline = require('parser-readline')
21-
const fileReader = require('fs').createReadStream('/tmp/some-file.txt');
22-
const port = new SerialPort('/dev/tty-usbserial1')
23-
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' }));
24-
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false }));
25-
encoder.pipe(port);
26-
*/
27-
class SlipEncoderParser extends Transform {
28-
constructor(options = {}) {
29-
super(options)
30-
31-
if (options.bluetoothQuirk) {
32-
this._bluetoothQuirk = true
33-
}
34-
}
35-
36-
_transform(chunk, encoding, cb) {
37-
const chunkLength = chunk.length
38-
39-
if (this._bluetoothQuirk && chunkLength === 0) {
40-
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like
41-
// lots of 0xC0s together.
42-
return cb()
43-
}
44-
45-
// Allocate memory for the worst-case scenario: all bytes are escaped,
46-
// plus start and end separators.
47-
const encoded = Buffer.alloc(chunkLength * 2 + 2)
48-
let j = 0
49-
50-
if (this._bluetoothQuirk) {
51-
encoded[j++] = END
52-
}
53-
54-
for (let i = 0; i < chunkLength; i++) {
55-
let byte = chunk[i]
56-
if (byte === END) {
57-
encoded[j++] = ESC
58-
byte = ESC_END
59-
} else if (byte === ESC) {
60-
encoded[j++] = ESC
61-
byte = ESC_ESC
62-
}
63-
64-
encoded[j++] = byte
65-
}
66-
67-
encoded[j++] = END
68-
69-
cb(null, encoded.slice(0, j))
70-
}
1+
module.exports = {
2+
SlipEncoder: require('./encoder'),
3+
SlipDecoder: require('./decoder'),
714
}
72-
73-
module.exports = SlipEncoderParser

0 commit comments

Comments
 (0)