SXXXXXXX_PyDownloadFwViaSRIO/_OLD/Vecchia_app/FpgaBeamMeUp/bsk/bsk_tftpd.cpp
2026-01-22 17:10:05 +01:00

674 lines
16 KiB
C++

/*
* bsk_tftpd.cpp
*
* Created on: 25/ago/2016
* Author: chessaa
*/
#include "bsk_tftpd.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/*
* Trivial File Transfer Protocol (IEN-133)
*/
#define TFTP_SEGSIZE 512 /* data segment size */
//#define TFTP_MAXSEGSIZE 16384 /* data segment size */
#define TFTP_MAXSEGSIZE 1428 /*16384 */ /* data segment size */
#define TFTP_MINSEGSIZE 8 /* data segment size */
/*
* Packet types.
*/
#define TFTP_RRQ 01 /* read request */
#define TFTP_WRQ 02 /* write request */
#define TFTP_DATA 03 /* data packet */
#define TFTP_ACK 04 /* acknowledgement */
#define TFTP_ERROR 05 /* error code */
#define TFTP_OACK 06 /* option acknowledgement */
typedef struct {
unsigned char th_opcode[2]; /* packet type */
union {
unsigned char tu_block[2]; /* block # */
unsigned char tu_code[2]; /* error code */
unsigned char tu_stuff[1]; /* request packet stuff */
} th_u;
} tftphdr_t;
typedef struct
{
tftphdr_t header;
char th_data[2]; /* data or error string */
} tftppkt_t;
static void tftp_htons(unsigned char* const p, unsigned short v)
{
p[0]=(unsigned char)(v>>8);
p[1]=(unsigned char)(v);
}
static unsigned short tftp_ntos(const unsigned char* n)
{
return n[1]|(n[0]<<8);
}
#define th_block th_u.tu_block
#define th_code th_u.tu_code
#define th_stuff th_u.tu_stuff
#define th_msg th_data
#define TFTP_DATA_HEADERSIZE ( offsetof (struct tftphdr, th_data ) )
#define TFTP_ACK_HEADERSIZE ( offsetof (struct tftphdr, th_block ) \
+ sizeof ( ((struct tftphdr *) (0))->th_block) )
/*
* Error codes.
*/
#define EUNDEF 0 /* not defined */
#define ENOTFOUND 1 /* file not found */
#define EACCESS 2 /* access violation */
#define ENOSPACE 3 /* disk full or allocation exceeded */
#define EBADOP 4 /* illegal TFTP operation */
#define EBADID 5 /* unknown transfer ID */
#define EEXISTS 6 /* file already exists */
#define ENOUSER 7 /* no such user */
#define EBADOPTION 8 /* bad option */
#define ECANCELLED 99 /* cancelled by administrator */
/*
* options
*/
#define TFTP_OPT_TSIZE "tsize"
#define TFTP_OPT_TIMEOUT "timeout"
#define TFTP_OPT_BLKSIZE "blksize"
#define TFTP_OPT_MCAST "multicast"
#define TFTP_OPT_PORT "udpport"
#define IS_OPT(s,opt) (lstrcmpi (s, opt)==0)
#define PKTSIZE TFTP_SEGSIZE+4
#define MAXPKTSIZE TFTP_MAXSEGSIZE+4
static int fake_log_delegate(bsk_tftpd_context_id_t, const char* msg, ...)
{
(void)msg;
return 0;
}
static int fake_send_delegate(bsk_tftpd_context_id_t, unsigned int, unsigned int, const void* const data, unsigned int size)
{
(void)data;
(void)size;
return 0;
}
static bsk_tftpd_receive_status_t fake_receive_delegate(bsk_tftpd_context_id_t, bsk_tftpd_receive_delegate_operation_t op, const void* const data, unsigned int block, unsigned int offset, unsigned int size_bytes)
{
static bsk_tftpd_receive_status_t reply={bsk_tftpd_ok, 0};
(void)op;
(void)data;
(void)block;
(void)offset;
(void)size_bytes;
return reply;
}
typedef enum { t_unconnected=0, t_connected, t_pending} tftp_state_t;
struct bsk_tftpd_cx_t
{
bsk_tftpd_context_info_t info;
bsk_tftpd_log_delegate_t log_delegate;
bsk_tftpd_send_delegate_t send_delegate;
bsk_tftpd_receive_delegate_t receive_delegate;
tftp_state_t state;
unsigned int client_port;
unsigned int client_address;
unsigned int block_size;
const char* busy_msg;
char filename[1024];
char optmp[1024];
char optval[1024];
unsigned long tsize;
unsigned int ack_buff[1024];
tftppkt_t* ackmsg;
unsigned int ack_len;
int force_blocksize;
};
#define MAX_CX 8
static struct bsk_tftpd_cx_t cxdb[MAX_CX];
static void validate_delegates(bsk_tftpd_context_id_t id)
{
bsk_tftpd_cx_t& cx=cxdb[id];
//static struct bsk_tftpd_cx_t cx={fake_log_delegate, fake_send_delegate, fake_receive_delegate};
if (!cx.receive_delegate)
cx.receive_delegate=fake_receive_delegate;
if (!cx.log_delegate)
cx.log_delegate=fake_log_delegate;
if (!cx.send_delegate)
cx.send_delegate=fake_send_delegate;
}
const bsk_tftpd_context_info_t* bsk_tftpd_context_info(bsk_tftpd_context_id_t id)
{
return &cxdb[id].info;
}
bsk_tftpd_context_id_t bsk_tftpd_context_initialize(bsk_tftpd_context_id_t id, const char* name, unsigned int port, bsk_tftp_cookie_t cookie)
{
static int allocated_cx;
if (id<0)
{
id=allocated_cx;
++allocated_cx;
}
cxdb[id].info.name=name;
cxdb[id].info.port=port;
cxdb[id].info.cookie=cookie;
return id;
}
void bsk_tftpd_set_receive_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_receive_delegate_t receive_delegate)
{
cxdb[id].receive_delegate=receive_delegate ? receive_delegate : fake_receive_delegate;
}
void bsk_tftpd_set_send_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_send_delegate_t send_delegate)
{
cxdb[id].send_delegate=send_delegate ? send_delegate : fake_send_delegate;
}
void bsk_tftpd_set_log_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_log_delegate_t log_delegate)
{
cxdb[id].log_delegate=log_delegate ? log_delegate : fake_log_delegate;
}
#define LOG(msg_, ...) cx.log_delegate(id, msg_, ##__VA_ARGS__)
//#define LOG_DBG(msg_) /*cx.log_delegate msg_*/ ((void)0)
//#define LOG_DBG(msg_) cx.log_delegate msg_
#define LOG_DBG(msg_, ...) cx.log_delegate(id, msg_, ##__VA_ARGS__)
static void nakmsg(bsk_tftpd_context_id_t id, unsigned short code, const char* msg, unsigned int adr=0, unsigned int port=0)
{
const struct errmsg {
int e_code;
const char *e_msg;
} errmsgs[] = {
{ EUNDEF, "Undefined error code" },
{ ENOTFOUND, "File not found" },
{ EACCESS, "Access violation" },
{ ENOSPACE, "Disk full or allocation exceeded" },
{ EBADOP, "Illegal TFTP operation" },
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ ECANCELLED, "Cancelled by administrator" },
{ -1, 0 }
};
if (msg==0)
{
int i;
int n=0;
for(i=0; /*i<*/errmsgs[i].e_code!=-1; ++i)
if (errmsgs[i].e_code==code)
{
n=i;
break;
}
msg=errmsgs[n].e_msg;
}
bsk_tftpd_cx_t& cx=cxdb[id];
tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ERROR);
tftp_htons(cx.ackmsg->header.th_u.tu_code, code);
strcpy((char*)cx.ackmsg->th_data, msg);
if (adr==0)
adr=cx.client_address;
if (port==0)
port=cx.client_port;
cx.send_delegate(id, adr, port, cx.ackmsg, sizeof(cx.ackmsg->header)+2+strlen(msg));
LOG("%d: %u %s (%x:%d)", id, tftp_ntos(cx.ackmsg->header.th_u.tu_code), cx.ackmsg->th_data, adr, port);
}
static void nak(bsk_tftpd_context_id_t id, unsigned short code)
{
nakmsg(id, code, 0);
}
static int extract_str(char* dst, unsigned int dsize, char** src, unsigned int* ssize, const char* errmsg)
{
bsk_tftpd_context_id_t id=0;
bsk_tftpd_cx_t& cx=cxdb[id];
char* const p=*src;
unsigned int s=*ssize;
unsigned int i;
for(i=0; (i<s) && (p[i]!=0); ++i)
{
if (i>=(dsize-1))
{
LOG("TFTP: %s too long", errmsg);
return -1;
}
if (p[i]>='A' && p[i]<='Z')
dst[i]=p[i]; //disable case conversion!!!! +('a'-'A');
else
dst[i]=p[i];
dst[i+1]=0;
}
if (i==0)
return -1;
s=s-i;
if (s>1 && p[i]==0)
{
--s;
++i;
}
*ssize=s;
*src=&p[i];
return 0;
}
static void tftp_itoa(int v, char* const dst)
{
int i;
char str[16];
for(i=0; v; v/=10, ++i)
{
int tmp=v/10;
tmp=v-tmp*10;
str[i]='0'+tmp;
}
str[i]=0;
--i;
if (i<0)
i=0;
int j;
for(j=0; i>=0; --i, ++j)
{
dst[j]=str[i];
}
dst[j]=0;
}
static int tftp_strcmp(const char* const s1, const char* const s2)
{
int i;
for(i=0; s1[i] && s2[i]; ++i)
{
char c1=s1[i];
if (c1>='A' && c1<='Z')
c1=c1+('a'-'A');
char c2=s2[i];
if (c2>='A' && c2<='Z')
c2=c2+('a'-'A');
if (c1!=c2)
return -1;
}
return 0;
}
static unsigned int tftp_atoi(const char* s)
{
unsigned int value=0;
for(; *s; ++s)
{
value=value*10;
value=value+*s-'0';
}
return value;
}
static int doConnect(bsk_tftpd_context_id_t id, unsigned int c_adr, unsigned int c_port, const tftppkt_t* const pkt, unsigned int size)
{
char* p=(char*)pkt->header.th_u.tu_stuff;
unsigned int s=size-sizeof(tftphdr_t);
int opterr=0;
int ack_counter=0;
int has_bklsize=0;
bsk_tftpd_receive_status_t sts;
bsk_tftpd_cx_t& cx=cxdb[id];
cx.block_size=512;
cx.tsize=0;
memset(cx.ack_buff, 0, sizeof cx.ack_buff);
cx.ackmsg=(tftppkt_t*)cx.ack_buff;
tftp_htons(cx.ackmsg->header.th_opcode, TFTP_OACK);
if (extract_str(cx.filename, sizeof cx.filename, &p, &s, "filename"))
{
LOG("TFTP: invalid filename");
nak(id, EBADOP);
return 1;
}
/*TODO: add grant request*/
LOG("TFTP: filename <%s>\n", cx.filename);
if (extract_str(cx.optmp, sizeof cx.optmp, &p, &s, "mode"))
{
LOG("TFTP: unsupported mode");
nak(id, EBADOP);
return 1;
}
if (tftp_strcmp(cx.optmp, "octet") && tftp_strcmp(cx.optmp, "binary"))
{
LOG(("TFTP: unsupported mode\n"));
nak(id, EBADOP);
return 1;
}
while(extract_str(cx.optmp, sizeof cx.optmp, &p, &s, "opt")==0)
{
if (extract_str(cx.optval, sizeof cx.optval, &p, &s, "optval"))
{
LOG("TFTP: opt=%s, value=ERROR", cx.optmp);
nak(id, EBADOP);
return 1;
}
if (tftp_strcmp(cx.optmp, TFTP_OPT_BLKSIZE)==0)
{
unsigned int v=tftp_atoi(cx.optval);
LOG_DBG("TFTP: opt=%s, value=%s (%u)", cx.optmp, cx.optval, v);
if (v<TFTP_MINSEGSIZE)
v=TFTP_MINSEGSIZE;
if (v>TFTP_MAXSEGSIZE)
v=TFTP_MAXSEGSIZE;
strcpy((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter], TFTP_OPT_BLKSIZE);
ack_counter+=sizeof(TFTP_OPT_BLKSIZE);
char* p=(char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter];
tftp_itoa(v, p);
ack_counter+=strlen((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]);
has_bklsize=1;
cx.block_size=v;
LOG("TFTP: block size=%d (%s)", cx.block_size, p);
}
else if (tftp_strcmp(cx.optmp, TFTP_OPT_TSIZE)==0)
{
cx.tsize=tftp_atoi(cx.optval);
LOG("TFTP: opt=%s, value=%s", cx.optmp, cx.optval);
}
else
{
LOG("TFTP: opt=%s, value=%s - unsupported option", cx.optmp, cx.optval);
++opterr;
}
}
if (opterr)
{
nak(id, EBADOP);
return 1;
}
sts=cx.receive_delegate(id, bsk_tftpd_open_file, cx.filename, 0, 0, cx.tsize);
if (sts.sts)
{
nakmsg(id, sts.sts, sts.ack_msg); //EBADOP);
return 1;
}
if (!has_bklsize && cx.force_blocksize)
{
strcpy((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter], TFTP_OPT_BLKSIZE);
ack_counter+=sizeof(TFTP_OPT_BLKSIZE);
tftp_itoa(TFTP_MAXSEGSIZE, (char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]);
ack_counter+=strlen((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]);
}
if (ack_counter==0)
{
tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ACK);
cx.ackmsg->header.th_u.tu_block[0]=0;
cx.ackmsg->header.th_u.tu_block[1]=0;
ack_counter=1;
}
cx.send_delegate(id, c_adr, c_port, cx.ackmsg, (ack_counter+sizeof(tftphdr_t))-1);
return 0;
}
static int doProcessData(bsk_tftpd_context_id_t id, const tftppkt_t* const pkt, unsigned int size)
{
bsk_tftpd_receive_status_t sts;
unsigned short block=tftp_ntos(pkt->header.th_u.tu_block);
unsigned int s=size-sizeof(pkt->header);
bsk_tftpd_cx_t& cx=cxdb[id];
LOG_DBG("TFTP: receive b%u, %ubytes\n", block, s);
int end=s<cx.block_size;
unsigned long offset=0;
if (block>0)
offset=(block-1)*cx.block_size;
sts=cx.receive_delegate(id, end ? bsk_tftpd_end : bsk_tftpd_data, pkt->th_data, block, offset, s);
if (sts.sts && sts.sts<bsk_tftpd_dont_ack)
{
nakmsg(id, sts.sts, sts.ack_msg); //EBADOP);
return -1;
}
tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ACK);
cx.ackmsg->header.th_u.tu_block[0]=pkt->header.th_u.tu_block[0];
cx.ackmsg->header.th_u.tu_block[1]=pkt->header.th_u.tu_block[1];
if (sts.sts!=bsk_tftpd_dont_ack)
{
cx.send_delegate(id, cx.client_address, cx.client_port, cx.ackmsg, sizeof(cx.ackmsg->header));
}
if (end)
return sts.sts!=bsk_tftpd_dont_ack ? 1 : 2;
return 0;
}
int bsk_tftpd_process_packet(bsk_tftpd_context_id_t id, unsigned int client_address, unsigned int client_port, const void* packet, unsigned int size_bytes)
{
//static int initialized;
unsigned short opcode;
tftphdr_t* hdr=(tftphdr_t*)packet;
tftppkt_t* pkt=(tftppkt_t*)packet;
bsk_tftpd_cx_t& cx=cxdb[id];
//if (!initialized)
{
cx.ackmsg=(tftppkt_t*)cx.ack_buff;
memset(cx.ack_buff, 0, sizeof cx.ack_buff);
//initialized=1;
}
LOG_DBG("TFTP: process packet: %u %x %u", size_bytes, client_address, client_port);
if (size_bytes<sizeof(tftphdr_t))
return -1;
validate_delegates(id);
opcode=tftp_ntos(hdr->th_opcode);
if (opcode==TFTP_ERROR)
{
unsigned short code=tftp_ntos(pkt->header.th_u.tu_code);
char* p=(char*)pkt->th_data;
unsigned int s=size_bytes-sizeof(pkt->header);
if (!extract_str(cx.optmp, sizeof cx.optmp, &p, &s, 0))
LOG("TFTP: error from peer: %u %s\n", code, cx.optmp);
else
LOG("TFTP: error from peer: %u (unknown)\n", code);
if (cx.state==t_connected)
{
cx.receive_delegate(id, bsk_tftpd_error, 0, 0, 0, 0);
cx.state=t_unconnected;
}
return -1;
}
if (opcode==TFTP_ACK)
{
LOG(("TFTP: unexpected ACK from peer\n"));
return -1;
}
if (opcode==TFTP_RRQ)
{
char* p=(char*)pkt->header.th_u.tu_stuff;
#if 0
unsigned int s=size_bytes-sizeof(tftphdr_t);
if (extract_str(cx.filename, sizeof cx.filename, &p, &s, "filename"))
{
LOG("TFTP: invalid filename");
nak(id, EBADOP);
return 0;
}
#endif
bsk_tftpd_receive_status_t sts=cx.receive_delegate(id, bsk_tftpd_open_read_not_supported, p, 0, 0, 0);
nakmsg(id, sts.sts, sts.ack_msg, client_address, client_port);
return -100;
}
if (cx.busy_msg)
{
nakmsg(id, EACCESS, cx.busy_msg, client_address, client_port);
return -10;
}
switch(cx.state)
{
case t_unconnected:
cx.client_address=client_address;
cx.client_port=client_port;
if (opcode!=TFTP_WRQ)
{
LOG("TFTP: Unexpected request %d from peer", opcode);
nak(id, EBADOP);
return -2;
}
if (!doConnect(id, client_address, client_port, pkt, size_bytes))
{
LOG("TFTP: connected: %x %u", client_address, client_port);
cx.state=t_connected;
}
break;
case t_connected:
case t_pending:
if (opcode==TFTP_WRQ)
{
if (cx.state==t_pending)
{
nakmsg(id, EACCESS, "pending operation");
return 2;
}
}
if ((cx.client_address!=client_address) || (cx.client_port!=client_port))
{
LOG("TFTP: ERROR - already connected!");
nak(id, EBADID);
return -5;
}
if (opcode==TFTP_DATA)
{
if (cx.state==t_pending)
{
return 2;
}
int res=doProcessData(id, pkt, size_bytes);
if (res<0)
{
LOG("TFTP: ERROR - disconnect!\n");
cx.state=t_unconnected;
return -3;
}
if (res>0)
{
if (res==2)
{
cx.state=t_pending;
LOG("TFTP: completed - pending ack\n");
}
else
{
LOG("TFTP: completed (%d blocks)", 0);
cx.state=t_unconnected;
}
return res;
}
}
else
{
LOG("TFTP: disconnect - invalid opcode: %u\n", opcode);
nak(id, EBADOP);
cx.state=t_unconnected;
return -4;
}
break;
}
return 0;
}
int bsk_tftpd_send_ack(bsk_tftpd_context_id_t id, bsk_tftpd_status_t /*code*/, const char* /*msg*/)
{
bsk_tftpd_cx_t& cx=cxdb[id];
LOG("ACK!");
tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ACK);
cx.ackmsg->header.th_u.tu_block[0]=0;
cx.ackmsg->header.th_u.tu_block[1]=0;
cx.send_delegate(id, cx.client_address, cx.client_port, cx.ackmsg, sizeof(cx.ackmsg->header));
cx.state=t_unconnected;
return 0;
}
int bsk_tftpd_set_busy(bsk_tftpd_context_id_t id, const char* msg) //msg=0 means NO BUSY
{
bsk_tftpd_cx_t& cx=cxdb[id];
cx.busy_msg=msg;
return 0;
}
//Client I/F