<?php namespace App\Classes\Protocol; use Illuminate\Support\Facades\Log; use App\Classes\Protocol; use App\Classes\Protocol\Zmodem as ZmodemClass; use App\Classes\File\{Receive,Send}; use App\Classes\Sock\{SocketClient,SocketException}; use App\Interfaces\CRC as CRCInterface; use App\Interfaces\Zmodem as ZmodemInterface; use App\Models\Address; use App\Traits\CRC as CRCTrait; 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; /* Ctrl-X ZmodemReceive escape - `ala BISYNC DLE */ 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 = 0; /* Request receive init */ private const ZRINIT = 1; /* Receive init */ private const ZSINIT = 2; /* Send init sequence (optional) */ private const ZACK = 3; /* ACK to above */ private const ZFILE = 4; /* File name from sender */ public const ZSKIP = 5; /* To sender: skip this file */ private const ZNAK = 6; /* Last packet was garbled */ private const ZABORT = 7; /* Abort batch transfers */ private const ZFIN = 8; /* Finish session */ private const ZRPOS = 9; /* Resume data trans at this position */ private const ZDATA = 10; /* Data packet(s) follow */ private const ZEOF = 11; /* End of file */ public const ZFERR = 12; /* Fatal Read or Write error Detected (we use for refuse -- suspend) */ private const ZCRC = 13; /* Request for file CRC and response */ private const ZCHALLENGE = 14; /* Receiver's Challenge */ private const ZCOMPL = 15; /* Request is complete */ private const ZCAN = 16; /* Other end canned session with CAN*5 */ private const ZFREECNT = 17; /* Request for free bytes on filesystem (OK, we always send unlimited) */ private const ZCOMMAND = 18; /* Command from sending program (NOT SUPPORTED!) */ private const ZSTDERR = 19; /* 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_OK = self::OK; /* 0 */ private const LSZ_TIMEOUT = self::TIMEOUT; /* -2 */ private const LSZ_RCDO = self::RCDO; /* -3 */ private const LSZ_CAN = self::GCOUNT; /* -4 */ private const LSZ_ERROR = self::ERROR; /* -5 */ 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(self::SESSION_ZMODEM,$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: ! init Strange cancap [%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 * * @return int * @throws \Exception */ public function protocol_session(): 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->total_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): int { Log::debug(sprintf('%s:+ 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)) { case self::ZFIN: Log::debug(sprintf('%s:= zmodem_receive ZFIN after INIT, empty batch',self::LOGKEY)); $this->ls_zdonereceiver(); return self::LSZ_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::LSZ_ERROR; } while (TRUE) { switch ($rc) { case self::ZFIN: Log::debug(sprintf('%s:= zmodem_receive ZFIN',self::LOGKEY)); $this->ls_zdonereceiver(); return self::LSZ_OK; case self::ZFILE: if (! $this->recv->to_get) { Log::error(sprintf('%s: ! zmodem_receive No files to get?',self::LOGKEY)); $frame = self::ZSKIP; } else { switch ($this->recv->open($ao)) { case self::FOP_SKIP: Log::info(sprintf('%s: = zmodem_receive Skip this file [%s]',self::LOGKEY,$this->recv->name)); $frame = self::ZSKIP; break; case self::FOP_SUSPEND: Log::info(sprintf('%s: = zmodem_receive Suspend this file [%s]',self::LOGKEY,$this->recv->name)); $frame = self::ZFERR; break; case self::FOP_CONT: case self::FOP_OK: Log::info(sprintf('%s: = zmodem_receive Receving [%s] from [%d]',self::LOGKEY,$this->recv->name,$this->recv->filepos)); $frame = self::ZRINIT; switch (($rc=$this->ls_zrecvfile($recv->filepos))) { 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::LSZ_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::LSZ_ERROR; } break; } } break; case self::ZABORT: Log::debug(sprintf('%s:= zmodem_receive ZABORT',self::LOGKEY)); $this->ls_zabort(); $this->ls_zdonereceiver(); return self::LSZ_ERROR; default: Log::error(sprintf('%s:? zmodem_receive Something strange [%d]',self::LOGKEY,$rc)); $this->ls_zabort(); $this->ls_zdonereceiver(); return self::LSZ_ERROR; } $rc = $this->ls_zrecvfinfo($frame,1); } return self::LSZ_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::LSZ_OK; case self::ZNAK: case self::LSZ_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): int { Log::debug(sprintf('%s:+ zmodem_sendfile',self::LOGKEY)); while ($send->total_count && $send->open()) { try { $rc = $this->ls_zsendfile($send,$this->ls_SerialNum++,$send->total_count,$send->total_size); switch ($rc) { case self::LSZ_OK: $send->close(TRUE); break; case self::ZSKIP: case self::ZFERR: $send->close(FALSE); break; default: $send->close(FALSE); $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::LSZ_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:+ ls_zdonereceiver',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::LSZ_OK; case self::XON: case self::XOFF: case self::XON|0x80: case self::XOFF|0x80: if ($this->DEBUG) Log::debug(sprintf('%s: - ls_zdonereceiver XON/XOFF, skip it',self::LOGKEY)); break; case self::ZPAD: if ($this->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::LSZ_OK; $retransmit = 1; break; default: Log::error(sprintf('%s: ? ls_zdonereceiver Something strange [%d]',self::LOGKEY,$rc)); if ($rc < 0) return $rc; $retransmit = 1; } } while ($trys < 10); Log::error(sprintf('%s:? ls_zdonereceiver Something strange [%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::LSZ_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::LSZ_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::LSZ_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::LSZ_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 ($this->DEBUG) Log::debug(sprintf('%s:+ ls_zrecvdata16',self::LOGKEY),['d'=>$data]); $got = 0; /* Bytes total got */ $crc = 0; /* Received CRC */ $frametype = self::LSZ_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 ($this->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 ($this->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 ($this->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 ($this->DEBUG) Log::debug(sprintf('%s:+ ls_zrecvdata32',self::LOGKEY),['d'=>$data]); $got = 0; /* Bytes total got */ $crc = 0; /* Received CRC */ $frametype = self::LSZ_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 ($this->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 ($this->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 ($this->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::LSZ_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::LSZ_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::LSZ_OK; } /** * @param int $frame * @param int $first * @return int * @throws \Exception */ private function ls_zrecvfinfo(int $frame,int $first): int { Log::debug(sprintf('%s:+ ls_zrecvfinfo - 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: - ls_zrecvfinfo 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::LSZ_TIMEOUT: Log::debug(sprintf('%s: - ls_zrecvfinfo LSZ_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); } 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::LSZ_TIMEOUT; } private function ls_zrecvhdr(array &$hdr,int $timeout): int { if ($this->DEBUG) Log::debug(sprintf('%s:+ ls_zrecvhdr with [%d] timeout',self::LOGKEY,$timeout)); $state = self::rhInit; $readmode = self::rm7BIT; static $frametype = self::LSZ_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 ($this->DEBUG) Log::debug(sprintf('%s: - ls_zrecvhdr Init State',self::LOGKEY)); $frametype = self::LSZ_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 ($this->DEBUG) Log::debug(sprintf('%s: - ls_zrecvhdr hasData - readmode [%d] - Garbage [%d]',self::LOGKEY,$readmode,$garbage)); 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 ($this->DEBUG) Log::debug(sprintf('%s: - ls_zrecvhdr %s [%x] (%c)',self::LOGKEY,$readmode,$c,$c)); /* Here is error */ if ($c < 0) return $c; /* Strip high bits */ $c &= 0xff; if ($this->DEBUG) Log::debug(sprintf('%s: = ls_zrecvhdr %x (%d)',self::LOGKEY,$c,$state)); 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 ($this->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 ($this->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 ($this->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 ($this->DEBUG) Log::debug(sprintf('%s:? ls_zrecvhdr rhCR, ignoring any remaining chars [%d]',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 ($this->DEBUG) Log::debug(sprintf('%s:? ls_zrecvhdr rhCR, got XON without CR/LF [%d]',self::LOGKEY,$c)); return $frametype; default: Log::error(sprintf('%s:? ls_zrecvhdr Something strange [%d]',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 [%d]',self::LOGKEY,$c)); return self::LSZ_BADCRC; } break; case self::rhXON: $state = self::rhInit; if ($this->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 [%d]',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::LSZ_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::LSZ_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::LSZ_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::LSZ_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::LSZ_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->filepos)); return self::LSZ_ERROR; } if ($this->ls_txCurBlockSize > 32) $this->ls_txCurBlockSize >>= 1; $this->ls_txGoodBlocks = 0; return self::LSZ_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 ($this->DEBUG) Log::debug(sprintf('%s:+ ls_zsenddata [%d] (%d)',self::LOGKEY,strlen($data),$frame)); if ($this->ls_Protocol&self::LSZ_OPTCRC32) { if ($this->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 ($this->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::LSZ_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->name)); $trys = 0; $needack = 0; switch (($rc = $this->ls_zsendfinfo($send,$sernum,$send->filepos,$fileleft,$bytesleft))) { /* Ok, It's OK! */ case self::ZRPOS: Log::debug(sprintf('%s: - ls_zsendfile ZRPOS to [%d]',self::LOGKEY,$send->filepos)); break; /* Skip it */ case self::ZSKIP: /* Suspend it */ 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::LSZ_ERROR; default: if ($rc < 0) return $rc; Log::debug(sprintf('%s:- ls_zsendfile Strange answer on ZFILE [%d]',self::LOGKEY,$rc)); return self::LSZ_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->filepos)); if (($rc=$this->ls_zsendbhdr(self::ZDATA,$this->ls_storelong($send->filepos))) < 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::LSZ_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->filepos + 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->filepos > $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 ($this->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::LSZ_RCDO: case self::LSZ_ERROR: return self::LSZ_ERROR; case self::LSZ_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->filepos>($this->ls_txLastACK+$this->ls_txWinSize))) || ((self::ZCRCW == $frame) && ($send->filepos>$this->ls_txLastACK))) && ++$trys < 10); if ($trys >= 10) return self::LSZ_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->filepos))) < 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::LSZ_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::LSZ_ERROR; /* Ok, here is no header */ case self::LSZ_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::LSZ_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->name)); $trys = 0; $retransmit = 1; $crc = self::LSZ_INIT_CRC32; $buf = ''; $this->client->buffer_clear(); $buf = $send->sendas.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::LSZ_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 ($this->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::LSZ_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 LSZ_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; } }