Skip to content

Instantly share code, notes, and snippets.

@v3l0c1r4pt0r
Created July 6, 2014 09:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save v3l0c1r4pt0r/500396302da4f8e86818 to your computer and use it in GitHub Desktop.
Save v3l0c1r4pt0r/500396302da4f8e86818 to your computer and use it in GitHub Desktop.
XMODEM protocol implementation
#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(&params,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