iBoot/lib/tftp/tftp.c

294 lines
7.8 KiB
C

/*
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
*
* This document is the property of Apple Computer, Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Computer, Inc.
*/
#include <debug.h>
#include <stdint.h>
#include <sys.h>
#include <sys/task.h>
#include <sys/callout.h>
#include <sys/security.h>
#include <lib/net.h>
#include <lib/net/xp.h>
#include <lib/net/arp.h>
#include <lib/net/ipv4.h>
#include <lib/net/udp.h>
static struct {
uint32_t destip;
uint16_t destport;
uint16_t srcport;
mymbuf_t * outgoingbuf;
int errcount;
struct callout timer;
volatile int complete;
int operation;
union {
struct r {
char * destbuffer;
int destlen;
int pos;
int blocknum;
}r;
struct {
char * sourcebuffer;
int left;
int blocknum;
}w;
} u;
} tftp_stat;
#define TFTP_RECEIVE 0
#define TFTP_SEND 1
#define TFTP_BLOCKSIZE 512
#define TFTP_RRQ 1
#define TFTP_WRQ 2
#define TFTP_DATA 3
#define TFTP_ACK 4
#define TFTP_ERR 5
static void tftp_resetbuf(void)
{
mbuf_setlength(tftp_stat.outgoingbuf,0);
mbuf_setoffset(tftp_stat.outgoingbuf,14+20+8);
}
static int tftp_sendack(uint32_t destip,uint32_t destport,int blocknum)
{
// mymbuf_t *data = mbuf_initialize(4,14+20+8,0);
uint16_t *rawdata;
tftp_resetbuf();
rawdata = (uint16_t *)mbuf_tail(tftp_stat.outgoingbuf,4);
rawdata[0] = htons(TFTP_ACK);
rawdata[1] = htons(blocknum);
transmit_udp(tftp_stat.outgoingbuf,destip,destport,tftp_stat.srcport);
// mbuf_destroy(data);
return 0;
}
static int tftp_senddata(uint32_t destip,uint32_t destport, int blocknum, char *inputdata,int datalen)
{
// mymbuf_t *data = mbuf_initialize(4+datalen,14+20+8,0);
uint16_t *rawdata;
tftp_resetbuf();
rawdata = (uint16_t *)mbuf_tail(tftp_stat.outgoingbuf,4+datalen);
rawdata[0] = htons(TFTP_DATA);
rawdata[1] = htons(blocknum);
memcpy((char*)&rawdata[2],inputdata,datalen);
transmit_udp(tftp_stat.outgoingbuf,destip,destport,tftp_stat.srcport);
// mbuf_destroy(data);
return 0;
}
static int tftp_receive_listener(char *buf, int offset,int len, void *arg)
{
uint16_t *databuf =(uint16_t *)(buf+offset);
uint16_t opcode = ntohs(databuf[0]);
int blocknum;
udpinfo_t *info = (udpinfo_t *)arg;
int sendlength;
int errnum;
int datalen = info->length - 4; //len - offset - 4; //<- would fail if the ethernet added a trailer.
static const char *spinchars = "|/-\\";
static int spinidx = 0;
static int spincount = 0;
callout_reset(&tftp_stat.timer, 0);
tftp_stat.destip = info->srcip;
tftp_stat.destport = info->srcport;
blocknum = ntohs(databuf[1]);
// printf("tftp_recv: opcode %d, blocknum %d\n", opcode, blocknum);
switch(opcode) {
case TFTP_DATA:
//send ack
if(tftp_stat.u.r.pos + datalen <= tftp_stat.u.r.destlen) {
memcpy(tftp_stat.u.r.destbuffer+tftp_stat.u.r.pos,&databuf[2],datalen);
} else {
printf("Not enough space in databuffer(pos:%d,datalen: %d, destlen: %d\n",tftp_stat.u.r.pos,datalen,tftp_stat.u.r.destlen);
tftp_stat.complete = -1;
return -1;
}
tftp_stat.u.r.pos += datalen;
if(datalen != TFTP_BLOCKSIZE) {
tftp_sendack(info->srcip,info->srcport,blocknum);
tftp_stat.complete = 1;
break;
}
tftp_stat.u.r.blocknum = blocknum;
tftp_sendack(info->srcip,info->srcport,blocknum);
// a user-comfort UI that uses less space than a bunch of dots
spincount += datalen;
if (spincount >= 65536) {
spincount = 0;
spinidx++;
if (spinchars[spinidx] == 0)
spinidx = 0;
putchar(spinchars[spinidx]);
putchar('\r');
}
break;
case TFTP_ACK:
//blocknum 0 is ack for our metadata.
if(blocknum != 0) {
tftp_stat.u.w.left -= 512;
if(tftp_stat.u.w.left <= 0) {
tftp_stat.u.w.left = 0;
tftp_stat.complete = 1;
return 0;
}
}
sendlength = __min(tftp_stat.u.w.left,TFTP_BLOCKSIZE);
tftp_stat.u.w.blocknum = blocknum + 1;
tftp_senddata(tftp_stat.destip,tftp_stat.destport,tftp_stat.u.w.blocknum,tftp_stat.u.w.sourcebuffer+((tftp_stat.u.w.blocknum-1) * TFTP_BLOCKSIZE),sendlength);
break;
case TFTP_ERR:
errnum = ntohs(databuf[1]);
printf("tftp error (%d) (%s)\n",errnum,(char*)&databuf[2]);
tftp_stat.complete = -1;
break;
default:
printf("unkown package %d\n",opcode);
}
return 0;
}
static void tftp_timeout(struct callout *c, void *arg)
{
callout_reset(c, 0);
tftp_stat.errcount++;
if(tftp_stat.errcount > 20) { // 10 seconds worth of retransmits
tftp_stat.complete = -1;
return;
}
if(tftp_stat.operation == TFTP_RECEIVE) {
tftp_sendack(tftp_stat.destip,tftp_stat.destport,tftp_stat.u.r.blocknum);
} else if(tftp_stat.operation == TFTP_SEND) {
tftp_senddata(tftp_stat.destip, tftp_stat.destport, tftp_stat.u.w.blocknum,
tftp_stat.u.w.sourcebuffer+((tftp_stat.u.w.blocknum-1) * TFTP_BLOCKSIZE),
__min(tftp_stat.u.w.left,TFTP_BLOCKSIZE));
} else {
printf("Unknown mode\n");
tftp_stat.complete = -1;
}
printf("Timeout\n");
}
int tftp_transfer(const char *filename, uint32_t ipaddr, char *buffer, int *maxlen, bool write)
{
#if WITH_NET
char *pktbuf;
int handlerid;
int ret = 0;
utime_t perftime = system_time();
int kb, filename_length, buf_length;
char serveripstr[32];
if(*maxlen > (0xffff * 512)) {
printf("TFTP (without extensions) can only handle max 32M tranfers\n");
return -1;
}
if(!security_allow_memory(buffer, *maxlen)) {
printf("Permission Denied\n");
return -1;
}
/* try to start the network stack, if it isn't already running */
ret = start_network_stack();
if (ret < 0)
return ret;
ip2str((uint8_t *)&ipaddr, serveripstr, 32);
dprintf(DEBUG_INFO, "tftp: starting transfer of file '%s' on server %s to address %p\n", filename, serveripstr, buffer);
tftp_stat.outgoingbuf = mbuf_initialize(516,14+20+8,0); //ip head + ether head
tftp_resetbuf();
memset(&tftp_stat.u,0,sizeof(tftp_stat.u));
if(write) {
tftp_stat.u.w.sourcebuffer = buffer;
tftp_stat.u.w.left = *maxlen;
} else {
tftp_stat.u.r.destbuffer = buffer;
tftp_stat.u.r.destlen = *maxlen;
}
tftp_stat.operation = write ? TFTP_SEND : TFTP_RECEIVE;
tftp_stat.destip = ipaddr;
tftp_stat.destport = 69;
tftp_stat.complete = tftp_stat.errcount = 0;
tftp_stat.srcport = (rand() + 1024) % 65536;
filename_length = strlen(filename);
pktbuf = mbuf_tail(tftp_stat.outgoingbuf,2+filename_length+1+6);
if (pktbuf == 0) return -1;
buf_length = mbuf_getlength(tftp_stat.outgoingbuf);
*(uint16_t *)&pktbuf[0] = ntohs(write ? TFTP_WRQ : TFTP_RRQ);
strlcpy(&pktbuf[2],filename,buf_length-2);
strlcpy(&pktbuf[filename_length+3],"octet",buf_length-filename_length-3);
/* try to prepopulate the arp cache */
arp_fill(tftp_stat.destip, 5*1000*1000);
handlerid = registerUDPHandler(tftp_stat.srcport,tftp_receive_listener);
if(transmit_udp(tftp_stat.outgoingbuf, tftp_stat.destip, tftp_stat.destport, tftp_stat.srcport) < 0) {
printf("Not able to transmit package.\n");
unregisterUDPHandler(handlerid);
return -1;
}
//Start timeout handler
callout_enqueue(&tftp_stat.timer, 500*1000, tftp_timeout, 0); //500ms without activity, send a new package.
while(tftp_stat.complete == 0)
task_yield();
if(tftp_stat.complete < 0) {
ret = -1;
goto out;
}
if(write == false) {
*maxlen = tftp_stat.u.r.pos;
}
perftime = system_time() - perftime;
if(write) {
kb = (*maxlen - tftp_stat.u.w.left)/1024;
} else {
kb = tftp_stat.u.r.pos/1024;
}
printf("%d KiB (%d bytes) tftp transfer completed at %d.%03d KiB/s\n",
kb, *maxlen, (kb*1000000)/perftime, (kb*1000000)%perftime);
out:
unregisterUDPHandler(handlerid);
callout_dequeue(&tftp_stat.timer);
mbuf_destroy(tftp_stat.outgoingbuf);
tftp_stat.outgoingbuf = 0;
return ret;
#else /* !WITH_NET */
printf ("network not available\n");
return -1;
#endif /* WITH_NET */
}