Skip to content
Snippets Groups Projects
tftp.c 43.1 KiB
Newer Older

  Curl_pgrsStartNow(conn->data);

  *done = TRUE;
  code = CURLE_OK;
  return(code);
}

/**********************************************************
 *
 *
 * The done callback
 *
 **********************************************************/
static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
  CURLcode code = CURLE_OK;
  tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;

  /* If we have encountered an error */
  code = tftp_translate_code(state->error);

/**********************************************************
 *
 *
 **********************************************************/
static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
                       int numsocks)
{
  if(!numsocks)
    return GETSOCK_BLANK;
  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)
Yang Tse's avatar
 
Yang Tse committed
  curl_socklen_t        fromlen;
  CURLcode              result = CURLE_OK;
  struct SessionHandle  *data = conn->data;
  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);
  if(state->remote_addrlen==0) {
    memcpy(&state->remote_addr, &fromaddr, fromlen);
    state->remote_addrlen = fromlen;
  }
  /* 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 &&
          ((state->block+1) == getrpacketblock(&state->rpacket))) {
        result = Curl_client_write(conn, CLIENTWRITE_BODY,
                                 (char *)state->rpacket.data+4,
                                 state->rbytes-4);
        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);
      if(result)
        return result;
      break;
    case TFTP_EVENT_RRQ:
    case TFTP_EVENT_WRQ:
    default:
      failf(data, "%s", "Internal error: Unexpected packet");
      break;
    }
    /* Update the progress meter */
    if(Curl_pgrsUpdate(conn)) {
      tftp_state_machine(state, TFTP_EVENT_ERROR);
      return CURLE_ABORTED_BY_CALLBACK;
    }
  }
  return result;
}
/**********************************************************
 *
 * 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;

  if (event)
    *event = TFTP_EVENT_NONE;

  time(&current);
  if(current > state->max_time) {
Yang Tse's avatar
 
Yang Tse committed
    DEBUGF(infof(conn->data, "timeout: %ld > %ld\n",
                 (long)current, (long)state->max_time));
    state->error = TFTP_ERR_TIMEOUT;
    state->state = TFTP_STATE_FIN;
    return(0);
  else if (current > state->rx_time+state->retry_time) {
    if (event)
      *event = TFTP_EVENT_TIMEOUT;
    time(&state->rx_time); /* update even though we received nothing */
    return(state->max_time-current);
  }
  else {
    return(state->max_time-current);
  }
}

static curl_off_t sleep_time(curl_off_t rate_bps, curl_off_t cur_rate_bps,
                             int pkt_size)
{
  curl_off_t min_sleep = 0;
  curl_off_t rv = 0;

  if (rate_bps == 0)
    return 0;

  if (cur_rate_bps > (rate_bps + (rate_bps >> 10))) {
    /* running too fast */
    rate_bps -= rate_bps >> 6;
    min_sleep = 1;
  }
  else if (cur_rate_bps < (rate_bps - (rate_bps >> 10))) {
    /* running too slow */
    rate_bps += rate_bps >> 6;
  }

  rv = ((curl_off_t)((pkt_size * 8) * 1000) / rate_bps);

  if (rv < min_sleep)
    rv = min_sleep;

  return rv;
}


/**********************************************************
 *
 * tftp_easy_statemach
 *
 * Handle easy request until completion
 *
 **********************************************************/
