blob: 0457901b27e92dda8dacc45b09636bb5c3f8a63d [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <assert.h>
#include <endian.h>
#include <libpayload.h>
#include <stdint.h>
#include "drivers/net/net.h"
#include "net/net.h"
#include "net/uip.h"
#include "net/uip_udp_packet.h"
#include "netboot/tftp.h"
typedef enum TftpStatus
{
TftpPending = 0,
TftpSuccess = 1,
TftpFailure = 2
} TftpStatus;
static TftpStatus tftp_status;
static uint8_t *tftp_dest;
static int tftp_got_response;
static int tftp_blocknum;
static uint32_t tftp_total_size;
static uint32_t tftp_max_size;
typedef struct TftpAckPacket
{
uint16_t opcode;
uint16_t block;
} TftpAckPacket;
static void tftp_print_error_pkt(void)
{
if (uip_datalen() >= 4) {
uint16_t error;
memcpy(&error, (uint8_t *)uip_appdata + 2,
sizeof(error));
error = ntohw(error);
printf("Error code %d", error);
switch (error) {
case TftpUndefined:
printf(" (Undefined)\n");
break;
case TftpFileNotFound:
printf(" (File not found)\n");
break;
case TftpAccessViolation:
printf(" (Access violation)\n");
break;
case TftpNoSpace:
printf(" (Not enough space)\n");
break;
case TftpIllegalOp:
printf(" (Illegal operation)\n");
break;
case TftpUnknownId:
printf(" (Unknown transfer ID)\n");
break;
case TftpFileExists:
printf(" (File already exists)\n");
break;
case TftpNoSuchUser:
printf(" (No such user)\n");
break;
default:
printf("\n");
}
}
if (uip_datalen() > 4) {
// Copy out the error message so we can null terminate it.
int message_len = uip_datalen() - 4 + 1;
char *message = xmalloc(message_len);
message[message_len - 1] = 0;
memcpy(message, uip_appdata, message_len - 1);
printf("Error message: %s\n", message);
free(message);
}
}
static void tftp_callback(void)
{
// If there isn't at least an opcode, ignore the packet.
if (!uip_newdata())
return;
if (uip_datalen() < 2)
return;
if (!uip_udp_conn->rport) {
int srcport_offset = offsetof(struct uip_udpip_hdr, srcport);
memcpy(&uip_udp_conn->rport,
&uip_buf[CONFIG_UIP_LLH_LEN + srcport_offset],
sizeof(uip_udp_conn->rport));
}
// Extract the opcode.
uint16_t opcode;
memcpy(&opcode, uip_appdata, sizeof(opcode));
opcode = ntohw(opcode);
// If there was an error, report it and stop the transfer.
if (opcode == TftpError) {
tftp_status = TftpFailure;
printf(" error!\n");
tftp_print_error_pkt();
return;
}
// We should only get data packets. Those are at least 4 bytes long.
if (opcode != TftpData || uip_datalen() < 4)
return;
// Get the block number.
uint16_t blocknum;
memcpy(&blocknum, (uint8_t *)uip_appdata + 2, sizeof(blocknum));
blocknum = ntohw(blocknum);
// Ignore blocks which are duplicated or out of order.
if (blocknum != tftp_blocknum)
return;
void *new_data = (uint8_t *)uip_appdata + 4;
int new_data_len = uip_datalen() - 4;
// If the block is too big, reject it.
if (new_data_len > TftpMaxBlockSize)
return;
// If we're out of space give up.
if (new_data_len > tftp_max_size - tftp_total_size) {
tftp_status = TftpFailure;
printf("TFTP transfer too large.\n");
return;
}
// If there's any data, copy it in.
if (new_data_len) {
memcpy(tftp_dest, new_data, new_data_len);
tftp_dest += new_data_len;
}
tftp_total_size += new_data_len;
// If this block was less than the maximum size, the transfer is done.
if (new_data_len < TftpMaxBlockSize) {
tftp_status = TftpSuccess;
return;
}
// Prepare an ack.
TftpAckPacket ack = {
htonw(TftpAck),
htonw(tftp_blocknum)
};
memcpy(uip_appdata, &ack, sizeof(ack));
uip_udp_send(sizeof(ack));
// Move on to the next block.
tftp_blocknum++;
if (!(tftp_blocknum % 10)) {
// Give some feedback that something is happening.
printf("#");
}
tftp_got_response = 1;
}
int tftp_read(void *dest, uip_ipaddr_t *server_ip, const char *bootfile,
uint32_t *size, uint32_t max_size)
{
// Build the read request packet.
uint16_t opcode = htonw(TftpReadReq);
int opcode_len = sizeof(opcode);
int name_len = strlen(bootfile) + 1;
const char mode[] = "Octet";
int mode_len = sizeof(mode);
int read_req_len = opcode_len + name_len + mode_len;
uint8_t *read_req = xmalloc(read_req_len);
memcpy(read_req, &opcode, opcode_len);
memcpy(read_req + opcode_len, bootfile, name_len);
memcpy(read_req + opcode_len + name_len, mode, mode_len);
// Set up the UDP connection.
struct uip_udp_conn *conn = uip_udp_new(server_ip, htonw(TftpPort));
if (!conn) {
printf("Failed to set up UDP connection.\n");
free(read_req);
return -1;
}
// Send the request.
printf("Sending tftp read request... ");
uip_udp_packet_send(conn, read_req, read_req_len);
conn->rport = 0;
printf("done.\n");
// Prepare for the transfer.
printf("Waiting for the transfer... ");
tftp_status = TftpPending;
tftp_dest = dest;
tftp_blocknum = 1;
tftp_total_size = 0;
tftp_max_size = max_size;
// Poll the network driver until the transaction is done.
net_set_callback(&tftp_callback);
while (tftp_status == TftpPending) {
tftp_got_response = 0;
net_poll();
if (tftp_got_response)
continue;
// No response. Resend our last packet and try again.
if (tftp_blocknum == 1) {
// Resend the read request.
conn->rport = htonw(TftpPort);
uip_udp_packet_send(conn, read_req, read_req_len);
conn->rport = 0;
} else {
// Resend the last ack.
TftpAckPacket ack = {
htonw(TftpAck),
htonw(tftp_blocknum - 1)
};
uip_udp_packet_send(conn, &ack, sizeof(ack));
}
}
uip_udp_remove(conn);
free(read_req);
net_set_callback(NULL);
// See what happened.
if (tftp_status == TftpFailure) {
// The error was printed when it was received.
return -1;
} else {
if (size)
*size = tftp_total_size;
printf(" done.\n");
return 0;
}
}