@masx200/zmodem.js
Version:
ZMODEM file transfers in JavaScript
244 lines (202 loc) • 7.73 kB
JavaScript
;
var Zmodem = module.exports;
Object.assign(
Zmodem,
require("./zmlib"),
);
//encode() variables - declare them here so we don’t
//create them in the function.
var encode_cur, encode_todo;
const ZDLE = Zmodem.ZMLIB.ZDLE;
/**
* Class that handles ZDLE encoding and decoding.
* Encoding is subject to a given configuration--specifically, whether
* we want to escape all control characters. Decoding is static; however
* a given string is encoded we can always decode it.
*/
Zmodem.ZDLE = class ZmodemZDLE {
/**
* Create a ZDLE encoder.
*
* @param {object} [config] - The initial configuration.
* @param {object} config.escape_ctrl_chars - Whether the ZDLE encoder
* should escape control characters.
*/
constructor(config) {
this._config = {};
if (config) {
this.set_escape_ctrl_chars(!!config.escape_ctrl_chars);
}
}
/**
* Enable or disable control-character escaping.
* You should probably enable this for sender sessions.
*
* @param {boolean} value - Whether to enable (true) or disable (false).
*/
set_escape_ctrl_chars(value) {
if (typeof value !== "boolean") throw "need boolean!";
if (value !== this._config.escape_ctrl_chars) {
this._config.escape_ctrl_chars = value;
this._setup_zdle_table();
}
}
/**
* Whether or not control-character escaping is enabled.
*
* @return {boolean} Whether the escaping is on (true) or off (false).
*/
escapes_ctrl_chars() {
return !!this._config.escape_ctrl_chars;
}
//I don’t know of any Zmodem implementations that use ZESC8
//(“escape_8th_bit”)??
/*
ZMODEM software escapes ZDLE, 020, 0220, 021, 0221, 023, and 0223. If
preceded by 0100 or 0300 (@), 015 and 0215 are also escaped to protect the
Telenet command escape CR-@-CR.
*/
/**
* Encode an array of octet values and return it.
* This will mutate the given array.
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The passed-in array, transformed. This is the
* same object that is passed in.
*/
encode(octets) {
//NB: Performance matters here!
if (!this._zdle_table) throw "No ZDLE encode table configured!";
var zdle_table = this._zdle_table;
var last_code = this._lastcode;
var arrbuf = new ArrayBuffer(2 * octets.length);
var arrbuf_uint8 = new Uint8Array(arrbuf);
var escctl_yn = this._config.escape_ctrl_chars;
var arrbuf_i = 0;
for (encode_cur = 0; encode_cur < octets.length; encode_cur++) {
encode_todo = zdle_table[octets[encode_cur]];
if (!encode_todo) {
console.trace();
console.error("bad encode() call:", JSON.stringify(octets));
this._lastcode = last_code;
throw ("Invalid octet: " + octets[encode_cur]);
}
last_code = octets[encode_cur];
if (encode_todo === 1) {
//Do nothing; we append last_code below.
} //0x40 = '@'; i.e., only escape if the last
//octet was '@'.
else if (
escctl_yn || (encode_todo === 2) ||
((last_code & 0x7f) === 0x40)
) {
arrbuf_uint8[arrbuf_i] = ZDLE;
arrbuf_i++;
last_code ^= 0x40; //0100
}
arrbuf_uint8[arrbuf_i] = last_code;
arrbuf_i++;
}
this._lastcode = last_code;
octets.splice(0);
octets.push.apply(octets, new Uint8Array(arrbuf, 0, arrbuf_i));
return octets;
}
/**
* Decode an array of octet values and return it.
* This will mutate the given array.
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The passed-in array.
* This is the same object that is passed in.
*/
static decode(octets) {
for (var o = octets.length - 1; o >= 0; o--) {
if (octets[o] === ZDLE) {
octets.splice(o, 2, octets[o + 1] - 64);
}
}
return octets;
}
/**
* Remove, ZDLE-decode, and return bytes from the passed-in array.
* If the requested number of ZDLE-encoded bytes isn’t available,
* then the passed-in array is unmodified (and the return is undefined).
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @param {number} offset - The number of (undecoded) bytes to skip
* at the beginning of the “octets” array.
*
* @param {number} count - The number of bytes (octet values) to return.
*
* @returns {number[]|undefined} An array with the requested number of
* decoded octet values, or undefined if that number of decoded
* octets isn’t available (given the passed-in offset).
*/
static splice(octets, offset, count) {
var so_far = 0;
if (!offset) offset = 0;
for (var i = offset; i < octets.length && so_far < count; i++) {
so_far++;
if (octets[i] === ZDLE) i++;
}
if (so_far === count) {
//Don’t accept trailing ZDLE. This check works
//because of the i++ logic above.
if (octets.length === (i - 1)) return;
octets.splice(0, offset);
return ZmodemZDLE.decode(octets.splice(0, i - offset));
}
return;
}
_setup_zdle_table() {
var zsendline_tab = new Array(256);
for (var i = 0; i < zsendline_tab.length; i++) {
//1 = never escape
//2 = always escape
//3 = escape only if the previous byte was '@'
//Never escape characters from 0x20 (32) to 0x7f (127).
//This is the range of printable characters, plus DEL.
//I guess ZMODEM doesn’t consider DEL to be a control character?
if (i & 0x60) {
zsendline_tab[i] = 1;
} else {
switch (i) {
case ZDLE: //NB: no (ZDLE | 0x80)
case Zmodem.ZMLIB.XOFF:
case Zmodem.ZMLIB.XON:
case (Zmodem.ZMLIB.XOFF | 0x80):
case (Zmodem.ZMLIB.XON | 0x80):
zsendline_tab[i] = 2;
break;
case 0x10: // 020
case 0x90: // 0220
zsendline_tab[i] = this._config.turbo_escape ? 1 : 2;
break;
case 0x0d: // 015
case 0x8d: // 0215
zsendline_tab[i] = this._config.escape_ctrl_chars
? 2
: !this._config.turbo_escape
? 3
: 1;
break;
default:
zsendline_tab[i] = this._config.escape_ctrl_chars
? 2
: 1;
}
}
}
this._zdle_table = zsendline_tab;
}
};