rt-thread/components/utilities/zmodem/zcore.c

887 lines
21 KiB
C
Raw Normal View History

/*
2013-01-08 21:05:02 +08:00
* File : rz.c
* the core functions of implementing zmodem protocol
* Change Logs:
* Date Author Notes
2021-03-08 18:19:04 +08:00
* 2011-03-29 itspy
2013-01-08 21:05:02 +08:00
*/
#include <rtthread.h>
#include <finsh.h>
#include <shell.h>
#include <rtdef.h>
#include <dfs.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/statfs.h>
2013-01-08 21:05:02 +08:00
#include <stdio.h>
#include "zdef.h"
2021-03-08 18:19:04 +08:00
char ZF0_CMD; /* file conversion request */
char ZF1_CMD; /* file management request */
char ZF2_CMD; /* file transport request */
char ZF3_CMD;
rt_uint8_t Rxframeind; /* ZBIN ZBIN32, or ZHEX type of frame */
rt_uint16_t Rxcount; /* received count*/
char header_type; /* header type */
rt_uint8_t rx_header[4]; /* received header */
rt_uint8_t tx_header[4]; /* transmitted header */
rt_uint32_t Rxpos; /* received file position */
rt_uint32_t Txpos; /* transmitted file position */
rt_uint8_t Txfcs32; /* TURE means send binary frames with 32 bit FCS */
rt_uint8_t TxCRC; /* controls 32 bit CRC being sent */
rt_uint8_t RxCRC; /* indicates/controls 32 bit CRC being received */
/* 0 == CRC16, 1 == CRC32, 2 == CRC32 + RLE */
2021-03-08 18:19:04 +08:00
char Attn[ZATTNLEN+1]; /* attention string rx sends to tx on err */
2013-01-08 21:05:02 +08:00
void zinit_parameter(void);
void zsend_bin_header(rt_uint8_t type, rt_uint8_t *hdr);
void zsend_hex_header(rt_uint8_t type, rt_uint8_t *hdr);
void zsend_bin_data(rt_uint8_t *buf, rt_int16_t len, rt_uint8_t frameend);
static rt_int16_t zrec_data16(rt_uint8_t *buf, rt_uint16_t len);
static rt_int16_t zrec_data32(rt_uint8_t *buf, rt_int16_t len);
static rt_int16_t zrec_data32r(rt_uint8_t *buf, rt_int16_t len);
rt_int16_t zget_data(rt_uint8_t *buf, rt_uint16_t len);
rt_int16_t zget_header(rt_uint8_t *hdr);
static rt_int16_t zget_bin_header(rt_uint8_t *hdr);
static rt_int16_t zget_bin_fcs(rt_uint8_t *hdr);
rt_int16_t zget_hex_header(rt_uint8_t *hdr);
static void zsend_ascii(rt_uint8_t c);
void zsend_zdle_char(rt_uint16_t ch);
static rt_int16_t zget_hex(void);
rt_int16_t zread_byte(void);
rt_int16_t zxor_read(void);
void zput_pos(rt_uint32_t pos);
void zget_pos(rt_uint32_t pos);
2021-03-08 18:19:04 +08:00
2013-01-08 21:05:02 +08:00
void zinit_parameter(void)
{
rt_uint8_t i;
2021-03-08 18:19:04 +08:00
ZF0_CMD = CANFC32|CANFDX|CANOVIO; /* not chose CANFC32,CANRLE,although it have been supported */
ZF1_CMD = 0; /* fix header length,not support CANVHDR */
ZF2_CMD = 0;
ZF3_CMD = 0;
Rxframeind =0;
header_type = 0;
Rxcount = 0;
for (i=0;i<4;i++) rx_header[i] = tx_header[i] = 0;
Rxpos = Txpos = 0;
RxCRC = 0;
Txfcs32 = 0;
return ;
2013-01-08 21:05:02 +08:00
}
/* send binary header */
void zsend_bin_header(rt_uint8_t type, rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_uint8_t i;
rt_uint32_t crc;
zsend_byte(ZPAD);
zsend_byte(ZDLE);
TxCRC = Txfcs32;
if (TxCRC == 0)
{
zsend_byte(ZBIN);
zsend_zdle_char(type);
/* add 16bits crc */
crc = 0L;
crc = updcrc16(type, 0);
for (i=0;i<4;i++)
{
zsend_zdle_char(*hdr);
crc = updcrc16((0377 & *hdr++),crc);
}
crc = updcrc16(0,updcrc16(0,crc));
zsend_zdle_char(((int)(crc>>8)));
zsend_zdle_char(crc);
}
else if(TxCRC == 1)
{
2013-01-08 21:05:02 +08:00
zsend_byte(ZBIN32);
2021-03-08 18:19:04 +08:00
zsend_zdle_char(type);
/* add 32bits crc */
crc = 0xffffffffL;
crc = updcrc32(type, crc);
2013-01-08 21:05:02 +08:00
for (i=0;i<4;i++)
2021-03-08 18:19:04 +08:00
{
zsend_zdle_char(*hdr);
crc = updcrc32((0377 & *hdr++), crc);
}
crc = ~crc;
for (i=0; i<4;i++)
{
zsend_zdle_char(crc);
crc >>= 8;
}
}
else if (TxCRC == 2)
{
zsend_byte(ZBINR32);
zsend_zdle_char(type);
/* add 32bits crc */
crc = 0xffffffffL;
crc = updcrc32(type, crc);
2013-01-08 21:05:02 +08:00
for (i=0;i<4;i++)
2021-03-08 18:19:04 +08:00
{
zsend_zdle_char(*hdr);
crc = updcrc32((0377 & *hdr++), crc);
}
crc = ~crc;
for (i=0; i<4;i++)
{
zsend_zdle_char(crc);
crc >>= 8;
}
}
return;
}
/* send hex header */
void zsend_hex_header(rt_uint8_t type, rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_uint8_t i;
rt_uint16_t crc;
zsend_line(ZPAD); zsend_line(ZPAD); zsend_line(ZDLE);
zsend_line(ZHEX);
zsend_ascii(type);
crc = updcrc16(type, 0);
for (i=0; i<4; i++)
{
zsend_ascii(*hdr);
crc = updcrc16((0377 & *hdr++), crc);
}
crc = updcrc16(0,updcrc16(0,crc));
zsend_ascii(crc>>8);
zsend_ascii(crc);
/* send display control cmd */
zsend_line(015); zsend_line(0212);
if (type != ZFIN && type != ZACK)
zsend_line(021);
TxCRC = 0; /* clear tx crc type */
return;
}
/* send binary data,with frameend */
void zsend_bin_data(rt_uint8_t *buf, rt_int16_t len, rt_uint8_t frameend)
2013-01-08 21:05:02 +08:00
{
rt_int16_t i,c,tmp;
2021-03-08 18:19:04 +08:00
rt_uint32_t crc;
2013-01-08 21:05:02 +08:00
if (TxCRC == 0) /* send binary data with 16bits crc check */
2021-03-08 18:19:04 +08:00
{
crc = 0x0L;
for (i=0;i<len;i++)
{
zsend_zdle_char(*buf);
crc = updcrc16((0377 & *buf++), crc);
}
zsend_byte(ZDLE); zsend_byte(frameend);
crc = updcrc16(frameend, crc);
crc = updcrc16(0,updcrc16(0,crc));
zsend_zdle_char(crc>>8);
zsend_zdle_char(crc);
}
else if (TxCRC == 1) /* send binary data with 32 bits crc check */
{
crc = 0xffffffffL;
for (i=0;i<len;i++)
{
c = *buf++ & 0377;
zsend_zdle_char(c);
crc = updcrc32(c, crc);
}
zsend_byte(ZDLE); zsend_byte(frameend);
crc = updcrc32(frameend, crc);
crc = ~crc;
for (i=0;i<4;i++)
{
zsend_zdle_char((int)crc); crc >>= 8;
}
}
else if (TxCRC == 2) /* send binary data with 32bits crc check,RLE encode */
{
crc = 0xffffffffL;
tmp = *buf++ & 0377;
2021-03-08 18:19:04 +08:00
for (i = 0; --len >= 0; ++buf)
{
if ((c = *buf & 0377) == tmp && i < 126 && len>0)
{
++i; continue;
}
if (i==0)
{
zsend_zdle_char(tmp);
crc = updcrc32(tmp, crc);
if (tmp == ZRESC)
{
zsend_zdle_char(0100); crc = updcrc32(0100, crc);
}
tmp = c;
}
else if (i == 1)
{
if (tmp != ZRESC)
{
zsend_zdle_char(tmp); zsend_zdle_char(tmp);
crc = updcrc32(tmp, crc);
crc = updcrc32(tmp, crc);
i = 0; tmp = c;
}
}
else
{
zsend_zdle_char(ZRESC); crc = updcrc32(ZRESC, crc);
if (tmp == 040 && i < 34)
{
i += 036;
zsend_zdle_char(i);
crc = updcrc32(i, crc);
}
else
{
i += 0101;
zsend_zdle_char(i); crc = updcrc32(i, crc);
zsend_zdle_char(tmp); crc = updcrc32(tmp, crc);
}
i = 0; tmp = c;
}
}
zsend_byte(ZDLE); zsend_byte(frameend);
crc = updcrc32(frameend, crc);
crc = ~crc;
for (i=0;i<4;i++)
{
zsend_zdle_char(crc);
crc >>= 8;
}
}
if (frameend == ZCRCW)
zsend_byte(XON);
return;
2013-01-08 21:05:02 +08:00
}
/* receive data,with 16bits CRC check */
static rt_int16_t zrec_data16(rt_uint8_t *buf, rt_uint16_t len)
{
2021-03-08 18:19:04 +08:00
rt_int16_t c,crc_cnt;
rt_uint16_t crc;
rt_err_t res = -RT_ERROR;
rt_uint8_t *p,flag = 0;
p = buf;
crc_cnt = 0; crc = 0L;
Rxcount = 0;
while(buf <= p+len)
{
if ((res = zread_byte()) & ~0377)
{
if (res == GOTCRCE || res == GOTCRCG ||
res == GOTCRCQ || res == GOTCRCW)
{
c = res;
c = res;
crc = updcrc16(res&0377, crc);
flag = 1;
continue;
}
else if (res == GOTCAN) return ZCAN;
else if (res == TIMEOUT) return TIMEOUT;
else return res;
}
else
{
if (flag)
{
crc = updcrc16(res, crc);
crc_cnt++;
if (crc_cnt < 2) continue;
if ((crc & 0xffff))
{
#ifdef ZDEBUG
2021-03-08 18:19:04 +08:00
rt_kprintf("error code: CRC16 error \r\n");
#endif
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
2013-01-08 21:05:02 +08:00
return c;
2021-03-08 18:19:04 +08:00
}
else
{
*buf++ = res;
Rxcount++;
crc = updcrc16(res, crc);
}
}
}
return -RT_ERROR;
2013-01-08 21:05:02 +08:00
}
/* receive data,with 32bits CRC check */
static rt_int16_t zrec_data32(rt_uint8_t *buf, rt_int16_t len)
{
2021-03-08 18:19:04 +08:00
rt_int16_t c,crc_cnt;
rt_uint32_t crc;
rt_err_t res = -RT_ERROR;
rt_uint8_t *p,flag = 0;
crc_cnt = 0; crc = 0xffffffffL;
Rxcount = 0;
while (buf <= p+len)
{
if ((res = zread_byte()) & ~0377)
{
if (res == GOTCRCE || res == GOTCRCG ||
res == GOTCRCQ || res == GOTCRCW)
{
c = res;
crc = updcrc32(res&0377, crc);
flag = 1;
continue;
}
else if (res == GOTCAN) return ZCAN;
else if (res == TIMEOUT) return TIMEOUT;
else return res;
}
else
{
if (flag)
{
crc = updcrc32(res, crc);
crc_cnt++;
if (crc_cnt < 4) continue;
if ((crc & 0xDEBB20E3))
{
#ifdef ZDEBUG
2021-03-08 18:19:04 +08:00
rt_kprintf("error code: CRC32 error \r\n");
#endif
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
2013-01-08 21:05:02 +08:00
return c;
2021-03-08 18:19:04 +08:00
}
else
{
*buf++ = res;
Rxcount++;
crc = updcrc32(res, crc);
}
}
}
return -RT_ERROR;
2013-01-08 21:05:02 +08:00
}
/* receive data,with RLE encoded,32bits CRC check */
static rt_int16_t zrec_data32r(rt_uint8_t *buf, rt_int16_t len)
{
2021-03-08 18:19:04 +08:00
rt_int16_t c,crc_cnt;
rt_uint32_t crc;
rt_err_t res = -RT_ERROR;
rt_uint8_t *p,flag = 0;
crc_cnt = 0; crc = 0xffffffffL;
Rxcount = 0;
p = buf;
while (buf <= p+len)
{
if ((res = zread_byte()) & ~0377)
{
if (res == GOTCRCE || res == GOTCRCG ||
res == GOTCRCQ || res == GOTCRCW)
{
c = res;
crc = updcrc32(res&0377, crc);
flag = 1;
continue;
}
else if (res == GOTCAN) return ZCAN;
else if (res == TIMEOUT) return TIMEOUT;
else return res;
}
else
{
if (flag)
{
crc = updcrc32(res, crc);
crc_cnt++;
if (crc_cnt < 4) continue;
if ((crc & 0xDEBB20E3))
{
#ifdef ZDEBUG
2021-03-08 18:19:04 +08:00
rt_kprintf("error code: CRC32 error \r\n");
#endif
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
2013-01-08 21:05:02 +08:00
return c;
2021-03-08 18:19:04 +08:00
}
else
{
crc = updcrc32(res, crc);
switch (c)
{
case 0:
if (res == ZRESC)
{
c = -1; continue;
}
*buf++ = res;
Rxcount++;
continue;
case -1:
if (res >= 040 && res < 0100)
{
c = res - 035; res = 040;
goto spaces;
}
if (res == 0100)
{
c = 0;
*buf++ = ZRESC;
Rxcount++;
continue;
}
c = res; continue;
default:
c -= 0100;
if (c < 1)
goto end;
spaces:
2021-03-08 18:19:04 +08:00
if ((buf + c) > p+len)
goto end;
while ( --res >= 0)
{
*buf++ = res;
Rxcount++;
}
c = 0; continue;
}
}
} // if -else
}
end:
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
rt_int16_t zget_data(rt_uint8_t *buf, rt_uint16_t len)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res = -RT_ERROR;
if (RxCRC == 0)
{
res = zrec_data16(buf,len);
}
else if (RxCRC == 1)
{
res = zrec_data32(buf, len);
}
else if (RxCRC == 2)
{
res = zrec_data32r(buf, len);
}
return res;
}
/* get type and cmd of header, fix lenght */
rt_int16_t zget_header(rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_int16_t c,prev_char;
rt_uint32_t bit;
rt_uint16_t get_can,step_out;
2021-03-08 18:19:04 +08:00
bit = get_device_baud(); /* get console baud rate */
Rxframeind = header_type = 0;
2013-01-08 21:05:02 +08:00
step_out = 0;
2021-03-08 18:19:04 +08:00
prev_char = 0xff;
for (;;)
{
c = zread_line(100);
switch(c)
{
case 021:
case 0221:
if (prev_char == CAN) break;
if (prev_char == ZCRCW) goto start_again;
break;
case RCDO:
goto end;
case TIMEOUT:
if (prev_char == CAN) break;
if (prev_char == ZCRCW)
{
c = -RT_ERROR; goto end;
}
goto end;
case ZCRCW:
if (prev_char == CAN) goto start_again;
break;
case CAN:
2013-01-08 21:05:02 +08:00
get_can:
2021-03-08 18:19:04 +08:00
if (++get_can > 5)
{
c = ZCAN; goto end;
2013-01-08 21:05:02 +08:00
}
2021-03-08 18:19:04 +08:00
break;
case ZPAD:
if (prev_char == CAN) break;
if (prev_char == ZCRCW) goto start_again;
step_out = 1;
break;
default:
if (prev_char == CAN) break;
if (prev_char == ZCRCW) goto start_again;
2013-01-08 21:05:02 +08:00
start_again:
2021-03-08 18:19:04 +08:00
if (--bit == 0)
{
c = GCOUNT; goto end;
}
get_can = 0;
break;
}
prev_char = c;
if (step_out) break; /* exit loop */
}
step_out = get_can = 0;
for (;;)
{
c = zxor_read();
switch(c)
{
case ZPAD:
break;
case RCDO:
case TIMEOUT:
goto end;
case ZDLE:
step_out = 1;
break;
default:
goto start_again;
}
if (step_out) break;
}
Rxframeind = c = zxor_read();
switch (c)
{
case ZBIN32:
RxCRC = 1; c = zget_bin_fcs(hdr); break;
case ZBINR32:
RxCRC = 2; c = zget_bin_fcs(hdr); break;
case ZBIN:
RxCRC = 0; c = zget_bin_header(hdr); break;
case ZHEX:
RxCRC = 0; c = zget_hex_header(hdr); break;
case CAN:
goto get_can;
case RCDO:
case TIMEOUT:
goto end;
default:
goto start_again;
}
end:
2021-03-08 18:19:04 +08:00
return c;
}
/* receive a binary header */
static rt_int16_t zget_bin_header(rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res, i;
rt_uint16_t crc;
if ((res = zread_byte()) & ~0377)
return res;
header_type = res;
crc = updcrc16(res, 0);
for (i=0;i<4;i++)
{
if ((res = zread_byte()) & ~0377)
return res;
crc = updcrc16(res, crc);
*hdr++ = res;
}
if ((res = zread_byte()) & ~0377)
return res;
crc = updcrc16(res, crc);
if ((res = zread_byte()) & ~0377)
return res;
crc = updcrc16(res, crc);
if (crc & 0xFFFF)
{
rt_kprintf("CRC error\n");
return -RT_ERROR;
}
return header_type;
}
/* receive a binary header,with 32bits FCS */
static rt_int16_t zget_bin_fcs(rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res, i;
rt_uint32_t crc;
if ((res = zread_byte()) & ~0377)
return res;
header_type = res;
crc = 0xFFFFFFFFL;
crc = updcrc32(res, crc);
for (i=0;i<4;i++) /* 4headers */
{
if ((res = zread_byte()) & ~0377)
return res;
crc = updcrc32(res, crc);
*hdr++ = res;
}
for (i=0;i<4;i++) /* 4bytes crc */
{
if ((res = zread_byte()) & ~0377)
return res;
crc = updcrc32(res, crc);
}
if (crc != 0xDEBB20E3)
{
#ifdef ZDEBUG
2021-03-08 18:19:04 +08:00
rt_kprintf("CRC error\n");
#endif
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
2021-03-08 18:19:04 +08:00
return header_type;
}
/* receive a hex style header (type and position) */
rt_int16_t zget_hex_header(rt_uint8_t *hdr)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res,i;
rt_uint16_t crc;
if ((res = zget_hex()) < 0)
return res;
header_type = res;
crc = updcrc16(res, 0);
for (i=0;i<4;i++)
{
if ((res = zget_hex()) < 0)
return res;
crc = updcrc16(res, crc);
*hdr++ = res;
}
if ((res = zget_hex()) < 0)
return res;
crc = updcrc16(res, crc);
if ((res = zget_hex()) < 0)
return res;
crc = updcrc16(res, crc);
if (crc & 0xFFFF)
{
#ifdef ZDEBUG
2021-03-08 18:19:04 +08:00
rt_kprintf("error code : CRC error\r\n");
2013-01-08 21:05:02 +08:00
#endif
2021-03-08 18:19:04 +08:00
return -RT_ERROR;
}
res = zread_line(100);
if (res < 0)
return res;
res = zread_line(100);
if (res < 0)
return res;
return header_type;
}
/* convert to ascii */
static void zsend_ascii(rt_uint8_t c)
{
2021-03-08 18:19:04 +08:00
const char hex[] = "0123456789abcdef";
2021-03-08 18:19:04 +08:00
zsend_line(hex[(c&0xF0)>>4]);
zsend_line(hex[(c)&0xF]);
2013-01-08 21:05:02 +08:00
2021-03-08 18:19:04 +08:00
return;
}
/*
* aend character c with ZMODEM escape sequence encoding.
*/
void zsend_zdle_char(rt_uint16_t ch)
2013-01-08 21:05:02 +08:00
{
2021-03-08 18:19:04 +08:00
rt_uint16_t res;
res = ch & 0377;
switch (res)
{
case 0377:
zsend_byte(res);
break;
case ZDLE:
zsend_byte(ZDLE);
res ^= 0100;
zsend_byte(res);
break;
case 021:
case 023:
case 0221:
case 0223:
zsend_byte(ZDLE);
res ^= 0100;
zsend_byte(res);
break;
default:
zsend_byte(res);
}
}
/* decode two lower case hex digits into an 8 bit byte value */
static rt_int16_t zget_hex(void)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res,n;
if ((res = zxor_read()) < 0)
return res;
n = res - '0';
if (n > 9)
n -= ('a' - ':');
if (n & ~0x0f)
return -RT_ERROR;
if ((res = zxor_read()) < 0)
return res;
res -= '0';
if (res > 9)
res -= ('a' - ':');
if (res & ~0x0f)
return -RT_ERROR;
res += (n<<4);
return res;
}
/*
* read a byte, checking for ZMODEM escape encoding
* including CAN*5 which represents a quick abort
*/
rt_int16_t zread_byte(void)
{
2021-03-08 18:19:04 +08:00
register int res;
again:
2021-03-08 18:19:04 +08:00
/* Quick check for non control characters */
if ((res = zread_line(100)) & 0140)
return res;
switch (res)
{
case ZDLE:
break;
case 023:
case 0223:
case 021:
case 0221:
goto again;
default:
return res;
}
again2:
2021-03-08 18:19:04 +08:00
if ((res = zread_line(100)) < 0)
return res;
if (res == CAN && (res = zread_line(100)) < 0)
return res;
if (res == CAN && (res = zread_line(100)) < 0)
return res;
if (res == CAN && (res = zread_line(100)) < 0)
return res;
switch (res)
{
case CAN:
return GOTCAN;
case ZCRCE:
case ZCRCG:
case ZCRCQ:
case ZCRCW:
return (res | GOTOR);
case ZRUB0:
return 0177;
case ZRUB1:
return 0377;
case 023:
case 0223:
case 021:
case 0221:
goto again2;
default:
if ((res & 0140) == 0100)
return (res ^ 0100);
break;
}
return -RT_ERROR;
}
/*
* @read a character from the modem line with timeout.
* @eat parity, XON and XOFF characters.
*/
rt_int16_t zxor_read(void)
{
2021-03-08 18:19:04 +08:00
rt_int16_t res;
for (;;)
{
if ((res = zread_line(100)) < 0)
return res;
switch (res &= 0177) {
case XON:
case XOFF:
continue;
case '\r':
case '\n':
case ZDLE:
default:
return res;
}
}
}
/* put file posistion into the header*/
void zput_pos(rt_uint32_t pos)
{
2021-03-08 18:19:04 +08:00
tx_header[ZP0] = pos;
tx_header[ZP1] = pos>>8;
tx_header[ZP2] = pos>>16;
tx_header[ZP3] = pos>>24;
2013-01-08 21:05:02 +08:00
2021-03-08 18:19:04 +08:00
return;
}
/* Recover a long integer from a header */
void zget_pos(rt_uint32_t pos)
{
2021-03-08 18:19:04 +08:00
Rxpos = (rx_header[ZP3] & 0377);
Rxpos = (Rxpos << 8) | (rx_header[ZP2] & 0377);
Rxpos = (Rxpos << 8) | (rx_header[ZP1] & 0377);
Rxpos = (Rxpos << 8) | (rx_header[ZP0] & 0377);
2013-01-08 21:05:02 +08:00
2021-03-08 18:19:04 +08:00
return;
2013-01-08 21:05:02 +08:00
}
2013-01-08 21:05:02 +08:00
/* end of zcore.c */