blob: 5f8b6d92d34cf0fcbebaba7029cc53522c30fb7a [file] [log] [blame]
/****************************************************************************
* apps/system/zmodem/zm_state.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* References:
* "The ZMODEM Inter Application File Transfer Protocol", Chuck Forsberg,
* Omen Technology Inc., October 14, 1988
*
* This is an original work, but I want to make sure that credit is given
* where due: Parts of the state machine design were inspired by the
* Zmodem library of Edward A. Falk, dated January, 1995. License
* unspecified.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ctype.h>
#include <fcntl.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/crc16.h>
#include <nuttx/crc32.h>
#include <nuttx/ascii.h>
#include "zm.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* State-specific data receipt handlers */
static int zm_idle(FAR struct zm_state_s *pzm, uint8_t ch);
static int zm_header(FAR struct zm_state_s *pzm, uint8_t ch);
static int zm_data(FAR struct zm_state_s *pzm, uint8_t ch);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: zm_event
*
* Description:
* This is the heart of the Zmodem state machine. Logic initiated by
* zm_parse() will detect events and, eventually call this function.
* This function will make the state transition, performing any action
* associated with the event.
*
****************************************************************************/
static int zm_event(FAR struct zm_state_s *pzm, int event)
{
FAR const struct zm_transition_s *ptr;
zmdbg("ZM[R|S]_state: %d event: %d\n", pzm->state, event);
/* Look up the entry associated with the event in the current state
* transition table. NOTE that each state table must be terminated with a
* ZME_ERROR entry that provides indicates that the event was not
* expected. Thus, the following search will always be successful.
*/
ptr = pzm->evtable[pzm->state];
while (ptr->type != ZME_ERROR && ptr->type != event)
{
/* Skip to the next entry */
ptr++;
}
zmdbg("Transition ZM[R|S]_state %d->%d discard: %d action: %p\n",
pzm->state, ptr->next, ptr->bdiscard, ptr->action);
/* Perform the state transition */
pzm->state = ptr->next;
/* Discard buffered data if so requested */
if (ptr->bdiscard)
{
pzm->rcvlen = 0;
pzm->rcvndx = 0;
}
/* And, finally, perform the associated action */
return ptr->action(pzm);
}
/****************************************************************************
* Name: zm_nakhdr
*
* Description:
* Send a NAK in response to a malformed or unsupported header.
*
****************************************************************************/
static int zm_nakhdr(FAR struct zm_state_s *pzm)
{
zmdbg("PSTATE %d:%d->%d:%d: NAKing\n",
pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);
/* Revert to the IDLE state */
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
/* And NAK the header */
return zm_sendhexhdr(pzm, ZNAK, g_zeroes);
}
/****************************************************************************
* Name: zm_hdrevent
*
* Description:
* Process an event associated with a header.
*
****************************************************************************/
static int zm_hdrevent(FAR struct zm_state_s *pzm)
{
zmdbg("Received type: %d data: %02x %02x %02x %02x\n",
pzm->hdrdata[0],
pzm->hdrdata[1], pzm->hdrdata[2], pzm->hdrdata[3], pzm->hdrdata[4]);
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);
/* Revert to the IDLE state */
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
/* Verify the checksum. 16- or 32-bit? */
if (pzm->hdrfmt == ZBIN32)
{
uint32_t crc;
/* Checksum is over 9 bytes:
* The header type, 4 data bytes, plus 4 CRC bytes
*/
crc = crc32part(pzm->hdrdata, 9, 0xffffffff);
if (crc != 0xdebb20e3)
{
zmdbg("ERROR: ZBIN32 CRC32 failure: %08x vs debb20e3\n", crc);
return zm_nakhdr(pzm);
}
}
else
{
uint16_t crc;
/* Checksum is over 7 bytes:
* The header type, 4 data bytes, plus 2 CRC bytes
*/
crc = crc16part(pzm->hdrdata, 7, 0);
if (crc != 0)
{
zmdbg("ERROR: ZBIN/ZHEX CRC16 failure: %04x vs 0000\n", crc);
return zm_nakhdr(pzm);
}
}
return zm_event(pzm, pzm->hdrdata[0]);
}
/****************************************************************************
* Name: zm_dataevent
*
* Description:
* Process an event associated with a header.
*
****************************************************************************/
static int zm_dataevent(FAR struct zm_state_s *pzm)
{
zmdbg("Received type: %d length: %d\n", pzm->pkttype, pzm->pktlen);
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);
/* Revert to the IDLE state */
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
/* Verify the checksum. 16- or 32-bit? */
if (pzm->hdrfmt == ZBIN32)
{
uint32_t crc;
crc = crc32part(pzm->pktbuf, pzm->pktlen, 0xffffffff);
if (crc != 0xdebb20e3)
{
zmdbg("ERROR: ZBIN32 CRC32 failure: %08x vs debb20e3\n", crc);
pzm->flags &= ~ZM_FLAG_CRKOK;
}
else
{
pzm->flags |= ZM_FLAG_CRKOK;
}
/* Adjust the back length to exclude the packet type length of the 4-
* byte checksum.
*/
pzm->pktlen -= 5;
}
else
{
uint16_t crc;
crc = crc16part(pzm->pktbuf, pzm->pktlen, 0);
if (crc != 0)
{
zmdbg("ERROR: ZBIN/ZHEX CRC16 failure: %04x vs 0000\n", crc);
pzm->flags &= ~ZM_FLAG_CRKOK;
}
else
{
pzm->flags |= ZM_FLAG_CRKOK;
}
/* Adjust the back length to exclude the packet type length of
* the 2- byte checksum.
*/
pzm->pktlen -= 3;
}
/* Then handle the data received event */
return zm_event(pzm, ZME_DATARCVD);
}
/****************************************************************************
* Name: zm_idle
*
* Description:
* Data has been received in state PSTATE_IDLE. In this state we are
* looking for the beginning of a header indicated by the receipt of
* ZDLE. We skip over ZPAD characters and flush the received buffer in
* the case where anything else is received.
*
****************************************************************************/
static int zm_idle(FAR struct zm_state_s *pzm, uint8_t ch)
{
switch (ch)
{
/* One or more ZPAD characters must precede the ZDLE */
case ZPAD:
{
/* The ZDLE character is expected next */
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZDLE);
pzm->psubstate = PIDLE_ZDLE;
}
break;
/* ZDLE indicates the beginning of a header. */
case ZDLE:
/* Was the ZDLE preceded by ZPAD[s]? If not, revert to the PIDLE_ZPAD
* substate.
*/
if (pzm->psubstate == PIDLE_ZDLE)
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, PSTATE_HEADER, PHEADER_FORMAT);
pzm->flags &= ~ZM_FLAG_OO;
pzm->pstate = PSTATE_HEADER;
pzm->psubstate = PHEADER_FORMAT;
break;
}
else
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);
pzm->psubstate = PIDLE_ZPAD;
}
/* O might be the first character of "OO". "OO" might be part of the
* file receiver protocol. After receiving on e file in a group of
* files, the receiver expected either "OO" indicating that all files
* have been sent, or a ZRQINIT header indicating the start of the next
* file.
*/
case 'O':
/* Is "OO" a possibility in this context? Fall through to the default
* case if not.
*/
if ((pzm->flags & ZM_FLAG_OO) != 0)
{
/* Yes... did we receive an 'O' before this one? */
if (pzm->psubstate == PIDLE_OO)
{
/* This is the second 'O' of "OO". the receiver operation is
* finished.
*/
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);
pzm->flags &= ~ZM_FLAG_OO;
pzm->psubstate = PIDLE_ZPAD;
return zm_event(pzm, ZME_OO);
}
else
{
/* No... then this is the first 'O' that we have seen */
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_OO);
pzm->psubstate = PIDLE_OO;
}
break;
}
/* Unexpected character.
* Wait for the next ZPAD to get us back in sync.
*/
default:
if (pzm->psubstate != PIDLE_ZPAD)
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);
pzm->psubstate = PIDLE_ZPAD;
}
break;
}
return OK;
}
/****************************************************************************
* Name: zm_header
*
* Description:
* Data has been received in state PSTATE_HEADER (i.e., ZDLE was received
* in PSTAT_IDLE).
*
* The following headers are supported:
*
* 16-bit Binary:
* ZPAD ZDLE ZBIN type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2
* Payload length: 7 (type, 4 bytes data, 2 byte CRC)
* 32-bit Binary:
* ZPAD ZDLE ZBIN32 type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2 crc-3 crc-4
* Payload length: 9 (type, 4 bytes data, 4 byte CRC)
* Hex:
* ZPAD ZPAD ZDLE ZHEX type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2 CR LF [XON]
* Payload length: 16 (14 hex digits, cr, lf, ignoring optional XON)
*
****************************************************************************/
static int zm_header(FAR struct zm_state_s *pzm, uint8_t ch)
{
/* ZDLE encountered in this state means that the following character is
* escaped.
*/
if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
{
/* Indicate that we are beginning the escape sequence and return */
pzm->flags |= ZM_FLAG_ESC;
return OK;
}
/* Handle the escaped character in an escape sequence */
if ((pzm->flags & ZM_FLAG_ESC) != 0)
{
switch (ch)
{
/* Two special cases */
case ZRUB0:
ch = ASCII_DEL;
break;
case ZRUB1:
ch = 0xff;
break;
/* The typical case: Toggle bit 6 */
default:
ch ^= 0x40;
break;
}
/* We are no longer in an escape sequence */
pzm->flags &= ~ZM_FLAG_ESC;
}
/* Now handle the next character, escaped or not, according to the current
* PSTATE_HEADER substate.
*/
switch (pzm->psubstate)
{
/* Waiting for the header format {ZBIN, ZBIN32, ZHEX} */
case PHEADER_FORMAT:
{
switch (ch)
{
/* Supported header formats */
case ZHEX:
case ZBIN:
case ZBIN32:
{
/* Save the header format character. Next we expect the header
* data payload beginning with the header type.
*/
pzm->hdrfmt = ch;
pzm->psubstate = PHEADER_PAYLOAD;
pzm->hdrndx = 0;
}
break;
default:
{
/* Unrecognized header format. */
return zm_nakhdr(pzm);
}
}
}
break;
/* Waiting for header payload */
case PHEADER_PAYLOAD:
{
int ndx = pzm->hdrndx;
switch (pzm->hdrfmt)
{
/* Supported header formats */
case ZHEX:
{
if (!isxdigit(ch))
{
return zm_nakhdr(pzm);
}
/* Save the MS nibble; setup to receive the LS nibble. Index
* is not incremented.
*/
pzm->hdrdata[ndx] = zm_decnibble(ch) << 4;
pzm->psubstate = PHEADER_LSPAYLOAD;
}
break;
case ZBIN:
case ZBIN32:
{
/* Save the payload byte and increment the index. */
pzm->hdrdata[ndx] = ch;
ndx++;
/* Check if the full header payload has bee buffered.
*
* The ZBIN format uses 16-bit CRC so the binary length of the
* full payload is 1+4+2 = 7 bytes; the ZBIN32 uses a 32-bit
* CRC so the binary length of the payload is 1+4+4 = 9 bytes;
*/
if (ndx >= 9 || (pzm->hdrfmt == ZBIN && ndx >= 7))
{
return zm_hdrevent(pzm);
}
else
{
/* Setup to receive the next byte */
pzm->psubstate = PHEADER_PAYLOAD;
pzm->hdrndx = ndx;
}
}
break;
default: /* Should not happen */
break;
}
}
break;
/* Waiting for LS nibble header type (ZHEX only) */
case PHEADER_LSPAYLOAD:
{
int ndx = pzm->hdrndx;
if (pzm->hdrfmt == ZHEX && isxdigit(ch))
{
/* Save the LS nibble and increment the index. */
pzm->hdrdata[ndx] |= zm_decnibble(ch);
ndx++;
/* The ZHEX format uses 16-bit CRC. So the binary length
* of the sequence is 1+4+2 = 7 bytes.
*/
if (ndx >= 7)
{
return zm_hdrevent(pzm);
}
else
{
/* Setup to receive the next MS nibble */
pzm->psubstate = PHEADER_PAYLOAD;
pzm->hdrndx = ndx;
}
}
else
{
return zm_nakhdr(pzm);
}
}
break;
}
return OK;
}
/****************************************************************************
* Name: zm_data
*
* Description:
* Data has been received in state PSTATE_DATA. PSTATE_DATA is set by
* Zmodem transfer logic when it expects to received data from the
* remote peer.
*
* FORMAT:
* xx xx xx xx ... xx ZDLE <type> crc-1 crc-2 [crc-3 crc-4]
*
* Where xx is binary data (that may be escaped). The 16- or 32-bit CRC
* is selected based on a preceding header. ZHEX data packets are not
* supported.
*
* When setting pstate to PSTATE_DATA, it is also expected that the
* following initialization is performed:
*
* - The crc value is initialized appropriately
* - ncrc is set to zero.
* - pktlen is set to zero
*
****************************************************************************/
static int zm_data(FAR struct zm_state_s *pzm, uint8_t ch)
{
int ret;
/* ZDLE encountered in this state means that the following character is
* escaped. Escaped characters may appear anywhere within the data packet.
*/
if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
{
/* Indicate that we are beginning the escape sequence and return */
pzm->flags |= ZM_FLAG_ESC;
return OK;
}
/* Make sure that there is space for another byte in the packet buffer */
if (pzm->pktlen >= ZM_PKTBUFSIZE)
{
zmdbg("ERROR: The packet buffer is full\n");
zmdbg(" ch=%c[%02x] pktlen=%d pkttype=%02x ncrc=%d\n",
isprint(ch) ? ch : '.', ch, pzm->pktlen,
pzm->pkttype, pzm->ncrc);
zmdbg(" rcvlen=%d rcvndx=%d\n",
pzm->rcvlen, pzm->rcvndx);
return -ENOSPC;
}
/* Handle the escaped character in an escape sequence */
if ((pzm->flags & ZM_FLAG_ESC) != 0)
{
switch (ch)
{
/* The data packet type may immediately follow the ZDLE in PDATA_READ
* substate.
*/
case ZCRCW: /* Data packet (Non-streaming, ZACK response expected) */
case ZCRCE: /* Data packet (End-of-file, no response unless an error occurs) */
case ZCRCG: /* Data packet (Full streaming, no response) */
case ZCRCQ: /* Data packet (ZACK response expected) */
{
/* Save the packet type, change substates, and set of count that
* indicates the number of bytes still to be added to the packet
* buffer:
*
* ZBIN: 1+2 = 3
* ZBIN32: 1+4 = 5
*/
pzm->pkttype = ch;
pzm->psubstate = PDATA_CRC;
pzm->ncrc = (pzm->hdrfmt == ZBIN32) ? 5 : 3;
}
break;
/* Some special cases */
case ZRUB0:
ch = ASCII_DEL;
break;
case ZRUB1:
ch = 0xff;
break;
/* The typical case: Toggle bit 6 */
default:
ch ^= 0x40;
break;
}
/* We are no longer in an escape sequence */
pzm->flags &= ~ZM_FLAG_ESC;
}
/* Transfer received data from the I/O buffer to the packet buffer.
* Accumulate the CRC for the received data. This includes the data
* payload plus the packet type code plus the CRC itself.
*/
pzm->pktbuf[pzm->pktlen++] = ch;
if (pzm->ncrc == 1)
{
/* We are at the end of the packet.
* Check the CRC and post the event
*/
ret = zm_dataevent(pzm);
/* The packet data has been processed. Discard the old buffered
* packet data.
*/
pzm->pktlen = 0;
pzm->ncrc = 0;
return ret;
}
else if (pzm->ncrc > 1)
{
/* We are still parsing the CRC. Decrement the count of CRC bytes
* remaining.
*/
pzm->ncrc--;
}
return OK;
}
/****************************************************************************
* Name: zm_parse
*
* Description:
* New data from the remote peer is available in pzm->rcvbuf. The number
* number of bytes of new data is given by rcvlen.
*
* This function will parse the data in the buffer and, based on the
* current state and the contents of the buffer, will drive the Zmodem
* state machine.
*
****************************************************************************/
static int zm_parse(FAR struct zm_state_s *pzm, size_t rcvlen)
{
uint8_t ch;
int ret;
DEBUGASSERT(pzm && rcvlen <= CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE);
zm_dumpbuffer("Received", pzm->rcvbuf, rcvlen);
/* We keep a copy of the length and buffer index in the state structure.
* This is only so that deeply nested logic can use these values.
*/
pzm->rcvlen = rcvlen;
pzm->rcvndx = 0;
/* Process each byte until we reach the end of the buffer (or until the
* data is discarded.
*/
while (pzm->rcvndx < pzm->rcvlen)
{
/* Get the next byte from the buffer */
ch = pzm->rcvbuf[pzm->rcvndx];
pzm->rcvndx++;
/* Handle sequences of CAN characters. When we encounter 5 in a row,
* then we consider this a request to cancel the file transfer.
*/
if (ch == ASCII_CAN)
{
if (++pzm->ncan >= 5)
{
zmdbg("Remote end has canceled\n");
pzm->rcvlen = 0;
pzm->rcvndx = 0;
return zm_event(pzm, ZME_CANCEL);
}
}
else
{
/* Not CAN... reset the sequence count */
pzm->ncan = 0;
}
/* Skip over XON and XOFF */
if (ch != ASCII_XON && ch != ASCII_XOFF)
{
/* And process what follows based on the current parsing state */
switch (pzm->pstate)
{
case PSTATE_IDLE:
ret = zm_idle(pzm, ch);
break;
case PSTATE_HEADER:
ret = zm_header(pzm, ch);
break;
case PSTATE_DATA:
ret = zm_data(pzm, ch);
break;
/* This should not happen */
default:
zmdbg("ERROR: Invalid state: %d\n", pzm->pstate);
ret = -EINVAL;
break;
}
/* Handle end-of-transfer and irrecoverable errors by breaking out
* of the loop and return a non-zero return value to indicate that
* transfer is complete.
*/
if (ret != OK)
{
zmdbg("%s: %d\n", ret < 0 ? "Aborting" : "Done", ret);
return ret;
}
}
}
/* If we made it through the entire buffer with no errors detected, then
* return OK == 0 meaning that everything is okay, but we are not finished
* with the transfer.
*/
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: zm_datapump
*
* Description:
* Drive the Zmodem state machine by reading data from the remote peer and
* providing that data to the parser. This loop runs until a fatal error
* is detected or until the state machine reports that the transfer has
* completed successfully.
*
****************************************************************************/
int zm_datapump(FAR struct zm_state_s *pzm)
{
int ret = OK;
ssize_t nread;
/* Loop until either a read error occurs or until a non-zero value is
* returned by the parser.
*/
do
{
/* Start/restart the timer. Whenever we read data from the peer we
* must anticipate a timeout because we can never be sure that the peer
* is still responding.
*/
sched_lock();
zm_timerstart(pzm, pzm->timeout);
/* Read a block of data. read() will return: (1) nread > 0 and nread
* <= CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE on success, (2) nread == 0 on end
* of file, or (3) nread < 0 on a read error or interruption by a
* signal.
*/
nread = read(pzm->remfd, pzm->rcvbuf, CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE);
/* Stop the timer */
zm_timerstop(pzm);
sched_unlock();
/* EOF from the remote peer can only mean that we lost the connection
* somehow.
*/
if (nread == 0)
{
zmdbg("ERROR: Unexpected end-of-file\n");
return -ENOTCONN;
}
/* Did some error occur? */
else if (nread < 0)
{
int errorcode = errno;
/* EINTR is not an error... it simply means that this read was
* interrupted by an signal before it obtained in data. However,
* the signal may be SIGALRM indicating an timeout condition.
* We will know in this case because the signal handler will set
* ZM_FLAG_TIMEOUT.
*/
if (errorcode == EINTR)
{
/* Check for a timeout */
if ((pzm->flags & ZM_FLAG_TIMEOUT) != 0)
{
/* Yes... a timeout occurred */
ret = zm_timeout(pzm);
}
/* No.. then just ignore the EINTR. */
}
else
{
/* But anything else is bad and we will return the failure
* in those cases.
*/
zmdbg("ERROR: read failed: %d\n", errorcode);
return -errorcode;
}
}
/* Then provide that data to the state machine via zm_parse().
* zm_parse() will return a non-zero value if we need to terminate
* the loop (with a negative value indicating a failure).
*/
else /* nread > 0 */
{
ret = zm_parse(pzm, nread);
if (ret < 0)
{
zmdbg("ERROR: zm_parse failed: %d\n", ret);
}
}
}
while (ret == OK);
return ret;
}
/****************************************************************************
* Name: zm_readstate
*
* Description:
* Enter PSTATE_DATA.
*
****************************************************************************/
void zm_readstate(FAR struct zm_state_s *pzm)
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, PSTATE_DATA, PDATA_READ);
pzm->pstate = PSTATE_DATA;
pzm->psubstate = PDATA_READ;
pzm->pktlen = 0;
pzm->ncrc = 0;
}
/****************************************************************************
* Name: zm_timeout
*
* Description:
* Called by the watchdog logic if/when a timeout is detected.
*
****************************************************************************/
int zm_timeout(FAR struct zm_state_s *pzm)
{
return zm_event(pzm, ZME_TIMEOUT);
}