Created
July 6, 2014 09:01
-
-
Save v3l0c1r4pt0r/500396302da4f8e86818 to your computer and use it in GitHub Desktop.
XMODEM protocol implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <getopt.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <termios.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <sys/stat.h> | |
//Flags | |
#define F_TRANSFER 0x01 | |
#define F_RECEIVE 0x02 | |
#define F_CRC 0x04 | |
#define F_CRCCONFIRMED 0x10 | |
#define F_NORMALCONFIR 0x20 | |
#define F_STOP 0x40 | |
#define F_VERBOSE 0x80 | |
#define F_NONE 0x00 | |
#define VERBOSE (params.flags & F_VERBOSE) | |
//Parity bit | |
#define N 0 | |
#define E 1 | |
#define O 2 | |
#define M 3 | |
#define S 4 | |
//Errors | |
#define E_ERROR 1 | |
#define E_NOTIMPLEMENTED 2 | |
#define E_CANCEL 3 | |
//Special chars | |
#define SOH 0x01 | |
#define EOT 0x04 | |
#define ACK 0x06 | |
#define NAK 0x15 | |
#define CAN 0x18 | |
#define SUB 0x1A | |
typedef struct s_params | |
{ | |
uint8_t flags; | |
char *device; | |
char *filename; | |
int baudrate; | |
int databits; | |
uint8_t parity; | |
} Params; | |
typedef enum istimeout | |
{ | |
TIMEOUT = 0, | |
NOTOUT | |
} ISTIMEOUT; | |
/* | |
* initialize serial port | |
*/ | |
int portInit(Params* params, char* pname); | |
/* | |
* convert baudrate as integer to one of the valid consts; | |
* when not valid speed given returns (speed_t)-1 | |
*/ | |
speed_t intToSpeed(int baud); | |
/* | |
* returns simple xmodem checksum for BUF of length N | |
*/ | |
uint8_t checksum(char *buf, int n); | |
/* | |
* returns crc-xmodem for BUF of length N | |
*/ | |
uint16_t crc16(char *buf, int n); | |
/* | |
* wait for data read on FD, then return TIMEOUT after SECONDS or NOTOUT if data ready | |
*/ | |
ISTIMEOUT wait_max(int fd, int seconds); | |
int main(int argc, char **argv) | |
{ | |
//default values | |
int opt; | |
Params params = {F_NONE, NULL, NULL, 9600, 8, N}; | |
params.device = (char*)malloc(256); | |
params.device[0] = '\0'; | |
params.filename = (char*)malloc(256); | |
//params from argv | |
while ((opt = getopt(argc, argv, "cp:b:trd:hv")) != -1) { | |
switch (opt) { | |
case 'r': | |
//receiver mode | |
params.flags |= F_RECEIVE; | |
break; | |
case 't': | |
//transmitter mode | |
params.flags |= F_TRANSFER; | |
break; | |
case 'd': | |
//device path | |
strcpy(params.device,optarg); | |
params.device = realloc(params.device,strlen(params.device) + 1); | |
break; | |
case 'b': | |
//baudrate | |
params.baudrate = strtol(optarg,NULL,10); | |
if(intToSpeed(params.baudrate) == (speed_t)-1) | |
{ | |
fprintf(stderr,"%s: Wrong baudrate given!\n",argv[0]); | |
return 1; | |
} | |
break; | |
case 'p': | |
//connection parameters | |
if(strlen(optarg)!=3) | |
{ | |
fprintf(stderr, | |
"%s: Conection params should be given as [Data][Parity][Stop],\n" | |
"eg. 8N1 means 8 data bits, no parity bit and 1 stop bit\n", | |
argv[0]); | |
return 1; | |
} | |
if(optarg[0]>='5' && optarg[0]<='8') | |
params.databits = optarg[0] & ~0x30; | |
else | |
{ | |
fprintf(stderr, | |
"%s: Conection params should be given as [Data][Parity][Stop],\n" | |
"eg. 8N1 means 8 data bits, no parity bit and 1 stop bit\n", | |
argv[0]); | |
return 1; | |
} | |
switch(optarg[1]) | |
{ | |
case 'n': | |
case 'N': | |
params.parity = N; | |
break; | |
case 'e': | |
case 'E': | |
params.parity = E; | |
break; | |
case 'o': | |
case 'O': | |
params.parity = O; | |
break; | |
case 'm': | |
case 'M': | |
params.parity = M; | |
break; | |
case 's': | |
case 'S': | |
params.parity = S; | |
break; | |
default: | |
fprintf(stderr, | |
"%s: Conection params should be given as [Data][Parity][Stop],\n" | |
"eg. 8N1 means 8 data bits, no parity bit and 1 stop bit\n", | |
argv[0]); | |
return 1; | |
} | |
if(optarg[2] == '1') | |
params.flags &= ~F_STOP; | |
else if(optarg[2] == '2') | |
params.flags |= F_STOP; | |
else | |
{ | |
fprintf(stderr, | |
"%s: Conection params should be given as [Data][Parity][Stop],\n" | |
"eg. 8N1 means 8 data bits, no parity bit and 1 stop bit\n", | |
argv[0]); | |
return 1; | |
} | |
break; | |
case 'c': | |
//crc16 mode | |
params.flags |= F_CRC; | |
break; | |
case 'v': | |
params.flags |= F_VERBOSE; | |
break; | |
default: /* '?' */ | |
fprintf(stderr, "Usage: %s [-tr] -d device filename\n", | |
argv[0]); | |
return 1; | |
} | |
} | |
//check if given data is valid | |
if(strlen(params.device) == 0) | |
{ | |
fprintf(stderr, "Usage: %s [-tr] -d device filename\n", | |
argv[0]); | |
return 1; | |
} | |
if (optind >= argc) { | |
fprintf(stderr, "%s: Expected argument after options\n",argv[0]); | |
return 1; | |
} | |
if(params.flags&F_TRANSFER && params.flags&F_RECEIVE) | |
{ | |
fprintf(stderr, "%s: You cannot transfer and receive at the same time\n", argv[0]); | |
return 1; | |
} | |
strcpy(params.filename, argv[optind]); | |
params.filename = realloc(params.filename, strlen(params.filename) + 1); | |
//init serial device | |
int dfd = 0; | |
if((dfd = portInit(¶ms,argv[0])) == -1) | |
{ | |
fprintf(stderr, "%s: Port initialization failed! Do you have rights to open it?\n", argv[0]); | |
return 1; | |
} | |
//open file to transfer | |
int ffd = 0; | |
int oflag = 0; | |
mode_t perm = S_IRWXU | S_IRWXG | S_IRWXO; | |
if(params.flags & F_TRANSFER) | |
oflag = O_RDONLY; | |
else | |
oflag = O_WRONLY | O_CREAT/* | O_NONBLOCK*/ | O_TRUNC; | |
if((ffd = open(params.filename, oflag, perm)) == -1) | |
{ | |
fprintf(stderr, "%s: Cannot open file! Do you have rights to open it?\n", argv[0]); | |
return 1; | |
} | |
//do the transfer! | |
if(params.flags&F_TRANSFER) | |
{ | |
if(VERBOSE) | |
printf("Transmit mode; device: %s; filename: %s\n", params.device, params.filename); | |
//transmitter | |
params.flags &= ~F_CRC; //turn off CRC flag | |
char *buf = malloc(256); | |
//decide if we will use checksum whether crc16 | |
while((buf[0] != NAK) && (buf[0] != 'C')) | |
read(dfd,buf,256); | |
char *packet; | |
if(buf[0] == 'C') | |
{ | |
params.flags |= F_CRC; | |
packet = malloc(133); | |
} | |
else | |
packet = malloc(132); | |
#ifdef DEBUG | |
printf("got: %c\n",buf[0]); | |
#endif //DEBUG | |
free(buf); | |
buf = NULL; | |
packet[0] = SOH; | |
packet[1] = (char)1; | |
packet[2] = (char)254; | |
packet[131] = '\0'; | |
ssize_t size = 0; | |
do | |
{ | |
buf = malloc(128); | |
size = read(ffd,buf,128); | |
memset(packet+3,SUB,128); | |
strncpy(packet+3,buf,size); | |
if(params.flags & F_CRC) | |
{ | |
uint16_t crc = crc16(packet+3, 128); | |
packet[131] = (char)(crc / 0x100); | |
packet[132] = (char)(crc % 0x100); | |
} | |
else | |
packet[131] = checksum(packet+3,128); | |
free(buf); | |
buf = malloc(256); | |
int i = 0; | |
for(i = 0; i < 10; i++) | |
{ | |
#ifdef DEBUG | |
int j; | |
for(j = 0; j < 133; j++) | |
printf("%02X ",(unsigned char)packet[j]); | |
printf("\n"); | |
#endif //DEBUG | |
write(dfd,packet,(params.flags & F_CRC) ? 133 : 132); | |
read(dfd,buf,256); | |
if(buf[0] == ACK) | |
break; | |
if(buf[0] == CAN) | |
{ | |
fprintf(stderr, "%s: Cancel received! Exiting...",argv[0]); | |
return E_CANCEL; | |
} | |
fprintf(stderr, "%s: Did I just received a NAK?? It was error No. %d\n",argv[0], i+1); | |
} | |
if(buf[0] == NAK) | |
{ | |
fprintf(stderr, "%s: Transfer failed! Received 10 NAKs in a row\n", argv[0]); | |
char can = CAN; | |
write(dfd,&can,1); | |
return 1; | |
} | |
//packet sent | |
packet[1] += 1; | |
packet[2] = 255 - packet[1]; | |
free(buf); | |
} | |
while(size!=0); | |
char eot = EOT; | |
char ans = '\0'; | |
while(ans!=ACK) | |
{ | |
write(dfd,&eot,1); | |
read(dfd,&ans,1); | |
} | |
close(ffd); | |
close(dfd); | |
} | |
else if(params.flags&F_RECEIVE) | |
{ | |
if(VERBOSE) | |
printf("Receive mode; device: %s; filename: %s\n", params.device, params.filename); | |
//receiver | |
char nak = NAK; | |
char c = 'C'; | |
/*{ | |
//clear buffer | |
char nth = '\0'; | |
int cnt; | |
while(cnt = read(dfd,&nth,1)); | |
}*/ | |
params.flags &= ~F_CRCCONFIRMED; | |
params.flags &= ~F_NORMALCONFIR; | |
if(params.flags & F_CRC) | |
{ | |
int j; | |
for(j = 0; j < 3; j++) | |
{ | |
//send 'C' | |
write(dfd,&c,1); | |
if(wait_max(dfd,3) == NOTOUT) | |
break; | |
#ifdef DEBUG | |
fprintf(stderr, "%s: CRC mode: No. %d failed!\n", argv[0], j+1); | |
#endif //DEBUG | |
} | |
if(j!=3) | |
{ | |
params.flags |= F_CRCCONFIRMED; | |
} | |
#ifdef DEBUG | |
else | |
fprintf(stderr, "%s: CRC mode unavailable!\n", argv[0]); | |
#endif //DEBUG | |
} | |
if(!(params.flags & F_CRCCONFIRMED)) | |
{ | |
int i; | |
for(i = 0; i < ((params.flags & F_CRC) ? 5 : 6); i++) | |
{ | |
//send NAKs | |
write(dfd,&nak,1); | |
if(wait_max(dfd,10) == NOTOUT) | |
break; | |
#ifdef DEBUG | |
fprintf(stderr, "%s: No. %d failed!\n", argv[0], i+1); | |
#endif //DEBUG | |
} | |
if(i!=((params.flags & F_CRC) ? 5 : 6)) | |
params.flags |= F_NORMALCONFIR; | |
} | |
if(!(params.flags & F_CRCCONFIRMED) && !(params.flags & F_NORMALCONFIR)) | |
{ | |
//no response, give up | |
fprintf(stderr, "%s: After one minute of waiting, no response has been received. I'm giving up!\n", argv[0]); | |
return 1; | |
} | |
//sender understood one of the methods | |
int EXPLEN = ((params.flags & F_CRCCONFIRMED) ? 133: 132); | |
char *buf = malloc(EXPLEN); | |
int length = 0; | |
int blkcnt = 0; | |
while((length = read(dfd,buf,EXPLEN)) == EXPLEN) | |
{ | |
// if(buf[0] == EOT) | |
// break; | |
#ifdef DEBUG | |
int i; | |
for(i = 0; i < EXPLEN; i++) | |
printf("%02X ",(unsigned char)buf[i]); | |
printf("\n"); | |
#endif //DEBUG | |
//check crc/checksum | |
uint16_t sum = 0; | |
uint16_t expected = 0; | |
sum = ((params.flags & F_CRCCONFIRMED) ? crc16(buf+3,128) : (uint16_t)checksum(buf+3,128)); | |
expected = ((params.flags & F_CRCCONFIRMED) ? ((buf[131] * 0x100) + (uint8_t)buf[132]) : (uint8_t)buf[131]); | |
if( | |
(sum == expected) && | |
(buf[0] == SOH) && | |
(buf[1] == (char)(blkcnt+1)) && | |
(buf[2] == (char)(0xFF - (blkcnt+1))) | |
) | |
{ | |
//checksums matched | |
char ack = ACK; | |
write(dfd,&ack,1); | |
//write data to file | |
int i; | |
int lng = 128; | |
for(i = 130; i > 2; i--) | |
if(buf[i] == SUB) | |
lng--; | |
else | |
break; | |
write(ffd,buf+3,lng); | |
} | |
else | |
{ | |
//wrong checksum | |
#ifdef DEBUG | |
printf("checksum: %02X, expected: %02X\n",(uint16_t)sum, (uint16_t)expected); | |
#endif //DEBUG | |
char nak = NAK; | |
write(dfd,&nak,1); | |
continue; | |
} | |
blkcnt++; | |
} | |
if(buf[0] == EOT) | |
{ | |
//transmission successful, close files | |
if(VERBOSE) | |
printf("Transmission finished! %d blocks received.\n",blkcnt+1); | |
char ack = ACK; | |
write(dfd,&ack,1); | |
close(dfd); | |
close(ffd); | |
free(buf); | |
} | |
else | |
{ | |
fprintf(stderr,"Transmission failed on block no. %d\n",blkcnt+1); | |
free(buf); | |
return E_ERROR; | |
} | |
} | |
else | |
{ | |
fprintf(stderr, "%s: You need to define if you want to send or receive\n", argv[0]); | |
return 1; | |
} | |
return EXIT_SUCCESS; | |
} | |
uint8_t checksum(char *buf, int n) | |
{ | |
int i =0; | |
uint8_t ret = 0; | |
for(i = 0; i < n; i++) | |
{ | |
ret += buf[i]; | |
} | |
return ret; | |
} | |
uint16_t crc16(char *buf, int n) | |
{ | |
uint16_t crc = 0; | |
int i; | |
while (n--) | |
{ | |
crc ^= (unsigned short) (*buf++)<<8; | |
for ( i=0 ; i<8 ; ++i ) { | |
if (crc & 0x8000) | |
crc = (crc << 1) ^ 0x1021; | |
else | |
crc <<= 1; | |
} | |
} | |
return crc; | |
} | |
ISTIMEOUT wait_max(int fd, int seconds) | |
{ | |
// Initialize file descriptor sets | |
fd_set read_fds, write_fds, except_fds; | |
FD_ZERO(&read_fds); | |
FD_ZERO(&write_fds); | |
FD_ZERO(&except_fds); | |
FD_SET(fd, &read_fds); | |
// Set timeout to 1.0 seconds | |
struct timeval timeout; | |
timeout.tv_sec = seconds; | |
timeout.tv_usec = 0; | |
/* | |
* Wait for input to become ready or until the time out; the first parameter is | |
* 1 more than the largest file descriptor in any of the sets | |
*/ | |
if (select(fd + 1, &read_fds, &write_fds, &except_fds, &timeout) == 1) | |
{ | |
//returned | |
return NOTOUT; | |
} | |
else | |
{ | |
//timeout | |
return TIMEOUT; | |
} | |
} | |
int portInit(Params *params, char *pname) | |
{ | |
int fd = open(params->device,O_RDWR|O_NOCTTY); | |
if( fd== -1) | |
{ | |
fprintf(stderr,"%s: Error: failed to open terminal device\n",pname); | |
return -1; | |
} | |
struct termios attr; | |
if(tcgetattr(fd,&attr)) | |
{ | |
fprintf(stderr,"%s: Error: failed to get tty attributes\n",pname); | |
return -2; | |
} | |
#ifdef DEBUG | |
fprintf(stderr,"%s: Terminal device opened: %s!\n",pname, params->device); | |
#endif //DEBUG | |
cfsetospeed(&attr,intToSpeed(params->baudrate)); //output baud rate | |
cfsetispeed(&attr,intToSpeed(params->baudrate)); //input baud rate | |
attr.c_cflag &= ~CRTSCTS; //hardware flow control off | |
if(params->flags&F_STOP) | |
attr.c_cflag &= ~CSTOPB; //1 stop bit | |
if(params->parity!=N) | |
attr.c_cflag |= PARENB; //parity check | |
switch(params->parity) | |
{ | |
case N: | |
attr.c_cflag &= ~PARENB; | |
break; //no parity? | |
case E: | |
attr.c_cflag &= ~PARODD; | |
break; //parity even | |
case O: | |
attr.c_cflag |= PARODD; | |
break; //parity odd | |
case M: | |
attr.c_cflag |= PARODD|CMSPAR; | |
break; //parity mark | |
case S: | |
attr.c_cflag |= CMSPAR; | |
break; //parity space | |
default: | |
fprintf(stderr,"%s: Parity mode not supported!\n",pname); | |
return -3; | |
} | |
attr.c_cflag &= ~CSIZE; | |
switch(params->databits) //data bits | |
{ | |
case 5: | |
attr.c_cflag |= CS5; | |
break; | |
case 6: | |
attr.c_cflag |= CS6; | |
break; | |
case 7: | |
attr.c_cflag |= CS7; | |
break; | |
case 8: | |
attr.c_cflag |= CS8; | |
break; | |
} | |
if(tcsetattr(fd,TCSANOW,&attr)) | |
{ | |
fprintf(stderr,"%s: Error: failed to set tty attributes\n",pname); | |
return -4; | |
} | |
return fd; | |
} | |
speed_t intToSpeed(int baud) | |
{ | |
int bauds[] = { | |
0, | |
50, | |
75, | |
110, | |
134, | |
150, | |
200, | |
300, | |
600, | |
1200, | |
1800, | |
2400, | |
4800, | |
9600, | |
19200, | |
38400, | |
57600, | |
115200, | |
230400 | |
}; | |
speed_t Bauds[] = { | |
B0, | |
B50, | |
B75, | |
B110, | |
B134, | |
B150, | |
B200, | |
B300, | |
B600, | |
B1200, | |
B1800, | |
B2400, | |
B4800, | |
B9600, | |
B19200, | |
B38400, | |
B57600, | |
B115200, | |
B230400 | |
}; | |
int i; | |
for(i = 0; i < (sizeof(bauds)/sizeof(int)); i++) | |
{ | |
if(bauds[i]==baud) | |
return Bauds[i]; | |
} | |
return (speed_t)-1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment