Adding upstream version 0.1.10+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
157f539082
commit
4d3e0bf859
42 changed files with 10556 additions and 0 deletions
119
tests/encode.js
Executable file
119
tests/encode.js
Executable file
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var enclib = Zmodem.ENCODELIB;
|
||||
|
||||
tape('round-trip: 32-bit little-endian', function(t) {
|
||||
var times = 1000;
|
||||
|
||||
t.doesNotThrow(
|
||||
() => {
|
||||
for (var a=0; a<times; a++) {
|
||||
var orig = Math.floor( 0xffffffff * Math.random() );
|
||||
|
||||
var enc = enclib.pack_u32_le(orig);
|
||||
var roundtrip = enclib.unpack_u32_le(enc);
|
||||
|
||||
if (roundtrip !== orig) {
|
||||
throw( `Orig: ${orig}, Packed: ` + JSON.stringify(enc) + `, Parsed: ${roundtrip}` );
|
||||
}
|
||||
}
|
||||
},
|
||||
`round-trip 32-bit little-endian: ${times} times`
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('unpack_u32_le', function(t) {
|
||||
t.equals(
|
||||
enclib.unpack_u32_le([222,233,202,254]),
|
||||
4274711006,
|
||||
'unpack 4-byte number'
|
||||
);
|
||||
|
||||
var highest = 0xffffffff;
|
||||
t.equals(
|
||||
enclib.unpack_u32_le([255,255,255,255]),
|
||||
highest,
|
||||
`highest number possible (${highest})`
|
||||
);
|
||||
|
||||
t.equals(
|
||||
enclib.unpack_u32_le([1, 0, 0, 0]),
|
||||
1,
|
||||
'1'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
tape('unpack_u16_be', function(t) {
|
||||
t.equals(
|
||||
enclib.unpack_u16_be([202,254]),
|
||||
51966,
|
||||
'unpack 2-byte number'
|
||||
);
|
||||
|
||||
var highest = 0xffff;
|
||||
t.equals(
|
||||
enclib.unpack_u16_be([255,255]),
|
||||
highest,
|
||||
`highest number possible (${highest})`
|
||||
);
|
||||
|
||||
t.equals(
|
||||
enclib.unpack_u16_be([0, 1]),
|
||||
1,
|
||||
'1'
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
tape('octets_to_hex', function(t) {
|
||||
t.deepEquals(
|
||||
enclib.octets_to_hex( [ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x0a ] ),
|
||||
'123456789abcdef00a'.split("").map( (c) => c.charCodeAt(0) ),
|
||||
'hex encoding'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('parse_hex_octets', function(t) {
|
||||
t.deepEquals(
|
||||
enclib.parse_hex_octets( [ 48, 49, 102, 101 ] ),
|
||||
[ 0x01, 0xfe ],
|
||||
'parse hex excoding',
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('round-trip: 16-bit big-endian', function(t) {
|
||||
var times = 10000;
|
||||
|
||||
t.doesNotThrow(
|
||||
() => {
|
||||
for (var a=0; a<times; a++) {
|
||||
var orig = Math.floor( 0x10000 * Math.random() );
|
||||
|
||||
var enc = enclib.pack_u16_be(orig);
|
||||
var roundtrip = enclib.unpack_u16_be(enc);
|
||||
|
||||
if (roundtrip !== orig) {
|
||||
throw( `Orig: ${orig}, Packed: ` + JSON.stringify(enc) + `, Parsed: ${roundtrip}` );
|
||||
}
|
||||
}
|
||||
},
|
||||
`round-trip 16-bit big-endian: ${times} times`
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
121
tests/lib/testhelp.js
Normal file
121
tests/lib/testhelp.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
var Zmodem = require('./zmodem');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Return an array with the given number of random octet values.
|
||||
*
|
||||
* @param {Array} count - The number of octet values to return.
|
||||
*
|
||||
* @returns {Array} The octet values.
|
||||
*/
|
||||
get_random_octets(count) {
|
||||
if (!(count > 0)) throw( "Must be positive, not " + count );
|
||||
|
||||
var octets = [];
|
||||
|
||||
//This assigns backwards both for convenience and so that
|
||||
//the initial assignment allocates the needed size.
|
||||
while (count) {
|
||||
octets[count - 1] = Math.floor( Math.random() * 256 );
|
||||
count--;
|
||||
}
|
||||
|
||||
return octets;
|
||||
},
|
||||
|
||||
//This is meant NOT to do UTF-8 stuff since it handles \xXX.
|
||||
string_to_octets(string) {
|
||||
return string.split("").map( (c) => c.charCodeAt(0) );
|
||||
},
|
||||
|
||||
make_temp_dir() {
|
||||
return require('tmp').dirSync().name;
|
||||
},
|
||||
|
||||
make_temp_file(size) {
|
||||
const fs = require('fs');
|
||||
const tmp = require('tmp');
|
||||
|
||||
var tmpobj = tmp.fileSync();
|
||||
var content = Array(size).fill("x").join("");
|
||||
fs.writeSync( tmpobj.fd, content );
|
||||
fs.writeSync( tmpobj.fd, "=THE_END" );
|
||||
fs.closeSync( tmpobj.fd );
|
||||
|
||||
return tmpobj.name;
|
||||
},
|
||||
|
||||
make_empty_temp_file() {
|
||||
const fs = require('fs');
|
||||
const tmp = require('tmp');
|
||||
|
||||
var tmpobj = tmp.fileSync();
|
||||
fs.closeSync( tmpobj.fd );
|
||||
|
||||
return tmpobj.name;
|
||||
},
|
||||
|
||||
exec_lrzsz_steps(t, binpath, z_args, steps) {
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
var child;
|
||||
|
||||
var zsession;
|
||||
var zsentry = new Zmodem.Sentry( {
|
||||
to_terminal: Object,
|
||||
on_detect: (d) => { zsession = d.confirm() },
|
||||
on_retract: console.error.bind(console),
|
||||
sender: (d) => {
|
||||
child.stdin.write( new Buffer(d) );
|
||||
},
|
||||
} );
|
||||
|
||||
var step = 0;
|
||||
var inputs = [];
|
||||
|
||||
child = spawn(binpath, z_args);
|
||||
console.log("child PID:", child.pid);
|
||||
|
||||
child.on("error", console.error.bind(console));
|
||||
|
||||
child.stdin.on("close", () => console.log(`# PID ${child.pid} STDIN closed`));
|
||||
child.stdout.on("close", () => console.log(`# PID ${child.pid} STDOUT closed`));
|
||||
child.stderr.on("close", () => console.log(`# PID ${child.pid} STDERR closed`));
|
||||
|
||||
//We can’t just pipe this on through because there can be lone CR
|
||||
//bytes which screw up TAP::Harness.
|
||||
child.stderr.on("data", (d) => {
|
||||
d = d.toString().replace(/\r\n?/g, "\n");
|
||||
if (d.substr(-1) !== "\n") d += "\n";
|
||||
process.stderr.write(`STDERR: ${d}`);
|
||||
});
|
||||
|
||||
child.stdout.on("data", (d) => {
|
||||
//console.log(`STDOUT from PID ${child.pid}`, d);
|
||||
inputs.push( Array.from(d) );
|
||||
|
||||
zsentry.consume( Array.from(d) );
|
||||
|
||||
if (zsession) {
|
||||
if ( steps[step] ) {
|
||||
if ( steps[step](zsession, child) ) {
|
||||
step++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`End of task list; closing PID ${child.pid}’s STDIN`);
|
||||
child.stdin.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var exit_promise = new Promise( (res, rej) => {
|
||||
child.on("exit", (code, signal) => {
|
||||
console.log(`# "${binpath}" exit: code ${code}, signal ${signal}`);
|
||||
res([code, signal]);
|
||||
} );
|
||||
} );
|
||||
|
||||
return exit_promise.then( () => { return inputs } );
|
||||
},
|
||||
};
|
1
tests/lib/zmodem.js
Normal file
1
tests/lib/zmodem.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('../../src/zmodem.js');
|
45
tests/text.js
Executable file
45
tests/text.js
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
var Zmodem = require('../src/zmodem');
|
||||
|
||||
var ZText = Zmodem.Text;
|
||||
|
||||
const TEXTS = [
|
||||
[ "-./", [45, 46, 47] ],
|
||||
[ "épée", [195, 169, 112, 195, 169, 101] ],
|
||||
[ "“words”", [226, 128, 156, 119, 111, 114, 100, 115, 226, 128, 157] ],
|
||||
[ "🍊", [240, 159, 141, 138] ],
|
||||
[ "🍊🍊", [240, 159, 141, 138, 240, 159, 141, 138] ],
|
||||
];
|
||||
|
||||
tape('decoder', function(t) {
|
||||
var decoder = new ZText.Decoder();
|
||||
|
||||
TEXTS.forEach( (tt) => {
|
||||
t.is(
|
||||
decoder.decode( new Uint8Array(tt[1]) ),
|
||||
tt[0],
|
||||
`decode: ${tt[1]} -> ${tt[0]}`
|
||||
);
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('encoder', function(t) {
|
||||
var encoder = new ZText.Encoder();
|
||||
|
||||
TEXTS.forEach( (tt) => {
|
||||
t.deepEquals(
|
||||
encoder.encode(tt[0]),
|
||||
new Uint8Array( tt[1] ),
|
||||
`encode: ${tt[0]} -> ${tt[1]}`
|
||||
);
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
113
tests/zcrc.js
Executable file
113
tests/zcrc.js
Executable file
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
var Zmodem = Object.assign(
|
||||
{},
|
||||
require('../src/zcrc')
|
||||
);
|
||||
|
||||
var zcrc = Zmodem.CRC;
|
||||
|
||||
tape('crc16', function(t) {
|
||||
t.deepEqual(
|
||||
zcrc.crc16( [ 0x0d, 0x0a ] ),
|
||||
[ 0xd7, 0x16 ],
|
||||
'crc16 - first test'
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
zcrc.crc16( [ 0x11, 0x17, 0, 0, 0 ] ),
|
||||
[ 0xe4, 0x81 ],
|
||||
'crc16 - second test'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('verify16', function(t) {
|
||||
t.doesNotThrow(
|
||||
() => zcrc.verify16( [ 0x0d, 0x0a ], [ 0xd7, 0x16 ] ),
|
||||
'verify16 - no throw on good'
|
||||
);
|
||||
|
||||
var err;
|
||||
try { zcrc.verify16( [ 0x0d, 0x0a ], [ 0xd7, 16 ] ) }
|
||||
catch(e) { err = e };
|
||||
|
||||
t.ok(
|
||||
/215,16.*215,22/.test(err.message),
|
||||
'verify16 - throw on bad (message)'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
err instanceof Zmodem.Error,
|
||||
'verify16 - typed error'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
err.type,
|
||||
'verify16 - error type'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// The crc32 logic is unused for now, but some misbehaving ZMODEM
|
||||
// implementation might send CRC32 regardless of that we don’t
|
||||
// advertise it.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
tape('crc32', function(t) {
|
||||
const tests = [
|
||||
[ [ 4, 0, 0, 0, 0 ], [ 0xdd, 0x51, 0xa2, 0x33 ] ],
|
||||
[ [ 11, 17, 0, 0, 0 ], [ 0xf6, 0xf6, 0x57, 0x59 ] ],
|
||||
[ [ 3, 0, 0, 0, 0 ], [ 205, 141, 130, 129 ] ],
|
||||
];
|
||||
// } [ 3, 0, 0, 0, 0 ] [ 205, 141, 131, -127 ]
|
||||
//2172816845
|
||||
//crc32 [ 3, 0, 0, 0, 0 ] -2122150451
|
||||
|
||||
tests.forEach( (cur_t) => {
|
||||
let [ input, output ] = cur_t;
|
||||
|
||||
t.deepEqual(
|
||||
zcrc.crc32(input),
|
||||
output,
|
||||
"crc32: " + input.join(", ")
|
||||
);
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('verify32', function(t) {
|
||||
t.doesNotThrow(
|
||||
() => zcrc.verify32( [ 4, 0, 0, 0, 0 ], [ 0xdd, 0x51, 0xa2, 0x33 ] ),
|
||||
'verify32 - no throw on good'
|
||||
);
|
||||
|
||||
var err;
|
||||
try { zcrc.verify32( [ 4, 0, 0, 0, 0 ], [ 1,2,3,4 ] ) }
|
||||
catch(e) { err = e };
|
||||
|
||||
t.ok(
|
||||
/1,2,3,4.*221,81,162,51/.test(err.message),
|
||||
'verify32 - throw on bad (message)'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
err instanceof Zmodem.Error,
|
||||
'verify32 - typed error'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
err.type,
|
||||
'verify32 - error type'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
41
tests/zdle.js
Executable file
41
tests/zdle.js
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
const helper = require('./lib/testhelp');
|
||||
|
||||
var zmlib = Zmodem.ZMLIB;
|
||||
var ZDLE = Zmodem.ZDLE;
|
||||
|
||||
tape('round-trip', function(t) {
|
||||
var zdle = new ZDLE( { escape_ctrl_chars: true } );
|
||||
|
||||
var times = 1000;
|
||||
|
||||
t.doesNotThrow(
|
||||
() => {
|
||||
for (let a of Array(times)) {
|
||||
var orig = helper.get_random_octets(38);
|
||||
var enc = zdle.encode( orig.slice(0) );
|
||||
var dec = ZDLE.decode( enc.slice(0) );
|
||||
|
||||
var orig_j = orig.join();
|
||||
var dec_j = dec.join();
|
||||
|
||||
if (orig_j !== dec_j) {
|
||||
console.error("Original", orig.join());
|
||||
console.error("Encoded", enc.join());
|
||||
console.error("Decoded", dec.join());
|
||||
|
||||
throw 'mismatch';
|
||||
}
|
||||
}
|
||||
},
|
||||
`round-trip`
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
82
tests/zerror.js
Normal file
82
tests/zerror.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
const tape = require('blue-tape'),
|
||||
TYPE_CHECKS = {
|
||||
aborted: [ [] ],
|
||||
peer_aborted: [],
|
||||
already_aborted: [],
|
||||
crc: [
|
||||
[ [ 1, 2 ], [ 3, 4 ] ],
|
||||
(t, err) => {
|
||||
t.ok(
|
||||
/1,2/.test(err.message),
|
||||
'"got" values are in the message'
|
||||
);
|
||||
t.ok(
|
||||
/3,4/.test(err.message),
|
||||
'"expected" values are in the message'
|
||||
);
|
||||
t.ok(
|
||||
/CRC/i.test(err.message),
|
||||
'"CRC" is in the message'
|
||||
);
|
||||
},
|
||||
],
|
||||
validation: [
|
||||
[ "some string" ],
|
||||
(t, err) => {
|
||||
t.is(
|
||||
err.message,
|
||||
"some string",
|
||||
'message is given value'
|
||||
);
|
||||
},
|
||||
],
|
||||
}
|
||||
;
|
||||
|
||||
tape("typed", (t) => {
|
||||
let Ctr = Zmodem.Error;
|
||||
|
||||
for (let type in TYPE_CHECKS) {
|
||||
let args = [type].concat( TYPE_CHECKS[type][0] );
|
||||
|
||||
//https://stackoverflow.com/questions/33193310/constr-applythis-args-in-es6-classes
|
||||
var err = new (Ctr.bind.apply(Ctr, [null].concat(args)));
|
||||
|
||||
t.ok(
|
||||
(err instanceof Zmodem.Error),
|
||||
`${type} type isa ZmodemError`
|
||||
);
|
||||
t.ok(
|
||||
!!err.message.length,
|
||||
`${type}: message has length`
|
||||
);
|
||||
|
||||
if ( TYPE_CHECKS[type][1] ) {
|
||||
TYPE_CHECKS[type][1](t, err);
|
||||
}
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
tape("generic", (t) => {
|
||||
let err = new Zmodem.Error("Van Gogh was a guy.");
|
||||
|
||||
t.ok(
|
||||
(err instanceof Zmodem.Error),
|
||||
`generic isa ZmodemError`
|
||||
);
|
||||
t.is(
|
||||
err.message,
|
||||
"Van Gogh was a guy.",
|
||||
"passthrough of string"
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
309
tests/zheader.js
Executable file
309
tests/zheader.js
Executable file
|
@ -0,0 +1,309 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
var testhelp = require('./lib/testhelp');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var zdle = new Zmodem.ZDLE( { escape_ctrl_chars: true } );
|
||||
|
||||
tape('trim_leading_garbage', function(t) {
|
||||
var header = Zmodem.Header.build('ZACK');
|
||||
|
||||
var header_octets = new Map( [
|
||||
[ "hex", header.to_hex(), ],
|
||||
[ "b16", header.to_binary16(zdle), ],
|
||||
[ "b32", header.to_binary32(zdle), ],
|
||||
] );
|
||||
|
||||
var leading_garbage = [
|
||||
"",
|
||||
" ",
|
||||
"\n\n",
|
||||
"\r\n\r\n",
|
||||
"*",
|
||||
"**",
|
||||
"*\x18",
|
||||
"*\x18D",
|
||||
"**\x18",
|
||||
];
|
||||
|
||||
leading_garbage.forEach( (garbage) => {
|
||||
let garbage_json = JSON.stringify(garbage);
|
||||
let garbage_octets = testhelp.string_to_octets( garbage );
|
||||
|
||||
for ( let [label, hdr_octets] of header_octets ) {
|
||||
var input = garbage_octets.slice(0).concat( hdr_octets );
|
||||
var trimmed = Zmodem.Header.trim_leading_garbage(input);
|
||||
|
||||
t.deepEquals(trimmed, garbage_octets, `${garbage_json} + ${label}: garbage trimmed`);
|
||||
t.deepEquals(input, hdr_octets, `… leaving the header`);
|
||||
}
|
||||
} );
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
//input, number of bytes trimmed
|
||||
var partial_trims = [
|
||||
[ "*", 0 ],
|
||||
[ "**", 0 ],
|
||||
[ "***", 1 ],
|
||||
[ "*\x18**", 2 ],
|
||||
[ "*\x18*\x18", 2 ],
|
||||
[ "*\x18*\x18**", 4 ],
|
||||
[ "*\x18*\x18*\x18", 4 ],
|
||||
];
|
||||
|
||||
partial_trims.forEach( (cur) => {
|
||||
let [ input, trimmed_count ] = cur;
|
||||
|
||||
let input_json = JSON.stringify(input);
|
||||
|
||||
let input_octets = testhelp.string_to_octets(input);
|
||||
|
||||
let garbage = Zmodem.Header.trim_leading_garbage(input_octets.slice(0));
|
||||
|
||||
t.deepEquals(
|
||||
garbage,
|
||||
input_octets.slice(0, trimmed_count),
|
||||
`${input_json}: trim first ${trimmed_count} byte(s)`
|
||||
);
|
||||
} );
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
//Test that we parse a trailing 0x8a, since we ourselves follow the
|
||||
//documentation and put a plain LF (0x0a).
|
||||
tape('parse_hex', function(t) {
|
||||
var octets = testhelp.string_to_octets( "**\x18B0901020304a57f\x0d\x8a" );
|
||||
|
||||
var parsed = Zmodem.Header.parse( octets );
|
||||
|
||||
t.is( parsed[1], 16, 'CRC size' );
|
||||
|
||||
t.is(
|
||||
parsed[0].NAME,
|
||||
'ZRPOS',
|
||||
'parsed NAME'
|
||||
);
|
||||
|
||||
t.is(
|
||||
parsed[0].TYPENUM,
|
||||
9,
|
||||
'parsed TYPENUM'
|
||||
);
|
||||
|
||||
t.is(
|
||||
parsed[0].get_offset(),
|
||||
0x04030201, //it’s little-endian
|
||||
'parsed offset'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('round-trip, empty headers', function(t) {
|
||||
["ZRQINIT", "ZSKIP", "ZABORT", "ZFIN", "ZFERR"].forEach( (n) => {
|
||||
var orig = Zmodem.Header.build(n);
|
||||
|
||||
var hex = orig.to_hex();
|
||||
var b16 = orig.to_binary16(zdle);
|
||||
var b32 = orig.to_binary32(zdle);
|
||||
|
||||
var rounds = new Map( [
|
||||
[ "to_hex", hex ],
|
||||
[ "to_binary16", b16 ],
|
||||
[ "to_binary32", b32 ],
|
||||
] );
|
||||
|
||||
for ( const [ enc, h ] of rounds ) {
|
||||
let [ parsed, crclen ] = Zmodem.Header.parse(h);
|
||||
|
||||
t.is( parsed.NAME, orig.NAME, `${n}, ${enc}: NAME` );
|
||||
t.is( parsed.TYPENUM, orig.TYPENUM, `${n}, ${enc}: TYPENUM` );
|
||||
|
||||
//Here’s where we test the CRC length in the response.
|
||||
t.is(
|
||||
crclen,
|
||||
/32/.test(enc) ? 32 : 16,
|
||||
`${n}, ${enc}: CRC length`,
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('round-trip, offset headers', function(t) {
|
||||
["ZRPOS", "ZDATA", "ZEOF"].forEach( (n) => {
|
||||
var orig = Zmodem.Header.build(n, 12345);
|
||||
|
||||
var hex = orig.to_hex();
|
||||
var b16 = orig.to_binary16(zdle);
|
||||
var b32 = orig.to_binary32(zdle);
|
||||
|
||||
var rounds = new Map( [
|
||||
[ "to_hex", hex ],
|
||||
[ "to_binary16", b16 ],
|
||||
[ "to_binary32", b32 ],
|
||||
] );
|
||||
|
||||
for ( const [ enc, h ] of rounds ) {
|
||||
//Here’s where we test that parse() leaves in trailing bytes.
|
||||
let extra = [99, 99, 99];
|
||||
let bytes_with_extra = h.slice().concat(extra);
|
||||
|
||||
let parsed = Zmodem.Header.parse(bytes_with_extra)[0];
|
||||
|
||||
t.is( parsed.NAME, orig.NAME, `${n}, ${enc}: NAME` );
|
||||
t.is( parsed.TYPENUM, orig.TYPENUM, `${n}, ${enc}: TYPENUM` );
|
||||
t.is( parsed.get_offset(), orig.get_offset(), `${n}, ${enc}: get_offset()` );
|
||||
|
||||
let expected = extra.slice(0);
|
||||
if (enc === "to_hex") {
|
||||
expected.splice( 0, 0, Zmodem.ZMLIB.XON );
|
||||
}
|
||||
|
||||
t.deepEquals(
|
||||
bytes_with_extra,
|
||||
expected,
|
||||
`${enc}: parse() leaves in trailing bytes`,
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('round-trip, ZSINIT', function(t) {
|
||||
var opts = [
|
||||
[],
|
||||
["ESCCTL"],
|
||||
];
|
||||
|
||||
opts.forEach( (args) => {
|
||||
var orig = Zmodem.Header.build("ZSINIT", args);
|
||||
|
||||
var hex = orig.to_hex();
|
||||
var b16 = orig.to_binary16(zdle);
|
||||
var b32 = orig.to_binary32(zdle);
|
||||
|
||||
var rounds = new Map( [
|
||||
[ "to_hex", hex ],
|
||||
[ "to_binary16", b16 ],
|
||||
[ "to_binary32", b32 ],
|
||||
] );
|
||||
|
||||
var args_str = JSON.stringify(args);
|
||||
|
||||
for ( const [ enc, h ] of rounds ) {
|
||||
let parsed = Zmodem.Header.parse(h)[0];
|
||||
|
||||
t.is( parsed.NAME, orig.NAME, `opts ${args_str}: ${enc}: NAME` );
|
||||
t.is( parsed.TYPENUM, orig.TYPENUM, `opts ${args_str}: ${enc}: TYPENUM` );
|
||||
|
||||
t.is( parsed.escape_ctrl_chars(), orig.escape_ctrl_chars(), `opts ${args_str}: ${enc}: escape_ctrl_chars()` );
|
||||
t.is( parsed.escape_8th_bit(), orig.escape_8th_bit(), `opts ${args_str}: ${enc}: escape_8th_bit()` );
|
||||
}
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('round-trip, ZRINIT', function(t) {
|
||||
var opts = [];
|
||||
|
||||
[ [], ["CANFDX"] ].forEach( (canfdx) => {
|
||||
[ [], ["CANOVIO"] ].forEach( (canovio) => {
|
||||
[ [], ["CANBRK"] ].forEach( (canbrk) => {
|
||||
[ [], ["CANFC32"] ].forEach( (canfc32) => {
|
||||
[ [], ["ESCCTL"] ].forEach( (escctl) => {
|
||||
opts.push( [
|
||||
...canfdx,
|
||||
...canovio,
|
||||
...canbrk,
|
||||
...canfc32,
|
||||
...escctl,
|
||||
] );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
opts.forEach( (args) => {
|
||||
var orig = Zmodem.Header.build("ZRINIT", args);
|
||||
|
||||
var hex = orig.to_hex();
|
||||
var b16 = orig.to_binary16(zdle);
|
||||
var b32 = orig.to_binary32(zdle);
|
||||
|
||||
var rounds = new Map( [
|
||||
[ "to_hex", hex ],
|
||||
[ "to_binary16", b16 ],
|
||||
[ "to_binary32", b32 ],
|
||||
] );
|
||||
|
||||
var args_str = JSON.stringify(args);
|
||||
|
||||
for ( const [ enc, h ] of rounds ) {
|
||||
let parsed = Zmodem.Header.parse(h)[0];
|
||||
|
||||
t.is( parsed.NAME, orig.NAME, `opts ${args_str}: ${enc}: NAME` );
|
||||
t.is( parsed.TYPENUM, orig.TYPENUM, `opts ${args_str}: ${enc}: TYPENUM` );
|
||||
|
||||
t.is( parsed.can_full_duplex(), orig.can_full_duplex(), `opts ${args_str}: ${enc}: can_full_duplex()` );
|
||||
t.is( parsed.can_overlap_io(), orig.can_overlap_io(), `opts ${args_str}: ${enc}: can_overlap_io()` );
|
||||
t.is( parsed.can_break(), orig.can_break(), `opts ${args_str}: ${enc}: can_break()` );
|
||||
t.is( parsed.can_fcs_32(), orig.can_fcs_32(), `opts ${args_str}: ${enc}: can_fcs_32()` );
|
||||
t.is( parsed.escape_ctrl_chars(), orig.escape_ctrl_chars(), `opts ${args_str}: ${enc}: escape_ctrl_chars()` );
|
||||
t.is( parsed.escape_8th_bit(), orig.escape_8th_bit(), `opts ${args_str}: ${enc}: escape_8th_bit()` );
|
||||
}
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('hex_final_XON', function(t) {
|
||||
var hex_ZFIN = Zmodem.Header.build("ZFIN").to_hex();
|
||||
|
||||
t.notEquals(
|
||||
hex_ZFIN.slice(-1)[0],
|
||||
Zmodem.ZMLIB.XON,
|
||||
'ZFIN hex does NOT end with XON',
|
||||
);
|
||||
|
||||
var hex_ZACK = Zmodem.Header.build("ZACK").to_hex();
|
||||
|
||||
t.notEquals(
|
||||
hex_ZACK.slice(-1)[0],
|
||||
Zmodem.ZMLIB.XON,
|
||||
'ZACK hex does NOT end with XON',
|
||||
);
|
||||
|
||||
var headers = [
|
||||
"ZRQINIT",
|
||||
Zmodem.Header.build("ZRINIT", []),
|
||||
Zmodem.Header.build("ZSINIT", []),
|
||||
"ZRPOS",
|
||||
"ZABORT",
|
||||
"ZFERR",
|
||||
];
|
||||
|
||||
//These are the only headers we expect to send as hex … right?
|
||||
headers.forEach( hdr => {
|
||||
if (typeof hdr === "string") hdr = Zmodem.Header.build(hdr);
|
||||
|
||||
t.is(
|
||||
hdr.to_hex().slice(-1)[0],
|
||||
Zmodem.ZMLIB.XON,
|
||||
`${hdr.NAME} hex ends with XON`
|
||||
);
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
81
tests/zmlib.js
Executable file
81
tests/zmlib.js
Executable file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var zmlib = Zmodem.ZMLIB;
|
||||
|
||||
tape('constants', function(t) {
|
||||
t.equal(typeof zmlib.ZDLE, "number", 'ZDLE');
|
||||
t.equal(typeof zmlib.XON, "number", 'XON');
|
||||
t.equal(typeof zmlib.XOFF, "number", 'XOFF');
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('strip_ignored_bytes', function(t) {
|
||||
var input = [ zmlib.XOFF, 12, 45, 76, zmlib.XON, 22, zmlib.XOFF, 32, zmlib.XON | 0x80, 0, zmlib.XOFF | 0x80, 255, zmlib.XON ];
|
||||
var should_be = [ 12, 45, 76, 22, 32, 0, 255 ];
|
||||
|
||||
var input_copy = input.slice(0);
|
||||
|
||||
var out = zmlib.strip_ignored_bytes(input_copy);
|
||||
|
||||
t.deepEqual( out, should_be, 'intended bytes are stripped' );
|
||||
t.equal( out, input_copy, 'output is the mutated input' );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
/*
|
||||
tape('get_random_octets', function(t) {
|
||||
t.equal(
|
||||
zmlib.get_random_octets(42).length,
|
||||
42,
|
||||
'length is correct'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
typeof zmlib.get_random_octets(42)[0],
|
||||
"number",
|
||||
'type is correct'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
zmlib.get_random_octets(999999).every( (i) => i>=0 && i<=255 ),
|
||||
'values are all octet values'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
*/
|
||||
|
||||
tape('find_subarray', function(t) {
|
||||
t.equal(
|
||||
zmlib.find_subarray([12, 56, 43, 77], [43, 77]),
|
||||
2,
|
||||
'finds at end'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
zmlib.find_subarray([12, 56, 43, 77], [12, 56]),
|
||||
0,
|
||||
'finds at begin'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
zmlib.find_subarray([12, 56, 43, 77], [56, 43]),
|
||||
1,
|
||||
'finds in the middle'
|
||||
);
|
||||
|
||||
t.equal(
|
||||
zmlib.find_subarray([12, 56, 43, 77], [56, 43, 43]),
|
||||
-1,
|
||||
'non-find'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
226
tests/zsentry.js
Executable file
226
tests/zsentry.js
Executable file
|
@ -0,0 +1,226 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
var tape = require('blue-tape');
|
||||
|
||||
var helper = require('./lib/testhelp');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var ZSentry = Zmodem.Sentry;
|
||||
|
||||
function _generate_tester() {
|
||||
var tester = {
|
||||
reset() {
|
||||
this.to_terminal = [];
|
||||
this.to_server = [];
|
||||
this.retracted = 0;
|
||||
}
|
||||
};
|
||||
|
||||
tester.sentry = new ZSentry( {
|
||||
to_terminal(octets) { tester.to_terminal.push.apply( tester.to_terminal, octets ) },
|
||||
on_detect(z) { tester.detected = z; },
|
||||
on_retract(z) { tester.retracted++; },
|
||||
sender(octets) { tester.to_server.push.apply( tester.to_server, octets ) },
|
||||
} );
|
||||
|
||||
tester.reset();
|
||||
|
||||
return tester;
|
||||
}
|
||||
|
||||
tape('user says deny() to detection', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var makes_offer = helper.string_to_octets("hey**\x18B00000000000000\x0d\x0a\x11");
|
||||
tester.sentry.consume(makes_offer);
|
||||
|
||||
t.is( typeof tester.detected, "object", 'There is a session after ZRQINIT' );
|
||||
|
||||
var sent_before = tester.to_server.length;
|
||||
|
||||
tester.detected.deny();
|
||||
|
||||
t.deepEqual(
|
||||
tester.to_server.slice(-Zmodem.ZMLIB.ABORT_SEQUENCE.length),
|
||||
Zmodem.ZMLIB.ABORT_SEQUENCE,
|
||||
'deny() sends abort sequence to server',
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('retraction because of non-ZMODEM', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var makes_offer = helper.string_to_octets("hey**\x18B00000000000000\x0d\x0a\x11");
|
||||
tester.sentry.consume(makes_offer);
|
||||
|
||||
t.is( typeof tester.detected, "object", 'There is a session after ZRQINIT' );
|
||||
|
||||
tester.sentry.consume([ 0x20, 0x21, 0x22 ]);
|
||||
|
||||
t.is( tester.retracted, 1, 'retraction since we got non-ZMODEM input' );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('retraction because of YMODEM downgrade', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var makes_offer = helper.string_to_octets("**\x18B00000000000000\x0d\x0a\x11");
|
||||
tester.sentry.consume(makes_offer);
|
||||
|
||||
t.deepEquals( tester.to_server, [], 'nothing sent to server before' );
|
||||
|
||||
tester.sentry.consume( helper.string_to_octets("C") );
|
||||
|
||||
t.deepEquals( tester.to_server, Zmodem.ZMLIB.ABORT_SEQUENCE, 'abort sent to server' );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('replacement ZMODEM is not of same type', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var zrqinit = helper.string_to_octets("**\x18B00000000000000\x0d\x0a\x11");
|
||||
tester.sentry.consume(zrqinit);
|
||||
|
||||
var before = tester.to_terminal.length;
|
||||
|
||||
var zrinit = helper.string_to_octets("**\x18B0100000000aa51\x0d\x0a\x11");
|
||||
tester.sentry.consume(zrinit);
|
||||
|
||||
t.notEqual(
|
||||
tester.to_terminal.length,
|
||||
before,
|
||||
'output to terminal when replacement session is of different type'
|
||||
);
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('retraction because of duplicate ZMODEM, and confirm()', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var makes_offer = helper.string_to_octets("**\x18B00000000000000\x0d\x0a\x11");
|
||||
tester.sentry.consume(makes_offer);
|
||||
|
||||
t.is( typeof tester.detected, "object", 'There is a detection after ZRQINIT' );
|
||||
|
||||
var first_detected = tester.detected;
|
||||
t.is( first_detected.is_valid(), true, 'detection is valid' );
|
||||
|
||||
tester.reset();
|
||||
|
||||
tester.sentry.consume(makes_offer);
|
||||
|
||||
t.is( tester.retracted, 1, 'retraction since we got non-ZMODEM input' );
|
||||
t.deepEquals( tester.to_terminal, [], 'nothing sent to terminal on dupe session' );
|
||||
|
||||
t.notEqual(
|
||||
tester.detected,
|
||||
first_detected,
|
||||
'… but a new detection happened in its place',
|
||||
);
|
||||
|
||||
t.is( first_detected.is_valid(), false, 'old detection is invalid' );
|
||||
t.is( tester.detected.is_valid(), true, 'new detection is valid' );
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
var session = tester.detected.confirm();
|
||||
|
||||
t.is( (session instanceof Zmodem.Session), true, 'confirm() on the detection' );
|
||||
t.is( session.type, "receive", 'session is of the right type' );
|
||||
|
||||
tester.reset();
|
||||
|
||||
//Verify that the Detection configures the Session correctly.
|
||||
session.start();
|
||||
t.is( !!tester.to_server.length, true, 'sent output after start()' );
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('parse passthrough', (t) => {
|
||||
var tester = _generate_tester();
|
||||
|
||||
var strings = new Map( [
|
||||
[ "plain", "heyhey", ],
|
||||
[ "one_asterisk", "hey*hey", ],
|
||||
[ "two_asterisks", "hey**hey", ],
|
||||
[ "wrong_header", "hey**\x18B09010203040506\x0d\x0a", ],
|
||||
[ "ZRQINIT but not at end", "hey**\x18B00000000000000\x0d\x0ahahahaha", ],
|
||||
[ "ZRINIT but not at end", "hey**\x18B01010203040506\x0d\x0ahahahaha", ],
|
||||
|
||||
//Use \x2a here to avoid tripping up ZMODEM-detection in
|
||||
//text editors when working on this code.
|
||||
[ "no_ZDLE", "hey\x2a*B00000000000000\x0d\x0a", ],
|
||||
] );
|
||||
|
||||
for (let [name, string] of strings) {
|
||||
tester.reset();
|
||||
|
||||
var octets = helper.string_to_octets(string);
|
||||
|
||||
var before = octets.slice(0);
|
||||
|
||||
tester.sentry.consume(octets);
|
||||
|
||||
t.deepEquals(
|
||||
tester.to_terminal,
|
||||
before,
|
||||
`regular text goes through: ${name}`
|
||||
);
|
||||
|
||||
t.is( tester.detected, undefined, '... and there is no session' );
|
||||
t.deepEquals( octets, before, '... and the array is unchanged' );
|
||||
}
|
||||
|
||||
t.end();
|
||||
} );
|
||||
|
||||
tape('parse', (t) => {
|
||||
var hdrs = new Map( [
|
||||
[ "receive", Zmodem.Header.build("ZRQINIT"), ],
|
||||
[ "send", Zmodem.Header.build("ZRINIT", ["CANFDX", "CANOVIO", "ESCCTL"]), ],
|
||||
] );
|
||||
|
||||
for ( let [sesstype, hdr] of hdrs ) {
|
||||
var full_input = helper.string_to_octets("before").concat(
|
||||
hdr.to_hex()
|
||||
);
|
||||
|
||||
for (var start=1; start<full_input.length - 1; start++) {
|
||||
let octets1 = full_input.slice(0, start);
|
||||
let octets2 = full_input.slice(start);
|
||||
|
||||
var tester = _generate_tester();
|
||||
tester.sentry.consume(octets1);
|
||||
|
||||
t.deepEquals(
|
||||
tester.to_terminal,
|
||||
octets1,
|
||||
`${sesstype}: Parse first ${start} byte(s) of text (${full_input.length} total)`
|
||||
);
|
||||
t.is( tester.detected, undefined, '... and there is no session' );
|
||||
|
||||
tester.reset();
|
||||
|
||||
tester.sentry.consume(octets2);
|
||||
t.deepEquals(
|
||||
tester.to_terminal,
|
||||
octets2,
|
||||
`Rest of text goes through`
|
||||
);
|
||||
t.is( typeof tester.detected, "object", '... and now there is a session' );
|
||||
t.is( tester.detected.get_session_role(), sesstype, '... of the right type' );
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
t.end();
|
||||
} );
|
312
tests/zsession.js
Executable file
312
tests/zsession.js
Executable file
|
@ -0,0 +1,312 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const test = require('tape');
|
||||
|
||||
const helper = require('./lib/testhelp');
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var ZSession = Zmodem.Session;
|
||||
|
||||
var receiver, sender, sender_promise, received_file;
|
||||
|
||||
var offer;
|
||||
|
||||
function wait(seconds) {
|
||||
return new Promise( resolve => setTimeout(_ => resolve("theValue"), 1000 * seconds) );
|
||||
}
|
||||
|
||||
function _init(async) {
|
||||
sender = null;
|
||||
receiver = new Zmodem.Session.Receive();
|
||||
|
||||
/*
|
||||
receiver.on("receive", function(hdr) {
|
||||
console.log("Receiver input", hdr);
|
||||
} );
|
||||
receiver.on("offer", function(my_offer) {
|
||||
//console.log("RECEIVED OFFER (window.offer)", my_offer);
|
||||
offer = my_offer;
|
||||
});
|
||||
*/
|
||||
|
||||
var resolver;
|
||||
sender_promise = new Promise( (res, rej) => { resolver = res; } );
|
||||
|
||||
function receiver_sender(bytes_arr) {
|
||||
//console.log("receiver sending", String.fromCharCode.apply(String, bytes_arr), bytes_arr);
|
||||
|
||||
if (sender) {
|
||||
var consumer = () => {
|
||||
sender.consume(bytes_arr);
|
||||
};
|
||||
|
||||
if (async) {
|
||||
wait(0.5).then(consumer);
|
||||
}
|
||||
else consumer();
|
||||
}
|
||||
else {
|
||||
var hdr = Zmodem.Header.parse(bytes_arr)[0];
|
||||
sender = new Zmodem.Session.Send(hdr);
|
||||
resolver(sender);
|
||||
|
||||
sender.set_sender( function(bytes_arr) {
|
||||
var consumer = () => {
|
||||
receiver.consume(bytes_arr);
|
||||
};
|
||||
|
||||
if (async) {
|
||||
wait(0.5).then(consumer);
|
||||
}
|
||||
else consumer();
|
||||
} );
|
||||
|
||||
/*
|
||||
sender.on("receive", function(hdr) {
|
||||
console.log("Sender input", hdr);
|
||||
} );
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
receiver.set_sender(receiver_sender);
|
||||
}
|
||||
|
||||
test('Sender receives extra ZRPOS', (t) => {
|
||||
_init();
|
||||
|
||||
var zrinit = Zmodem.Header.build("ZRINIT", ["CANFDX", "CANOVIO", "ESCCTL"]);
|
||||
var mysender = new Zmodem.Session.Send(zrinit);
|
||||
|
||||
var zrpos = Zmodem.Header.build("ZRPOS", 12345);
|
||||
|
||||
var err;
|
||||
|
||||
try {
|
||||
mysender.consume(zrpos.to_hex());
|
||||
}
|
||||
catch(e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
t.match(err.toString(), /header/, "error as expected");
|
||||
t.match(err.toString(), /ZRPOS/, "error as expected");
|
||||
|
||||
return Promise.resolve();
|
||||
} );
|
||||
|
||||
test('Offer events', (t) => {
|
||||
_init();
|
||||
|
||||
var inputs = [];
|
||||
var completed = false;
|
||||
|
||||
var r_pms = receiver.start().then( (offer) => {
|
||||
t.deepEquals(
|
||||
offer.get_details(),
|
||||
{
|
||||
name: "my file",
|
||||
size: 32,
|
||||
mode: null,
|
||||
mtime: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
'get_details() returns expected values'
|
||||
);
|
||||
|
||||
offer.on("input", (payload) => {
|
||||
inputs.push(
|
||||
{
|
||||
offset: offer.get_offset(),
|
||||
payload: payload,
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
offer.on("complete", () => { completed = true });
|
||||
|
||||
return offer.accept();
|
||||
} );
|
||||
|
||||
var s_pms = sender.send_offer(
|
||||
{ name: "my file", size: 32 }
|
||||
).then( (sender_xfer) => {
|
||||
sender_xfer.send( [1, 2, 3] );
|
||||
sender_xfer.send( [4, 5, 6, 7] );
|
||||
sender_xfer.end( [8, 9] ).then( () => {
|
||||
return sender.close();
|
||||
} );
|
||||
} );
|
||||
|
||||
return Promise.all( [ r_pms, s_pms ] ).then( () => {
|
||||
t.deepEquals(
|
||||
inputs,
|
||||
[
|
||||
{
|
||||
payload: [1, 2, 3],
|
||||
offset: 3,
|
||||
},
|
||||
{
|
||||
payload: [4, 5, 6, 7],
|
||||
offset: 7,
|
||||
},
|
||||
{
|
||||
payload: [8, 9],
|
||||
offset: 9,
|
||||
},
|
||||
],
|
||||
'Offer “input” events',
|
||||
);
|
||||
|
||||
t.ok( completed, 'Offer “complete” event' );
|
||||
} );
|
||||
} );
|
||||
|
||||
test('receive one, promises', (t) => {
|
||||
_init();
|
||||
|
||||
var r_pms = receiver.start().then( (offer) => {
|
||||
t.deepEquals(
|
||||
offer.get_details(),
|
||||
{
|
||||
name: "my file",
|
||||
size: 32,
|
||||
mode: null,
|
||||
mtime: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
'get_details() returns expected values'
|
||||
);
|
||||
|
||||
return offer.accept();
|
||||
} );
|
||||
|
||||
//r_pms.then( () => { console.log("RECEIVER DONE") } );
|
||||
|
||||
var s_pms = sender.send_offer(
|
||||
{ name: "my file", size: 32 }
|
||||
).then( (sender_xfer) => {
|
||||
sender_xfer.end( [12, 23, 34] ).then( () => {
|
||||
return sender.close();
|
||||
} );
|
||||
} );
|
||||
|
||||
return Promise.all( [ r_pms, s_pms ] );
|
||||
} );
|
||||
|
||||
test('receive one, events', (t) => {
|
||||
_init();
|
||||
|
||||
var content = [ 1,2,3,4,5,6,7,8,9,2,3,5,1,5,33,2,23,7 ];
|
||||
|
||||
var now_epoch = Math.floor(Date.now() / 1000);
|
||||
|
||||
receiver.on("offer", (offer) => {
|
||||
t.deepEquals(
|
||||
offer.get_details(),
|
||||
{
|
||||
name: "my file",
|
||||
size: content.length,
|
||||
mode: parseInt("100644", 8),
|
||||
mtime: new Date( now_epoch * 1000 ),
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
'get_details() returns expected values'
|
||||
);
|
||||
|
||||
offer.accept();
|
||||
} );
|
||||
receiver.start();
|
||||
|
||||
return sender.send_offer( {
|
||||
name: "my file",
|
||||
size: content.length,
|
||||
mtime: now_epoch,
|
||||
mode: parseInt("0644", 8),
|
||||
} ).then(
|
||||
(sender_xfer) => {
|
||||
sender_xfer.end(content).then( sender.close.bind(sender) );
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
test('skip one, receive the next', (t) => {
|
||||
_init();
|
||||
|
||||
var r_pms = receiver.start().then( (offer) => {
|
||||
//console.log("first offer", offer);
|
||||
|
||||
t.equals( offer.get_details().name, "my file", "first file’s name" );
|
||||
var next_pms = offer.skip();
|
||||
//console.log("next", next_pms);
|
||||
return next_pms;
|
||||
} ).then( (offer) => {
|
||||
t.equals( offer.get_details().name, "file 2", "second file’s name" );
|
||||
return offer.skip();
|
||||
} );
|
||||
|
||||
var s_pms = sender.send_offer(
|
||||
{ name: "my file" }
|
||||
).then(
|
||||
(sender_xfer) => {
|
||||
t.ok( !sender_xfer, "skip() -> sender sees no transfer object" );
|
||||
return sender.send_offer( { name: "file 2" } );
|
||||
}
|
||||
).then(
|
||||
(xfer) => {
|
||||
t.ok( !xfer, "2nd skip() -> sender sees no transfer object" );
|
||||
return sender.close();
|
||||
}
|
||||
);
|
||||
|
||||
return Promise.all( [ r_pms, s_pms ] );
|
||||
} );
|
||||
|
||||
test('abort mid-download', (t) => {
|
||||
_init();
|
||||
|
||||
var transferred_bytes = [];
|
||||
|
||||
var aborted;
|
||||
|
||||
var r_pms = receiver.start().then( (offer) => {
|
||||
offer.on("input", (payload) => {
|
||||
[].push.apply(transferred_bytes, payload);
|
||||
|
||||
if (aborted) throw "already aborted!";
|
||||
aborted = true;
|
||||
|
||||
receiver.abort();
|
||||
});
|
||||
return offer.accept();
|
||||
} );
|
||||
|
||||
var s_pms = sender.send_offer(
|
||||
{ name: "my file" }
|
||||
).then(
|
||||
(xfer) => {
|
||||
xfer.send( [1, 2, 3] );
|
||||
xfer.end( [99, 99, 99] ); //should never get here
|
||||
}
|
||||
);
|
||||
|
||||
return Promise.all( [r_pms, s_pms] ).catch(
|
||||
(err) => {
|
||||
t.ok( err.message.match('abort'), 'error message is about abort' );
|
||||
}
|
||||
).then( () => {
|
||||
t.deepEquals(
|
||||
transferred_bytes,
|
||||
[1, 2, 3],
|
||||
'abort() stopped us from sending more',
|
||||
);
|
||||
} );
|
||||
} );
|
295
tests/zsession_receive.js
Executable file
295
tests/zsession_receive.js
Executable file
|
@ -0,0 +1,295 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const tape = require('blue-tape');
|
||||
|
||||
const SZ_PATH = require('which').sync('sz', {nothrow: true});
|
||||
|
||||
if (!SZ_PATH) {
|
||||
tape.only('SKIP: no “sz” in PATH!', (t) => {
|
||||
t.end();
|
||||
});
|
||||
}
|
||||
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
var helper = require('./lib/testhelp');
|
||||
|
||||
Object.assign(
|
||||
global,
|
||||
{
|
||||
Zmodem: require('./lib/zmodem'),
|
||||
}
|
||||
);
|
||||
|
||||
var FILE1 = helper.make_temp_file(10 * 1024 * 1024); //10 MiB
|
||||
|
||||
function _test_steps(t, sz_args, steps) {
|
||||
return helper.exec_lrzsz_steps( t, SZ_PATH, sz_args, steps );
|
||||
}
|
||||
|
||||
tape('abort() after ZRQINIT', (t) => {
|
||||
return _test_steps( t, [FILE1], [
|
||||
(zsession, child) => {
|
||||
zsession.abort();
|
||||
return true;
|
||||
},
|
||||
] ).then( (inputs) => {
|
||||
//console.log("inputs", inputs);
|
||||
|
||||
var str = String.fromCharCode.apply( String, inputs[ inputs.length - 1 ]);
|
||||
t.ok(
|
||||
str.match(/\x18\x18\x18\x18\x18/),
|
||||
'abort() right after receipt of ZRQINIT',
|
||||
);
|
||||
} );
|
||||
});
|
||||
|
||||
tape('abort() after ZFILE', (t) => {
|
||||
return _test_steps( t, [FILE1], [
|
||||
(zsession) => {
|
||||
zsession.start();
|
||||
return true;
|
||||
},
|
||||
(zsession) => {
|
||||
zsession.abort();
|
||||
return true;
|
||||
},
|
||||
] ).then( (inputs) => {
|
||||
//console.log("inputs", inputs);
|
||||
|
||||
var str = String.fromCharCode.apply( String, inputs[ inputs.length - 1 ]);
|
||||
t.ok(
|
||||
str.match(/\x18\x18\x18\x18\x18/),
|
||||
'abort() right after receipt of ZFILE',
|
||||
);
|
||||
} );
|
||||
});
|
||||
|
||||
//NB: This test is not unlikely to flap since it depends
|
||||
//on sz reading the abort sequence prior to finishing its read
|
||||
//of the file.
|
||||
tape('abort() during download', { timeout: 30000 }, (t) => {
|
||||
var child_pms = _test_steps( t, [FILE1], [
|
||||
(zsession) => {
|
||||
zsession.on("offer", (offer) => offer.accept() );
|
||||
zsession.start();
|
||||
return true;
|
||||
},
|
||||
(zsession) => {
|
||||
zsession.abort();
|
||||
return true;
|
||||
},
|
||||
] );
|
||||
|
||||
return child_pms.then( (inputs) => {
|
||||
t.notEquals( inputs, undefined, 'abort() during download ends the transmission' );
|
||||
|
||||
t.ok(
|
||||
inputs.every( function(bytes) {
|
||||
var str = String.fromCharCode.apply( String, bytes );
|
||||
return !/THE_END/.test(str);
|
||||
} ),
|
||||
"the end of the file was not sent",
|
||||
);
|
||||
} );
|
||||
});
|
||||
|
||||
//This only works because we use CRC32 to receive. CRC16 in lsz has a
|
||||
//buffer overflow bug, fixed here:
|
||||
//
|
||||
// https://github.com/gooselinux/lrzsz/blob/master/lrzsz-0.12.20.patch
|
||||
//
|
||||
tape('skip() during download', { timeout: 30000 }, (t) => {
|
||||
var filenames = [FILE1, helper.make_temp_file(12345678)];
|
||||
//filenames = ["-vvvvvvvvvvvvv", FILE1, _make_temp_file()];
|
||||
|
||||
var started, second_offer;
|
||||
|
||||
return _test_steps( t, filenames, [
|
||||
(zsession) => {
|
||||
if (!started) {
|
||||
function offer_taker(offer) {
|
||||
offer.accept();
|
||||
offer.skip();
|
||||
zsession.off("offer", offer_taker);
|
||||
zsession.on("offer", (offer2) => {
|
||||
second_offer = offer2;
|
||||
offer2.skip();
|
||||
});
|
||||
}
|
||||
zsession.on("offer", offer_taker);
|
||||
zsession.start();
|
||||
started = true;
|
||||
}
|
||||
//return true;
|
||||
},
|
||||
] ).then( (inputs) => {
|
||||
var never_end = inputs.every( function(bytes) {
|
||||
var str = String.fromCharCode.apply( String, bytes );
|
||||
return !/THE_END/.test(str);
|
||||
} );
|
||||
|
||||
// This is race-prone.
|
||||
//t.ok( never_end, "the end of a file is never sent" );
|
||||
|
||||
t.ok( !!second_offer, "we got a 2nd offer after the first" );
|
||||
} );
|
||||
});
|
||||
|
||||
tape('skip() - immediately - at end of download', { timeout: 30000 }, (t) => {
|
||||
var filenames = [helper.make_temp_file(123)];
|
||||
|
||||
var started;
|
||||
|
||||
return _test_steps( t, filenames, [
|
||||
(zsession) => {
|
||||
if (!started) {
|
||||
function offer_taker(offer) {
|
||||
offer.accept();
|
||||
offer.skip();
|
||||
}
|
||||
zsession.on("offer", offer_taker);
|
||||
zsession.start();
|
||||
|
||||
started = true;
|
||||
}
|
||||
},
|
||||
] );
|
||||
});
|
||||
|
||||
// Verify a skip() that happens after a transfer is complete.
|
||||
// There are no assertions here.
|
||||
tape('skip() - after a parse - at end of download', { timeout: 30000 }, (t) => {
|
||||
var filenames = [helper.make_temp_file(123)];
|
||||
|
||||
var the_offer, started, skipped, completed;
|
||||
|
||||
return _test_steps( t, filenames, [
|
||||
(zsession) => {
|
||||
if (!started) {
|
||||
function offer_taker(offer) {
|
||||
the_offer = offer;
|
||||
var promise = the_offer.accept();
|
||||
promise.then( () => {
|
||||
completed = 1;
|
||||
} );
|
||||
}
|
||||
zsession.on("offer", offer_taker);
|
||||
zsession.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
return the_offer;
|
||||
},
|
||||
() => {
|
||||
if (!skipped && !completed) {
|
||||
the_offer.skip();
|
||||
skipped = true;
|
||||
}
|
||||
},
|
||||
] );
|
||||
});
|
||||
|
||||
var happy_filenames = [
|
||||
helper.make_temp_file(5),
|
||||
helper.make_temp_file(3),
|
||||
helper.make_temp_file(1),
|
||||
helper.make_empty_temp_file(),
|
||||
];
|
||||
|
||||
tape('happy-path: single batch', { timeout: 30000 }, (t) => {
|
||||
var started, the_offer;
|
||||
|
||||
var args = happy_filenames;
|
||||
|
||||
var buffers = [];
|
||||
|
||||
var child_pms = _test_steps( t, args, [
|
||||
(zsession) => {
|
||||
if (!started) {
|
||||
function offer_taker(offer) {
|
||||
the_offer = offer;
|
||||
the_offer.accept( { on_input: "spool_array" } ).then( (byte_lists) => {
|
||||
var flat = [].concat.apply([], byte_lists);
|
||||
var str = String.fromCharCode.apply( String, flat );
|
||||
buffers.push(str);
|
||||
} );
|
||||
}
|
||||
zsession.on("offer", offer_taker);
|
||||
zsession.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
] );
|
||||
|
||||
return child_pms.then( (inputs) => {
|
||||
t.equals( buffers[0], "xxxxx=THE_END", '5-byte transfer plus end' );
|
||||
t.equals( buffers[1], "xxx=THE_END", '3-byte transfer plus end' );
|
||||
t.equals( buffers[2], "x=THE_END", '1-byte transfer plus end' );
|
||||
t.equals( buffers[3], "", 'empty transfer plus end' );
|
||||
} );
|
||||
});
|
||||
|
||||
tape('happy-path: individual transfers', { timeout: 30000 }, (t) => {
|
||||
var promises = happy_filenames.map( (fn) => {
|
||||
var str;
|
||||
|
||||
var started;
|
||||
|
||||
var child_pms = _test_steps( t, [fn], [
|
||||
(zsession) => {
|
||||
if (!started) {
|
||||
function offer_taker(offer) {
|
||||
offer.accept( { on_input: "spool_array" } ).then( (byte_lists) => {
|
||||
var flat = [].concat.apply([], byte_lists);
|
||||
str = String.fromCharCode.apply( String, flat );
|
||||
} );
|
||||
}
|
||||
zsession.on("offer", offer_taker);
|
||||
zsession.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
] );
|
||||
|
||||
return child_pms.then( () => str );
|
||||
} );
|
||||
|
||||
return Promise.all(promises).then( (strs) => {
|
||||
t.equals( strs[0], "xxxxx=THE_END", '5-byte transfer plus end' );
|
||||
t.equals( strs[1], "xxx=THE_END", '3-byte transfer plus end' );
|
||||
t.equals( strs[2], "x=THE_END", '1-byte transfer plus end' );
|
||||
t.equals( strs[3], "", 'empty transfer plus end' );
|
||||
} );
|
||||
});
|
||||
|
||||
//This doesn’t work because we automatically send ZFIN once we receive it,
|
||||
//which prompts the child to finish up.
|
||||
tape.skip("abort() after ZEOF", (t) => {
|
||||
var received;
|
||||
|
||||
return _test_steps( t, [FILE1], [
|
||||
(zsession) => {
|
||||
zsession.on("offer", (offer) => {
|
||||
offer.accept().then( () => { received = true } );
|
||||
} );
|
||||
zsession.start();
|
||||
return true;
|
||||
},
|
||||
(zsession) => {
|
||||
if (received) {
|
||||
zsession.abort();
|
||||
return true;
|
||||
}
|
||||
},
|
||||
] ).then( (inputs) => {
|
||||
var str = String.fromCharCode.apply( String, inputs[ inputs.length - 1 ]);
|
||||
t.is( str, "OO", "successful close despite abort" );
|
||||
} );
|
||||
});
|
248
tests/zsession_send.js
Executable file
248
tests/zsession_send.js
Executable file
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require('fs');
|
||||
const tape = require('blue-tape');
|
||||
|
||||
const RZ_PATH = require('which').sync('rz', {nothrow: true});
|
||||
|
||||
if (!RZ_PATH) {
|
||||
tape.only('SKIP: no “rz” in PATH!', (t) => {
|
||||
t.end();
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(
|
||||
global,
|
||||
{
|
||||
Zmodem: require('./lib/zmodem'),
|
||||
}
|
||||
);
|
||||
|
||||
var helper = require('./lib/testhelp');
|
||||
|
||||
var dir_before = process.cwd();
|
||||
tape.onFinish( () => process.chdir( dir_before ) );
|
||||
|
||||
let TEST_STRINGS = [
|
||||
"",
|
||||
"0",
|
||||
"123",
|
||||
"\x00",
|
||||
"\x18",
|
||||
"\x18\x18\x18\x18\x18", //invalid as UTF-8
|
||||
"\x8a\x9a\xff\xfe", //invalid as UTF-8
|
||||
"épée",
|
||||
"Hi diddle-ee, dee! A sailor’s life for me!",
|
||||
];
|
||||
|
||||
var text_encoder = require('text-encoding').TextEncoder;
|
||||
text_encoder = new text_encoder();
|
||||
|
||||
function _send_batch(t, batch, on_offer) {
|
||||
batch = batch.slice(0);
|
||||
|
||||
return helper.exec_lrzsz_steps( t, RZ_PATH, [], [
|
||||
(zsession, child) => {
|
||||
function offer_sender() {
|
||||
if (!batch.length) {
|
||||
zsession.close();
|
||||
return; //batch finished
|
||||
}
|
||||
|
||||
return zsession.send_offer(
|
||||
batch[0][0]
|
||||
).then( (xfer) => {
|
||||
if (on_offer) {
|
||||
on_offer(xfer, batch[0]);
|
||||
}
|
||||
|
||||
let file_contents = batch.shift()[1];
|
||||
|
||||
var octets;
|
||||
if ("string" === typeof file_contents) {
|
||||
octets = text_encoder.encode(file_contents);
|
||||
}
|
||||
else {
|
||||
octets = file_contents; // Buffer
|
||||
}
|
||||
|
||||
return xfer && xfer.end( Array.from(octets) );
|
||||
} ).then( offer_sender );
|
||||
}
|
||||
|
||||
return offer_sender();
|
||||
},
|
||||
(zsession, child) => {
|
||||
return zsession.has_ended();
|
||||
},
|
||||
] );
|
||||
}
|
||||
|
||||
function _do_in_temp_dir( todo ) {
|
||||
var ret;
|
||||
|
||||
process.chdir( helper.make_temp_dir() );
|
||||
|
||||
try {
|
||||
ret = todo();
|
||||
}
|
||||
catch(e) {
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
if (!ret) {
|
||||
process.chdir( dir_before );
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
ret = ret.then( () => process.chdir( dir_before ) );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
tape("rz accepts one, then skips next", (t) => {
|
||||
return _do_in_temp_dir( () => {
|
||||
let filename = "no-clobberage";
|
||||
|
||||
var batch = [
|
||||
[
|
||||
{ name: filename },
|
||||
"the first",
|
||||
],
|
||||
[
|
||||
{ name: filename },
|
||||
"the second",
|
||||
],
|
||||
];
|
||||
|
||||
var offers = [];
|
||||
function offer_cb(xfer, batch_item) {
|
||||
offers.push( xfer );
|
||||
}
|
||||
|
||||
return _send_batch(t, batch, offer_cb).then( () => {
|
||||
var got_contents = fs.readFileSync(filename, "utf-8");
|
||||
t.equals( got_contents, "the first", 'second offer was rejected' );
|
||||
|
||||
t.notEquals( offers[0], undefined, 'got an offer at first' );
|
||||
t.equals( offers[1], undefined, '… but no offer second' );
|
||||
} );
|
||||
} );
|
||||
});
|
||||
|
||||
tape("send batch", (t) => {
|
||||
return _do_in_temp_dir( () => {
|
||||
var string_num = 0;
|
||||
|
||||
var base = "batch_";
|
||||
var mtime_1990 = new Date("1990-01-01T00:00:00Z");
|
||||
|
||||
var batch = TEST_STRINGS.map( (str, i) => {
|
||||
return [
|
||||
{
|
||||
name: base + i,
|
||||
mtime: mtime_1990,
|
||||
},
|
||||
str,
|
||||
];
|
||||
} );
|
||||
|
||||
return _send_batch(t, batch).then( () => {
|
||||
for (var sn=0; sn < TEST_STRINGS.length; sn++) {
|
||||
var got_contents = fs.readFileSync(base + sn, "utf-8");
|
||||
t.equals( got_contents, TEST_STRINGS[sn], `rz wrote out the file: ` + JSON.stringify(TEST_STRINGS[sn]) );
|
||||
t.equals( 0 + fs.statSync(base + sn).mtime, 0 + mtime_1990, `... and observed the sent mtime` );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
});
|
||||
|
||||
tape("send one at a time", (t) => {
|
||||
return _do_in_temp_dir( () => {
|
||||
var xfer;
|
||||
|
||||
let test_strings = TEST_STRINGS.slice(0);
|
||||
|
||||
function doer() {
|
||||
var file_contents = test_strings.shift();
|
||||
if (typeof(file_contents) !== "string") return; //we’re done
|
||||
|
||||
return helper.exec_lrzsz_steps( t, RZ_PATH, ["--overwrite"], [
|
||||
(zsession, child) => {
|
||||
zsession.send_offer( { name: "single" } ).then( (xf) => {
|
||||
t.ok( !!xf, 'rz accepted offer' );
|
||||
xfer = xf;
|
||||
} ).then(
|
||||
() => xfer.end( Array.from( text_encoder.encode(file_contents) ) )
|
||||
).then(
|
||||
() => zsession.close()
|
||||
);
|
||||
|
||||
return true;
|
||||
},
|
||||
(zsession, child) => {
|
||||
return zsession.has_ended();
|
||||
},
|
||||
] ).then( () => {
|
||||
var got_contents = fs.readFileSync("single", "utf-8");
|
||||
t.equals( got_contents, file_contents, `rz wrote out the file: ` + JSON.stringify(file_contents) );
|
||||
} ).then( doer );
|
||||
}
|
||||
|
||||
return doer();
|
||||
} );
|
||||
});
|
||||
|
||||
tape("send single large file", (t) => {
|
||||
return _do_in_temp_dir( () => {
|
||||
var string_num = 0;
|
||||
|
||||
var mtime_1990 = new Date("1990-01-01T00:00:00Z");
|
||||
var big_string = Array(30 * 1024 * 1024).fill('x').join("");
|
||||
|
||||
var batch = [
|
||||
[
|
||||
{
|
||||
name: "big_kahuna",
|
||||
},
|
||||
big_string,
|
||||
],
|
||||
];
|
||||
|
||||
return _send_batch(t, batch).then( () => {
|
||||
var got_contents = fs.readFileSync("big_kahuna", "utf-8");
|
||||
t.equals( got_contents, big_string, 'rz wrote out the file');
|
||||
} );
|
||||
} );
|
||||
});
|
||||
|
||||
tape("send single random file", (t) => {
|
||||
return _do_in_temp_dir( () => {
|
||||
var string_num = 0;
|
||||
|
||||
var mtime_1990 = new Date("1990-01-01T00:00:00Z");
|
||||
|
||||
var big_buffer = new Buffer(1024 * 1024);
|
||||
for (var i=0; i<big_buffer.length; i++) {
|
||||
big_buffer[i] = Math.floor( Math.random(256) );
|
||||
}
|
||||
|
||||
var batch = [
|
||||
[
|
||||
{
|
||||
name: "big_kahuna",
|
||||
},
|
||||
big_buffer,
|
||||
],
|
||||
];
|
||||
|
||||
return _send_batch(t, batch).then( () => {
|
||||
var got_contents = fs.readFileSync("big_kahuna");
|
||||
t.equals( got_contents.join(), big_buffer.join(), 'rz wrote out the file');
|
||||
} );
|
||||
} );
|
||||
});
|
62
tests/zsubpacket.js
Executable file
62
tests/zsubpacket.js
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const tape = require('blue-tape');
|
||||
|
||||
const testhelp = require('./lib/testhelp');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
var zdle = new Zmodem.ZDLE( { escape_ctrl_chars: true } );
|
||||
|
||||
tape('build, encode, parse', function(t) {
|
||||
let content = [1, 2, 3, 4];
|
||||
|
||||
["end_ack", "no_end_ack", "end_no_ack", "no_end_no_ack"].forEach( end => {
|
||||
var header = Zmodem.Subpacket.build( content, end );
|
||||
|
||||
t.deepEquals(
|
||||
header.get_payload(),
|
||||
content,
|
||||
`${end}: get_payload()`
|
||||
);
|
||||
|
||||
t.is(
|
||||
header.frame_end(),
|
||||
!/no_end/.test(end),
|
||||
`${end}: frame_end()`
|
||||
);
|
||||
|
||||
t.is(
|
||||
header.ack_expected(),
|
||||
!/no_ack/.test(end),
|
||||
`${end}: ack_expected()`
|
||||
);
|
||||
|
||||
[16, 32].forEach( crclen => {
|
||||
var encoded = header["encode" + crclen](zdle);
|
||||
var parsed = Zmodem.Subpacket["parse" + crclen](encoded);
|
||||
|
||||
t.deepEquals(
|
||||
parsed.get_payload(),
|
||||
content,
|
||||
`${end}, CRC${crclen} rount-trip: get_payload()`
|
||||
);
|
||||
|
||||
t.is(
|
||||
parsed.frame_end(),
|
||||
header.frame_end(),
|
||||
`${end}, CRC${crclen} rount-trip: frame_end()`
|
||||
);
|
||||
|
||||
t.is(
|
||||
parsed.ack_expected(),
|
||||
header.ack_expected(),
|
||||
`${end}, CRC${crclen} rount-trip: ack_expected()`
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
t.end();
|
||||
} );
|
227
tests/zvalidation.js
Normal file
227
tests/zvalidation.js
Normal file
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const tape = require('blue-tape');
|
||||
|
||||
global.Zmodem = require('./lib/zmodem');
|
||||
|
||||
const zcrc = Zmodem.CRC;
|
||||
|
||||
var now = new Date();
|
||||
var now_epoch = Math.floor( now.getTime() / 1000 );
|
||||
|
||||
var failures = [
|
||||
[
|
||||
'empty name',
|
||||
{ name: "" },
|
||||
function(t, e) {
|
||||
t.ok( /name/.test(e.message), 'has “name”' );
|
||||
},
|
||||
],
|
||||
[
|
||||
'non-string name',
|
||||
{ name: 123 },
|
||||
function(t, e) {
|
||||
t.ok( /name/.test(e.message), 'has “name”' );
|
||||
t.ok( /string/.test(e.message), 'has “string”' );
|
||||
},
|
||||
],
|
||||
[
|
||||
'non-empty serial',
|
||||
{ name: "123", serial: 0 },
|
||||
function(t, e) {
|
||||
t.ok( /serial/.test(e.message), 'has “serial”' );
|
||||
},
|
||||
],
|
||||
[
|
||||
'files_remaining === 0',
|
||||
{ name: "123", files_remaining: 0 },
|
||||
function(t, e) {
|
||||
t.ok( /files_remaining/.test(e.message), 'has “files_remaining”' );
|
||||
},
|
||||
],
|
||||
[
|
||||
'pre-epoch mtime',
|
||||
{ name: "123", mtime: new Date("1969-12-30T01:02:03Z") },
|
||||
function(t, e) {
|
||||
t.ok( /mtime/.test(e.message), 'has “mtime”' );
|
||||
t.ok( /1969/.test(e.message), 'has “1969”' );
|
||||
t.ok( /1970/.test(e.message), 'has “1970”' );
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
["size", "mode", "mtime", "files_remaining", "bytes_remaining"].forEach( (k) => {
|
||||
var input = { name: "the name" };
|
||||
input[k] = "123123";
|
||||
|
||||
var key_regexp = new RegExp(k);
|
||||
var value_regexp = new RegExp(input[k]);
|
||||
|
||||
failures.push( [
|
||||
`string “${k}”`,
|
||||
input,
|
||||
function(t, e) {
|
||||
t.ok( key_regexp.test(e.message), `has “${k}”` );
|
||||
t.ok( value_regexp.test(e.message), 'has value' );
|
||||
t.ok( /number/.test(e.message), 'has “number”' );
|
||||
},
|
||||
] );
|
||||
|
||||
input = Object.assign( {}, input );
|
||||
input[k] = -input[k];
|
||||
|
||||
var negative_regexp = new RegExp(input[k]);
|
||||
|
||||
failures.push( [
|
||||
`negative “${k}”`,
|
||||
input,
|
||||
function(t, e) {
|
||||
t.ok( key_regexp.test(e.message), `has “${k}”` );
|
||||
t.ok( negative_regexp.test(e.message), 'has value' );
|
||||
},
|
||||
] );
|
||||
|
||||
input = Object.assign( {}, input );
|
||||
input[k] = -input[k] - 0.1;
|
||||
|
||||
var fraction_regexp = new RegExp( ("" + input[k]).replace(/\./, "\\.") );
|
||||
|
||||
failures.push( [
|
||||
`fraction “${k}”`,
|
||||
input,
|
||||
function(t, e) {
|
||||
t.ok( key_regexp.test(e.message), `has “${k}”` );
|
||||
t.ok( fraction_regexp.test(e.message), 'has value' );
|
||||
},
|
||||
] );
|
||||
} );
|
||||
|
||||
|
||||
var transformations = [
|
||||
[
|
||||
'name only',
|
||||
{ name: "My name", },
|
||||
{
|
||||
name: "My name",
|
||||
size: null,
|
||||
mtime: null,
|
||||
mode: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
],
|
||||
[
|
||||
'name is all numerals',
|
||||
{ name: "0", },
|
||||
{
|
||||
name: "0",
|
||||
size: null,
|
||||
mtime: null,
|
||||
mode: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
],
|
||||
[
|
||||
'name only (undefined rather than null)',
|
||||
{
|
||||
name: "My name",
|
||||
size: undefined,
|
||||
mtime: undefined,
|
||||
mode: undefined,
|
||||
serial: undefined,
|
||||
files_remaining: undefined,
|
||||
bytes_remaining: undefined,
|
||||
},
|
||||
{
|
||||
name: "My name",
|
||||
size: null,
|
||||
mtime: null,
|
||||
mode: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
],
|
||||
[
|
||||
'name and all numbers',
|
||||
{
|
||||
name: "My name",
|
||||
size: 0,
|
||||
mtime: 0,
|
||||
mode: parseInt("0644", 8),
|
||||
serial: null,
|
||||
files_remaining: 1,
|
||||
bytes_remaining: 0,
|
||||
},
|
||||
{
|
||||
name: "My name",
|
||||
size: 0,
|
||||
mtime: 0,
|
||||
mode: parseInt("100644", 8),
|
||||
serial: null,
|
||||
files_remaining: 1,
|
||||
bytes_remaining: 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
'name, zero size',
|
||||
{ name: "My name", mtime: now },
|
||||
{
|
||||
name: "My name",
|
||||
size: null,
|
||||
mtime: now_epoch,
|
||||
mode: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
],
|
||||
[
|
||||
'name, mtime as Date',
|
||||
{ name: "My name", size: 0 },
|
||||
{
|
||||
name: "My name",
|
||||
size: 0,
|
||||
mtime: null,
|
||||
mode: null,
|
||||
serial: null,
|
||||
files_remaining: null,
|
||||
bytes_remaining: null,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
tape('offer_parameters - failures', function(t) {
|
||||
|
||||
for (const [label, input, todo] of failures) {
|
||||
let err;
|
||||
try {
|
||||
Zmodem.Validation.offer_parameters(input);
|
||||
}
|
||||
catch(e) { err = e }
|
||||
|
||||
t.ok( err instanceof Zmodem.Error, `throws ok: ${label}` );
|
||||
|
||||
todo(t, err);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
tape('offer_parameters - happy path', function(t) {
|
||||
|
||||
for (const [label, input, output] of transformations) {
|
||||
t.deepEquals(
|
||||
Zmodem.Validation.offer_parameters(input),
|
||||
output,
|
||||
label,
|
||||
);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue