parser-slip-encoder/slip-encoder.js

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('@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) {
		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.
		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 = SlipEncoderParser;