Skip to content
Snippets Groups Projects
tftp.c 43.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •      * 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) {
    
    
      Curl_pgrsStartNow(conn->data);
    
      *done = TRUE;
      code = CURLE_OK;
      return(code);
    }
    
    /**********************************************************
     *
    
     *
     * The done callback
     *
     **********************************************************/
    
    static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
    
                              bool premature)
    
      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)
    
      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 &&
    
             (NEXT_BLOCKNUM(state->block) == 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;
    
    
        *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 */
      }
    
    
      /* 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);
    
    /**********************************************************
     *
     * 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;
    
      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 = Curl_sleep_time(data->set.max_send_speed,
                                         data->progress.ulspeed, state->blksize);
    
          if(data->set.max_recv_speed &&
             (data->progress.dlspeed > data->set.max_recv_speed)) {
    
            timeout_ms = Curl_sleep_time(data->set.max_recv_speed,
                                         data->progress.dlspeed, state->blksize);
    
            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));
    
            /* A timeout occurred, 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 {
    
              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);
    
      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 = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE;
    
          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 = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE;
    
            Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
    
    
      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 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. */
        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;
    }