static CURLcode tftp_easy_statemach(struct connectdata *conn)
{
  int                   rc;
  int                   check_time = 0;
  CURLcode              result = CURLE_OK;
  struct SessionHandle  *data = conn->data;
  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
  int                   fd_read;
  curl_off_t            timeout_ms;
  struct SingleRequest  *k = &data->req;
  struct timeval        transaction_start = Curl_tvnow();

  k->start = transaction_start;
  k->now = transaction_start;
  for(; (state->state != TFTP_STATE_FIN) && (result == CURLE_OK); ) {

    timeout_ms = state->retry_time * 1000;

    if (data->set.upload) {
      if (data->set.max_send_speed &&
          (data->progress.ulspeed > data->set.max_send_speed)) {
        fd_read = CURL_SOCKET_BAD;
        timeout_ms = sleep_time(data->set.max_send_speed,
                                data->progress.ulspeed, state->blksize);
      }
      else {
        fd_read = state->sockfd;
      }
    }
    else {
      if (data->set.max_recv_speed &&
          (data->progress.dlspeed > data->set.max_recv_speed)) {
        fd_read = CURL_SOCKET_BAD;
        timeout_ms = sleep_time(data->set.max_recv_speed,
                                data->progress.dlspeed, state->blksize);
      }
      else {
        fd_read = state->sockfd;
      }
    }

    if(data->set.timeout) {
      timeout_ms = data->set.timeout - Curl_tvdiff(k->now, k->start);
      if (timeout_ms > state->retry_time * 1000)
        timeout_ms = state->retry_time * 1000;
      else if(timeout_ms < 0)
        timeout_ms = 0;
    }


    /* Wait until ready to read or timeout occurs */
    rc=Curl_socket_ready(fd_read, CURL_SOCKET_BAD, timeout_ms);

    k->now = Curl_tvnow();

    /* Force a progress callback if it's been too long */
    if (Curl_tvdiff(k->now, k->start) >= data->set.timeout) {
       if(Curl_pgrsUpdate(conn)) {
          tftp_state_machine(state, TFTP_EVENT_ERROR);
          return CURLE_ABORTED_BY_CALLBACK;
       }
       k->start = k->now;
    }
      failf(data, "%s", Curl_strerror(conn, error));

      if(rc==0) {
        /* A timeout occured, but our timeout is variable, so maybe
           just continue? */
        long rtms = state->retry_time * 1000;
        if (Curl_tvdiff(k->now, transaction_start) > rtms) {
          state->event = TFTP_EVENT_TIMEOUT;
          /* Force a look at transfer timeouts */
          check_time = 1;
        }
        else {
          continue; /* skip state machine */
        }
      }
      else {
        if (result == CURLE_OK)
           transaction_start = Curl_tvnow();

        if(k->bytecountp)
          *k->bytecountp = k->bytecount; /* read count */
        if(k->writebytecountp)
          *k->writebytecountp = k->writebytecount; /* write count */
      }

    result = tftp_state_machine(state, state->event);
  result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
/**********************************************************
 *
 * 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 SessionHandle  *data = conn->data;
  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) {
    result = tftp_state_machine(state, event);
    if(result != CURLE_OK)
      return(result);
    *done = (bool)(state->state == TFTP_STATE_FIN);
    if(*done)
      /* Tell curl we're done */
      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
  }
  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;
    else if(rc != 0) {
      result = tftp_receive_packet(conn);
      if(result != CURLE_OK)
        return(result);
      result = tftp_state_machine(state, state->event);
      if(result != CURLE_OK)
        return(result);
      *done = (bool)(state->state == TFTP_STATE_FIN);
      if(*done)
        /* Tell curl we're done */
        result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
    }
    /* if rc == 0, then select() timed out */

  return result;
}

/**********************************************************
 *
 * tftp_doing
 *
 * Called from multi.c while DOing
 *
 **********************************************************/
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"));
  }
  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 != CURLE_OK)
    return(result);

  if(conn->data->state.used_interface == Curl_if_multi)
    tftp_multi_statemach(conn, dophase_done);
  else {
    result = tftp_easy_statemach(conn);
    *dophase_done = TRUE; /* with the easy interface we are done here */
  }

  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              code;

  *done = FALSE;

  /*
    Since connections can be re-used between SessionHandles, this might be a
    connection already existing but on a fresh SessionHandle struct so we must
    make sure we have a good 'struct TFTP' to play with. For new connections,
    the struct TFTP is allocated and setup in the tftp_connect() function.
  */
  Curl_reset_reqproto(conn);

  if(!conn->proto.tftpc) {
    code = tftp_connect(conn, done);
    if(code)
      return code;
  }
  state = (tftp_state_data_t *)conn->proto.tftpc;

  code = tftp_perform(conn, done);

  /* If we have encountered an error */
  code = tftp_translate_code(state->error);

static CURLcode tftp_setup_connection(struct connectdata * conn)
{
  struct SessionHandle *data = conn->data;
  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! */
  type = strstr(data->state.path, ";mode=");
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(!type)
    type = strstr(conn->host.rawalloc, ";mode=");

Daniel Stenberg's avatar
Daniel Stenberg committed
  if(type) {
    *type = 0;                   /* it was in the middle of the hostname */
    command = Curl_raw_toupper(type[6]);

    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;
}