Skip to content
This repository has been archived by the owner on Jul 24, 2018. It is now read-only.

Adds a SLIP encoder #12

Merged
merged 1 commit into from
May 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/parser-slip-encoder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See our api docs https://node-serialport.github.io/parsers/SlipEncoderParser.html
11 changes: 11 additions & 0 deletions packages/parser-slip-encoder/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions packages/parser-slip-encoder/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
76 changes: 76 additions & 0 deletions packages/parser-slip-encoder/slip-encoder.js
Original file line number Diff line number Diff line change
@@ -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<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 = SlipEncoderParser
113 changes: 113 additions & 0 deletions packages/parser-slip-encoder/slip-encoder.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use strict'
/* eslint-disable no-new */

const Buffer = require('safe-buffer').Buffer
const sinon = require('sinon')

const SlipEncoder = require('./slip-encoder')

describe('SlipEncoderParser', () => {

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]));
});
});