2643 lines
67 KiB
PHP
2643 lines
67 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\Protocol;
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
use App\Classes\{Node,Protocol};
|
|
use App\Classes\File\{Receive,Send};
|
|
use App\Classes\Sock\Exception\SocketException;
|
|
use App\Classes\Sock\SocketClient;
|
|
use App\Interfaces\CRC as CRCInterface;
|
|
use App\Interfaces\Zmodem as ZmodemInterface;
|
|
use App\Models\Address;
|
|
use App\Traits\CRC as CRCTrait;
|
|
|
|
/**
|
|
* ZModem File Transfer protocol
|
|
*
|
|
* NOTES:
|
|
* + If a data packet follows a HEX header, it is protected with CRC-16.
|
|
* + A hex header begins with the sequence ZPAD, ZPAD, ZDLE, ZHEX. [2a 2a 18 42]
|
|
* + Type byte(2), 4 flag bytes(8), 2 bytes CRC16(4)
|
|
* + An XON character is appended to all HEX packets except ZACK and ZFIN. [11]
|
|
*/
|
|
final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
|
{
|
|
private const LOGKEY = 'Z--';
|
|
|
|
use CRCTrait;
|
|
|
|
/* ZModem constants */
|
|
private const LSZ_MAXHLEN = 16; /* Max header information length NEVER CHANGE */
|
|
private const LSZ_MAXATTNLEN = 32; /* Max length of Attn string */
|
|
private const LSZ_MAXFRAME = self::ZSTDERR;
|
|
private const LSZ_WINDOW = 1024;
|
|
private const LSZ_BLOCKSIZE = 1024;
|
|
|
|
/* Special characters */
|
|
private const ZPAD = 0x2a; //* /* 052 Padding character begins frames */
|
|
private const ZDLE = 0x18; /* ZDLE - Zmodem Data Link Escape: Ctrl-X ZmodemReceive escape - `ala BISYNC DLE \030 */
|
|
private const ZDLEE = (self::ZDLE^0x40); /* Escaped ZDLE as transmitted */
|
|
private const ZDEL = 0x7f; /* DEL character */
|
|
private const ZBIN = 0x41; //A /* Binary frame indicator (CRC-16) */
|
|
private const ZHEX = 0x42; //B /* HEX frame indicator */
|
|
private const ZBIN32 = 0x43; //C /* Binary frame with 32 bit FCS */
|
|
private const ZBINR32 = 0x44; //D /* RLE packed Binary frame with 32 bit FCS */
|
|
private const ZVBIN = 0x61; //a /* Binary frame indicator (CRC-16) and v.header */
|
|
private const ZVHEX = 0x62; //b /* HEX frame indicator and v.header */
|
|
private const ZVBIN32 = 0x63; //c /* Binary frame with 32 bit FCS and v.header */
|
|
private const ZVBINR32 = 0x64; //d /* RLE packed Binary frame with 32 bit FCS and v.header */
|
|
private const ZRESC = 0x7e; /* RLE flag/escape character */
|
|
|
|
private const ZRQINIT = 0x00; /* Request receive init */
|
|
private const ZRINIT = 0x01; /* Receive init */
|
|
private const ZSINIT = 0x02; /* Send init sequence (optional) */
|
|
private const ZACK = 0x03; /* ACK to above */
|
|
private const ZFILE = 0x04; /* File name from sender */
|
|
public const ZSKIP = 0x05; /* To sender: skip this file */
|
|
private const ZNAK = 0x06; /* Last packet was garbled */
|
|
private const ZABORT = 0x07; /* Abort batch transfers */
|
|
private const ZFIN = 0x08; /* Finish session */
|
|
private const ZRPOS = 0x09; /* Resume data trans at this position */
|
|
private const ZDATA = 0x0a; /* Data packet(s) follow */
|
|
private const ZEOF = 0x0b; /* End of file */
|
|
public const ZFERR = 0x0c; /* Fatal Read or Write error Detected (we use for refuse -- suspend) */
|
|
private const ZCRC = 0x0d; /* Request for file CRC and response */
|
|
private const ZCHALLENGE = 0x0e; /* Receiver's Challenge */
|
|
private const ZCOMPL = 0x0f; /* Request is complete */
|
|
private const ZCAN = 0x10; /* Other end canned session with CAN*5 */
|
|
private const ZFREECNT = 0x11; /* Request for free bytes on filesystem (OK, we always send unlimited) */
|
|
private const ZCOMMAND = 0x12; /* Command from sending program (NOT SUPPORTED!) */
|
|
private const ZSTDERR = 0x13; /* Output to standard error, data follows (NOT SUPPORTED!) */
|
|
|
|
private const ZCRCE = 0x68; //h /* CRC next, frame ends, header packet follows */
|
|
private const ZCRCG = 0x69; //i /* CRC next, frame continues nonstop */
|
|
private const ZCRCQ = 0x6a; //j /* CRC next, frame continues, ZACK expected */
|
|
private const ZCRCW = 0x6b; //k /* CRC next, ZACK expected, end of frame */
|
|
private const ZRUB0 = 0x6c; //l /* Translate to rubout 0177 */
|
|
private const ZRUB1 = 0x6d; //m /* Translate to rubout 0377 */
|
|
|
|
/* Return codes -- pseudo-characters */
|
|
|
|
private const LSZ_CAN = -4;
|
|
private const LSZ_NOHEADER = -6;
|
|
private const LSZ_BADCRC = -7;
|
|
private const LSZ_XONXOFF = -8;
|
|
private const LSZ_CRCE = (self::ZCRCE|0x0100);
|
|
private const LSZ_CRCG = (self::ZCRCG|0x0100);
|
|
private const LSZ_CRCQ = (self::ZCRCQ|0x0100);
|
|
private const LSZ_CRCW = (self::ZCRCW|0x0100);
|
|
|
|
/* Dirty hack */
|
|
private const LSZ_TRUSTZFINS = 3; /* We trust only in MANY ZFINs during initialization */
|
|
|
|
/* Byte positions within header array */
|
|
private const LSZ_F0 = 3; /* First flags byte */
|
|
private const LSZ_F1 = 2;
|
|
private const LSZ_F2 = 1;
|
|
private const LSZ_F3 = 0;
|
|
private const LSZ_P0 = 0; /* Low order 8 bits of position */
|
|
private const LSZ_P1 = 1;
|
|
private const LSZ_P2 = 2;
|
|
private const LSZ_P3 = 3; /* High order 8 bits of file position */
|
|
|
|
/* different protocol variations */
|
|
private const LSZ_OPTZEDZAP = 0x00000001; /* We could big blocks, ZModem variant (ZedZap) */
|
|
private const LSZ_OPTDIRZAP = 0x00000002; /* We escape nothing, ZModem variant (DirZap) */
|
|
private const LSZ_OPTESCAPEALL = 0x00000004; /* We must escape ALL contorlo characters */
|
|
private const LSZ_OPTCRC32 = 0x00000008; /* We must send CRC 32 */
|
|
private const LSZ_OPTVHDR = 0x00000010; /* We must send variable headers */
|
|
private const LSZ_OPTRLE = 0x00000020; /* We must send RLEd data (NOT SUPPORTED)! */
|
|
private const LSZ_OPTESC8 = 0x00000040; /* We must escape all 8-bit data (NOT SUPPORTED!) */
|
|
private const LSZ_OPTSKIPGUARD = 0x00000080; /* We use double-skip guard */
|
|
private const LSZ_OPTFIRSTBATCH = 0x00000100; /* It is first batch -- trust in first ZFIN */
|
|
|
|
/* Peer's capabilities from ZRINIT header */
|
|
private const LSZ_RXCANDUPLEX = 0x0001; /* Receiver can send and receive true FDX */
|
|
private const LSZ_RXCANOVIO = 0x0002; /* Receiver can receive data during disk I/O */
|
|
private const LSZ_RXCANBRK = 0x0004; /* Receiver can send a break signal */
|
|
private const LSZ_RXCANRLE = 0x0008; /* Receiver can decode RLE */
|
|
private const LSZ_RXCANLZW = 0x0010; /* Receiver can uncompress LZW */
|
|
private const LSZ_RXCANFC32 = 0x0020; /* Receiver can use 32 bit Frame Check */
|
|
private const LSZ_RXWNTESCCTL = 0x0040; /* Receiver expects ctl chars to be escaped */
|
|
private const LSZ_RXWNTESC8 = 0x0080; /* Receiver expects 8th bit to be escaped */
|
|
private const LSZ_RXCANVHDR = 0x0100; /* Receiver can variable headers */
|
|
|
|
/* Conversion options (ZF0 in ZFILE frame) */
|
|
private const LSZ_CONVBIN = 1; /* Binary transfer - inhibit conversion */
|
|
private const LSZ_CONVCNL = 2; /* Convert NL to local end of line convention */
|
|
private const LSZ_CONVRECOV = 3; /* Resume interrupted file transfer -- binary */
|
|
|
|
/* Read Mode */
|
|
private const rm8BIT = 0;
|
|
private const rm7BIT = 1;
|
|
private const rmZDLE = 2;
|
|
private const rmHEX = 3;
|
|
|
|
/* State */
|
|
private const rhInit = 0;
|
|
private const rhZPAD = 1;
|
|
private const rhXON = 2;
|
|
private const rhLF = 3;
|
|
private const rhCR = 4;
|
|
private const rhCRC = 5;
|
|
private const rhBYTE = 6;
|
|
private const rhFrameType = 7;
|
|
private const rhZHEX = 8;
|
|
private const rhZBIN = 9;
|
|
private const rhZBIN32 = 10;
|
|
private const rhZVHEX = 11;
|
|
private const rhZVBIN = 12;
|
|
private const rhZVBIN32 = 13;
|
|
private const rhZDLE = 14;
|
|
|
|
private const sfSlidingWindow = 0; /* Window requested, could do fullduplex (use stream with ZCRCQ and sliding window) */
|
|
private const sfStream = 1; /* No window requested (use ZCRCG) */
|
|
private const sfBuffered = 2; /* Window requested, couldn't use fullduplex (use frames ZCRCG + ZCRCW) */
|
|
|
|
private int $ls_GotZDLE = 0; /* We seen DLE as last character */
|
|
private int $ls_GotHexNibble = 0; /* We seen one hex digit as last character */
|
|
private int $ls_Protocol; /* Plain/ZedZap/DirZap and other options */
|
|
private int $ls_CANCount = 0; /* Count of CANs to go */
|
|
private int $ls_SerialNum = 0; /* Serial number of file -- for Double-skip protection */
|
|
|
|
private int $ls_DataTimeout; /* Timeout for data blocks */
|
|
private int $ls_HeaderTimeout; /* Timeout for headers */
|
|
private int $ls_MaxBlockSize = 8192; /* Maximum block size */
|
|
|
|
/* Special table to FAST calculate header type */
|
|
/* CRC32,VAR,RLE */
|
|
private const HEADER_TYPE = [[[self::ZBIN,-1],[self::ZVBIN,-1]],[[self::ZBIN32,self::ZBINR32],[self::ZVBIN32,self::ZVBINR32]]];
|
|
|
|
/* Variables to control sender */
|
|
private array $ls_rxHdr = []; /* Receiver header */
|
|
private int $ls_rxCould = 0; /* Receiver could fullduplex/streamed IO (from ZRINIT) */
|
|
|
|
private int $ls_txWinSize = 0; /* Receiver Window/Buffer size (0 for streaming) */
|
|
private int $ls_txCurBlockSize; /* Current block size */
|
|
private int $ls_txLastSent; /* Last sent character -- for escaping */
|
|
private int $ls_txLastACK = 0; /* Last ACKed byte */
|
|
private int $ls_txLastRepos = 0; /* Last requested byte */
|
|
private int $ls_txReposCount = 0; /* Count of REPOSes on one position */
|
|
private int $ls_txGoodBlocks = 0; /* Good blocks sent */
|
|
|
|
private const NUL = 0x00;
|
|
private const ACK = 0x06;
|
|
private const BEL = 0x07;
|
|
private const BS = 0x08;
|
|
private const HT = 0x09;
|
|
private const LF = 0x0a;
|
|
private const VT = 0x0b;
|
|
private const CR = 0x0d;
|
|
private const DLE = 0x10;
|
|
private const XON = 0x11; //(81&037); 'Q'
|
|
private const XOFF = 0x13; //(83&037); 'S'
|
|
private const CAN = 0x18;
|
|
private const ESC = 0x1b;
|
|
|
|
private const RX_SKIP = 1;
|
|
private const RX_SUSPEND = 2;
|
|
|
|
private string $rxbuf = '';
|
|
private string $txbuf = '';
|
|
|
|
/**
|
|
* @param SocketClient $client
|
|
* @return null
|
|
* @throws SocketException
|
|
*/
|
|
public function onConnect(SocketClient $client): ?int
|
|
{
|
|
// If our parent returns a PID, we've forked
|
|
if (! parent::onConnect($client)) {
|
|
Log::withContext(['pid'=>getmypid()]);
|
|
|
|
$this->session($client);
|
|
$this->client->close();
|
|
|
|
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
|
|
exit(0);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Initialise our session
|
|
*/
|
|
private function init(SocketClient $client,int $canzap): void
|
|
{
|
|
$this->client = $client;
|
|
|
|
/* Set all options to requested state -- this may be alerted by other side in ZSINIT */
|
|
$this->ls_Protocol = self::LSZ_OPTCRC32|self::LSZ_OPTSKIPGUARD;
|
|
|
|
switch ($canzap&0xff) {
|
|
case 2:
|
|
$this->ls_Protocol |= self::LSZ_OPTDIRZAP;
|
|
/* FALL THROUGH */
|
|
|
|
case 1:
|
|
$this->ls_Protocol |= self::LSZ_OPTZEDZAP;
|
|
/* FALL THROUGH */
|
|
|
|
case 0:
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:! Strange canzap during init [%d]',self::LOGKEY,$canzap));
|
|
}
|
|
|
|
/* Maximum block size -- by protocol, may be reduced by window size later */
|
|
$this->ls_MaxBlockSize = ($this->ls_Protocol&self::LSZ_OPTZEDZAP) ? 8192 : 1024;
|
|
|
|
/* Calculate timeouts */
|
|
/* Timeout for header waiting, if no data sent -- 3*TransferTime or 10 seconds */
|
|
$this->ls_HeaderTimeout = (self::LSZ_MAXHLEN * 30) / $this->client->speed;
|
|
$this->ls_HeaderTimeout = ($this->ls_HeaderTimeout > 10) ? $this->ls_HeaderTimeout : 10;
|
|
|
|
/* Timeout for data packet (3*TransferTime or 60 seconds) */
|
|
$this->ls_DataTimeout = ($this->ls_MaxBlockSize * 30) / $this->client->speed;
|
|
$this->ls_DataTimeout = ($this->ls_DataTimeout > 60) ? $this->ls_DataTimeout : 60;
|
|
|
|
$this->ls_SkipGuard = ($this->ls_Protocol&self::LSZ_OPTSKIPGUARD) ? 1 : 0;
|
|
}
|
|
|
|
public function protocol_init(): int
|
|
{
|
|
// Not used
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Setup our ZMODEM session
|
|
*
|
|
* @param bool $force_queue Not used here
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
public function protocol_session(bool $force_queue=FALSE): int
|
|
{
|
|
$proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK);
|
|
|
|
if ($this->originate) {
|
|
if (! $z->zmodem_sendinit($this->client,$proto) && $this->send->togo_count)
|
|
$this->zmodem_sendfile($this->send);
|
|
|
|
$rc = $this->zmodem_senddone();
|
|
|
|
} else {
|
|
$rc = $this->zmodem_receive($this->client,$proto,$this->recv,$this->node->address);
|
|
}
|
|
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Receive files via Zmodem
|
|
*
|
|
* @param SocketClient $client
|
|
* @param Receive $recv
|
|
* @param int $canzap
|
|
* @return int
|
|
*/
|
|
public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao,bool $force_queue=FALSE): int
|
|
{
|
|
Log::debug(sprintf('%s:+ Starting ZModem Receive [%d]',self::LOGKEY,$canzap));
|
|
|
|
$opts = $this->init($client,$canzap);
|
|
|
|
$this->ls_txWinSize = self::LSZ_WINDOW;
|
|
$this->recv = $recv;
|
|
|
|
if ($canzap&0x0100)
|
|
$opts |= self::LSZ_OPTFIRSTBATCH;
|
|
|
|
switch ($rc=$this->ls_zrecvfinfo(self::ZRINIT,($this->ls_Protocol&self::LSZ_OPTFIRSTBATCH) ? 1 : 0,$ao)) {
|
|
case self::ZFIN:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZFIN after INIT, empty batch',self::LOGKEY));
|
|
$this->ls_zdonereceiver();
|
|
|
|
return self::OK;
|
|
|
|
case self::ZFILE:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZFILE after INIT',self::LOGKEY));
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:! zmodem_receive Something strange after init [%d]',self::LOGKEY,$rc));
|
|
|
|
$this->ls_zabort();
|
|
$this->ls_zdonereceiver();
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
while (TRUE) {
|
|
switch ($rc) {
|
|
case self::ZFIN:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZFIN',self::LOGKEY));
|
|
$this->ls_zdonereceiver();
|
|
|
|
return self::OK;
|
|
|
|
case self::ZFILE:
|
|
if (! $this->recv->togo_count) {
|
|
Log::error(sprintf('%s:! zmodem_receive No files to get?',self::LOGKEY));
|
|
|
|
$frame = self::ZSKIP;
|
|
|
|
} else {
|
|
switch ($this->recv->open()) {
|
|
case self::FOP_SKIP:
|
|
Log::info(sprintf('%s:= zmodem_receive Skip this file [%s]',self::LOGKEY,$this->recv->nameas));
|
|
$frame = self::ZSKIP;
|
|
|
|
break;
|
|
|
|
case self::FOP_SUSPEND:
|
|
Log::info(sprintf('%s:= zmodem_receive Suspend this file [%s]',self::LOGKEY,$this->recv->nameas));
|
|
$frame = self::ZFERR;
|
|
|
|
break;
|
|
|
|
case self::FOP_CONT:
|
|
case self::FOP_OK:
|
|
Log::info(sprintf('%s:= zmodem_receive Receiving [%s] from [%d]',self::LOGKEY,$this->recv->nameas,$this->recv->pos));
|
|
$frame = self::ZRINIT;
|
|
|
|
switch (($rc=$this->ls_zrecvfile($recv->pos))) {
|
|
case self::ZFERR:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZFERR',self::LOGKEY));
|
|
$this->recv->close();
|
|
$frame = self::ZFERR;
|
|
|
|
break;
|
|
|
|
case self::ZSKIP:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZSKIP',self::LOGKEY));
|
|
$this->recv->close();
|
|
$frame = self::ZSKIP;
|
|
|
|
break;
|
|
|
|
case self::OK:
|
|
Log::debug(sprintf('%s:= zmodem_receive OK',self::LOGKEY));
|
|
$this->recv->close();
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:! zmodem_receive OTHER [%d]',self::LOGKEY,$rc));
|
|
$this->recv->close();
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case self::ZABORT:
|
|
Log::debug(sprintf('%s:= zmodem_receive ZABORT',self::LOGKEY));
|
|
|
|
$this->ls_zabort();
|
|
$this->ls_zdonereceiver();
|
|
|
|
return self::ERROR;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? zmodem_receive Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
$this->ls_zabort();
|
|
$this->ls_zdonereceiver();
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
$rc = $this->ls_zrecvfinfo($frame,1,$ao);
|
|
}
|
|
|
|
return self::OK;
|
|
}
|
|
|
|
/**
|
|
* Done sender -- good way
|
|
*
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
public function zmodem_senddone():int
|
|
{
|
|
$trys = 0;
|
|
$retransmit = 1;
|
|
$this->txbuf = '';
|
|
|
|
do {
|
|
if ($retransmit) {
|
|
if (($rc=$this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
$trys++;
|
|
$retransmit = 0;
|
|
}
|
|
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
|
/* Ok, GOOD */
|
|
case self::ZFIN:
|
|
$this->client->buffer_add('OO');
|
|
$this->client->buffer_flush(5);
|
|
|
|
return self::OK;
|
|
|
|
case self::ZNAK:
|
|
case self::TIMEOUT:
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? zmodem_senddone Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 1;
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:? zmodem_senddone Something strange or timeeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Init sending -- wrapper
|
|
*
|
|
* @param SocketClient $client
|
|
* @param int $canzap
|
|
* @return int
|
|
*/
|
|
public function zmodem_sendinit(SocketClient $client,int $canzap): int
|
|
{
|
|
Log::debug(sprintf('%s:+ zmodem_sendinit [%d]',self::LOGKEY,$canzap));
|
|
|
|
$this->init($client,$canzap);
|
|
|
|
if (($rc=$this->ls_zinitsender(self::LSZ_WINDOW,'')) < 0)
|
|
return $rc;
|
|
|
|
Log::debug(sprintf('%s:- ZMODEM Link Options %d/%d, %s%s%s%s',
|
|
self::LOGKEY,
|
|
$this->ls_MaxBlockSize,
|
|
$this->ls_txWinSize,
|
|
($this->ls_Protocol&self::LSZ_OPTCRC32) ? 'CRC32' : 'CRC16',
|
|
($this->ls_rxCould&self::LSZ_RXCANDUPLEX) ? ',DUPLEX' : '',
|
|
($this->ls_Protocol&self::LSZ_OPTVHDR) ? ',VHEADER' : '',
|
|
($this->ls_Protocol&self::LSZ_OPTESCAPEALL) ? ',ESCALL' : ''
|
|
));
|
|
|
|
Log::debug(sprintf('%s:= zmodem_sendinit [%x]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Send a file with the Zmodem Protocol
|
|
*
|
|
* @param Send $send
|
|
* @return int
|
|
*/
|
|
public function zmodem_sendfile(Send $send,Node $node): int
|
|
{
|
|
Log::debug(sprintf('%s:+ zmodem_sendfile',self::LOGKEY));
|
|
|
|
while ($send->togo_count && $send->open()) {
|
|
try {
|
|
$rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->togo_count,$send->total_size);
|
|
|
|
switch ($rc) {
|
|
case self::OK:
|
|
case self::ZSKIP:
|
|
$send->close(TRUE,$node);
|
|
break;
|
|
|
|
case self::ZFERR:
|
|
$send->close(FALSE,$node);
|
|
break;
|
|
|
|
default:
|
|
$send->close(FALSE,$node);
|
|
$this->ls_zabort();
|
|
break;
|
|
}
|
|
|
|
return $rc;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()));
|
|
}
|
|
}
|
|
|
|
return self::OK;
|
|
}
|
|
|
|
/**
|
|
* Fetch long integer (4 bytes) from buffer, as it must be stored in header
|
|
*
|
|
* @param array $buf
|
|
* @return int
|
|
*/
|
|
private function ls_fetchlong(array $buf): int
|
|
{
|
|
$l = $buf[self::LSZ_P3];
|
|
$l<<=8;
|
|
$l |= $buf[self::LSZ_P2];
|
|
$l<<=8;
|
|
$l |= $buf[self::LSZ_P1];
|
|
$l<<=8;
|
|
$l |= $buf[self::LSZ_P0];
|
|
|
|
return $l;
|
|
}
|
|
|
|
/**
|
|
* Return 7bit character, strip XON/XOFF if not DirZap, with timeout
|
|
*
|
|
* @param int $timeout
|
|
* @return int
|
|
* @throws SocketException
|
|
*/
|
|
private function ls_read7bit(int $timeout): int
|
|
{
|
|
$this->ls_CANCount = 0;
|
|
|
|
do {
|
|
if (($c = $this->client->read_ch($timeout)) < 0)
|
|
return $c;
|
|
|
|
} while ((! ($this->ls_Protocol&self::LSZ_OPTDIRZAP)) && (($c === self::XON) || ($c === self::XOFF)));
|
|
|
|
if ($c === self::CAN) {
|
|
if (++$this->ls_CANCount === 5)
|
|
return self::LSZ_CAN;
|
|
|
|
} else {
|
|
$this->ls_CANCount = 0;
|
|
}
|
|
|
|
if (ord($c) === 0)
|
|
return self::ERROR;
|
|
|
|
return $c&0x7f;
|
|
}
|
|
|
|
/**
|
|
* Read one character, check for five CANs
|
|
*
|
|
* @param int $timeout
|
|
* @return int
|
|
*/
|
|
private function ls_readcanned(int $timeout): int
|
|
{
|
|
if (($ch = $this->client->read_ch($timeout)) < 0)
|
|
return $ch;
|
|
|
|
if ($ch === self::CAN) {
|
|
if (++$this->ls_CANCount === 5)
|
|
return self::LSZ_CAN;
|
|
|
|
} else {
|
|
$this->ls_CANCount = 0;
|
|
}
|
|
|
|
return $ch&0xff;
|
|
}
|
|
|
|
/**
|
|
* Read character as two hex digit
|
|
*
|
|
* @param int $timeout
|
|
* @return int
|
|
*/
|
|
private function ls_readhex(int $timeout): int
|
|
{
|
|
static $c = 0;
|
|
|
|
if (! $this->ls_GotHexNibble) {
|
|
if (($c = $this->ls_readhexnibble($timeout)) < 0)
|
|
return $c;
|
|
|
|
$c <<= 4;
|
|
}
|
|
|
|
if (($c2 = $this->ls_readhexnibble($timeout)) >= 0) {
|
|
$this->ls_GotHexNibble = 0;
|
|
|
|
return ($c|$c2);
|
|
|
|
} else {
|
|
$this->ls_GotHexNibble = 1;
|
|
|
|
return $c2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read one hex character
|
|
*
|
|
* @param int $timeout
|
|
* @return int
|
|
*/
|
|
private function ls_readhexnibble(int $timeout): int
|
|
{
|
|
if (($c = $this->ls_readcanned($timeout)) < 0)
|
|
return $c;
|
|
|
|
if (chr($c) >= '0' && chr($c) <= '9') {
|
|
return $c-ord('0');
|
|
|
|
} elseif (chr($c) >= 'a' && chr($c) <= 'f') {
|
|
return $c-ord('a')+10;
|
|
|
|
} else {
|
|
/* will be CRC error */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return 8bit character, strip <DLE>
|
|
*
|
|
* @param int $timeout
|
|
* @return int
|
|
*/
|
|
private function ls_readzdle(int $timeout): int
|
|
{
|
|
$r = 0;
|
|
|
|
/* There was no ZDLE in stream, try to read one */
|
|
if (! $this->ls_GotZDLE) {
|
|
do {
|
|
if (($c=$this->ls_readcanned($timeout)) < 0)
|
|
return $c;
|
|
|
|
/* Check for unescaped XON/XOFF */
|
|
if (! ($this->ls_Protocol&self::LSZ_OPTDIRZAP)) {
|
|
switch ($c) {
|
|
case self::XON:
|
|
case self::XON|0x80:
|
|
case self::XOFF:
|
|
case self::XOFF|0x80:
|
|
$c = self::LSZ_XONXOFF;
|
|
}
|
|
}
|
|
|
|
if ($c === self::ZDLE)
|
|
$this->ls_GotZDLE = 1;
|
|
|
|
elseif ($c != self::LSZ_XONXOFF)
|
|
return $c&0xff;
|
|
|
|
} while ($c === self::LSZ_XONXOFF);
|
|
}
|
|
|
|
/* We will be here only in case of DLE */
|
|
/* We have data */
|
|
if (($c=$this->ls_readcanned($timeout)) >= 0) {
|
|
$this->ls_GotZDLE = 0;
|
|
|
|
switch ($c) {
|
|
case self::ZCRCE:
|
|
case self::ZCRCG:
|
|
case self::ZCRCW:
|
|
case self::ZCRCQ:
|
|
$r = ($c|0x100);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
*chat*
|
|
if ($r && isset($rnode) && $rnode->opt&self::MO_CHAT) {
|
|
do {
|
|
$rr = $this->ls_readcanned($timeout);
|
|
// @todo to implement
|
|
$this->z_devrecv_c($rr,0);
|
|
} while ($rr);
|
|
|
|
$this->z_devsend_c(0);
|
|
}
|
|
*/
|
|
|
|
if ($r)
|
|
return $r;
|
|
|
|
switch ($c) {
|
|
case self::ZRUB0:
|
|
return self::ZDEL;
|
|
|
|
case self::ZRUB1:
|
|
return (self::ZDEL|0x80);
|
|
|
|
default:
|
|
if (($c&0x60) != 0x40)
|
|
return self::LSZ_BADCRC;
|
|
|
|
return ($c^0x40)&0xff;
|
|
}
|
|
}
|
|
|
|
return $c;
|
|
}
|
|
|
|
/**
|
|
* Send one char with escaping
|
|
*
|
|
* @param int $c
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_sendchar(int $c): void
|
|
{
|
|
$c &= 0xff;
|
|
|
|
/* We are Direct ZedZap -- escape only <DLE> */
|
|
if ($this->ls_Protocol&self::LSZ_OPTDIRZAP) {
|
|
$esc = (self::ZDLE === $c);
|
|
|
|
/* We are normal ZModem (may be ZedZap) */
|
|
} else {
|
|
/* Receiver want to escape ALL */
|
|
if (($this->ls_Protocol&self::LSZ_OPTESCAPEALL) && (($c&0x60) === 0)) {
|
|
$esc = 1;
|
|
|
|
} else {
|
|
switch ($c) {
|
|
case self::XON:
|
|
case (self::XON|0x80):
|
|
case self::XOFF:
|
|
case (self::XOFF|0x80):
|
|
case self::DLE:
|
|
case (self::DLE|0x80):
|
|
case self::ZDLE:
|
|
$esc = 1;
|
|
break;
|
|
|
|
default:
|
|
$esc = ((($this->ls_txLastSent&0x7f) === ord('@')) && (($c&0x7f) === self::CR));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($esc) {
|
|
$this->client->buffer_add(chr(self::ZDLE));
|
|
$c ^= 0x40;
|
|
}
|
|
|
|
$this->client->buffer_add(chr($this->ls_txLastSent = $c));
|
|
}
|
|
|
|
/**
|
|
* Send one char as two hex digits
|
|
*
|
|
* @param int $i
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_sendhex(int $i): void
|
|
{
|
|
$str = hexstr($i);
|
|
$this->ls_txLastSent = ord(substr($str,-1));
|
|
|
|
$this->client->buffer_add($str);
|
|
}
|
|
|
|
/**
|
|
* Store long integer (4 bytes) in buffer, as it must be stored in header
|
|
*
|
|
* @param int $l
|
|
* @return array
|
|
*/
|
|
private function ls_storelong(int $l): array
|
|
{
|
|
$buf[self::LSZ_P0] = ($l)&0xff;
|
|
$buf[self::LSZ_P1] = ($l>>8)&0xff;
|
|
$buf[self::LSZ_P2] = ($l>>16)&0xff;
|
|
$buf[self::LSZ_P3] = ($l>>24)&0xff;
|
|
|
|
return $buf;
|
|
}
|
|
|
|
/**
|
|
* Abort the session
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zabort(): void
|
|
{
|
|
$this->client->buffer_flush($this->ls_DataTimeout);
|
|
$this->client->buffer_add(chr(self::XON));
|
|
|
|
for ($i=0;$i<8;$i++)
|
|
$this->client->buffer_add(chr(self::CAN));
|
|
|
|
$this->client->buffer_flush($this->ls_DataTimeout);
|
|
}
|
|
|
|
/**
|
|
* Finished receiving
|
|
*
|
|
* @return int
|
|
* @throws SocketException
|
|
*/
|
|
private function ls_zdonereceiver(): int
|
|
{
|
|
Log::debug(sprintf('%s:+ Finished Receiving',self::LOGKEY));
|
|
|
|
$trys = 0;
|
|
$retransmit = 1;
|
|
$this->rxbuf = '';
|
|
|
|
do {
|
|
if ($retransmit) {
|
|
if (($rc=$this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 0;
|
|
$trys++;
|
|
}
|
|
|
|
switch ($rc=$this->client->read_ch($this->ls_HeaderTimeout)) {
|
|
/* Ok, GOOD */
|
|
case ord('O'):
|
|
$rc = $this->client->read_ch(0);
|
|
return self::OK;
|
|
|
|
case self::XON:
|
|
case self::XOFF:
|
|
case self::XON|0x80:
|
|
case self::XOFF|0x80:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zdonereceiver XON/XOFF, skip it',self::LOGKEY));
|
|
break;
|
|
|
|
case self::ZPAD:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zdonereceiver ZPAD',self::LOGKEY));
|
|
|
|
if (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout)) < 0)
|
|
return $rc;
|
|
|
|
if (self::ZFIN != $rc)
|
|
return self::OK;
|
|
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:! Unexpected read from client [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 1;
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:! Finished Receiving, we should have returned earlier [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Init sender, preapre to send files (initialize timeouts too!)
|
|
*
|
|
* @param int $protocol
|
|
* @param int $window
|
|
* @return int
|
|
*/
|
|
private function ls_zinitsender(int $window,string $attstr)
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zinitsender',self::LOGKEY));
|
|
|
|
$trys = 0;
|
|
|
|
/* Options from ZRINIT header */
|
|
$this->rxOptions = 0;
|
|
|
|
/* ZFIN counter -- we will trust only MAY of them on this stage */
|
|
$zfins = 0;
|
|
|
|
$this->ls_SerialNum = 1;
|
|
|
|
/* Why we need to send this? Old, good times... */
|
|
$this->client->send("rz\r",5);
|
|
$retransmit = 1;
|
|
|
|
do {
|
|
if ($retransmit) {
|
|
/* Send first ZRQINIT (do we need it?) */
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRQINIT,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 0;
|
|
$trys++;
|
|
}
|
|
|
|
switch ($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout)) {
|
|
/* Ok, We got RINIT! */
|
|
case self::ZRINIT:
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT',self::LOGKEY));
|
|
|
|
$this->rxOptions= (($this->ls_rxHdr[self::LSZ_F1]&0xff)<<8)|($this->ls_rxHdr[self::LSZ_F0]&0xff);
|
|
|
|
/* What receiver could (in hardware) -- Duplex, sim. I/O, send break signals */
|
|
$this->ls_rxCould = ($this->rxOptions&(self::LSZ_RXCANDUPLEX|self::LSZ_RXCANOVIO|self::LSZ_RXCANBRK));
|
|
|
|
/* Strip RLE from ls_Protocol, if peer could not use RLE (WE COULD NOT RLE in ANY CASE!) */
|
|
if (! ($this->rxOptions&self::LSZ_RXCANRLE))
|
|
$this->ls_Protocol &= (~self::LSZ_OPTRLE);
|
|
|
|
/* Strip CRC32 from $this->ls_Protocol, if peer could not use CRC32 */
|
|
if (! ($this->rxOptions&self::LSZ_RXCANFC32))
|
|
$this->ls_Protocol &= (~self::LSZ_OPTCRC32);
|
|
|
|
/* Set EscapeAll if peer want it */
|
|
if ($this->rxOptions&self::LSZ_RXWNTESCCTL)
|
|
$this->ls_Protocol |= self::LSZ_OPTESCAPEALL;
|
|
|
|
/* Strip VHeaders from $this->ls_Protocol, if peer could not use VHDR */
|
|
if (! ($this->rxOptions&self::LSZ_RXCANVHDR))
|
|
$this->ls_Protocol &= (~self::LSZ_OPTVHDR);
|
|
|
|
/* Ok, options are ready */
|
|
/* Fetch window size */
|
|
$this->ls_txWinSize = (($this->ls_rxHdr[self::LSZ_P1]&0xff)<<8)|($this->ls_rxHdr[self::LSZ_P0]&0xff);
|
|
|
|
/* Override empty or big window by our window (if not emty too) */
|
|
if ($window && (! $this->ls_txWinSize || ($this->ls_txWinSize > $window)))
|
|
$this->ls_txWinSize = $window;
|
|
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT OK - effproto [%08x], winsize [%d]',self::LOGKEY,$this->ls_Protocol,$this->ls_txWinSize));
|
|
|
|
/* Ok, now we could calculate real max frame size and initial block size */
|
|
if ($this->ls_txWinSize && $this->ls_MaxBlockSize>$this->ls_txWinSize) {
|
|
for ($this->ls_MaxBlockSize=1;$this->ls_MaxBlockSize<$this->ls_txWinSize;$this->ls_MaxBlockSize<<=1) {}
|
|
|
|
/*ls_MaxBlockSize >>= 1;*/
|
|
if ($this->ls_MaxBlockSize<32)
|
|
$this->ls_txWinSize = $this->ls_MaxBlockSize=32;
|
|
}
|
|
|
|
if ($this->client->speed < 2400)
|
|
$this->ls_txCurBlockSize = 256;
|
|
|
|
elseif ($this->client->speed >= 2400 && $this->client->speed < 4800)
|
|
$this->ls_txCurBlockSize = 512;
|
|
|
|
else
|
|
$this->ls_txCurBlockSize = 1024;
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTZEDZAP) {
|
|
if ($this->client->speed >= 7200 && $this->client->speed < 9600)
|
|
$this->ls_txCurBlockSize = 2048;
|
|
|
|
elseif ($this->client->speed >= 9600 && $this->client->speed < 14400)
|
|
$this->ls_txCurBlockSize = 4096;
|
|
|
|
elseif ($this->client->speed >= 14400)
|
|
$this->ls_txCurBlockSize = 8192;
|
|
}
|
|
|
|
if ($this->ls_txCurBlockSize>$this->ls_MaxBlockSize)
|
|
$this->ls_txCurBlockSize=$this->ls_MaxBlockSize;
|
|
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT OK - block sizes Max [%d] Current [%d]',self::LOGKEY,$this->ls_MaxBlockSize,$this->ls_txCurBlockSize));
|
|
|
|
/* Send ZSINIT, if we need it */
|
|
if ($attstr || (! ($this->rxOptions&self::LSZ_RXWNTESCCTL) && ($this->ls_Protocol&self::LSZ_OPTESCAPEALL)))
|
|
return $this->ls_zsendsinit($attstr);
|
|
|
|
else
|
|
return self::OK;
|
|
|
|
/* Return number to peer, he is paranoid */
|
|
case self::ZCHALLENGE:
|
|
Log::debug(sprintf('%s:- ls_zinitsender CHALLENGE',self::LOGKEY));
|
|
|
|
if (($rc=$this->ls_zsendhhdr(ZACK,$this->ls_rxHdr)) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
/* Send ZRQINIT again */
|
|
case self::ZNAK:
|
|
case self::TIMEOUT:
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
/* ZFIN from previous session? Or may be real one? */
|
|
case self::ZFIN:
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZFIN [%d]',self::LOGKEY,$zfins));
|
|
|
|
if (++$zfins === self::LSZ_TRUSTZFINS)
|
|
return self::ERROR;
|
|
|
|
break;
|
|
|
|
/* Please, resend */
|
|
case self::LSZ_BADCRC:
|
|
Log::debug(sprintf('%s:- ls_zinitsender LSZ_BADCRC',self::LOGKEY));
|
|
|
|
/* We don't support it! */
|
|
case self::ZCOMMAND:
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZCOMMAND',self::LOGKEY));
|
|
$this->ls_zsendhhdr(ZNAK,$this->ls_storelong(0));
|
|
|
|
/* Abort this session -- we trust in ABORT! */
|
|
case self::ZABORT:
|
|
Log::debug(sprintf('%s:- ls_zinitsender ZABORT',self::LOGKEY));
|
|
return self::ERROR;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zinitsender Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:? ls_zinitsender Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
private function ls_zrecvdata(&$data,&$len,$timeout,$crc32) {
|
|
return $crc32 ? $this->ls_zrecvdata32($data,$len,$timeout) : $this->ls_zrecvdata16($data,$len,$timeout);
|
|
}
|
|
|
|
/**
|
|
* Receive data subframe with CRC16, return frame type or error (may be -- timeout)
|
|
*
|
|
* @return mixed
|
|
*/
|
|
private function ls_zrecvdata16(string &$data,int &$len,int $timeout): int
|
|
{
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:+ ls_zrecvdata16',self::LOGKEY),['d'=>$data]);
|
|
|
|
$got = 0; /* Bytes total got */
|
|
$crc = 0; /* Received CRC */
|
|
$frametype = self::ERROR; /* Type of frame - ZCRC(G|W|Q|E) */
|
|
$rcvdata = 1; /* Data is being received NOW (not CRC) */
|
|
|
|
while ($rcvdata && (($c = $this->ls_readzdle($timeout)) >= 0)) {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvdata16 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
|
|
|
if ($c < 256) {
|
|
$data .= chr($c&0xff);
|
|
|
|
if (++$got > $this->ls_MaxBlockSize)
|
|
return self::LSZ_BADCRC;
|
|
|
|
} else {
|
|
switch($c) {
|
|
case self::LSZ_CRCE:
|
|
case self::LSZ_CRCG:
|
|
case self::LSZ_CRCQ:
|
|
case self::LSZ_CRCW:
|
|
$rcvdata = 0;
|
|
$frametype = ($c & 0xff);
|
|
|
|
break;
|
|
|
|
default:
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We finish loop by error in ls_readzdle() */
|
|
if ($rcvdata) {
|
|
Log::error(sprintf('%s:? ls_zrecvdata16 Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
|
|
return $c;
|
|
}
|
|
|
|
/* Loops ar unrolled */
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc = $c;
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc <<= 8;
|
|
$crc |= $c;
|
|
|
|
$incrc = crc16($data.chr($frametype));
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvdata16 CRC%d got %08x - calculated %08x',self::LOGKEY,16,$incrc,$crc));
|
|
|
|
if ($incrc != $crc)
|
|
return self::LSZ_BADCRC;
|
|
|
|
$len = $got;
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:= ls_zrecvdata16 - frametype [%d]',self::LOGKEY,$frametype));
|
|
|
|
return $frametype;
|
|
}
|
|
|
|
/**
|
|
* Receive data subframe with CRC32, return frame type or error (may be -- timeout)
|
|
*
|
|
* @param string $data
|
|
* @param int $len
|
|
* @param int $timeout
|
|
* @return int
|
|
*/
|
|
private function ls_zrecvdata32(string &$data,int &$len,int $timeout): int
|
|
{
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:+ ls_zrecvdata32',self::LOGKEY),['d'=>$data]);
|
|
|
|
$got = 0; /* Bytes total got */
|
|
$crc = 0; /* Received CRC */
|
|
$frametype = self::ERROR; /* Type of frame - ZCRC(G|W|Q|E) */
|
|
$rcvdata = 1; /* Data is being received NOW (not CRC) */
|
|
|
|
while ($rcvdata && (($c=$this->ls_readzdle($timeout)) >= 0)) {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvdata32 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
|
|
|
if ($c < 256) {
|
|
$data .= chr($c&0xff);
|
|
|
|
if (++$got > $this->ls_MaxBlockSize)
|
|
return self::LSZ_BADCRC;
|
|
|
|
} else {
|
|
switch ($c) {
|
|
case self::LSZ_CRCE:
|
|
case self::LSZ_CRCG:
|
|
case self::LSZ_CRCQ:
|
|
case self::LSZ_CRCW:
|
|
$rcvdata = 0;
|
|
$frametype = ($c&0xff);
|
|
|
|
break;
|
|
|
|
default:
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We finish loop by error in ls_readzdle() */
|
|
if ($rcvdata) {
|
|
Log::error(sprintf('%s:? ls_zrecvdata32 Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
|
|
return $c;
|
|
}
|
|
|
|
/* Loops ar unrolled */
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc |= ($c << 0x00);
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc |= ($c << 0x08);
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc |= ($c << 0x10);
|
|
if (($c = $this->ls_readzdle($timeout)) < 0)
|
|
return $c;
|
|
|
|
$crc |= ($c << 0x18);
|
|
|
|
$incrc = crc32($data.chr($frametype));
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvdata32 CRC%d got %08x - calculated %08x',self::LOGKEY,32,$incrc,$crc));
|
|
|
|
if ($incrc != $crc)
|
|
return self::LSZ_BADCRC;
|
|
|
|
$len = $got;
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:= ls_zrecvdata32 - frametype [%d]',self::LOGKEY,$frametype));
|
|
|
|
return $frametype;
|
|
}
|
|
|
|
/**
|
|
* Receive one file
|
|
*
|
|
* @param int $pos
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zrecvfile(int $pos): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zrecvfile - pos [%d]',self::LOGKEY,$pos));
|
|
|
|
$needzdata = 1;
|
|
$len = 0;
|
|
$rxpos = $pos;
|
|
$rxstatus = 0;
|
|
$this->rxbuf = '';
|
|
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRPOS,$this->ls_storelong($rxpos))) < 0)
|
|
return $rc;
|
|
|
|
do {
|
|
if (! $needzdata) {
|
|
switch (($rc=$this->ls_zrecvdata($this->rxbuf,$len,$this->ls_DataTimeout,$this->ls_Protocol&self::LSZ_OPTCRC32))) {
|
|
case self::ZCRCE:
|
|
$needzdata = 1;
|
|
|
|
case self::ZCRCG:
|
|
Log::debug(sprintf('%s:- ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCE ? 'E' : 'G'),$len,$rxpos));
|
|
|
|
$rxpos += $len;
|
|
|
|
if ($len != $this->recv->write($this->rxbuf))
|
|
return self::ZFERR;
|
|
|
|
$this->rxbuf = '';
|
|
|
|
break;
|
|
|
|
case self::ZCRCW:
|
|
$needzdata = 1;
|
|
|
|
case self::ZCRCQ:
|
|
Log::debug(sprintf('%s:- ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCW ? 'W' : 'Q'),$len,$rxpos));
|
|
|
|
$rxpos += $len;
|
|
|
|
if ($len != $this->recv->write($this->rxbuf))
|
|
return self::ZFERR;
|
|
|
|
$this->rxbuf = '';
|
|
|
|
$this->ls_zsendhhdr(self::ZACK,$this->ls_storelong($rxpos));
|
|
|
|
break;
|
|
|
|
case self::LSZ_BADCRC:
|
|
case self::TIMEOUT:
|
|
if ($this->ls_rxAttnStr) {
|
|
$this->client->buffer_add($this->ls_rxAttnStr);
|
|
$this->client->buffer_flush(5);
|
|
}
|
|
|
|
$this->client->rx_purge();
|
|
$this->ls_zsendhhdr(self::ZRPOS,$this->ls_storelong($rxpos));
|
|
$needzdata = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvfile Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
if ($this->ls_rxAttnStr) {
|
|
$this->client->buffer_add($this->ls_rxAttnStr);
|
|
$this->client->buffer_flush(5);
|
|
}
|
|
|
|
$this->client->rx_purge();
|
|
$this->ls_zsendhhdr(self::ZRPOS,$this->ls_storelong($rxpos));
|
|
$needzdata = 1;
|
|
}
|
|
|
|
/* We need new position -- ZDATA (and may be ZEOF) */
|
|
} else {
|
|
Log::debug(sprintf('%s:- ls_zrecvfile Want ZDATA/ZEOF at [%d]',self::LOGKEY,$rxpos));
|
|
|
|
if (($rc=$this->ls_zrecvnewpos($rxpos,$newpos)) < 0)
|
|
return $rc;
|
|
|
|
if ($newpos != $rxpos) {
|
|
Log::error(sprintf('%s:- ls_zrecvfile Bad new position [%d] in [%d]',self::LOGKEY,$newpos,$rc));
|
|
|
|
if ($this->ls_rxAttnStr) {
|
|
$this->client->buffer_add($this->ls_rxAttnStr);
|
|
$this->client->buffer_flush(5);
|
|
}
|
|
|
|
$this->client->rx_purge();
|
|
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRPOS,$this->ls_storelong($rxpos))) < 0)
|
|
return $rc;
|
|
|
|
} else {
|
|
if ($rc === self::ZEOF) {
|
|
Log::debug(sprintf('%s:- ls_zrecvfile ZEOF',self::LOGKEY));
|
|
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRINIT,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
return self::OK;
|
|
}
|
|
|
|
Log::debug(sprintf('%s:- ls_zrecvfile ZDATA',self::LOGKEY));
|
|
$needzdata = 0;
|
|
}
|
|
}
|
|
|
|
if ($rxstatus)
|
|
return ($rxstatus==self::RX_SKIP) ? self::ZSKIP : self::ZFERR;
|
|
|
|
} while (TRUE);
|
|
|
|
return self::OK;
|
|
}
|
|
|
|
/**
|
|
* Prepare to receive next file (and, any be skip or refuse current file)
|
|
*
|
|
* @param int $frame
|
|
* @param int $first
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zrecvfinfo(int $frame,int $first,Address $ao): int
|
|
{
|
|
Log::debug(sprintf('%s:+ Preparing to receive file - Frame [%d], First [%d]',self::LOGKEY,$frame,$first));
|
|
|
|
$trys = 0;
|
|
$retransmit = ($frame != self::ZRINIT);
|
|
$len = 0;
|
|
$rc = 0;
|
|
|
|
/* ZFIN counter -- we will trust only MAY of them on first stage */
|
|
$zfins = 0;
|
|
|
|
$win = $this->ls_txWinSize;
|
|
$flags = (self::LSZ_RXCANDUPLEX|self::LSZ_RXCANOVIO);
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTCRC32)
|
|
$flags |= self::LSZ_RXCANFC32;
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTESCAPEALL)
|
|
$flags |= self::LSZ_RXWNTESCCTL;
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTESC8)
|
|
$flags |= self::LSZ_RXWNTESC8;
|
|
|
|
do {
|
|
if ($retransmit) {
|
|
if ($frame !== self::ZRINIT) {
|
|
if (($rc=$this->ls_zsendhhdr($frame,$this->ls_storelong($this->ls_SerialNum))) < 0)
|
|
return $rc;
|
|
}
|
|
|
|
Log::debug(sprintf('%s:- ZRINIT',self::LOGKEY));
|
|
|
|
$txHdr = [];
|
|
$txHdr[self::LSZ_P0] = ($win&0xff);
|
|
$txHdr[self::LSZ_P1] = (($win>>8)&0xff);
|
|
$txHdr[self::LSZ_F1] = ($this->ls_Protocol&self::LSZ_OPTVHDR) ? self::LSZ_RXCANVHDR : 0;
|
|
$txHdr[self::LSZ_F0] = $flags;
|
|
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRINIT,$txHdr)) < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 0;
|
|
$trys++;
|
|
}
|
|
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
|
/* Send ZRINIT again */
|
|
case self::ZRQINIT:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZRQINIT',self::LOGKEY));
|
|
/* We will trust in first ZFIN after ZRQINIT */
|
|
$first = 1;
|
|
|
|
case self::ZNAK:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZNAK',self::LOGKEY));
|
|
|
|
case self::TIMEOUT:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo TIMEOUT',self::LOGKEY));
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
/* He want to set some options */
|
|
case self::ZSINIT:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZSINIT',self::LOGKEY));
|
|
if (($rc=$this->ls_zrecvcrcw($this->rxbuf,$len)) < 0)
|
|
return $rc;
|
|
|
|
/* We will trust in first ZFIN after ZSINIT */
|
|
$first = 0;
|
|
|
|
/* Everything is OK */
|
|
if (! $rc) {
|
|
$this->ls_zsendhhdr(self::ZACK,$this->ls_storelong(1));
|
|
|
|
$this->ls_rxAttnStr = $this->rxbuf;
|
|
|
|
if ($this->ls_rxHdr[self::LSZ_F0]&self::LSZ_TXWNTESCCTL)
|
|
$this->ls_Protocol |= self::LSZ_OPTESCAPEALL;
|
|
|
|
if ($this->ls_rxHdr[self::LSZ_F0]&self::LSZ_TXWNTESC8)
|
|
$this->ls_Protocol |= self::LSZ_OPTESC8;
|
|
|
|
/* We could not receive ZCRCW subframe, but error is not fatal */
|
|
} else {
|
|
$trys++;
|
|
}
|
|
|
|
break;
|
|
|
|
/* Ok, File started! */
|
|
case self::ZFILE:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZFILE',self::LOGKEY));
|
|
|
|
if (($rc=$this->ls_zrecvcrcw($this->rxbuf,$len)) < 0)
|
|
return $rc;
|
|
|
|
/* Everything is OK, decode frame */
|
|
if (! $rc) {
|
|
$file = [];
|
|
$file['name'] = substr($this->rxbuf,0,$x=strpos($this->rxbuf,chr(0x00)));
|
|
|
|
if (sscanf(substr($this->rxbuf,$x+1),
|
|
'%ld %lo %o %o %ld %ld',
|
|
$file['size'],
|
|
$file['mtime'],
|
|
$len, // @todo What is $len?
|
|
$ls_SerialNum, // @todo Do we use this?
|
|
$filesleft, // @todo Should track this
|
|
$bytesleft) < 2)
|
|
{
|
|
Log::error(sprintf('%s:! ls_zrecvfinfo File info is corrupted [%s]',self::LOGKEY,$this->rxbuf));
|
|
$filesleft = -1;
|
|
|
|
} else {
|
|
$this->recv->new($file,$ao,$this->force_queue);
|
|
}
|
|
|
|
return self::ZFILE;
|
|
|
|
/* We could not receive ZCRCW subframe, but error is not fatal */
|
|
} else {
|
|
$trys++;
|
|
}
|
|
|
|
break;
|
|
|
|
/* ZFIN from previous session? Or may be real one? */
|
|
case self::ZFIN:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZFIN [%d], first [%d]',self::LOGKEY,$zfins,$first));
|
|
|
|
if ($first || (++$zfins === self::LSZ_TRUSTZFINS))
|
|
return self::ZFIN;
|
|
|
|
break;
|
|
|
|
/* Abort this session -- we trust in ABORT! */
|
|
case self::ZABORT:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo ZABORT',self::LOGKEY));
|
|
return self::ZABORT;
|
|
|
|
case self::LSZ_BADCRC:
|
|
Log::debug(sprintf('%s:- ls_zrecvfinfo BADCRC',self::LOGKEY));
|
|
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvfinfo Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:? ls_zrecvfinfo Something strange or timeout',self::LOGKEY));
|
|
return self::TIMEOUT;
|
|
}
|
|
|
|
/**
|
|
* Receive a header
|
|
*
|
|
* @param array $hdr
|
|
* @param int $timeout
|
|
* @return int
|
|
* @throws SocketException
|
|
*/
|
|
private function ls_zrecvhdr(array &$hdr,int $timeout): int
|
|
{
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:+ Receive header with [%d] timeout',self::LOGKEY,$timeout));
|
|
|
|
$state = self::rhInit;
|
|
$readmode = self::rm7BIT;
|
|
|
|
static $frametype = self::ERROR; /* Frame type */
|
|
static $crcl = 2; /* Length of CRC (CRC16 is default) */
|
|
static $crcgot = 0; /* Number of CRC bytes already got */
|
|
static $incrc = 0; /* Calculated CRC */
|
|
static $crc = 0; /* Received CRC */
|
|
static $len = 4; /* Length of header (4 is default) */
|
|
static $got = 0; /* Number of header bytes already got */
|
|
static $garbage = 0; /* Count of garbage characters */
|
|
$c = -1;
|
|
|
|
if ($state === self::rhInit) {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvhdr Init State',self::LOGKEY));
|
|
|
|
$frametype = self::ERROR;
|
|
$crc = 0;
|
|
$crcl = 2;
|
|
$crcgot = 0;
|
|
$incrc = self::LSZ_INIT_CRC16;
|
|
$len = 4;
|
|
$got = 0;
|
|
$readmode = self::rm7BIT;
|
|
}
|
|
|
|
while ($rc=$this->client->hasData($timeout)) {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvhdr hasData - readmode [%d], garbage chars [%d], state [%d]',self::LOGKEY,$readmode,$garbage,$state));
|
|
|
|
switch ($readmode) {
|
|
case self::rm8BIT:
|
|
$c = $this->ls_readcanned($timeout);
|
|
break;
|
|
|
|
case self::rm7BIT:
|
|
$c = $this->ls_read7bit($timeout);
|
|
break;
|
|
|
|
case self::rmZDLE:
|
|
$c = $this->ls_readzdle($timeout);
|
|
break;
|
|
|
|
case self::rmHEX:
|
|
$c = $this->ls_readhex($timeout);
|
|
break;
|
|
}
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvhdr [%x] (%c)',self::LOGKEY,$c,$c));
|
|
|
|
/* Here is error */
|
|
if ($c < 0)
|
|
return $c;
|
|
|
|
/* Strip high bits */
|
|
$c &= 0xff;
|
|
|
|
switch ($state) {
|
|
case self::rhInit:
|
|
if ($c === self::ZPAD)
|
|
$state = self::rhZPAD;
|
|
else
|
|
$garbage++;
|
|
|
|
break;
|
|
|
|
case self::rhZPAD:
|
|
switch ($c) {
|
|
case self::ZPAD:
|
|
break;
|
|
|
|
case self::ZDLE:
|
|
$state = self::rhZDLE;
|
|
break;
|
|
|
|
default:
|
|
$garbage++;
|
|
$state = self::rhInit;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case self::rhZDLE:
|
|
switch ($c) {
|
|
case self::ZBIN:
|
|
$state = self::rhZBIN;
|
|
$readmode = self::rmZDLE;
|
|
break;
|
|
|
|
case self::ZHEX:
|
|
$state = self::rhZHEX;
|
|
$readmode = self::rmHEX;
|
|
break;
|
|
|
|
case self::ZBIN32:
|
|
$state = self::rhZBIN32;
|
|
$readmode = self::rmZDLE;
|
|
break;
|
|
|
|
case self::ZVBIN:
|
|
$state = self::rhZVBIN;
|
|
$readmode = self::rmZDLE;
|
|
break;
|
|
|
|
case self::ZVHEX:
|
|
$state = self::rhZVHEX;
|
|
$readmode = self::rmHEX;
|
|
break;
|
|
|
|
case self::ZVBIN32:
|
|
$state = self::rhZVBIN32;
|
|
$readmode = self::rmZDLE;
|
|
break;
|
|
|
|
default:
|
|
$garbage++;
|
|
$state = self::rhInit;
|
|
$readmode = self::rm7BIT;
|
|
}
|
|
|
|
break;
|
|
|
|
case self::rhZVBIN32:
|
|
$crcl = 4;
|
|
/* Fall throught */
|
|
|
|
case self::rhZVBIN:
|
|
case self::rhZVHEX:
|
|
if ($c > self::LSZ_MAXHLEN) {
|
|
$state = self::rhInit;
|
|
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
|
|
$len = $c;
|
|
$state = self::rhFrameType;
|
|
|
|
break;
|
|
|
|
case self::rhZBIN32:
|
|
$crcl = 4;
|
|
/* Fall throught */
|
|
|
|
case self::rhZBIN:
|
|
case self::rhZHEX:
|
|
$len = 4;
|
|
|
|
case self::rhFrameType:
|
|
if (($c < 0) || ($c > self::LSZ_MAXFRAME)) {
|
|
$state = self::rhInit;
|
|
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
|
|
$frametype = $c;
|
|
$incrc = ($crcl === 2) ? $this->CRC16USD_UPDATE($c,self::LSZ_INIT_CRC16) : $this->CRC32_UPDATE($c,self::LSZ_INIT_CRC32);
|
|
$state = self::rhBYTE;
|
|
|
|
break;
|
|
|
|
case self::rhBYTE:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:/ ls_zrecvhdr [%02x] (%d)',self::LOGKEY,$c,$got));
|
|
|
|
$hdr[$got] = $c;
|
|
|
|
if ($len === ++$got)
|
|
$state = self::rhCRC;
|
|
|
|
$incrc = ($crcl === 2) ? $this->CRC16USD_UPDATE($c,$incrc) : $this->CRC32_UPDATE($c,$incrc);
|
|
|
|
break;
|
|
|
|
case self::rhCRC:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:%% ls_zrecvhdr [%02x] (%d|%d)',self::LOGKEY,$c,$crcgot+1,$got));
|
|
|
|
if ($crcl === 2) {
|
|
$crc <<= 8;
|
|
$crc |= $c;
|
|
|
|
} else {
|
|
$crc |= ($c << ($crcgot*8));
|
|
}
|
|
|
|
/* CRC finished */
|
|
if ($crcl === ++$crcgot) {
|
|
$state = self::rhInit;
|
|
$garbage = 0;
|
|
|
|
if ($crcl === 2) {
|
|
if (($this->ls_Protocol&self::LSZ_OPTCRC32) && ($readmode !== self::rmHEX))
|
|
Log::error(sprintf('%s:! ls_zrecvhdr was CRC32, got CRC16 binary header',self::LOGKEY));
|
|
|
|
$crc &= 0xffff;
|
|
|
|
if ($readmode !== self::rmHEX)
|
|
$this->ls_Protocol &= (~self::LSZ_OPTCRC32);
|
|
|
|
} else {
|
|
if (! ($this->ls_Protocol&self::LSZ_OPTCRC32))
|
|
Log::error(sprintf('%s:! ls_zrecvhdr was CRC16, got CRC32 binary header',self::LOGKEY));
|
|
|
|
$incrc = $this->CRC32_FINISH($incrc);
|
|
$this->ls_Protocol |= self::LSZ_OPTCRC32;
|
|
}
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvhdr CRC%d got [%08x] - calculated [%08x]',self::LOGKEY,(($crcl==2) ? 16 : 32),$incrc,$crc));
|
|
|
|
if ($incrc != $crc)
|
|
return self::LSZ_BADCRC;
|
|
|
|
/* We need to read <CR><LF> after HEX header */
|
|
if ($readmode === self::rmHEX) {
|
|
$state = self::rhCR;
|
|
$readmode = self::rm8BIT;
|
|
|
|
} else {
|
|
return $frametype;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case self::rhCR:
|
|
$state = self::rhInit;
|
|
|
|
switch ($c) {
|
|
/* we need LF after <CR> */
|
|
case self::CR:
|
|
case self::CR|0x80:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:? ls_zrecvhdr rhCR, ignoring any remaining chars [%x]',self::LOGKEY,$c));
|
|
|
|
/* At this point, most implementations ignore checking for the remaining chars */
|
|
return $frametype;
|
|
|
|
/* Ok, UNIX-like EOL */
|
|
case self::LF:
|
|
case self::LF|0x80:
|
|
$state = self::rhXON;
|
|
break;
|
|
|
|
case self::XON:
|
|
case self::XON|0x80:
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:? ls_zrecvhdr rhCR, got XON without CR/LF [%x]',self::LOGKEY,$c));
|
|
|
|
return $frametype;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvhdr Something strange [%x]',self::LOGKEY,$c));
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
|
|
break;
|
|
|
|
case self::rhLF:
|
|
$state = self::rhInit;
|
|
|
|
switch ($c) {
|
|
case self::LF:
|
|
case self::LF|0x80:
|
|
$state = self::rhXON;
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvhdr Something strange [%x]',self::LOGKEY,$c));
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
|
|
break;
|
|
|
|
case self::rhXON:
|
|
$state = self::rhInit;
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zrecvhdr rhXON',self::LOGKEY));
|
|
|
|
switch ($c) {
|
|
case self::ZPAD:
|
|
case self::ZPAD|0x80:
|
|
$state = self::rhZPAD;
|
|
$got = 0;
|
|
$crcgot = 0;
|
|
break;
|
|
|
|
case self::XON:
|
|
case self::XON|0x80:
|
|
return $frametype;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:! ls_zrecvhdr rhXON unexpcted [%x]',self::LOGKEY,$c));
|
|
return self::LSZ_BADCRC;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
Log::error(sprintf('%s:? ls_zrecvhdr Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Internal function -- receive ZCRCW frame in 10 trys, send ZNAK/ZACK
|
|
*
|
|
* @param string $buf
|
|
* @param int $len
|
|
* @return int
|
|
*/
|
|
private function ls_zrecvcrcw(string &$buf,int $len): int
|
|
{
|
|
$trys = 0;
|
|
|
|
do {
|
|
switch (($rc=$this->ls_zrecvdata($buf,$len,$this->ls_DataTimeout,$this->ls_Protocol&self::LSZ_OPTCRC32))) {
|
|
/* Ok, here it is */
|
|
case self::ZCRCW:
|
|
return self::OK;
|
|
|
|
case self::LSZ_BADCRC:
|
|
Log::debug(sprintf('%s:- ls_zrecvcrcw got BADCRC',self::LOGKEY));
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
|
|
return 1;
|
|
|
|
case self::TIMEOUT:
|
|
Log::debug(sprintf('%s:- ls_zrecvcrcw got TIMEOUT',self::LOGKEY));
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvcrcw Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
|
|
return 1;
|
|
}
|
|
|
|
} while (++$trys < 10);
|
|
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Receive ZDATA/ZEOF frame, do 10 trys, return position
|
|
*
|
|
* @param $oldpos
|
|
* @param $pos
|
|
* @return int
|
|
*/
|
|
private function ls_zrecvnewpos($oldpos,&$pos): int
|
|
{
|
|
$rc = 0;
|
|
$trys = 0;
|
|
|
|
do {
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
|
case self::ZDATA:
|
|
case self::ZEOF:
|
|
$pos = $this->ls_fetchlong($this->ls_rxHdr);
|
|
|
|
return $rc;
|
|
|
|
case self::ZNAK:
|
|
if (($rc=$this->ls_zsendhhdr(self::ZRPOS,$this->ls_storelong($oldpos))) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
case self::TIMEOUT:
|
|
break;
|
|
|
|
case self::LSZ_BADCRC:
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zrecvnewpos Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
|
}
|
|
|
|
} while (++$trys < 10);
|
|
|
|
Log::error(sprintf('%s:? ls_zrecvnewpos Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return self::TIMEOUT;
|
|
}
|
|
|
|
/**
|
|
* Internal function to process ZRPOS
|
|
*
|
|
* @param Send $send
|
|
* @param int $newpos
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zrpos(Send $send,int $newpos): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zrpos, newpos [%d]',self::LOGKEY,$newpos));
|
|
|
|
if ($newpos === $this->ls_txLastRepos) {
|
|
if (++$this->ls_txReposCount > 10) {
|
|
Log::error(sprintf('%s:! ZRPOS to [%ld] limit reached',self::LOGKEY,$newpos));
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
} else {
|
|
$this->ls_txReposCount = 0;
|
|
$this->ls_txLastRepos = $newpos;
|
|
}
|
|
|
|
/* Drop window */
|
|
$this->ls_txLastACK = $newpos;
|
|
|
|
if (! $send->seek($newpos)) {
|
|
Log::error(sprintf('%s:! ZRPOS to [%ld] seek error',self::LOGKEY,$send->pos));
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
if ($this->ls_txCurBlockSize > 32)
|
|
$this->ls_txCurBlockSize >>= 1;
|
|
|
|
$this->ls_txGoodBlocks = 0;
|
|
return self::OK;
|
|
}
|
|
|
|
/**
|
|
* Send data block, with CRC16 and framing
|
|
*
|
|
* @param string $data
|
|
* @param int $frame
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zsenddata(string $data,int $frame): int
|
|
{
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:+ ls_zsenddata [%d] (%d)',self::LOGKEY,strlen($data),$frame));
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTCRC32) {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zsenddata CRC32',self::LOGKEY));
|
|
|
|
for ($n=0;$n<strlen($data);$n++)
|
|
$this->ls_sendchar(ord($data[$n]));
|
|
|
|
$this->client->buffer_add(chr(self::ZDLE).chr($frame));
|
|
|
|
/*
|
|
*chat*
|
|
if (FALSE AND $rnode->opt&self::MO_CHAT) {
|
|
if ($frame==self::ZCRCG||$frame==self::ZCRCW) {
|
|
z_devsend_c(1);
|
|
}
|
|
$this->client->buffer_add(chr(0));
|
|
}
|
|
*/
|
|
|
|
$crc = crc32($data.chr($frame));
|
|
$this->ls_sendchar($crc&0xff);
|
|
$crc >>= 8;
|
|
$this->ls_sendchar($crc&0xff);
|
|
$crc >>= 8;
|
|
$this->ls_sendchar($crc&0xff);
|
|
$crc >>= 8;
|
|
$this->ls_sendchar($crc&0xff);
|
|
$crc >>= 8;
|
|
|
|
} else {
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zsenddata CRC16',self::LOGKEY));
|
|
|
|
for ($n=0;$n<strlen($data);$n++) {
|
|
$this->ls_sendchar(ord($data[$n]));
|
|
}
|
|
|
|
$this->client->buffer_add(chr(self::ZDLE).chr($frame));
|
|
|
|
/*
|
|
*chat*
|
|
if (FALSE AND $rnode->opt&self::MO_CHAT) {
|
|
if ($frame==self::ZCRCG||$frame==self::ZCRCW) {
|
|
z_devsend_c(1);
|
|
}
|
|
$this->client->buffer_add(chr(0));
|
|
}
|
|
*/
|
|
|
|
$crc = crc16($data.chr($frame));
|
|
$this->ls_sendchar($crc >> 8);
|
|
$this->ls_sendchar($crc&0xff);
|
|
}
|
|
|
|
if (! ($this->ls_Protocol&self::LSZ_OPTDIRZAP) && self::ZCRCW === $frame)
|
|
$this->client->buffer_add(chr(self::XON));
|
|
|
|
return $this->client->buffer_flush($this->ls_DataTimeout);
|
|
}
|
|
|
|
/**
|
|
* Send binary header. Use proper CRC, send var. len. if could
|
|
*
|
|
* @param int $frametype
|
|
* @param array $hdr
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zsendbhdr(int $frametype,array $hdr): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zsendbhdr',self::LOGKEY));
|
|
|
|
$crc = $this->LSZ_INIT_CRC();
|
|
|
|
/* First, calculate packet header byte */
|
|
if (($type = self::HEADER_TYPE
|
|
[($this->ls_Protocol&self::LSZ_OPTCRC32)==self::LSZ_OPTCRC32]
|
|
[($this->ls_Protocol&self::LSZ_OPTVHDR)==self::LSZ_OPTVHDR]
|
|
[($this->ls_Protocol&self::LSZ_OPTRLE)==self::LSZ_OPTRLE]) < 0)
|
|
{
|
|
return self::ERROR;
|
|
}
|
|
|
|
/* Send *<DLE> and packet type */
|
|
$this->client->buffer_add(chr(self::ZPAD).chr(self::ZDLE).chr($type));
|
|
|
|
/* Send length of header, if needed */
|
|
if ($this->ls_Protocol&self::LSZ_OPTVHDR)
|
|
$this->ls_sendchar(count($hdr));
|
|
|
|
/* Send type of frame */
|
|
$this->ls_sendchar($frametype);
|
|
|
|
$crc = $this->LSZ_UPDATE_CRC($frametype,$crc);
|
|
|
|
/* Send whole header */
|
|
for ($n=0; $n<count($hdr); $n++) {
|
|
$this->ls_sendchar($hdr[$n]);
|
|
$crc = $this->LSZ_UPDATE_CRC($hdr[$n],$crc);
|
|
}
|
|
|
|
$crc = $this->LSZ_FINISH_CRC($crc);
|
|
|
|
if ($this->ls_Protocol&self::LSZ_OPTCRC32) {
|
|
for ($n=0;$n<4;$n++) {
|
|
$this->ls_sendchar($crc&0xff);
|
|
$crc >>= 8;
|
|
}
|
|
|
|
} else {
|
|
$crc &= 0xffff;
|
|
$this->ls_sendchar($crc >> 8);
|
|
$this->ls_sendchar($crc&0xff);
|
|
}
|
|
|
|
/* Clean buffer, do real send */
|
|
return $this->client->buffer_flush($this->ls_HeaderTimeout);
|
|
}
|
|
|
|
/**
|
|
* Send one file to peer
|
|
*
|
|
* @param Send $send
|
|
* @param int $sernum
|
|
* @param int $fileleft
|
|
* @param int $bytesleft
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zsendfile(Send $send,int $sernum,int $fileleft,int $bytesleft): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zsendfile [%s]',self::LOGKEY,$send->nameas));
|
|
|
|
$trys = 0;
|
|
$needack = 0;
|
|
|
|
switch (($rc=$this->ls_zsendfinfo($send,$sernum,$send->pos,$fileleft,$bytesleft))) {
|
|
/* Ok, It's OK! */
|
|
case self::ZRPOS:
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZRPOS to [%d]',self::LOGKEY,$send->pos));
|
|
break;
|
|
|
|
/* Skip it */
|
|
case self::ZSKIP:
|
|
/* Suspend it */
|
|
// @todo Should ZFERR be next to ZABORT?
|
|
case self::ZFERR:
|
|
// @todo Mark the file as skipped
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZSKIP/ZFERR',self::LOGKEY));
|
|
return $rc;
|
|
|
|
case self::ZABORT:
|
|
/* Session is aborted */
|
|
case self::ZFIN:
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZABORT/ZFIN',self::LOGKEY));
|
|
$this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0));
|
|
|
|
return self::ERROR;
|
|
|
|
default:
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
Log::debug(sprintf('%s:- ls_zsendfile Strange answer on ZFILE [%d]',self::LOGKEY,$rc));
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
/* Send file data */
|
|
if ($this->ls_txWinSize)
|
|
$mode = ($this->ls_rxCould&self::LSZ_RXCANDUPLEX) ? self::sfSlidingWindow : self::sfBuffered;
|
|
else
|
|
$mode = self::sfStream;
|
|
|
|
$frame = self::ZCRCW;
|
|
|
|
while (! $send->feof()) {
|
|
/* We need to send ZDATA if previous frame was ZCRCW
|
|
Also, frame will be ZCRCW, if it is after RPOS */
|
|
if ($frame === self::ZCRCW) {
|
|
Log::debug(sprintf('%s:- ls_zsendfile send ZDATA at [%d]',self::LOGKEY,$send->pos));
|
|
|
|
if (($rc=$this->ls_zsendbhdr(self::ZDATA,$this->ls_storelong($send->pos))) < 0)
|
|
return $rc;
|
|
}
|
|
|
|
/* Send frame of data */
|
|
try {
|
|
$txbuf = $send->read($this->ls_txCurBlockSize);
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error(sprintf('%s:! ls_zsendfile Read error',self::LOGKEY));
|
|
|
|
return self::ERROR;
|
|
}
|
|
|
|
/* Select sub-frame type */
|
|
/* This is last sub-frame -- EOF */
|
|
if (strlen($txbuf) < $this->ls_txCurBlockSize) {
|
|
$frame = ($mode === self::sfStream) ? self::ZCRCE : self::ZCRCW;
|
|
|
|
/* This is not-last sub-frame */
|
|
} else {
|
|
switch ($mode) {
|
|
/* Simple sub-frame */
|
|
case self::sfStream:
|
|
$frame = self::ZCRCG;
|
|
break;
|
|
|
|
/* Simple sub-frame, but with SlWin */
|
|
case self::sfSlidingWindow:
|
|
$frame = self::ZCRCQ;
|
|
break;
|
|
|
|
case self::sfBuffered:
|
|
if (($send->pos + strlen($txbuf)) > $this->ls_txLastACK + $this->ls_txWinSize) {
|
|
$frame = self::ZCRCW; /* Last sub-frame in buffer */
|
|
|
|
} else {
|
|
$frame = self::ZCRCG; /* Simple sub-frame */
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (($rc=$this->ls_zsenddata($txbuf,$frame)) < 0)
|
|
return $rc;
|
|
|
|
/* Ok, now wait for ACKs if here is window, or sample for RPOSes */
|
|
$trys = 0;
|
|
|
|
do {
|
|
$needack = (self::ZCRCW === $frame) || ($this->ls_txWinSize && ($send->pos > $this->ls_txLastACK + $this->ls_txWinSize));
|
|
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$needack ? $this->ls_HeaderTimeout : 0))) { // @todo set timeout to 5 for debugging wtih POP
|
|
/* They don't need this file */
|
|
case self::ZSKIP:
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZSKIP',self::LOGKEY));
|
|
|
|
/* Problems occured -- suspend file */
|
|
case self::ZFERR:
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZFERR',self::LOGKEY));
|
|
return $rc;
|
|
|
|
/* Ok, position ACK */
|
|
case self::ZACK:
|
|
$this->ls_txLastACK = $this->ls_fetchlong($this->ls_rxHdr);
|
|
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZACK',self::LOGKEY),['ls_rxHdr'=>$this->ls_rxHdr,'ls_txLastACK'=>$this->ls_txLastACK,'ls_rxHdr'=>$this->ls_rxHdr]);
|
|
|
|
break;
|
|
|
|
/* Repos */
|
|
case self::ZRPOS:
|
|
Log::debug(sprintf('%s:- ZRPOS',self::LOGKEY));
|
|
|
|
if (($rc=$this->ls_zrpos($send,$this->ls_fetchlong($this->ls_rxHdr))) < 0)
|
|
return $rc;
|
|
|
|
Log::debug(sprintf('%s:- ZRPOS [%d]',self::LOGKEY,$rc));
|
|
|
|
/* Force to retransmit ZDATA */
|
|
$frame = self::ZCRCW;
|
|
|
|
break;
|
|
|
|
/* Abort transfer */
|
|
case self::ZABORT:
|
|
/* Strange? Ok, abort too */
|
|
case self::ZFIN:
|
|
/* Abort too */
|
|
case self::ZCAN:
|
|
case self::LSZ_CAN:
|
|
$this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0));
|
|
/* Fall through */
|
|
|
|
case self::RCDO:
|
|
case self::ERROR:
|
|
return self::ERROR;
|
|
|
|
case self::TIMEOUT: /* Ok! */
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zsendfile Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
}
|
|
|
|
} while (
|
|
/* Here is window, and we send more than window without ACK*/
|
|
/* Frame was ZCRCW and here is no ACK for it */
|
|
/* trys less than 10 */
|
|
(($this->ls_txWinSize && ($send->pos>($this->ls_txLastACK+$this->ls_txWinSize)))
|
|
|| ((self::ZCRCW === $frame) && ($send->pos>$this->ls_txLastACK)))
|
|
&& ++$trys < 10);
|
|
|
|
if ($trys >= 10)
|
|
return self::ERROR;
|
|
|
|
/* Ok, increase block, if here is MANY good blocks was sent */
|
|
if (++$this->ls_txGoodBlocks > 32) {
|
|
$this->ls_txCurBlockSize <<= 1;
|
|
|
|
if ($this->ls_txCurBlockSize > $this->ls_MaxBlockSize)
|
|
$this->ls_txCurBlockSize = $this->ls_MaxBlockSize;
|
|
|
|
$this->ls_txGoodBlocks = 0;
|
|
}
|
|
|
|
/* Ok, if here is EOF, send it and wait for ZRINIT or ZRPOS */
|
|
/* We do it here, because we coulde receive ZRPOS as answer */
|
|
if ($send->feof()) {
|
|
if (($rc=$this->ls_zsendhhdr(self::ZEOF,$this->ls_storelong($send->pos))) < 0)
|
|
return $rc;
|
|
|
|
$trys = 0;
|
|
|
|
do {
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
|
/* They don't need this file */
|
|
case self::ZSKIP:
|
|
/* Problems occured -- suspend file */
|
|
case self::ZFERR:
|
|
return $rc;
|
|
|
|
/* Repos */
|
|
case self::ZRPOS:
|
|
if (($rc=$this->ls_zrpos($send,$this->ls_fetchlong($this->ls_rxHdr))) < 0)
|
|
return $rc;
|
|
|
|
/* Force to retransmit ZDATA */
|
|
$frame = self::ZCRCW;
|
|
|
|
break;
|
|
|
|
/* OK! */
|
|
case self::ZRINIT:
|
|
return self::OK;
|
|
|
|
/* ACK for data -- it lost! */
|
|
case self::ZACK:
|
|
Log::debug(sprintf('%s:- ls_zsendfile ZACK after EOF',self::LOGKEY));
|
|
$this->ls_txLastACK = $this->ls_fetchlong($this->ls_rxHdr);
|
|
|
|
break;
|
|
|
|
/* Abort transfer */
|
|
case self::ZABORT:
|
|
/* Strange? Ok, abort too */
|
|
case self::ZFIN:
|
|
/* Abort too */
|
|
case self::ZCAN:
|
|
return self::ERROR;
|
|
|
|
/* Ok, here is no header */
|
|
case self::TIMEOUT:
|
|
$trys++;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? ls_zsendfile Something strange after ZEOF [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
|
|
$trys++;
|
|
}
|
|
|
|
} while ($send->feof() && $trys < 10);
|
|
|
|
if ($send->feof()) {
|
|
Log::error(sprintf('%s:! ls_zsendfile To many tries waiting for ZEOF ACK',self::LOGKEY));
|
|
|
|
return self::ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
Log::error(sprintf('%s:? ls_zsendfile Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/**
|
|
* Send file information to peer, get start position from them.
|
|
* Return packet type -- ZRPOS, ZSKIP, ZFERR, ZABORT or ZFIN (may be any error, too)
|
|
*
|
|
* @param int $sernum
|
|
* @param int $pos
|
|
* @return int
|
|
*/
|
|
private function ls_zsendfinfo(Send $send,int $sernum,int $pos,int $fileleft,int $bytesleft): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zsendfinfo [%s]',self::LOGKEY,$send->nameas));
|
|
|
|
$trys = 0;
|
|
$retransmit = 1;
|
|
$crc = self::LSZ_INIT_CRC32;
|
|
$buf = '';
|
|
|
|
$this->client->tx_purge();
|
|
$buf = $send->nameas.chr(0);
|
|
|
|
$buf .= sprintf('%ld %lo %o %o %ld %ld',
|
|
$send->size,
|
|
$send->mtime,
|
|
0,
|
|
$sernum,
|
|
$fileleft,
|
|
$bytesleft
|
|
);
|
|
|
|
do {
|
|
if ($retransmit) {
|
|
$txHdr = [];
|
|
$txHdr[self::LSZ_F0] = (self::LSZ_CONVBIN|self::LSZ_CONVRECOV);
|
|
$txHdr[self::LSZ_F1] = 0; /* No managment */
|
|
$txHdr[self::LSZ_F2] = 0; /* No compression/encryption */
|
|
$txHdr[self::LSZ_F3] = 0; /* No sparse files or variable headers */
|
|
|
|
if (($rc=$this->ls_zsendbhdr(self::ZFILE,$txHdr)) < 0)
|
|
return $rc;
|
|
|
|
if (($rc=$this->ls_zsenddata($buf,self::ZCRCW)) < 0)
|
|
return $rc;
|
|
|
|
$retransmit = 0;
|
|
$trys++;
|
|
}
|
|
|
|
switch ($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout)) {
|
|
/* Ok, he want our file */
|
|
case self::ZRPOS:
|
|
$pos = $this->ls_fetchlong($this->ls_rxHdr);
|
|
|
|
return self::ZRPOS;
|
|
|
|
/* Skip */
|
|
case self::ZSKIP:
|
|
/* Refuse */
|
|
case self::ZFERR:
|
|
/* Check for double-skip protection */
|
|
$sn = $this->ls_fetchlong($this->ls_rxHdr);
|
|
|
|
/* Here is skip protection */
|
|
if ($this->ls_SkipGuard && $sn && ($sn === $sernum-1)) {
|
|
if (($rc=$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
/* We don't need to skip this file */
|
|
break;
|
|
|
|
} elseif ($sn != $sernum) {
|
|
$this->ls_SkipGuard = 0;
|
|
}
|
|
/* Fall through */
|
|
|
|
/* Abort this session */
|
|
case self::ZABORT:
|
|
/* Finish this session */
|
|
case self::ZFIN:
|
|
return $rc;
|
|
|
|
/* Send CRC to peer */
|
|
case self::ZCRC:
|
|
$len = $this->ls_fetchlong($this->ls_rxHdr);
|
|
if (! $len)
|
|
$len = $send->size;
|
|
|
|
$cnt = 0;
|
|
$send->seek(0);
|
|
|
|
while (($cnt++ < $len) && (($c=$send->read(1)) > 0))
|
|
$crc = $this->CRC32_UPDATE($c,$crc);
|
|
|
|
$crc = $this->CRC32_FINISH($crc);
|
|
if (($rc=$this->ls_zsendhhdr(self::ZCRC,$this->ls_storelong($crc))) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
case self::ZRINIT:
|
|
break;
|
|
|
|
case self::ZNAK:
|
|
case self::TIMEOUT:
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
case self::LSZ_BADCRC:
|
|
if (($rc=$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:? Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
|
|
/* Send HEX header. Use CRC16, send var. len. if could */
|
|
private function ls_zsendhhdr(int $frametype,array $hdr): int
|
|
{
|
|
if (static::DEBUG)
|
|
Log::debug(sprintf('%s:+ ls_zsendhhdr',self::LOGKEY));
|
|
|
|
/* Send **<DLE> */
|
|
$this->client->buffer_add(chr(self::ZPAD).chr(self::ZPAD).chr(self::ZDLE));
|
|
|
|
/* Send header type */
|
|
if ($this->ls_Protocol&self::LSZ_OPTVHDR) {
|
|
$this->client->buffer_add(chr(self::ZVHEX));
|
|
$this->ls_sendhex(count($hdr));
|
|
|
|
} else {
|
|
$this->client->buffer_add(chr(self::ZHEX));
|
|
}
|
|
|
|
$this->ls_sendhex($frametype);
|
|
|
|
/* Send whole header */
|
|
for ($n=0;$n<count($hdr);$n++) {
|
|
$this->ls_sendhex($hdr[$n]);
|
|
}
|
|
|
|
$crc = crc16(chr($frametype).join('',array_map(function($item) { return chr($item); },$hdr)));
|
|
$this->ls_sendhex($crc >> 8);
|
|
$this->ls_sendhex($crc&0xff);
|
|
$this->client->buffer_add(chr(self::CR));
|
|
$this->client->buffer_add(chr(self::LF|0x80));
|
|
|
|
if ($frametype != self::ZACK && $frametype != self::ZFIN)
|
|
$this->client->buffer_add(chr(self::XON));
|
|
|
|
/* Clean buffer, do real send */
|
|
return $this->client->buffer_flush($this->ls_HeaderTimeout);
|
|
}
|
|
|
|
/**
|
|
* Send ZSINIT and wait for ZACK, skip ZRINIT, ZCOMMAND, answer on ZCHALLENGE
|
|
*
|
|
* @param string $attstr
|
|
* @return int
|
|
* @throws \Exception
|
|
*/
|
|
private function ls_zsendsinit(string $attstr): int
|
|
{
|
|
Log::debug(sprintf('%s:+ ls_zsendsinit [%s]',self::LOGKEY,$attrstr));
|
|
|
|
$trys = 0;
|
|
$retransmit = 1;
|
|
|
|
if ($attstr) {
|
|
if (strlen($attstr) > self::LSZ_MAXATTNLEN-1)
|
|
$attstr = substr($attrstr,LSZ_MAXATTNLEN);
|
|
|
|
$this->txbuf = $attrstr;
|
|
|
|
} else {
|
|
$this->txbuf = '';
|
|
}
|
|
|
|
do {
|
|
if (retransmit) {
|
|
/* We don't support ESC8, so don't ask for it in any case */
|
|
$txHdr = [];
|
|
$txHdr[self::LSZ_F0] = ($this->ls_Protocol&self::LSZ_OPTESCAPEALL) ? self::LSZ_TXWNTESCCTL : 0;
|
|
$txHdr[self::LSZ_F1] = $txHdr[self::LSZ_F2] = $txHdr[self::LSZ_F3] = 0;
|
|
|
|
if (($rc=$this->ls_zsendbhdr(self::ZSINIT,$txHdr)) < 0)
|
|
return $rc;
|
|
|
|
if ($rc=$this->ls_zsenddata($this->txbuf,self::ZCRCW))
|
|
return $rc;
|
|
|
|
$retransmit = 0;
|
|
$trys++;
|
|
}
|
|
|
|
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
|
/* Skip it */
|
|
case self::ZRINIT:
|
|
break;
|
|
|
|
/* Ok */
|
|
case self::ZACK:
|
|
return self::OK;
|
|
|
|
/* Return number to peer, he is paranoid */
|
|
case self::ZCHALLENGE:
|
|
if (($rc=$this->ls_zsendhhdr(self::ZACK,$this->ls_rxHdr)) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
case self::LSZ_BADCRC:
|
|
case self::ZCOMMAND:
|
|
if (($rc=$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0))) < 0)
|
|
return $rc;
|
|
|
|
break;
|
|
|
|
/* Retransmit */
|
|
case ZNAK:
|
|
case TIMEOUT:
|
|
$retransmit = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
Log::error(sprintf('%s:? Something strange [%d]',self::LOGKEY,$rc));
|
|
|
|
if ($rc < 0)
|
|
return $rc;
|
|
}
|
|
|
|
} while ($trys < 10);
|
|
|
|
Log::error(sprintf('%s:? Something strange or timeout [%d]',self::LOGKEY,$rc));
|
|
return $rc;
|
|
}
|
|
} |