Newer
Older
Daniel Stenberg
committed
state->blksize = TFTP_BLKSIZE_DEFAULT;
state->requested_blksize = blksize;
((struct sockaddr *)&state->local_addr)->sa_family =
tftp_set_timeouts(state);
if(!conn->bits.bound) {
/* If not already bound, bind to any interface, random UDP port. If it is
* reused or a custom local port was desired, this has already been done!
Daniel Stenberg
committed
*
Daniel Stenberg
committed
* We once used the size of the local_addr struct as the third argument
* for bind() to better work with IPv6 or whatever size the struct could
* have, but we learned that at least Tru64, AIX and IRIX *requires* the
* size of that argument to match the exact size of a 'sockaddr_in' struct
* when running IPv4-only.
Daniel Stenberg
committed
*
* Therefore we use the size from the address we connected to, which we
* assume uses the same IP version and thus hopefully this works for both
* IPv4 and IPv6...
*/
rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr,
conn->ip_addr->ai_addrlen);
if(rc) {
Daniel Stenberg
committed
failf(conn->data, "bind() failed; %s",
Curl_strerror(conn, SOCKERRNO));
Daniel Stenberg
committed
return CURLE_COULDNT_CONNECT;
}
conn->bits.bound = TRUE;
Curl_pgrsStartNow(conn->data);
*done = TRUE;
}
/**********************************************************
*
* tftp_done
*
* The done callback
*
**********************************************************/
static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
{
CURLcode result = CURLE_OK;
Daniel Stenberg
committed
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
(void)status; /* unused */
Daniel Stenberg
committed
(void)premature; /* not used */
if(Curl_pgrsDone(conn))
return CURLE_ABORTED_BY_CALLBACK;
Daniel Stenberg
committed
/* If we have encountered an error */
result = tftp_translate_code(state->error);
Daniel Stenberg
committed
}
/**********************************************************
*
Daniel Stenberg
committed
* tftp_getsock
*
Daniel Stenberg
committed
* The getsock callback
*
**********************************************************/
Daniel Stenberg
committed
static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
Daniel Stenberg
committed
{
if(!numsocks)
return GETSOCK_BLANK;
Daniel Stenberg
committed
socks[0] = conn->sock[FIRSTSOCKET];
return GETSOCK_READSOCK(0);
}
/**********************************************************
*
* tftp_receive_packet
*
* Called once select fires and data is ready on the socket
*
**********************************************************/
static CURLcode tftp_receive_packet(struct connectdata *conn)
{
Daniel Stenberg
committed
struct Curl_sockaddr_storage fromaddr;
Daniel Stenberg
committed
CURLcode result = CURLE_OK;
struct Curl_easy *data = conn->data;
Daniel Stenberg
committed
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
struct SingleRequest *k = &data->req;
/* Receive the packet */
fromlen = sizeof(fromaddr);
state->rbytes = (int)recvfrom(state->sockfd,
(void *)state->rpacket.data,
state->blksize+4,
0,
(struct sockaddr *)&fromaddr,
&fromlen);
Daniel Stenberg
committed
if(state->remote_addrlen==0) {
memcpy(&state->remote_addr, &fromaddr, fromlen);
state->remote_addrlen = fromlen;
}
Daniel Stenberg
committed
/* Sanity check packet length */
if(state->rbytes < 4) {
failf(data, "Received too short packet");
/* Not a timeout, but how best to handle it? */
state->event = TFTP_EVENT_TIMEOUT;
}
else {
/* The event is given by the TFTP packet time */
state->event = (tftp_event_t)getrpacketevent(&state->rpacket);
switch(state->event) {
case TFTP_EVENT_DATA:
/* Don't pass to the client empty or retransmitted packets */
if(state->rbytes > 4 &&
(NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) {
Daniel Stenberg
committed
result = Curl_client_write(conn, CLIENTWRITE_BODY,
(char *)state->rpacket.data+4,
state->rbytes-4);
Daniel Stenberg
committed
if(result) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return result;
}
k->bytecount += state->rbytes-4;
Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount);
}
break;
case TFTP_EVENT_ERROR:
state->error = (tftp_error_t)getrpacketblock(&state->rpacket);
infof(data, "%s\n", (const char *)state->rpacket.data+4);
break;
case TFTP_EVENT_ACK:
break;
case TFTP_EVENT_OACK:
result = tftp_parse_option_ack(state,
(const char *)state->rpacket.data+2,
state->rbytes-2);
Daniel Stenberg
committed
if(result)
return result;
break;
case TFTP_EVENT_RRQ:
case TFTP_EVENT_WRQ:
default:
failf(data, "%s", "Internal error: Unexpected packet");
break;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
/* Update the progress meter */
if(Curl_pgrsUpdate(conn)) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return CURLE_ABORTED_BY_CALLBACK;
}
}
return result;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
/**********************************************************
*
* tftp_state_timeout
*
* Check if timeouts have been reached
*
**********************************************************/
static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event)
{
time_t current;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
Daniel Stenberg
committed
*event = TFTP_EVENT_NONE;
time(¤t);
if(current > state->max_time) {
DEBUGF(infof(conn->data, "timeout: %ld > %ld\n",
(long)current, (long)state->max_time));
Daniel Stenberg
committed
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
Daniel Stenberg
committed
}
else if(current > state->rx_time+state->retry_time) {
if(event)
Daniel Stenberg
committed
*event = TFTP_EVENT_TIMEOUT;
time(&state->rx_time); /* update even though we received nothing */
}
/* there's a typecast below here since 'time_t' may in fact be larger than
'long', but we estimate that a 'long' will still be able to hold number
of seconds even if "only" 32 bit */
return (long)(state->max_time - current);
Daniel Stenberg
committed
}
/**********************************************************
*
* tftp_multi_statemach
*
* Handle single RX socket event and return
*
**********************************************************/
static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done)
{
int rc;
tftp_event_t event;
CURLcode result = CURLE_OK;
struct Curl_easy *data = conn->data;
Daniel Stenberg
committed
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
long timeout_ms = tftp_state_timeout(conn, &event);
*done = FALSE;
if(timeout_ms <= 0) {
failf(data, "TFTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
else if(event != TFTP_EVENT_NONE) {
Daniel Stenberg
committed
result = tftp_state_machine(state, event);
*done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE;
Daniel Stenberg
committed
if(*done)
/* Tell curl we're done */
Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
Daniel Stenberg
committed
}
else {
/* no timeouts to handle, check our socket */
rc = Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD, 0);
if(rc == -1) {
/* bail out */
int error = SOCKERRNO;
failf(data, "%s", Curl_strerror(conn, error));
state->event = TFTP_EVENT_ERROR;
}
Daniel Stenberg
committed
else if(rc != 0) {
result = tftp_receive_packet(conn);
Daniel Stenberg
committed
result = tftp_state_machine(state, state->event);
*done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE;
Daniel Stenberg
committed
if(*done)
/* Tell curl we're done */
Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
Daniel Stenberg
committed
}
/* if rc == 0, then select() timed out */
}
Daniel Stenberg
committed
return result;
}
/**********************************************************
*
* tftp_doing
*
* Called from multi.c while DOing
Daniel Stenberg
committed
*
**********************************************************/
static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done)
{
CURLcode result;
result = tftp_multi_statemach(conn, dophase_done);
if(*dophase_done) {
DEBUGF(infof(conn->data, "DO phase is complete\n"));
}
else if(!result) {
/* The multi code doesn't have this logic for the DOING state so we
provide it for TFTP since it may do the entire transfer in this
state. */
if(Curl_pgrsUpdate(conn))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(conn->data, Curl_tvnow());
}
Daniel Stenberg
committed
return result;
}
/**********************************************************
*
* tftp_peform
*
* Entry point for transfer from tftp_do, sarts state mach
*
**********************************************************/
static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done)
{
CURLcode result = CURLE_OK;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
*dophase_done = FALSE;
result = tftp_state_machine(state, TFTP_EVENT_INIT);
if((state->state == TFTP_STATE_FIN) || result)
return result;
Daniel Stenberg
committed
tftp_multi_statemach(conn, dophase_done);
Daniel Stenberg
committed
if(*dophase_done)
DEBUGF(infof(conn->data, "DO phase is complete\n"));
return result;
}
/**********************************************************
*
* tftp_do
*
* The do callback
*
* This callback initiates the TFTP transfer
*
**********************************************************/
static CURLcode tftp_do(struct connectdata *conn, bool *done)
{
tftp_state_data_t *state;
CURLcode result;
Daniel Stenberg
committed
*done = FALSE;
if(!conn->proto.tftpc) {
result = tftp_connect(conn, done);
if(result)
return result;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
state = (tftp_state_data_t *)conn->proto.tftpc;
if(!state)
return CURLE_BAD_CALLING_ORDER;
Daniel Stenberg
committed
result = tftp_perform(conn, done);
Daniel Stenberg
committed
/* If tftp_perform() returned an error, use that for return code. If it
was OK, see if tftp_translate_code() has an error. */
/* If we have encountered an internal tftp error, translate it. */
result = tftp_translate_code(state->error);
Daniel Stenberg
committed
}
Patrick Monnerat
committed
static CURLcode tftp_setup_connection(struct connectdata * conn)
Patrick Monnerat
committed
{
struct Curl_easy *data = conn->data;
Patrick Monnerat
committed
char * type;
char command;
conn->socktype = SOCK_DGRAM; /* UDP datagram based */
/* TFTP URLs support an extension like ";mode=<typecode>" that
* we'll try to get now! */
Daniel Stenberg
committed
type = strstr(data->state.path, ";mode=");
Patrick Monnerat
committed
Patrick Monnerat
committed
type = strstr(conn->host.rawalloc, ";mode=");
Patrick Monnerat
committed
*type = 0; /* it was in the middle of the hostname */
command = Curl_raw_toupper(type[6]);
Patrick Monnerat
committed
switch (command) {
case 'A': /* ASCII mode */
case 'N': /* NETASCII mode */
data->set.prefer_ascii = TRUE;
break;
case 'O': /* octet mode */
case 'I': /* binary mode */
default:
/* switch off ASCII */
data->set.prefer_ascii = FALSE;
break;
}
}
return CURLE_OK;
}