Skip to content

Commit

Permalink
feat: Add slip decoder to parser-slip-encoder (#2196)
Browse files Browse the repository at this point in the history
- Add option to specify slip escape codes
- Add optional start and start escape
  • Loading branch information
doggkruse authored May 26, 2021
1 parent 015bc17 commit 85297bc
Show file tree
Hide file tree
Showing 4 changed files with 402 additions and 74 deletions.
94 changes: 94 additions & 0 deletions packages/parser-slip-encoder/lib/decoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const { Transform } = require('stream')

/**
* A transform stream that decodes slip encoded data.
* @extends Transform
* @summary Runs in O(n) time, stripping out slip encoding and emitting decoded data. Optionally,
* custom slip escape and delimiters can be provided.
* @example
// Receive slip encoded data from a serialport and log decoded data
const SerialPort = require('serialport')
const { SlipDecoder } = require('@serialport/parser-slip-encoder')
const port = new SerialPort('/dev/tty-usbserial1')
const parser = port.pipe(new SlipDecoder())
parser.on('data', console.log)
*/
class SlipDecoder extends Transform {
constructor(options = {}) {
super(options)

const opts = {
START: undefined,
ESC: 0xdb,
END: 0xc0,

ESC_START: undefined,
ESC_END: 0xdc,
ESC_ESC: 0xdd,

...options,
}
this.opts = opts

this.buffer = Buffer.alloc(0)
this.escape = false
this.start = false
}

_transform(chunk, encoding, cb) {
for (let ndx = 0; ndx < chunk.length; ndx++) {
let byte = chunk[ndx]

if (byte === this.opts.START) {
this.start = true
continue
} else if (undefined == this.opts.START) {
this.start = true
}

if (this.escape) {
if (byte === this.opts.ESC_START) {
byte = this.opts.START
} else if (byte === this.opts.ESC_ESC) {
byte = this.opts.ESC
} else if (byte === this.opts.ESC_END) {
byte = this.opts.END
} else {
this.escape = false
this.push(this.buffer)
this.buffer = Buffer.alloc(0)
}
} else {
if (byte === this.opts.ESC) {
this.escape = true
continue
}

if (byte === this.opts.END) {
this.push(this.buffer)
this.buffer = Buffer.alloc(0)

this.escape = false
this.start = false
continue
}
}

this.escape = false

if (true === this.start) {
this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])])
}
}

cb()
}

_flush(cb) {
this.push(this.buffer)
this.buffer = Buffer.alloc(0)
cb()
}
}

module.exports = SlipDecoder
89 changes: 89 additions & 0 deletions packages/parser-slip-encoder/lib/encoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const { Transform } = require('stream')

/**
* A transform stream that emits SLIP-encoded data for each incoming packet.
* @extends Transform
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each
* received packet and escaping characters, according to RFC 1055. Adds another
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer").
* Optionally, custom slip escape and delimiters can be provided.
* @example
// Read lines from a text file, then SLIP-encode each and send them to a serial port
const SerialPort = require('serialport')
const { SlipEncoder } = require('@serialport/parser-slip-encoder')
const Readline = require('parser-readline')
const fileReader = require('fs').createReadStream('/tmp/some-file.txt');
const port = new SerialPort('/dev/tty-usbserial1')
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' }));
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false }));
encoder.pipe(port);
*/
class SlipEncoder extends Transform {
constructor(options = {}) {
super(options)

const opts = {
START: undefined,
ESC: 0xdb,
END: 0xc0,

ESC_START: undefined,
ESC_END: 0xdc,
ESC_ESC: 0xdd,

...options,
}
this.opts = opts

if (options.bluetoothQuirk) {
this._bluetoothQuirk = true
}
}

_transform(chunk, encoding, cb) {
const chunkLength = chunk.length

if (this._bluetoothQuirk && chunkLength === 0) {
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like
// lots of 0xC0s together.
return cb()
}

// Allocate memory for the worst-case scenario: all bytes are escaped,
// plus start and end separators.
const encoded = Buffer.alloc(chunkLength * 2 + 2)
let j = 0

if (this._bluetoothQuirk == true) {
encoded[j++] = this.opts.END
}

if (this.opts.START !== undefined) {
encoded[j++] = this.opts.START
}

for (let i = 0; i < chunkLength; i++) {
let byte = chunk[i]

if (byte === this.opts.START) {
encoded[j++] = this.opts.ESC
byte = this.opts.ESC_START
} else if (byte === this.opts.END) {
encoded[j++] = this.opts.ESC
byte = this.opts.ESC_END
} else if (byte === this.opts.ESC) {
encoded[j++] = this.opts.ESC
byte = this.opts.ESC_ESC
}

encoded[j++] = byte
}

encoded[j++] = this.opts.END

cb(null, encoded.slice(0, j))
}
}

module.exports = SlipEncoder
75 changes: 3 additions & 72 deletions packages/parser-slip-encoder/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,4 @@
const { Transform } = require('stream')

const END = 0xc0
const ESC = 0xdb
const ESC_END = 0xdc
const ESC_ESC = 0xdd

/**
* A transform stream that emits SLIP-encoded data for each incoming packet.
* @extends Transform
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each
* received packet and escaping characters, according to RFC 1055. Adds another
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer").
* Runs in O(n) time.
* @example
// Read lines from a text file, then SLIP-encode each and send them to a serial port
const SerialPort = require('serialport')
const SlipEncoder = require('@serialport/parser-slip-encoder')
const Readline = require('parser-readline')
const fileReader = require('fs').createReadStream('/tmp/some-file.txt');
const port = new SerialPort('/dev/tty-usbserial1')
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' }));
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false }));
encoder.pipe(port);
*/
class SlipEncoderParser extends Transform {
constructor(options = {}) {
super(options)

if (options.bluetoothQuirk) {
this._bluetoothQuirk = true
}
}

_transform(chunk, encoding, cb) {
const chunkLength = chunk.length

if (this._bluetoothQuirk && chunkLength === 0) {
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like
// lots of 0xC0s together.
return cb()
}

// Allocate memory for the worst-case scenario: all bytes are escaped,
// plus start and end separators.
const encoded = Buffer.alloc(chunkLength * 2 + 2)
let j = 0

if (this._bluetoothQuirk) {
encoded[j++] = END
}

for (let i = 0; i < chunkLength; i++) {
let byte = chunk[i]
if (byte === END) {
encoded[j++] = ESC
byte = ESC_END
} else if (byte === ESC) {
encoded[j++] = ESC
byte = ESC_ESC
}

encoded[j++] = byte
}

encoded[j++] = END

cb(null, encoded.slice(0, j))
}
module.exports = {
SlipEncoder: require('./encoder'),
SlipDecoder: require('./decoder'),
}

module.exports = SlipEncoderParser
Loading

0 comments on commit 85297bc

Please sign in to comment.