summaryrefslogtreecommitdiffstats
path: root/src/zheader.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-11-20 06:01:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-11-20 06:19:39 +0000
commit56eec1de7018759c0ec251dba4455c18f73c3bbd (patch)
tree3aeb2d10356530bc2cc3f24e74f41048a13885b4 /src/zheader.js
parentInitial commit. (diff)
downloadzmodemjs-56eec1de7018759c0ec251dba4455c18f73c3bbd.tar.xz
zmodemjs-56eec1de7018759c0ec251dba4455c18f73c3bbd.zip
Adding upstream version 0.1.10+dfsg.upstream/0.1.10+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/zheader.js')
-rw-r--r--src/zheader.js763
1 files changed, 763 insertions, 0 deletions
diff --git a/src/zheader.js b/src/zheader.js
new file mode 100644
index 0000000..56c22fc
--- /dev/null
+++ b/src/zheader.js
@@ -0,0 +1,763 @@
+"use strict";
+
+var Zmodem = module.exports;
+
+Object.assign(
+ Zmodem,
+ require("./encode"),
+ require("./zdle"),
+ require("./zmlib"),
+ require("./zcrc"),
+ require("./zerror")
+);
+
+const ZPAD = '*'.charCodeAt(0),
+ ZBIN = 'A'.charCodeAt(0),
+ ZHEX = 'B'.charCodeAt(0),
+ ZBIN32 = 'C'.charCodeAt(0)
+;
+
+//NB: lrzsz uses \x8a rather than \x0a where the specs
+//say to use LF. For simplicity, we avoid that and just use
+//the 7-bit LF character.
+const HEX_HEADER_CRLF = [ 0x0d, 0x0a ];
+const HEX_HEADER_CRLF_XON = HEX_HEADER_CRLF.slice(0).concat( [Zmodem.ZMLIB.XON] );
+
+//These are more or less duplicated by the logic in trim_leading_garbage().
+//
+//"**" + ZDLE_CHAR + "B"
+const HEX_HEADER_PREFIX = [ ZPAD, ZPAD, Zmodem.ZMLIB.ZDLE, ZHEX ];
+const BINARY16_HEADER_PREFIX = [ ZPAD, Zmodem.ZMLIB.ZDLE, ZBIN ];
+const BINARY32_HEADER_PREFIX = [ ZPAD, Zmodem.ZMLIB.ZDLE, ZBIN32 ];
+
+/** Class that represents a ZMODEM header. */
+Zmodem.Header = class ZmodemHeader {
+
+ //lrzsz’s “sz” command sends a random (?) CR/0x0d byte
+ //after ZEOF. Let’s accommodate 0x0a, 0x0d, 0x8a, and 0x8d.
+ //
+ //Also, when you skip a file, sz outputs a message about it.
+ //
+ //It appears that we’re supposed to ignore anything until
+ //[ ZPAD, ZDLE ] when we’re looking for a header.
+
+ /**
+ * Weed out the leading bytes that aren’t valid to start a ZMODEM header.
+ *
+ * @param {number[]} ibuffer - The octet values to parse.
+ * Each array member should be an 8-bit unsigned integer (0-255).
+ * This object is mutated in the function.
+ *
+ * @returns {number[]} The octet values that were removed from the start
+ * of “ibuffer”. Order is preserved.
+ */
+ static trim_leading_garbage(ibuffer) {
+ //Since there’s no escaping of the output it’s possible
+ //that the garbage could trip us up, e.g., by having a filename
+ //be a legit ZMODEM header. But that’s pretty unlikely.
+
+ //Everything up to the first ZPAD: garbage
+ //If first ZPAD has asterisk + ZDLE
+
+ var garbage = [];
+
+ var discard_all, parser, next_ZPAD_at_least = 0;
+
+ TRIM_LOOP:
+ while (ibuffer.length && !parser) {
+ var first_ZPAD = ibuffer.indexOf(ZPAD);
+
+ //No ZPAD? Then we purge the input buffer cuz it’s all garbage.
+ if (first_ZPAD === -1) {
+ discard_all = true;
+ break TRIM_LOOP;
+ }
+ else {
+ garbage.push.apply( garbage, ibuffer.splice(0, first_ZPAD) );
+
+ //buffer has only an asterisk … gotta see about more
+ if (ibuffer.length < 2) {
+ break TRIM_LOOP;
+ }
+ else if (ibuffer[1] === ZPAD) {
+ //Two leading ZPADs should be a hex header.
+
+ //We’re assuming the length of the header is 4 in
+ //this logic … but ZMODEM isn’t likely to change, so.
+ if (ibuffer.length < HEX_HEADER_PREFIX.length) {
+ if (ibuffer.join() === HEX_HEADER_PREFIX.slice(0, ibuffer.length).join()) {
+ //We have an incomplete fragment that matches
+ //HEX_HEADER_PREFIX. So don’t trim any more.
+ break TRIM_LOOP;
+ }
+
+ //Otherwise, we’ll discard one.
+ }
+ else if ((ibuffer[2] === HEX_HEADER_PREFIX[2]) && (ibuffer[3] === HEX_HEADER_PREFIX[3])) {
+ parser = _parse_hex;
+ }
+ }
+ else if (ibuffer[1] === Zmodem.ZMLIB.ZDLE) {
+ //ZPAD + ZDLE should be a binary header.
+ if (ibuffer.length < BINARY16_HEADER_PREFIX.length) {
+ break TRIM_LOOP;
+ }
+
+ if (ibuffer[2] === BINARY16_HEADER_PREFIX[2]) {
+ parser = _parse_binary16;
+ }
+ else if (ibuffer[2] === BINARY32_HEADER_PREFIX[2]) {
+ parser = _parse_binary32;
+ }
+ }
+
+ if (!parser) {
+ garbage.push( ibuffer.shift() );
+ }
+ }
+ }
+
+ if (discard_all) {
+ garbage.push.apply( garbage, ibuffer.splice(0) );
+ }
+
+ //For now we’ll throw away the parser.
+ //It’s not hard for parse() to discern anyway.
+
+ return garbage;
+ }
+
+ /**
+ * Parse out a Header object from a given array of octet values.
+ *
+ * An exception is thrown if the given bytes are definitively invalid
+ * as header values.
+ *
+ * @param {number[]} octets - The octet values to parse.
+ * Each array member should be an 8-bit unsigned integer (0-255).
+ * This object is mutated in the function.
+ *
+ * @returns {Header|undefined} An instance of the appropriate Header
+ * subclass, or undefined if not enough octet values are given
+ * to determine whether there is a valid header here or not.
+ */
+ static parse(octets) {
+ var hdr;
+ if (octets[1] === ZPAD) {
+ hdr = _parse_hex(octets);
+ return hdr && [ hdr, 16 ];
+ }
+
+ else if (octets[2] === ZBIN) {
+ hdr = _parse_binary16(octets, 3);
+ return hdr && [ hdr, 16 ];
+ }
+
+ else if (octets[2] === ZBIN32) {
+ hdr = _parse_binary32(octets);
+ return hdr && [ hdr, 32 ];
+ }
+
+ if (octets.length < 3) return;
+
+ throw( "Unrecognized/unsupported octets: " + octets.join() );
+ }
+
+ /**
+ * Build a Header subclass given a name and arguments.
+ *
+ * @param {string} name - The header type name, e.g., “ZRINIT”.
+ *
+ * @param {...*} args - The arguments to pass to the appropriate
+ * subclass constructor. These aren’t documented currently
+ * but are pretty easy to glean from the code.
+ *
+ * @returns {Header} An instance of the appropriate Header subclass.
+ */
+ static build(name /*, args */) {
+ var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
+
+ //TODO: make this better
+ var Ctr = FRAME_NAME_CREATOR[name];
+ if (!Ctr) throw("No frame class “" + name + "” is defined!");
+
+ args.shift();
+
+ //Plegh!
+ //https://stackoverflow.com/questions/33193310/constr-applythis-args-in-es6-classes
+ var hdr = new (Ctr.bind.apply(Ctr, [null].concat(args)));
+
+ return hdr;
+ }
+
+ /**
+ * Return the octet values array that represents the object
+ * in ZMODEM hex encoding.
+ *
+ * @returns {number[]} An array of octet values suitable for sending
+ * as binary data.
+ */
+ to_hex() {
+ var to_crc = this._crc_bytes();
+
+ return HEX_HEADER_PREFIX.concat(
+ Zmodem.ENCODELIB.octets_to_hex( to_crc.concat( Zmodem.CRC.crc16(to_crc) ) ),
+ this._hex_header_ending
+ );
+ }
+
+ /**
+ * Return the octet values array that represents the object
+ * in ZMODEM binary encoding with a 16-bit CRC.
+ *
+ * @param {ZDLE} zencoder - A ZDLE instance to use for
+ * ZDLE encoding.
+ *
+ * @returns {number[]} An array of octet values suitable for sending
+ * as binary data.
+ */
+ to_binary16(zencoder) {
+ return this._to_binary(zencoder, BINARY16_HEADER_PREFIX, Zmodem.CRC.crc16);
+ }
+
+ /**
+ * Return the octet values array that represents the object
+ * in ZMODEM binary encoding with a 32-bit CRC.
+ *
+ * @param {ZDLE} zencoder - A ZDLE instance to use for
+ * ZDLE encoding.
+ *
+ * @returns {number[]} An array of octet values suitable for sending
+ * as binary data.
+ */
+ to_binary32(zencoder) {
+ return this._to_binary(zencoder, BINARY32_HEADER_PREFIX, Zmodem.CRC.crc32);
+ }
+
+ //This is never called directly, but only as super().
+ constructor() {
+ if (!this._bytes4) {
+ this._bytes4 = [0, 0, 0, 0];
+ }
+ }
+
+ _to_binary(zencoder, prefix, crc_func) {
+ var to_crc = this._crc_bytes();
+
+ //Both the 4-byte payload and the CRC bytes are ZDLE-encoded.
+ var octets = prefix.concat(
+ zencoder.encode( to_crc.concat( crc_func(to_crc) ) )
+ );
+
+ return octets;
+ }
+
+ _crc_bytes() {
+ return [ this.TYPENUM ].concat(this._bytes4);
+ }
+}
+Zmodem.Header.prototype._hex_header_ending = HEX_HEADER_CRLF_XON;
+
+class ZRQINIT_HEADER extends Zmodem.Header {};
+
+//----------------------------------------------------------------------
+
+const ZRINIT_FLAG = {
+
+ //----------------------------------------------------------------------
+ // Bit Masks for ZRINIT flags byte ZF0
+ //----------------------------------------------------------------------
+ CANFDX: 0x01, // Rx can send and receive true FDX
+ CANOVIO: 0x02, // Rx can receive data during disk I/O
+ CANBRK: 0x04, // Rx can send a break signal
+ CANCRY: 0x08, // Receiver can decrypt -- nothing does this
+ CANLZW: 0x10, // Receiver can uncompress -- nothing does this
+ CANFC32: 0x20, // Receiver can use 32 bit Frame Check
+ ESCCTL: 0x40, // Receiver expects ctl chars to be escaped
+ ESC8: 0x80, // Receiver expects 8th bit to be escaped
+};
+
+function _get_ZRINIT_flag_num(fl) {
+ if (!ZRINIT_FLAG[fl]) {
+ throw new Zmodem.Error("Invalid ZRINIT flag: " + fl);
+ }
+ return ZRINIT_FLAG[fl];
+}
+
+class ZRINIT_HEADER extends Zmodem.Header {
+ constructor(flags_arr, bufsize) {
+ super();
+ var flags_num = 0;
+ if (!bufsize) bufsize = 0;
+
+ flags_arr.forEach( function(fl) {
+ flags_num |= _get_ZRINIT_flag_num(fl);
+ } );
+
+ this._bytes4 = [
+ bufsize & 0xff,
+ bufsize >> 8,
+ 0,
+ flags_num,
+ ];
+ }
+
+ //undefined if nonstop I/O is allowed
+ get_buffer_size() {
+ return Zmodem.ENCODELIB.unpack_u16_be( this._bytes4.slice(0, 2) ) || undefined;
+ }
+
+ //Unimplemented:
+ // can_decrypt
+ // can_decompress
+
+ //----------------------------------------------------------------------
+ //function names taken from Jacques Mattheij’s implementation,
+ //as used in syncterm.
+
+ can_full_duplex() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.CANFDX );
+ }
+
+ can_overlap_io() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.CANOVIO );
+ }
+
+ can_break() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.CANBRK );
+ }
+
+ can_fcs_32() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.CANFC32 );
+ }
+
+ escape_ctrl_chars() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.ESCCTL );
+ }
+
+ //Is this used? I don’t see it used in lrzsz or syncterm
+ //Looks like it was a “foreseen” feature that Forsberg
+ //never implemented. (The need for it went away, maybe?)
+ escape_8th_bit() {
+ return !!( this._bytes4[3] & ZRINIT_FLAG.ESC8 );
+ }
+};
+
+//----------------------------------------------------------------------
+
+//Since context makes clear what’s going on, we use these
+//rather than the T-prefixed constants in the specification.
+const ZSINIT_FLAG = {
+ ESCCTL: 0x40, // Transmitter will escape ctl chars
+ ESC8: 0x80, // Transmitter will escape 8th bit
+};
+
+function _get_ZSINIT_flag_num(fl) {
+ if (!ZSINIT_FLAG[fl]) {
+ throw("Invalid ZSINIT flag: " + fl);
+ }
+ return ZSINIT_FLAG[fl];
+}
+
+class ZSINIT_HEADER extends Zmodem.Header {
+ constructor( flags_arr, attn_seq_arr ) {
+ super();
+ var flags_num = 0;
+
+ flags_arr.forEach( function(fl) {
+ flags_num |= _get_ZSINIT_flag_num(fl);
+ } );
+
+ this._bytes4 = [ 0, 0, 0, flags_num ];
+
+ if (attn_seq_arr) {
+ if (attn_seq_arr.length > 31) {
+ throw("Attn sequence must be <= 31 bytes");
+ }
+ if (attn_seq_arr.some( function(num) { return num > 255 } )) {
+ throw("Attn sequence (" + attn_seq_arr + ") must be <256");
+ }
+ this._data = attn_seq_arr.concat([0]);
+ }
+ }
+
+ escape_ctrl_chars() {
+ return !!( this._bytes4[3] & ZSINIT_FLAG.ESCCTL );
+ }
+
+ //Is this used? I don’t see it used in lrzsz or syncterm
+ escape_8th_bit() {
+ return !!( this._bytes4[3] & ZSINIT_FLAG.ESC8 );
+ }
+}
+
+//Thus far it doesn’t seem we really need this header except to respond
+//to ZSINIT, which doesn’t require a payload.
+class ZACK_HEADER extends Zmodem.Header {
+ constructor(payload4) {
+ super();
+
+ if (payload4) {
+ this._bytes4 = payload4.slice();
+ }
+ }
+}
+ZACK_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF;
+
+//----------------------------------------------------------------------
+
+const ZFILE_VALUES = {
+
+ //ZF3 (i.e., first byte)
+ extended: {
+ sparse: 0x40, //ZXSPARS
+ },
+
+ //ZF2
+ transport: [
+ undefined,
+ "compress", //ZTLZW
+ "encrypt", //ZTCRYPT
+ "rle", //ZTRLE
+ ],
+
+ //ZF1
+ management: [
+ undefined,
+ "newer_or_longer", //ZF1_ZMNEWL
+ "crc", //ZF1_ZMCRC
+ "append", //ZF1_ZMAPND
+ "clobber", //ZF1_ZMCLOB
+ "newer", //ZF1_ZMNEW
+ "mtime_or_length", //ZF1_ZMNEW
+ "protect", //ZF1_ZMPROT
+ "rename", //ZF1_ZMPROT
+ ],
+
+ //ZF0 (i.e., last byte)
+ conversion: [
+ undefined,
+ "binary", //ZCBIN
+ "text", //ZCNL
+ "resume", //ZCRESUM
+ ],
+};
+
+const ZFILE_ORDER = ["extended", "transport", "management", "conversion"];
+
+const ZMSKNOLOC = 0x80,
+ MANAGEMENT_MASK = 0x1f,
+ ZXSPARS = 0x40
+;
+
+class ZFILE_HEADER extends Zmodem.Header {
+
+ //TODO: allow options on instantiation
+ get_options() {
+ var opts = {
+ sparse: !!(this._bytes4[0] & ZXSPARS),
+ };
+
+ var bytes_copy = this._bytes4.slice(0);
+
+ ZFILE_ORDER.forEach( function(key, i) {
+ if (ZFILE_VALUES[key] instanceof Array) {
+ if (key === "management") {
+ opts.skip_if_absent = !!(bytes_copy[i] & ZMSKNOLOC);
+ bytes_copy[i] &= MANAGEMENT_MASK;
+ }
+
+ opts[key] = ZFILE_VALUES[key][ bytes_copy[i] ];
+ }
+ else {
+ for (var extkey in ZFILE_VALUES[key]) {
+ opts[extkey] = !!(bytes_copy[i] & ZFILE_VALUES[key][extkey]);
+ if (opts[extkey]) {
+ bytes_copy[i] ^= ZFILE_VALUES[key][extkey]
+ }
+ }
+ }
+
+ if (!opts[key] && bytes_copy[i]) {
+ opts[key] = "unknown:" + bytes_copy[i];
+ }
+ } );
+
+ return opts;
+ }
+}
+
+//----------------------------------------------------------------------
+
+//Empty headers - in addition to ZRQINIT
+class ZSKIP_HEADER extends Zmodem.Header {}
+//No need for ZNAK
+class ZABORT_HEADER extends Zmodem.Header {}
+class ZFIN_HEADER extends Zmodem.Header {}
+class ZFERR_HEADER extends Zmodem.Header {}
+
+ZFIN_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF;
+
+class ZOffsetHeader extends Zmodem.Header {
+ constructor(offset) {
+ super();
+ this._bytes4 = Zmodem.ENCODELIB.pack_u32_le(offset);
+ }
+
+ get_offset() {
+ return Zmodem.ENCODELIB.unpack_u32_le(this._bytes4);
+ }
+}
+
+class ZRPOS_HEADER extends ZOffsetHeader {};
+class ZDATA_HEADER extends ZOffsetHeader {};
+class ZEOF_HEADER extends ZOffsetHeader {};
+
+//As request, receiver creates.
+/* UNIMPLEMENTED FOR NOW
+class ZCRC_HEADER extends ZHeader {
+ constructor(crc_le_bytes) {
+ super();
+ if (crc_le_bytes) { //response, sender creates
+ this._bytes4 = crc_le_bytes;
+ }
+ }
+}
+*/
+
+//No ZCHALLENGE implementation
+
+//class ZCOMPL_HEADER extends ZHeader {}
+//class ZCAN_HEADER extends Zmodem.Header {}
+
+//As described, this header represents an information disclosure.
+//It could be interpreted, I suppose, merely as “this is how much space
+//I have FOR YOU.”
+//TODO: implement if needed/requested
+//class ZFREECNT_HEADER extends ZmodemHeader {}
+
+//----------------------------------------------------------------------
+
+const FRAME_CLASS_TYPES = [
+ [ ZRQINIT_HEADER, "ZRQINIT" ],
+ [ ZRINIT_HEADER, "ZRINIT" ],
+ [ ZSINIT_HEADER, "ZSINIT" ],
+ [ ZACK_HEADER, "ZACK" ],
+ [ ZFILE_HEADER, "ZFILE" ],
+ [ ZSKIP_HEADER, "ZSKIP" ],
+ undefined, // [ ZNAK_HEADER, "ZNAK" ],
+ [ ZABORT_HEADER, "ZABORT" ],
+ [ ZFIN_HEADER, "ZFIN" ],
+ [ ZRPOS_HEADER, "ZRPOS" ],
+ [ ZDATA_HEADER, "ZDATA" ],
+ [ ZEOF_HEADER, "ZEOF" ],
+ [ ZFERR_HEADER, "ZFERR" ], //see note
+ undefined, //[ ZCRC_HEADER, "ZCRC" ],
+ undefined, //[ ZCHALLENGE_HEADER, "ZCHALLENGE" ],
+ undefined, //[ ZCOMPL_HEADER, "ZCOMPL" ],
+ undefined, //[ ZCAN_HEADER, "ZCAN" ],
+ undefined, //[ ZFREECNT_HEADER, "ZFREECNT" ],
+ undefined, //[ ZCOMMAND_HEADER, "ZCOMMAND" ],
+ undefined, //[ ZSTDERR_HEADER, "ZSTDERR" ],
+];
+
+/*
+ZFERR is described as “error in reading or writing file”. It’s really
+not a good idea from a security angle for the endpoint to expose this
+information. We should parse this and handle it as ZABORT but never send it.
+
+Likewise with ZFREECNT: the sender shouldn’t ask how much space is left
+on the other box; rather, the receiver should decide what to do with the
+file size as the sender reports it.
+*/
+
+var FRAME_NAME_CREATOR = {};
+
+for (var fc=0; fc<FRAME_CLASS_TYPES.length; fc++) {
+ if (!FRAME_CLASS_TYPES[fc]) continue;
+
+ FRAME_NAME_CREATOR[ FRAME_CLASS_TYPES[fc][1] ] = FRAME_CLASS_TYPES[fc][0];
+
+ Object.assign(
+ FRAME_CLASS_TYPES[fc][0].prototype,
+ {
+ TYPENUM: fc,
+ NAME: FRAME_CLASS_TYPES[fc][1],
+ }
+ );
+}
+
+//----------------------------------------------------------------------
+
+const CREATORS = [
+ ZRQINIT_HEADER,
+ ZRINIT_HEADER,
+ ZSINIT_HEADER,
+ ZACK_HEADER,
+ ZFILE_HEADER,
+ ZSKIP_HEADER,
+ 'ZNAK',
+ ZABORT_HEADER,
+ ZFIN_HEADER,
+ ZRPOS_HEADER,
+ ZDATA_HEADER,
+ ZEOF_HEADER,
+ ZFERR_HEADER,
+ 'ZCRC', //ZCRC_HEADER, -- leaving unimplemented?
+ 'ZCHALLENGE',
+ 'ZCOMPL',
+ 'ZCAN',
+ 'ZFREECNT', // ZFREECNT_HEADER,
+ 'ZCOMMAND',
+ 'ZSTDERR',
+];
+
+function _get_blank_header(typenum) {
+ var creator = CREATORS[typenum];
+ if (typeof(creator) === "string") {
+ throw( "Received unsupported header: " + creator );
+ }
+
+ /*
+ if (creator === ZCRC_HEADER) {
+ return new creator([0, 0, 0, 0]);
+ }
+ */
+
+ return _get_blank_header_from_constructor(creator);
+}
+
+//referenced outside TODO
+function _get_blank_header_from_constructor(creator) {
+ if (creator.prototype instanceof ZOffsetHeader) {
+ return new creator(0);
+ }
+
+ return new creator([]);
+}
+
+function _parse_binary16(bytes_arr) {
+
+ //The max length of a ZDLE-encoded binary header w/ 16-bit CRC is:
+ // 3 initial bytes, NOT ZDLE-encoded
+ // 2 typenum bytes (1 decoded)
+ // 8 data bytes (4 decoded)
+ // 4 CRC bytes (2 decoded)
+
+ //A 16-bit payload has 7 ZDLE-encoded octets.
+ //The ZDLE-encoded octets follow the initial prefix.
+ var zdle_decoded = Zmodem.ZDLE.splice( bytes_arr, BINARY16_HEADER_PREFIX.length, 7 );
+
+ return zdle_decoded && _parse_non_zdle_binary16(zdle_decoded);
+}
+
+function _parse_non_zdle_binary16(decoded) {
+ Zmodem.CRC.verify16(
+ decoded.slice(0, 5),
+ decoded.slice(5)
+ );
+
+ var typenum = decoded[0];
+ var hdr = _get_blank_header(typenum);
+ hdr._bytes4 = decoded.slice( 1, 5 );
+
+ return hdr;
+}
+
+function _parse_binary32(bytes_arr) {
+
+ //Same deal as with 16-bit CRC except there are two more
+ //potentially ZDLE-encoded bytes, for a total of 9.
+ var zdle_decoded = Zmodem.ZDLE.splice(
+ bytes_arr, //omit the leading "*", ZDLE, and "C"
+ BINARY32_HEADER_PREFIX.length,
+ 9
+ );
+
+ if (!zdle_decoded) return;
+
+ Zmodem.CRC.verify32(
+ zdle_decoded.slice(0, 5),
+ zdle_decoded.slice(5)
+ );
+
+ var typenum = zdle_decoded[0];
+ var hdr = _get_blank_header(typenum);
+ hdr._bytes4 = zdle_decoded.slice( 1, 5 );
+
+ return hdr;
+}
+
+function _parse_hex(bytes_arr) {
+
+ //A hex header always has:
+ // 4 bytes for the ** . ZDLE . 'B'
+ // 2 hex bytes for the header type
+ // 8 hex bytes for the header content
+ // 4 hex bytes for the CRC
+ // 1-2 bytes for (CR/)LF
+ // (...and at this point the trailing XON is already stripped)
+ //
+ //----------------------------------------------------------------------
+ //A carriage return and line feed are sent with HEX headers. The
+ //receive routine expects to see at least one of these characters, two
+ //if the first is CR.
+ //----------------------------------------------------------------------
+ //
+ //^^ I guess it can be either CR/LF or just LF … though those two
+ //sentences appear to be saying contradictory things.
+
+ var lf_pos = bytes_arr.indexOf( 0x8a ); //lrzsz sends this
+
+ if (-1 === lf_pos) {
+ lf_pos = bytes_arr.indexOf( 0x0a );
+ }
+
+ var hdr_err, hex_bytes;
+
+ if (-1 === lf_pos) {
+ if (bytes_arr.length > 11) {
+ hdr_err = "Invalid hex header - no LF detected within 12 bytes!";
+ }
+
+ //incomplete header
+ return;
+ }
+ else {
+ hex_bytes = bytes_arr.splice( 0, lf_pos );
+
+ //Trim off the LF
+ bytes_arr.shift();
+
+ if ( hex_bytes.length === 19 ) {
+
+ //NB: The spec says CR but seems to treat high-bit variants
+ //of control characters the same as the regulars; should we
+ //also allow 0x8d?
+ var preceding = hex_bytes.pop();
+ if ( preceding !== 0x0d && preceding !== 0x8d ) {
+ hdr_err = "Invalid hex header: (CR/)LF doesn’t have CR!";
+ }
+ }
+ else if ( hex_bytes.length !== 18 ) {
+ hdr_err = "Invalid hex header: invalid number of bytes before LF!";
+ }
+ }
+
+ if (hdr_err) {
+ hdr_err += " (" + hex_bytes.length + " bytes: " + hex_bytes.join() + ")";
+ throw hdr_err;
+ }
+
+ hex_bytes.splice(0, 4);
+
+ //Should be 7 bytes ultimately:
+ // 1 for typenum
+ // 4 for header data
+ // 2 for CRC
+ var octets = Zmodem.ENCODELIB.parse_hex_octets(hex_bytes);
+
+ return _parse_non_zdle_binary16(octets);
+}
+
+Zmodem.Header.parse_hex = _parse_hex;