diff --git a/packages/parser-slip-encoder/README.md b/packages/parser-slip-encoder/README.md new file mode 100644 index 0000000..892074f --- /dev/null +++ b/packages/parser-slip-encoder/README.md @@ -0,0 +1 @@ +See our api docs https://node-serialport.github.io/parsers/SlipEncoderParser.html diff --git a/packages/parser-slip-encoder/package-lock.json b/packages/parser-slip-encoder/package-lock.json new file mode 100644 index 0000000..7b0dca8 --- /dev/null +++ b/packages/parser-slip-encoder/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } + } +} diff --git a/packages/parser-slip-encoder/package.json b/packages/parser-slip-encoder/package.json new file mode 100644 index 0000000..983ab74 --- /dev/null +++ b/packages/parser-slip-encoder/package.json @@ -0,0 +1,13 @@ +{ + "name": "parser-slip-encoder", + "main": "slip-encoder.js", + "version": "1.0.2", + "dependencies": { + "safe-buffer": "^5.1.1" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/node-serialport/parsers.git" + } +} diff --git a/packages/parser-slip-encoder/slip-encoder.js b/packages/parser-slip-encoder/slip-encoder.js new file mode 100644 index 0000000..fe9ce8c --- /dev/null +++ b/packages/parser-slip-encoder/slip-encoder.js @@ -0,0 +1,76 @@ +'use strict' +const Buffer = require('safe-buffer').Buffer +const Transform = require('stream').Transform + +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('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) { + options = 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. + let encoded = Buffer.alloc((chunkLength * 2) + 2); + let j = 0; + + if (this._bluetoothQuirk) { + encoded[j++] = END; + } + + for (let i=0; i { + + it ('Adds one delimiter to one-byte messages', ()=>{ + const spy = sinon.spy() + const encoder = new SlipEncoder(); + encoder.on('data', spy); + + encoder.write(Buffer.from([0x01])); + encoder.write(Buffer.from([0x80])); + encoder.write(Buffer.from([0xFF])); + encoder.write(Buffer.from([0xA5])); + + assert.equal(spy.callCount, 4); + assert.deepEqual(spy.getCall(0).args[0], Buffer.from([0x01, 0xC0])); + assert.deepEqual(spy.getCall(1).args[0], Buffer.from([0x80, 0xC0])); + assert.deepEqual(spy.getCall(2).args[0], Buffer.from([0xFF, 0xC0])); + assert.deepEqual(spy.getCall(3).args[0], Buffer.from([0xA5, 0xC0])); + }); + + it ('Adds two delimiters to one-byte messages with the bluetooth quirk', ()=>{ + const spy = sinon.spy() + const encoder = new SlipEncoder({ bluetoothQuirk: true }); + encoder.on('data', spy); + + encoder.write(Buffer.from([0x01])); + encoder.write(Buffer.from([0x80])); + encoder.write(Buffer.from([0xFF])); + encoder.write(Buffer.from([0xA5])); + + assert.equal(spy.callCount, 4); + assert.deepEqual(spy.getCall(0).args[0], Buffer.from([0xC0, 0x01, 0xC0])); + assert.deepEqual(spy.getCall(1).args[0], Buffer.from([0xC0, 0x80, 0xC0])); + assert.deepEqual(spy.getCall(2).args[0], Buffer.from([0xC0, 0xFF, 0xC0])); + assert.deepEqual(spy.getCall(3).args[0], Buffer.from([0xC0, 0xA5, 0xC0])); + }); + + it ('Adds one delimiter to zero-byte messages', ()=>{ + const spy = sinon.spy() + const encoder = new SlipEncoder(); + encoder.on('data', spy); + + encoder.write(Buffer.from([])); + + assert.equal(spy.callCount, 1); + assert.deepEqual(spy.getCall(0).args[0], Buffer.from([0xC0])); + }); + + it ('Does nothing with zero-byte messages with the bluetooth quirk', ()=>{ + const spy = sinon.spy() + + const encoder = new SlipEncoder({ bluetoothQuirk: true }); + + encoder.on('data', spy); + + encoder.write(Buffer.from([])); + encoder.write(Buffer.from([])); + encoder.write(Buffer.from([])); + encoder.write(Buffer.from([])); + + assert.equal(spy.callCount, 0); + }); + + + it ('Escapes characters', ()=>{ + const spy = sinon.spy() + const encoder = new SlipEncoder(); + encoder.on('data', spy); + + encoder.write(Buffer.from([0x01])); + encoder.write(Buffer.from([0xC0])); + encoder.write(Buffer.from([0xDB])); + encoder.write(Buffer.from([0xDC])); + encoder.write(Buffer.from([0xDD])); + encoder.write(Buffer.from([0xFF])); + + assert.equal(spy.callCount, 6); + assert.deepEqual(spy.getCall(0).args[0], Buffer.from([0x01, 0xC0])); + assert.deepEqual(spy.getCall(1).args[0], Buffer.from([0xDB, 0xDC, 0xC0])); + assert.deepEqual(spy.getCall(2).args[0], Buffer.from([0xDB, 0xDD, 0xC0])); + assert.deepEqual(spy.getCall(3).args[0], Buffer.from([0xDC, 0xC0])); + assert.deepEqual(spy.getCall(4).args[0], Buffer.from([0xDD, 0xC0])); + assert.deepEqual(spy.getCall(5).args[0], Buffer.from([0xFF, 0xC0])); + }); + + it ('Escapes characters with the bluetooth quirk', ()=>{ + const spy = sinon.spy() + const encoder = new SlipEncoder({ bluetoothQuirk: true }); + encoder.on('data', spy); + + encoder.write(Buffer.from([0x01])); + encoder.write(Buffer.from([0xC0])); + encoder.write(Buffer.from([0xDB])); + encoder.write(Buffer.from([0xDC])); + encoder.write(Buffer.from([0xDD])); + encoder.write(Buffer.from([0xFF])); + + assert.equal(spy.callCount, 6); + assert.deepEqual(spy.getCall(0).args[0], Buffer.from([0xC0, 0x01, 0xC0])); + assert.deepEqual(spy.getCall(1).args[0], Buffer.from([0xC0, 0xDB, 0xDC, 0xC0])); + assert.deepEqual(spy.getCall(2).args[0], Buffer.from([0xC0, 0xDB, 0xDD, 0xC0])); + assert.deepEqual(spy.getCall(3).args[0], Buffer.from([0xC0, 0xDC, 0xC0])); + assert.deepEqual(spy.getCall(4).args[0], Buffer.from([0xC0, 0xDD, 0xC0])); + assert.deepEqual(spy.getCall(5).args[0], Buffer.from([0xC0, 0xFF, 0xC0])); + }); +});