text: #!/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',
          );
      } );
  } );
  