├── .gitignore ├── Makefile ├── README.md ├── doc └── Xmodem文件传输协议.pdf ├── rx.c ├── tty2tty.c ├── xmodem.c └── xmodem.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | *.o 3 | *~ 4 | *.log 5 | *.txt 6 | rx 7 | sx 8 | tty2tty 9 | 10 | .project 11 | .cproject 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC= gcc 2 | CFLAGS= -O2 -Wall $(MYCFLAGS) 3 | AR= ar rcu 4 | RANLIB= ranlib 5 | RM= rm -f 6 | LIBS= 7 | 8 | #MYCFLAGS=-ansi -std=c89 -pedantic 9 | MYLDFLAGS= 10 | MYLIBS= 11 | 12 | all: tty2tty rx 13 | 14 | tty2tty: tty2tty.o 15 | $(CC) -o $@ $^ 16 | 17 | rx: rx.o xmodem.o 18 | $(CC) -o $@ $^ 19 | 20 | clean: 21 | $(RM) tty2tty rx *.o 22 | 23 | # list targets that do not create files (but not all makes understand .PHONY) 24 | .PHONY: all clean 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Xmodem protocol receiver 3 | 4 | *tty2tty* is a tool to creat a pair of virsual tty lookback device 5 | 6 | *rx* Xmodem protocol receiver 7 | 8 | ## usage 9 | 10 | first run tty2tty to creat a pair of lookback tty device 11 | 12 | minicom -D /dev/pts/x -b 115200 13 | 14 | rx /dev/pts/xx recv.txt 15 | -------------------------------------------------------------------------------- /doc/Xmodem文件传输协议.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heavyii/rx/9da6e61068046046d4b71bbf7f916911028385ea/doc/Xmodem文件传输协议.pdf -------------------------------------------------------------------------------- /rx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "xmodem.h" 9 | 10 | struct xmodem_args { 11 | int fd; 12 | int filefd; 13 | }; 14 | 15 | /** 16 | * serial_set - Config serial port parameter 17 | * fd serial device file descriptor 18 | * baud_rate serial communication baud rate 19 | * data_bit data bit count, default is 8 20 | * stop_bit stop bit count, default is 1 21 | * parity odd or even parity, default is no parity 22 | * flow_ctrl hardware or software flow control, default is no fc 23 | * @return: 0 on success, -1 on error. 24 | */ 25 | int serial_set(int fd, int baud_rate, char data_bit, char stop_bit, char parity, 26 | char flow_ctrl) { 27 | struct termios termios_new; 28 | int baud; 29 | int ret = -1; 30 | 31 | memset(&termios_new, 0, sizeof(termios_new)); 32 | cfmakeraw(&termios_new); /* set termios raw data */ 33 | 34 | switch (baud_rate) { 35 | case 2400: 36 | baud = B2400; 37 | break; 38 | case 4800: 39 | baud = B4800; 40 | break; 41 | case 38400: 42 | baud = B38400; 43 | break; 44 | case 57600: 45 | baud = B57600; 46 | break; 47 | case 115200: 48 | baud = B115200; 49 | break; 50 | default: 51 | baud = B9600; 52 | break; 53 | }/* end switch */ 54 | cfsetispeed(&termios_new, baud); 55 | cfsetospeed(&termios_new, baud); 56 | 57 | termios_new.c_cflag |= (CLOCAL | CREAD); 58 | 59 | /* flow control */ 60 | switch (flow_ctrl) { 61 | case 'H': 62 | case 'h': 63 | termios_new.c_cflag |= CRTSCTS; /* hardware flow control */ 64 | break; 65 | case 'S': 66 | case 's': 67 | termios_new.c_cflag |= (IXON | IXOFF | IXANY); /* software flow control */ 68 | break; 69 | default: 70 | termios_new.c_cflag &= ~CRTSCTS; /* default no flow control */ 71 | break; 72 | }/* end switch */ 73 | 74 | /* data bit */ 75 | termios_new.c_cflag &= ~CSIZE; 76 | switch (data_bit) { 77 | case 5: 78 | termios_new.c_cflag |= CS5; 79 | break; 80 | case 6: 81 | termios_new.c_cflag |= CS6; 82 | break; 83 | case 7: 84 | termios_new.c_cflag |= CS7; 85 | break; 86 | default: 87 | termios_new.c_cflag |= CS8; 88 | break; 89 | }/* end switch */ 90 | 91 | /* parity check */ 92 | switch (parity) { 93 | case 'O': 94 | case 'o': 95 | termios_new.c_cflag |= PARENB; /* odd check */ 96 | termios_new.c_cflag &= ~PARODD; 97 | break; 98 | case 'E': 99 | case 'e': 100 | termios_new.c_cflag |= PARENB; /* even check */ 101 | termios_new.c_cflag |= PARODD; 102 | break; 103 | default: 104 | termios_new.c_cflag &= ~PARENB; /* default no check */ 105 | break; 106 | }/* end switch */ 107 | 108 | /* stop bit */ 109 | if (stop_bit == 2) 110 | termios_new.c_cflag |= CSTOPB; /* 2 stop bit */ 111 | else 112 | termios_new.c_cflag &= ~CSTOPB; /* 1 stop bit */ 113 | 114 | /* other attribute */ 115 | termios_new.c_oflag &= ~OPOST; 116 | termios_new.c_iflag &= ~ICANON; 117 | termios_new.c_cc[VMIN] = 1; /* read char min quantity */ 118 | termios_new.c_cc[VTIME] = 1; /* wait time unit (1/10) second */ 119 | 120 | /* clear data in receive buffer */ 121 | tcflush(fd, TCIFLUSH); 122 | 123 | /* set config data */ 124 | ret = tcsetattr(fd, TCSANOW, &termios_new); 125 | 126 | return ret; 127 | } 128 | 129 | int put_char(void *args, char c) { 130 | struct xmodem_args *p = (struct xmodem_args *)args; 131 | if (write(p->fd, &c, 1) != 1) 132 | return -1; 133 | return 0; 134 | } 135 | 136 | int char_avail(void *args) { 137 | int ret; 138 | fd_set input; 139 | struct timeval to; 140 | struct xmodem_args *p = (struct xmodem_args *)args; 141 | 142 | FD_ZERO(&input); 143 | FD_SET(p->fd, &input); 144 | to.tv_sec = 0; 145 | to.tv_usec = 5000; 146 | ret = select(p->fd + 1, &input, NULL, NULL, &to); 147 | if (ret < 0) { 148 | perror("select"); 149 | } else if (ret > 0) { 150 | if (FD_ISSET(p->fd, &input)) { 151 | return 1; 152 | } 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | int get_char(void *args) { 159 | char ch; 160 | struct xmodem_args *p = (struct xmodem_args *)args; 161 | while (!char_avail(args)) { 162 | /* nothing */ 163 | } 164 | if (read(p->fd, &ch, 1) == 1) 165 | return (int) ch; 166 | 167 | perror(__FUNCTION__); 168 | return 0; 169 | } 170 | 171 | void delay_1s() { 172 | sleep(1); 173 | } 174 | 175 | int writer(void *args, void *buf, int size) { 176 | struct xmodem_args *p = (struct xmodem_args *)args; 177 | return write(p->filefd, buf, size); 178 | } 179 | 180 | int main(int argc, char *argv[]) { 181 | struct xmodem_args args; 182 | struct xmodem recver; 183 | char *filename = argv[2]; 184 | if (argc != 3) { 185 | printf("receive file in xmodem protocol\n"); 186 | printf("usage: %s \n", argv[0]); 187 | return -1; 188 | } 189 | 190 | /* open tty */ 191 | args.fd = open(argv[1], (O_RDWR | O_NOCTTY | O_NONBLOCK)); 192 | if (args.fd < 0) { 193 | perror("open"); 194 | return -1; 195 | } 196 | serial_set(args.fd, 115200, 8, 1, 0, 0); 197 | 198 | /* open file to write recv file */ 199 | args.filefd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 200 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 201 | if (args.filefd < 0) { 202 | perror("open"); 203 | goto main_quit; 204 | } 205 | 206 | printf("filefd = %d\n", args.filefd); 207 | recver.args = &args; 208 | recver.get_char = get_char; 209 | recver.put_char = put_char; 210 | recver.char_avail = char_avail; 211 | recver.delay_1s = delay_1s; 212 | recver.write = writer; 213 | xmodem_recv(&recver); 214 | 215 | main_quit: 216 | if (args.filefd >= 0) { 217 | close(args.filefd); 218 | args.filefd = -1; 219 | } 220 | if (args.fd >= 0) { 221 | close(args.fd); 222 | args.fd = -1; 223 | } 224 | return 0; 225 | } 226 | -------------------------------------------------------------------------------- /tty2tty.c: -------------------------------------------------------------------------------- 1 | /** 2 | * tty2tty.c 3 | * 4 | * make a pair of virtual look back tty device 5 | */ 6 | #define _XOPEN_SOURCE 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | int ptym_open(char *pts_name, char *pts_name_s, int pts_namesz) { 15 | char *ptr; 16 | int fdm; 17 | 18 | strncpy(pts_name, "/dev/ptmx", pts_namesz); 19 | pts_name[pts_namesz - 1] = '\0'; 20 | 21 | fdm = posix_openpt(O_RDWR | O_NONBLOCK); 22 | if (fdm < 0) 23 | return (-1); 24 | if (grantpt(fdm) < 0) { 25 | close(fdm); 26 | return (-2); 27 | } 28 | if (unlockpt(fdm) < 0) { 29 | close(fdm); 30 | return (-3); 31 | } 32 | if ((ptr = ptsname(fdm)) == NULL) { 33 | close(fdm); 34 | return (-4); 35 | } 36 | 37 | strncpy(pts_name_s, ptr, pts_namesz); 38 | pts_name[pts_namesz - 1] = '\0'; 39 | 40 | return (fdm); 41 | } 42 | 43 | int main(void) { 44 | char master1[1024]; 45 | char slave1[1024]; 46 | char master2[1024]; 47 | char slave2[1024]; 48 | 49 | int fd1; 50 | int fd2; 51 | 52 | char c1, c2; 53 | 54 | fd1 = ptym_open(master1, slave1, 1024); 55 | 56 | fd2 = ptym_open(master2, slave2, 1024); 57 | 58 | printf("(%s) <=> (%s)\n", slave1, slave2); 59 | 60 | while (1) { 61 | int ret; 62 | fd_set input; 63 | 64 | FD_ZERO(&input); 65 | FD_SET(fd1, &input); 66 | FD_SET(fd2, &input); 67 | ret = select(fd2 + 1, &input, NULL, NULL, NULL); 68 | if (ret < 0) { 69 | perror("select"); 70 | break; 71 | } else if (ret == 0) { 72 | continue; 73 | } else { 74 | if (FD_ISSET(fd1, &input)) { 75 | if (read(fd1, &c1, 1) == 1 && write(fd2, &c1, 1) == 1) 76 | continue; 77 | } else if (FD_ISSET(fd2, &input)) { 78 | if (read(fd2, &c2, 1) == 1 && write(fd1, &c2, 1) == 1) 79 | continue; 80 | } 81 | } 82 | }; 83 | 84 | close(fd1); 85 | close(fd2); 86 | 87 | return EXIT_SUCCESS; 88 | } 89 | -------------------------------------------------------------------------------- /xmodem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "xmodem.h" 5 | 6 | #define MAX_ERR 100 7 | #define MAX_RETRY 25 8 | 9 | #define XMODEM_SOH 0x01 10 | #define XMODEM_STX 0x02 11 | #define XMODEM_EOT 0x04 12 | #define XMODEM_ACK 0x06 13 | #define XMODEM_NAK 0x15 14 | #define XMODEM_CAN 0x18 15 | #define XMODEM_CRC_CHR 'C' 16 | 17 | #define XMODEM_DATA_SIZE_SOH 128 /* for Xmodem protocol */ 18 | #define XMODEM_DATA_SIZE_STX 1024 /* for 1K xmodem protocol */ 19 | #define USE_1K_XMODEM 0 /* 1 for use 1k_xmodem 0 for xmodem */ 20 | 21 | #if (USE_1K_XMODEM) 22 | #define XMODEM_DATA_SIZE XMODEM_DATA_SIZE_STX 23 | #define XMODEM_HEAD XMODEM_STX 24 | #else 25 | #define XMODEM_DATA_SIZE XMODEM_DATA_SIZE_SOH 26 | #define XMODEM_HEAD XMODEM_SOH 27 | #endif 28 | 29 | #define XMODEM_PKT_SIZE (XMODEM_DATA_SIZE + 5) 30 | 31 | /* 32 | * Xmodem Frame form: <255-blk #><--128 data bytes--> 33 | */ 34 | typedef struct _pkt { 35 | uint8_t head; 36 | uint8_t id; 37 | uint8_t uid; 38 | uint8_t data[XMODEM_DATA_SIZE]; 39 | uint8_t crc_hi; 40 | uint8_t crc_lo; 41 | } xmodem_pkt; 42 | 43 | /** 44 | * return crc16 value 45 | */ 46 | static uint16_t crc16(const uint8_t *buf, int sz) { 47 | uint16_t crc = 0; 48 | while (--sz >= 0) { 49 | int i; 50 | crc = crc ^ ((uint16_t) *buf++) << 8; 51 | for (i = 0; i < 8; i++) { 52 | if (crc & 0x8000) { 53 | crc = crc << 1 ^ 0x1021; 54 | } else { 55 | crc <<= 1; 56 | } 57 | } 58 | } 59 | return crc; 60 | } 61 | 62 | /** 63 | * check crc16 64 | * @return: return 0 on match, -1 on not match 65 | */ 66 | static int check_pkt_crc(xmodem_pkt *pkt, uint8_t id) { 67 | uint16_t crc_pkt; 68 | if (id != pkt->id || pkt->id + pkt->uid != 0xFF) 69 | return -1; 70 | 71 | crc_pkt = ((uint16_t) pkt->crc_hi << 8) + pkt->crc_lo; 72 | if (crc16(pkt->data, sizeof(pkt->data)) == crc_pkt) 73 | return 0; 74 | 75 | return -1; 76 | } 77 | 78 | /** 79 | * start to receive a file 80 | * @return: 0 on success, -1 on error 81 | */ 82 | static int recv_start(struct xmodem *rx) { 83 | uint8_t errcnt = 0; 84 | uint8_t pktnum = 1; 85 | xmodem_pkt pkt; 86 | uint8_t *p = NULL; 87 | uint8_t * const endp = (uint8_t *) &pkt + XMODEM_PKT_SIZE; 88 | while (1) { 89 | /* receive one packet */ 90 | for (p = (uint8_t *) &pkt; p < endp; p++) { 91 | *p = rx->get_char(rx->args); 92 | if (p == (uint8_t *) &pkt) { 93 | switch (*p) { 94 | case XMODEM_HEAD: { 95 | /* start new frame */ 96 | break; 97 | } 98 | case XMODEM_EOT: { 99 | rx->put_char(rx->args, XMODEM_ACK); 100 | /*finished ok*/ 101 | return 0; 102 | } 103 | default: { 104 | errcnt++; 105 | if (errcnt > MAX_ERR) { 106 | /* to many error, cancel it */ 107 | rx->put_char(rx->args, XMODEM_CAN); 108 | return -1; 109 | } 110 | 111 | /* try again */ 112 | p--; 113 | rx->put_char(rx->args, XMODEM_NAK); 114 | break; 115 | } 116 | } /* end switch */ 117 | } /* end if p == buf */ 118 | }/* while receive one packet */ 119 | 120 | /* check crc */ 121 | if (check_pkt_crc(&pkt, pktnum) == 0) { 122 | /* handle one packet */ 123 | pktnum++; 124 | if (rx->write(rx->args, pkt.data, sizeof(pkt.data)) 125 | != sizeof(pkt.data)) { 126 | /* write error, stop */ 127 | rx->put_char(rx->args, XMODEM_CAN); 128 | return -1; 129 | } 130 | rx->put_char(rx->args, XMODEM_ACK); 131 | } else { 132 | rx->put_char(rx->args, XMODEM_NAK); /* error packet */ 133 | } 134 | }/* while 1 */ 135 | return -1; 136 | } 137 | 138 | /** 139 | * start to receive a file 140 | * @return: 0 on success, -1 on error 141 | */ 142 | int xmodem_recv(struct xmodem *rx) { 143 | uint8_t retry_count = 0; 144 | while (retry_count < MAX_RETRY) { 145 | rx->put_char(rx->args, XMODEM_CRC_CHR); 146 | if (rx->char_avail(rx->args)) { 147 | return recv_start(rx); 148 | } 149 | rx->delay_1s(); 150 | } 151 | 152 | return -1; 153 | } 154 | -------------------------------------------------------------------------------- /xmodem.h: -------------------------------------------------------------------------------- 1 | #ifndef XMODEM_H 2 | #define XMODEM_H 3 | 4 | struct xmodem { 5 | void *args; /* arguments pointer pass to operation function */ 6 | int (*get_char)(void *args); /* return one char */ 7 | int (*put_char)(void *args, char c); 8 | int (*char_avail)(void *args); /* return none zero when available */ 9 | void (*delay_1s)(void); /* delay 1s */ 10 | int (*write)(void *args, void *buf, int len); /* return numbers of bytes written */ 11 | }; 12 | 13 | /** 14 | * start to receive a file 15 | * @return: 0 on success, -1 on error 16 | */ 17 | int xmodem_recv(struct xmodem *rx); 18 | 19 | #endif 20 | --------------------------------------------------------------------------------