/*This file has been prepared for Doxygen automatic documentation generation.*/ | |
/*! \file ********************************************************************* | |
* | |
* \brief Basic TFTP Server for AVR32 UC3. | |
* | |
* - Compiler: GNU GCC for AVR32 | |
* - Supported devices: All AVR32 devices can be used. | |
* - AppNote: | |
* | |
* \author Atmel Corporation: http://www.atmel.com \n | |
* Support and FAQ: http://support.atmel.no/ | |
* | |
*****************************************************************************/ | |
/* Copyright (c) 2007, Atmel Corporation All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the following disclaimer. | |
* | |
* 2. Redistributions in binary form must reproduce the above copyright notice, | |
* this list of conditions and the following disclaimer in the documentation | |
* and/or other materials provided with the distribution. | |
* | |
* 3. The name of ATMEL may not be used to endorse or promote products derived | |
* from this software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY ATMEL ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY AND | |
* SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, | |
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
/* | |
Implements a simplistic TFTP server. | |
In order to put data on the TFTP server (not over 2048 bytes) | |
tftp 192.168.0.2 PUT <src_filename> | |
this will copy file from your hard drive to the RAM buffer of the application | |
tftp 192.168.0.2 GET <dst_filename> | |
this will copy file from the RAM buffer of the application to your hard drive | |
You can then check that src_filename and dst_filename are identical | |
*/ | |
#if (TFTP_USED == 1) | |
#include <string.h> | |
/* Scheduler includes. */ | |
#include "FreeRTOS.h" | |
#include "task.h" | |
#include "partest.h" | |
#include "BasicTFTP.h" | |
/* Demo includes. */ | |
#include "portmacro.h" | |
/* lwIP includes. */ | |
#include "lwip/api.h" | |
#include "lwip/tcpip.h" | |
#include "lwip/memp.h" | |
#include "lwip/stats.h" | |
#include "lwip/opt.h" | |
#include "lwip/api.h" | |
#include "lwip/arch.h" | |
#include "lwip/sys.h" | |
#include "netif/loopif.h" | |
#include "lwip/sockets.h" | |
#define O_WRONLY 1 | |
#define O_RDONLY 2 | |
/* The port on which we listen. */ | |
#define TFTP_PORT ( 69 ) | |
/* Delay on close error. */ | |
#define TFTP_DELAY ( 10 ) | |
/* Delay on bind error. */ | |
#define TFTP_ERROR_DELAY ( 40 ) | |
#define TFTP_LED ( 4 ) | |
char data_out[SEGSIZE+sizeof(struct tftphdr)]; | |
char data_in[SEGSIZE+sizeof(struct tftphdr)]; | |
//struct tftp_server *server; | |
/*------------------------------------------------------------*/ | |
static char * errmsg[] = { | |
"Undefined error code", // 0 nothing defined | |
"File not found", // 1 TFTP_ENOTFOUND | |
"Access violation", // 2 TFTP_EACCESS | |
"Disk full or allocation exceeded", // 3 TFTP_ENOSPACE | |
"Illegal TFTP operation", // 4 TFTP_EBADOP | |
"Unknown transfer ID", // 5 TFTP_EBADID | |
"File already exists", // 6 TFTP_EEXISTS | |
"No such user", // 7 TFTP_ENOUSER | |
}; | |
/* Send an error packet to the client */ | |
static void | |
tftpd_send_error(int s, struct tftphdr * reply, int err, | |
struct sockaddr_in *from_addr, int from_len) | |
{ | |
if ( ( 0 <= err ) && ( sizeof(errmsg)/sizeof(errmsg[0]) > err) ) { | |
reply->th_opcode = htons(ERROR); | |
reply->th_code = htons(err); | |
if ( (0 > err) || (sizeof(errmsg)/sizeof(errmsg[0]) <= err) ) | |
err = 0; // Do not copy a random string from hyperspace | |
strcpy(reply->th_msg, errmsg[err]); | |
sendto(s, reply, 4+strlen(reply->th_msg)+1, 0, | |
(struct sockaddr *)from_addr, from_len); | |
} | |
} | |
char cRamBuffer[2048]; | |
int lCurrentBlock = 0; | |
int lTotalLength = 0; | |
int tftpd_close_data_file(int fd) | |
{ | |
lCurrentBlock = 0; | |
return (5); | |
} | |
int tftpd_open_data_file(int fd, int mode) | |
{ | |
lCurrentBlock = 0; | |
return (5); | |
} | |
int tftpd_read_data_file(int fd, char * buffer, int length) | |
{ | |
int lReturnValue; | |
if ((lTotalLength -= length) >= 0) { | |
lReturnValue = length; | |
} | |
else | |
{ | |
lReturnValue = lTotalLength + length; | |
lTotalLength = 0; | |
} | |
memcpy(buffer, &cRamBuffer[lCurrentBlock * SEGSIZE], lReturnValue); | |
lCurrentBlock++; | |
return (lReturnValue); | |
} | |
// | |
// callback to store data to the RAM buffer | |
// | |
int tftpd_write_data_file(int fd, char * buffer, int length) | |
{ | |
lTotalLength += length; | |
memcpy(&cRamBuffer[lCurrentBlock * SEGSIZE], buffer, length); | |
lCurrentBlock++; | |
return (length); | |
} | |
// | |
// Receive a file from the client | |
// | |
static void | |
tftpd_write_file(struct tftphdr *hdr, | |
struct sockaddr_in *from_addr, int from_len) | |
{ | |
struct tftphdr *reply = (struct tftphdr *)data_out; | |
struct tftphdr *response = (struct tftphdr *)data_in; | |
int fd, len, ok, tries, closed, data_len, s; | |
unsigned short block; | |
struct timeval timeout; | |
fd_set fds; | |
int total_timeouts = 0; | |
struct sockaddr_in client_addr, local_addr; | |
int client_len; | |
s = socket(AF_INET, SOCK_DGRAM, 0); | |
if (s < 0) { | |
return; | |
} | |
memset((char *)&local_addr, 0, sizeof(local_addr)); | |
local_addr.sin_family = AF_INET; | |
local_addr.sin_len = sizeof(local_addr); | |
local_addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
local_addr.sin_port = htons(INADDR_ANY); | |
if (bind(s, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) { | |
// Problem setting up my end | |
close(s); | |
return; | |
} | |
if ((fd = tftpd_open_data_file((int)hdr->th_stuff, O_WRONLY)) < 0) { | |
tftpd_send_error(s,reply,TFTP_ENOTFOUND,from_addr, from_len); | |
close(s); | |
return; | |
} | |
ok = pdTRUE; | |
closed = pdFALSE; | |
block = 0; | |
while (ok) { | |
// Send ACK telling client he can send data | |
reply->th_opcode = htons(ACK); | |
reply->th_block = htons(block++); // postincrement | |
for (tries = 0; tries < TFTP_RETRIES_MAX; tries++) { | |
sendto(s, reply, 4, 0, (struct sockaddr *)from_addr, from_len); | |
repeat_select: | |
timeout.tv_sec = TFTP_TIMEOUT_PERIOD; | |
timeout.tv_usec = 0; | |
FD_ZERO(&fds); | |
FD_SET(s, &fds); | |
vParTestToggleLED( TFTP_LED ); | |
if (lwip_select(s+1, &fds, 0, 0, &timeout) <= 0) { | |
if (++total_timeouts > TFTP_TIMEOUT_MAX) { | |
tftpd_send_error(s,reply,TFTP_EBADOP,from_addr, from_len); | |
ok = pdFALSE; | |
break; | |
} | |
continue; // retry the send, using up one retry. | |
} | |
vParTestToggleLED( TFTP_LED ); | |
// Some data has arrived | |
data_len = sizeof(data_in); | |
client_len = sizeof(client_addr); | |
if ((data_len = recvfrom(s, data_in, data_len, 0, | |
(struct sockaddr *)&client_addr, &client_len)) < 0) { | |
// What happened? No data here! | |
continue; // retry the send, using up one retry. | |
} | |
if (ntohs(response->th_opcode) == DATA && | |
ntohs(response->th_block) < block) { | |
// Then it is repeat DATA with an old block; listen again, | |
// but do not repeat sending the current ack, and do not | |
// use up a retry count. (we do re-send the ack if | |
// subsequently we time out) | |
goto repeat_select; | |
} | |
if (ntohs(response->th_opcode) == DATA && | |
ntohs(response->th_block) == block) { | |
// Good data - write to file | |
len = tftpd_write_data_file(fd, response->th_data, data_len-4); | |
if (len < (data_len-4)) { | |
// File is "full" | |
tftpd_send_error(s,reply,TFTP_ENOSPACE, | |
from_addr, from_len); | |
ok = pdFALSE; // Give up | |
break; // out of the retries loop | |
} | |
if (data_len < (SEGSIZE+4)) { | |
// End of file | |
closed = pdTRUE; | |
ok = pdFALSE; | |
vParTestSetLED( 0 , 0 ); | |
if (tftpd_close_data_file(fd) == -1) { | |
tftpd_send_error(s,reply,TFTP_EACCESS, | |
from_addr, from_len); | |
break; // out of the retries loop | |
} | |
// Exception to the loop structure: we must ACK the last | |
// packet, the one that implied EOF: | |
reply->th_opcode = htons(ACK); | |
reply->th_block = htons(block++); // postincrement | |
sendto(s, reply, 4, 0, (struct sockaddr *)from_addr, from_len); | |
break; // out of the retries loop | |
} | |
// Happy! Break out of the retries loop. | |
break; | |
} | |
} // End of the retries loop. | |
if (TFTP_RETRIES_MAX <= tries) { | |
tftpd_send_error(s,reply,TFTP_EBADOP,from_addr, from_len); | |
ok = pdFALSE; | |
} | |
} | |
close(s); | |
if (!closed) { | |
tftpd_close_data_file(fd); | |
} | |
} | |
// | |
// Send a file to the client | |
// | |
static void | |
tftpd_read_file(struct tftphdr *hdr, | |
struct sockaddr_in *from_addr, int from_len) | |
{ | |
struct tftphdr *reply = (struct tftphdr *)data_out; | |
struct tftphdr *response = (struct tftphdr *)data_in; | |
int fd, len, tries, ok, data_len, s; | |
unsigned short block; | |
struct timeval timeout; | |
fd_set fds; | |
int total_timeouts = 0; | |
struct sockaddr_in client_addr, local_addr; | |
int client_len; | |
s = socket(AF_INET, SOCK_DGRAM, 0); | |
if (s < 0) { | |
return; | |
} | |
memset((char *)&local_addr, 0, sizeof(local_addr)); | |
local_addr.sin_family = AF_INET; | |
local_addr.sin_len = sizeof(local_addr); | |
local_addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
local_addr.sin_port = htons(INADDR_ANY); | |
if (bind(s, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) { | |
// Problem setting up my end | |
close(s); | |
return; | |
} | |
if ((fd = tftpd_open_data_file((int)hdr->th_stuff, O_RDONLY)) < 0) { | |
tftpd_send_error(s,reply,TFTP_ENOTFOUND,from_addr, from_len); | |
close(s); | |
return; | |
} | |
block = 0; | |
ok = pdTRUE; | |
while (ok) { | |
// Read next chunk of file | |
len = tftpd_read_data_file(fd, reply->th_data, SEGSIZE); | |
reply->th_block = htons(++block); // preincrement | |
reply->th_opcode = htons(DATA); | |
for (tries = 0; tries < TFTP_RETRIES_MAX; tries++) { | |
if (sendto(s, reply, 4+len, 0, | |
(struct sockaddr *)from_addr, from_len) < 0) { | |
// Something went wrong with the network! | |
ok = pdFALSE; | |
break; | |
} | |
repeat_select: | |
timeout.tv_sec = TFTP_TIMEOUT_PERIOD; | |
timeout.tv_usec = 0; | |
FD_ZERO(&fds); | |
FD_SET(s, &fds); | |
vParTestToggleLED( TFTP_LED ); | |
if (select(s+1, &fds, 0, 0, &timeout) <= 0) { | |
if (++total_timeouts > TFTP_TIMEOUT_MAX) { | |
tftpd_send_error(s,reply,TFTP_EBADOP,from_addr, from_len); | |
ok = pdFALSE; | |
break; | |
} | |
continue; // retry the send, using up one retry. | |
} | |
vParTestToggleLED( TFTP_LED ); | |
data_len = sizeof(data_in); | |
client_len = sizeof(client_addr); | |
if ((data_len = recvfrom(s, data_in, data_len, 0, | |
(struct sockaddr *)&client_addr, | |
&client_len)) < 0) { | |
// What happened? Maybe someone lied to us... | |
continue; // retry the send, using up one retry. | |
} | |
if ((ntohs(response->th_opcode) == ACK) && | |
(ntohs(response->th_block) < block)) { | |
// Then it is a repeat ACK for an old block; listen again, | |
// but do not repeat sending the current block, and do not | |
// use up a retry count. (we do re-send the data if | |
// subsequently we time out) | |
goto repeat_select; | |
} | |
if ((ntohs(response->th_opcode) == ACK) && | |
(ntohs(response->th_block) == block)) { | |
// Happy! Break out of the retries loop. | |
break; | |
} | |
} // End of the retries loop. | |
if (TFTP_RETRIES_MAX <= tries) { | |
tftpd_send_error(s,reply,TFTP_EBADOP,from_addr, from_len); | |
ok = pdFALSE; | |
} | |
if (len < SEGSIZE) { | |
break; // That's end of file then. | |
} | |
} | |
close(s); | |
tftpd_close_data_file(fd); | |
} | |
portTASK_FUNCTION( vBasicTFTPServer, pvParameters ) | |
{ | |
int lSocket; | |
int lDataLen, lRecvLen, lFromLen; | |
struct sockaddr_in sLocalAddr, sFromAddr; | |
char cData[SEGSIZE+sizeof(struct tftphdr)]; | |
struct tftphdr *sHdr = (struct tftphdr *)cData; | |
// Set up port | |
// Network order in info; host order in server: | |
for (;;) { | |
// Create socket | |
lSocket = socket(AF_INET, SOCK_DGRAM, 0); | |
if (lSocket < 0) { | |
return; | |
} | |
memset((char *)&sLocalAddr, 0, sizeof(sLocalAddr)); | |
sLocalAddr.sin_family = AF_INET; | |
sLocalAddr.sin_len = sizeof(sLocalAddr); | |
sLocalAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |
sLocalAddr.sin_port = TFTP_PORT; | |
if (bind(lSocket, (struct sockaddr *)&sLocalAddr, sizeof(sLocalAddr)) < 0) { | |
// Problem setting up my end | |
close(lSocket); | |
return; | |
} | |
lRecvLen = sizeof(cData); | |
lFromLen = sizeof(sFromAddr); | |
lDataLen = recvfrom(lSocket, sHdr, lRecvLen, 0, | |
(struct sockaddr *)&sFromAddr, &lFromLen); | |
vParTestSetLED( TFTP_LED , pdTRUE ); | |
close(lSocket); // so that other servers can bind to the TFTP socket | |
if ( lDataLen < 0) { | |
} else { | |
switch (ntohs(sHdr->th_opcode)) { | |
case WRQ: | |
tftpd_write_file(sHdr, &sFromAddr, lFromLen); | |
vParTestSetLED( TFTP_LED , pdFALSE ); | |
break; | |
case RRQ: | |
tftpd_read_file(sHdr, &sFromAddr, lFromLen); | |
vParTestSetLED( TFTP_LED , pdFALSE ); | |
break; | |
case ACK: | |
case DATA: | |
case ERROR: | |
vParTestSetLED( TFTP_LED , pdFALSE ); | |
// Ignore | |
break; | |
default: | |
for(;;) | |
{ | |
vParTestToggleLED( TFTP_LED ); | |
vTaskDelay(200); | |
} | |
} | |
} | |
} | |
} | |
#endif |