|  | @@ -0,0 +1,471 @@
 | 
		
	
		
			
			|  | 1 | +/*
 | 
		
	
		
			
			|  | 2 | + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
 | 
		
	
		
			
			|  | 3 | + *
 | 
		
	
		
			
			|  | 4 | + * This program is free software; you can redistribute it and/or
 | 
		
	
		
			
			|  | 5 | + * modify it under the terms of the GNU General Public License as
 | 
		
	
		
			
			|  | 6 | + * published by the Free Software Foundation; either version 2 of the
 | 
		
	
		
			
			|  | 7 | + * License, or any later version.
 | 
		
	
		
			
			|  | 8 | + *
 | 
		
	
		
			
			|  | 9 | + * This program is distributed in the hope that it will be useful, but
 | 
		
	
		
			
			|  | 10 | + * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
		
	
		
			
			|  | 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
		
	
		
			
			|  | 12 | + * General Public License for more details.
 | 
		
	
		
			
			|  | 13 | + *
 | 
		
	
		
			
			|  | 14 | + * You should have received a copy of the GNU General Public License
 | 
		
	
		
			
			|  | 15 | + * along with this program; if not, write to the Free Software
 | 
		
	
		
			
			|  | 16 | + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | 
		
	
		
			
			|  | 17 | + */
 | 
		
	
		
			
			|  | 18 | +
 | 
		
	
		
			
			|  | 19 | +#include <stdint.h>
 | 
		
	
		
			
			|  | 20 | +#include <stdlib.h>
 | 
		
	
		
			
			|  | 21 | +#include <string.h>
 | 
		
	
		
			
			|  | 22 | +#include <strings.h>
 | 
		
	
		
			
			|  | 23 | +#include <byteswap.h>
 | 
		
	
		
			
			|  | 24 | +#include <errno.h>
 | 
		
	
		
			
			|  | 25 | +#include <assert.h>
 | 
		
	
		
			
			|  | 26 | +#include <vsprintf.h>
 | 
		
	
		
			
			|  | 27 | +#include <gpxe/async.h>
 | 
		
	
		
			
			|  | 28 | +#include <gpxe/tftp.h>
 | 
		
	
		
			
			|  | 29 | +
 | 
		
	
		
			
			|  | 30 | +/** @file
 | 
		
	
		
			
			|  | 31 | + *
 | 
		
	
		
			
			|  | 32 | + * TFTP protocol
 | 
		
	
		
			
			|  | 33 | + *
 | 
		
	
		
			
			|  | 34 | + */
 | 
		
	
		
			
			|  | 35 | +
 | 
		
	
		
			
			|  | 36 | +/** A TFTP option */
 | 
		
	
		
			
			|  | 37 | +struct tftp_option {
 | 
		
	
		
			
			|  | 38 | +	/** Option name */
 | 
		
	
		
			
			|  | 39 | +	const char *name;
 | 
		
	
		
			
			|  | 40 | +	/** Option processor
 | 
		
	
		
			
			|  | 41 | +	 *
 | 
		
	
		
			
			|  | 42 | +	 * @v tftp	TFTP connection
 | 
		
	
		
			
			|  | 43 | +	 * @v value	Option value
 | 
		
	
		
			
			|  | 44 | +	 * @ret rc	Return status code
 | 
		
	
		
			
			|  | 45 | +	 */
 | 
		
	
		
			
			|  | 46 | +	int ( * process ) ( struct tftp_session *tftp, const char *value );
 | 
		
	
		
			
			|  | 47 | +};
 | 
		
	
		
			
			|  | 48 | +
 | 
		
	
		
			
			|  | 49 | +/**
 | 
		
	
		
			
			|  | 50 | + * Process TFTP "blksize" option
 | 
		
	
		
			
			|  | 51 | + *
 | 
		
	
		
			
			|  | 52 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 53 | + * @v value		Option value
 | 
		
	
		
			
			|  | 54 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 55 | + */
 | 
		
	
		
			
			|  | 56 | +static int tftp_process_blksize ( struct tftp_session *tftp,
 | 
		
	
		
			
			|  | 57 | +				  const char *value ) {
 | 
		
	
		
			
			|  | 58 | +	char *end;
 | 
		
	
		
			
			|  | 59 | +
 | 
		
	
		
			
			|  | 60 | +	tftp->blksize = strtoul ( value, &end, 10 );
 | 
		
	
		
			
			|  | 61 | +	if ( *end ) {
 | 
		
	
		
			
			|  | 62 | +		DBG ( "TFTP %p got invalid blksize \"%s\"\n", tftp, value );
 | 
		
	
		
			
			|  | 63 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 64 | +	}
 | 
		
	
		
			
			|  | 65 | +	DBG ( "TFTP %p blksize=%d\n", tftp, tftp->blksize );
 | 
		
	
		
			
			|  | 66 | +
 | 
		
	
		
			
			|  | 67 | +	return 0;
 | 
		
	
		
			
			|  | 68 | +}
 | 
		
	
		
			
			|  | 69 | +
 | 
		
	
		
			
			|  | 70 | +/**
 | 
		
	
		
			
			|  | 71 | + * Process TFTP "tsize" option
 | 
		
	
		
			
			|  | 72 | + *
 | 
		
	
		
			
			|  | 73 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 74 | + * @v value		Option value
 | 
		
	
		
			
			|  | 75 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 76 | + */
 | 
		
	
		
			
			|  | 77 | +static int tftp_process_tsize ( struct tftp_session *tftp,
 | 
		
	
		
			
			|  | 78 | +				const char *value ) {
 | 
		
	
		
			
			|  | 79 | +	char *end;
 | 
		
	
		
			
			|  | 80 | +
 | 
		
	
		
			
			|  | 81 | +	tftp->tsize = strtoul ( value, &end, 10 );
 | 
		
	
		
			
			|  | 82 | +	if ( *end ) {
 | 
		
	
		
			
			|  | 83 | +		DBG ( "TFTP %p got invalid tsize \"%s\"\n", tftp, value );
 | 
		
	
		
			
			|  | 84 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 85 | +	}
 | 
		
	
		
			
			|  | 86 | +	DBG ( "TFTP %p tsize=%ld\n", tftp, tftp->tsize );
 | 
		
	
		
			
			|  | 87 | +
 | 
		
	
		
			
			|  | 88 | +	return 0;
 | 
		
	
		
			
			|  | 89 | +}
 | 
		
	
		
			
			|  | 90 | +
 | 
		
	
		
			
			|  | 91 | +/** Recognised TFTP options */
 | 
		
	
		
			
			|  | 92 | +static struct tftp_option tftp_options[] = {
 | 
		
	
		
			
			|  | 93 | +	{ "blksize", tftp_process_blksize },
 | 
		
	
		
			
			|  | 94 | +	{ "tsize", tftp_process_tsize },
 | 
		
	
		
			
			|  | 95 | +	{ NULL, NULL }
 | 
		
	
		
			
			|  | 96 | +};
 | 
		
	
		
			
			|  | 97 | +
 | 
		
	
		
			
			|  | 98 | +/**
 | 
		
	
		
			
			|  | 99 | + * Process TFTP option
 | 
		
	
		
			
			|  | 100 | + *
 | 
		
	
		
			
			|  | 101 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 102 | + * @v name		Option name
 | 
		
	
		
			
			|  | 103 | + * @v value		Option value
 | 
		
	
		
			
			|  | 104 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 105 | + */
 | 
		
	
		
			
			|  | 106 | +static int tftp_process_option ( struct tftp_session *tftp,
 | 
		
	
		
			
			|  | 107 | +				 const char *name, const char *value ) {
 | 
		
	
		
			
			|  | 108 | +	struct tftp_option *option;
 | 
		
	
		
			
			|  | 109 | +
 | 
		
	
		
			
			|  | 110 | +	for ( option = tftp_options ; option->name ; option++ ) {
 | 
		
	
		
			
			|  | 111 | +		if ( strcasecmp ( name, option->name ) == 0 )
 | 
		
	
		
			
			|  | 112 | +			return option->process ( tftp, value );
 | 
		
	
		
			
			|  | 113 | +	}
 | 
		
	
		
			
			|  | 114 | +
 | 
		
	
		
			
			|  | 115 | +	DBG ( "TFTP %p received unknown option \"%s\" = \"%s\"\n",
 | 
		
	
		
			
			|  | 116 | +	      tftp, name, value );
 | 
		
	
		
			
			|  | 117 | +
 | 
		
	
		
			
			|  | 118 | +	return -EINVAL;
 | 
		
	
		
			
			|  | 119 | +}
 | 
		
	
		
			
			|  | 120 | +
 | 
		
	
		
			
			|  | 121 | +/** Translation between TFTP errors and internal error numbers */
 | 
		
	
		
			
			|  | 122 | +static const uint8_t tftp_errors[] = {
 | 
		
	
		
			
			|  | 123 | +	[TFTP_ERR_FILE_NOT_FOUND]	= PXENV_STATUS_TFTP_FILE_NOT_FOUND,
 | 
		
	
		
			
			|  | 124 | +	[TFTP_ERR_ACCESS_DENIED]	= PXENV_STATUS_TFTP_ACCESS_VIOLATION,
 | 
		
	
		
			
			|  | 125 | +	[TFTP_ERR_ILLEGAL_OP]		= PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
 | 
		
	
		
			
			|  | 126 | +};
 | 
		
	
		
			
			|  | 127 | +
 | 
		
	
		
			
			|  | 128 | +/**
 | 
		
	
		
			
			|  | 129 | + * Mark TFTP session as complete
 | 
		
	
		
			
			|  | 130 | + *
 | 
		
	
		
			
			|  | 131 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 132 | + * @v rc		Return status code
 | 
		
	
		
			
			|  | 133 | + */
 | 
		
	
		
			
			|  | 134 | +static void tftp_done ( struct tftp_session *tftp, int rc ) {
 | 
		
	
		
			
			|  | 135 | +
 | 
		
	
		
			
			|  | 136 | +	/* Stop the retry timer */
 | 
		
	
		
			
			|  | 137 | +	stop_timer ( &tftp->timer );
 | 
		
	
		
			
			|  | 138 | +
 | 
		
	
		
			
			|  | 139 | +	/* Close UDP connection */
 | 
		
	
		
			
			|  | 140 | +	udp_close ( &tftp->udp );
 | 
		
	
		
			
			|  | 141 | +
 | 
		
	
		
			
			|  | 142 | +	/* Mark async operation as complete */
 | 
		
	
		
			
			|  | 143 | +	async_done ( &tftp->aop, rc );
 | 
		
	
		
			
			|  | 144 | +}
 | 
		
	
		
			
			|  | 145 | +
 | 
		
	
		
			
			|  | 146 | +/**
 | 
		
	
		
			
			|  | 147 | + * Send next packet in TFTP session
 | 
		
	
		
			
			|  | 148 | + *
 | 
		
	
		
			
			|  | 149 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 150 | + */
 | 
		
	
		
			
			|  | 151 | +static void tftp_send_packet ( struct tftp_session *tftp ) {
 | 
		
	
		
			
			|  | 152 | +	start_timer ( &tftp->timer );
 | 
		
	
		
			
			|  | 153 | +	udp_senddata ( &tftp->udp );
 | 
		
	
		
			
			|  | 154 | +}
 | 
		
	
		
			
			|  | 155 | +
 | 
		
	
		
			
			|  | 156 | +/**
 | 
		
	
		
			
			|  | 157 | + * Handle TFTP retransmission timer expiry
 | 
		
	
		
			
			|  | 158 | + *
 | 
		
	
		
			
			|  | 159 | + * @v timer		Retry timer
 | 
		
	
		
			
			|  | 160 | + * @v fail		Failure indicator
 | 
		
	
		
			
			|  | 161 | + */
 | 
		
	
		
			
			|  | 162 | +static void tftp_timer_expired ( struct retry_timer *timer, int fail ) {
 | 
		
	
		
			
			|  | 163 | +	struct tftp_session *tftp =
 | 
		
	
		
			
			|  | 164 | +		container_of ( timer, struct tftp_session, timer );
 | 
		
	
		
			
			|  | 165 | +
 | 
		
	
		
			
			|  | 166 | +	if ( fail ) {
 | 
		
	
		
			
			|  | 167 | +		tftp_done ( tftp, -ETIMEDOUT );
 | 
		
	
		
			
			|  | 168 | +	} else {
 | 
		
	
		
			
			|  | 169 | +		tftp_send_packet ( tftp );
 | 
		
	
		
			
			|  | 170 | +	}
 | 
		
	
		
			
			|  | 171 | +}
 | 
		
	
		
			
			|  | 172 | +
 | 
		
	
		
			
			|  | 173 | +/**
 | 
		
	
		
			
			|  | 174 | + * Mark TFTP block as received
 | 
		
	
		
			
			|  | 175 | + *
 | 
		
	
		
			
			|  | 176 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 177 | + * @v block		Block number
 | 
		
	
		
			
			|  | 178 | + */
 | 
		
	
		
			
			|  | 179 | +static void tftp_received ( struct tftp_session *tftp, unsigned int block ) {
 | 
		
	
		
			
			|  | 180 | +
 | 
		
	
		
			
			|  | 181 | +	/* Stop the retry timer */
 | 
		
	
		
			
			|  | 182 | +	stop_timer ( &tftp->timer );
 | 
		
	
		
			
			|  | 183 | +
 | 
		
	
		
			
			|  | 184 | +	/* Update state to indicate which block we're now waiting for */
 | 
		
	
		
			
			|  | 185 | +	tftp->state = block;
 | 
		
	
		
			
			|  | 186 | +
 | 
		
	
		
			
			|  | 187 | +	/* Send next packet */
 | 
		
	
		
			
			|  | 188 | +	tftp_send_packet ( tftp );
 | 
		
	
		
			
			|  | 189 | +}
 | 
		
	
		
			
			|  | 190 | +
 | 
		
	
		
			
			|  | 191 | +/**
 | 
		
	
		
			
			|  | 192 | + * Transmit RRQ
 | 
		
	
		
			
			|  | 193 | + *
 | 
		
	
		
			
			|  | 194 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 195 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 196 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 197 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 198 | + */
 | 
		
	
		
			
			|  | 199 | +static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) {
 | 
		
	
		
			
			|  | 200 | +	struct tftp_rrq *rrq = buf;
 | 
		
	
		
			
			|  | 201 | +	void *data;
 | 
		
	
		
			
			|  | 202 | +	void *end;
 | 
		
	
		
			
			|  | 203 | +
 | 
		
	
		
			
			|  | 204 | +	DBG ( "TFTP %p requesting \"%s\"\n", tftp, tftp->filename );
 | 
		
	
		
			
			|  | 205 | +
 | 
		
	
		
			
			|  | 206 | +	data = rrq->data;
 | 
		
	
		
			
			|  | 207 | +	end = ( buf + len );
 | 
		
	
		
			
			|  | 208 | +	if ( data > end )
 | 
		
	
		
			
			|  | 209 | +		goto overflow;
 | 
		
	
		
			
			|  | 210 | +	data += snprintf ( data, ( end - data ),
 | 
		
	
		
			
			|  | 211 | +			   "%s%coctet%cblksize%c%d%ctsize%c0",
 | 
		
	
		
			
			|  | 212 | +			   tftp->filename, 0, 0, 0, tftp->blksize, 0, 0 ) + 1;
 | 
		
	
		
			
			|  | 213 | +	if ( data > end )
 | 
		
	
		
			
			|  | 214 | +		goto overflow;
 | 
		
	
		
			
			|  | 215 | +	rrq->opcode = htons ( TFTP_RRQ );
 | 
		
	
		
			
			|  | 216 | +
 | 
		
	
		
			
			|  | 217 | +	return udp_send ( &tftp->udp, buf, ( data - buf ) );
 | 
		
	
		
			
			|  | 218 | +
 | 
		
	
		
			
			|  | 219 | + overflow:
 | 
		
	
		
			
			|  | 220 | +	DBG ( "TFTP %p RRQ out of space\n", tftp );
 | 
		
	
		
			
			|  | 221 | +	return -ENOBUFS;
 | 
		
	
		
			
			|  | 222 | +}
 | 
		
	
		
			
			|  | 223 | +
 | 
		
	
		
			
			|  | 224 | +/**
 | 
		
	
		
			
			|  | 225 | + * Receive OACK
 | 
		
	
		
			
			|  | 226 | + *
 | 
		
	
		
			
			|  | 227 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 228 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 229 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 230 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 231 | + */
 | 
		
	
		
			
			|  | 232 | +static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) {
 | 
		
	
		
			
			|  | 233 | +	struct tftp_oack *oack = buf;
 | 
		
	
		
			
			|  | 234 | +	char *end = buf + len;
 | 
		
	
		
			
			|  | 235 | +	char *name;
 | 
		
	
		
			
			|  | 236 | +	char *value;
 | 
		
	
		
			
			|  | 237 | +	int rc;
 | 
		
	
		
			
			|  | 238 | +
 | 
		
	
		
			
			|  | 239 | +	/* Sanity check */
 | 
		
	
		
			
			|  | 240 | +	if ( len < sizeof ( *oack ) ) {
 | 
		
	
		
			
			|  | 241 | +		DBG ( "TFTP %p received underlength OACK packet length %d\n",
 | 
		
	
		
			
			|  | 242 | +		      tftp, len );
 | 
		
	
		
			
			|  | 243 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 244 | +	}
 | 
		
	
		
			
			|  | 245 | +	if ( end[-1] != '\0' ) {
 | 
		
	
		
			
			|  | 246 | +		DBG ( "TFTP %p received OACK missing final NUL\n", tftp );
 | 
		
	
		
			
			|  | 247 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 248 | +	}
 | 
		
	
		
			
			|  | 249 | +
 | 
		
	
		
			
			|  | 250 | +	/* Process each option in turn */
 | 
		
	
		
			
			|  | 251 | +	name = oack->data;
 | 
		
	
		
			
			|  | 252 | +	while ( name < end ) {
 | 
		
	
		
			
			|  | 253 | +		value = ( name + strlen ( name ) + 1 );
 | 
		
	
		
			
			|  | 254 | +		if ( value == end ) {
 | 
		
	
		
			
			|  | 255 | +			DBG ( "TFTP %p received OACK missing value for option "
 | 
		
	
		
			
			|  | 256 | +			      "\"%s\"\n", tftp, name );
 | 
		
	
		
			
			|  | 257 | +			return -EINVAL;
 | 
		
	
		
			
			|  | 258 | +		}
 | 
		
	
		
			
			|  | 259 | +		if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
 | 
		
	
		
			
			|  | 260 | +			return rc;
 | 
		
	
		
			
			|  | 261 | +		name = ( value + strlen ( value ) + 1 );
 | 
		
	
		
			
			|  | 262 | +	}
 | 
		
	
		
			
			|  | 263 | +
 | 
		
	
		
			
			|  | 264 | +	/* Mark as received block 0 (the OACK) */
 | 
		
	
		
			
			|  | 265 | +	tftp_received ( tftp, 0 );
 | 
		
	
		
			
			|  | 266 | +
 | 
		
	
		
			
			|  | 267 | +	return 0;
 | 
		
	
		
			
			|  | 268 | +}
 | 
		
	
		
			
			|  | 269 | +
 | 
		
	
		
			
			|  | 270 | +/**
 | 
		
	
		
			
			|  | 271 | + * Receive DATA
 | 
		
	
		
			
			|  | 272 | + *
 | 
		
	
		
			
			|  | 273 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 274 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 275 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 276 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 277 | + */
 | 
		
	
		
			
			|  | 278 | +static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) {
 | 
		
	
		
			
			|  | 279 | +	struct tftp_data *data = buf;
 | 
		
	
		
			
			|  | 280 | +	unsigned int block;
 | 
		
	
		
			
			|  | 281 | +	size_t data_len;
 | 
		
	
		
			
			|  | 282 | +
 | 
		
	
		
			
			|  | 283 | +	/* Sanity check */
 | 
		
	
		
			
			|  | 284 | +	if ( len < sizeof ( *data ) ) {
 | 
		
	
		
			
			|  | 285 | +		DBG ( "TFTP %p received underlength DATA packet length %d\n",
 | 
		
	
		
			
			|  | 286 | +		      tftp, len );
 | 
		
	
		
			
			|  | 287 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 288 | +	}
 | 
		
	
		
			
			|  | 289 | +
 | 
		
	
		
			
			|  | 290 | +	/* Pass to callback */
 | 
		
	
		
			
			|  | 291 | +	block = ntohs ( data->block );
 | 
		
	
		
			
			|  | 292 | +	data_len = ( len - offsetof ( typeof ( *data ), data ) );
 | 
		
	
		
			
			|  | 293 | +	tftp->callback ( tftp, block, data->data, data_len );
 | 
		
	
		
			
			|  | 294 | +
 | 
		
	
		
			
			|  | 295 | +	/* Mark block as received */
 | 
		
	
		
			
			|  | 296 | +	tftp_received ( tftp, block );
 | 
		
	
		
			
			|  | 297 | +
 | 
		
	
		
			
			|  | 298 | +	/* Finish when final block received */
 | 
		
	
		
			
			|  | 299 | +	if ( data_len < tftp->blksize )
 | 
		
	
		
			
			|  | 300 | +		tftp_done ( tftp, 0 );
 | 
		
	
		
			
			|  | 301 | +
 | 
		
	
		
			
			|  | 302 | +	return 0;
 | 
		
	
		
			
			|  | 303 | +}
 | 
		
	
		
			
			|  | 304 | +
 | 
		
	
		
			
			|  | 305 | +/**
 | 
		
	
		
			
			|  | 306 | + * Transmit ACK
 | 
		
	
		
			
			|  | 307 | + *
 | 
		
	
		
			
			|  | 308 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 309 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 310 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 311 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 312 | + */
 | 
		
	
		
			
			|  | 313 | +static int tftp_send_ack ( struct tftp_session *tftp ) {
 | 
		
	
		
			
			|  | 314 | +	struct tftp_ack ack;
 | 
		
	
		
			
			|  | 315 | +
 | 
		
	
		
			
			|  | 316 | +	ack.opcode = htons ( TFTP_ACK );
 | 
		
	
		
			
			|  | 317 | +	ack.block = htons ( tftp->state );
 | 
		
	
		
			
			|  | 318 | +	return udp_send ( &tftp->udp, &ack, sizeof ( ack ) );
 | 
		
	
		
			
			|  | 319 | +}
 | 
		
	
		
			
			|  | 320 | +
 | 
		
	
		
			
			|  | 321 | +/**
 | 
		
	
		
			
			|  | 322 | + * Receive ERROR
 | 
		
	
		
			
			|  | 323 | + *
 | 
		
	
		
			
			|  | 324 | + * @v tftp		TFTP connection
 | 
		
	
		
			
			|  | 325 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 326 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 327 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 328 | + */
 | 
		
	
		
			
			|  | 329 | +static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) {
 | 
		
	
		
			
			|  | 330 | +	struct tftp_error *error = buf;
 | 
		
	
		
			
			|  | 331 | +	unsigned int err;
 | 
		
	
		
			
			|  | 332 | +	int rc = 0;
 | 
		
	
		
			
			|  | 333 | +
 | 
		
	
		
			
			|  | 334 | +	/* Sanity check */
 | 
		
	
		
			
			|  | 335 | +	if ( len < sizeof ( *error ) ) {
 | 
		
	
		
			
			|  | 336 | +		DBG ( "TFTP %p received underlength ERROR packet length %d\n",
 | 
		
	
		
			
			|  | 337 | +		      tftp, len );
 | 
		
	
		
			
			|  | 338 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 339 | +	}
 | 
		
	
		
			
			|  | 340 | +
 | 
		
	
		
			
			|  | 341 | +	DBG ( "TFTP %p received ERROR packet with code %d, message \"%s\"\n",
 | 
		
	
		
			
			|  | 342 | +	      tftp, ntohs ( error->errcode ), error->errmsg );
 | 
		
	
		
			
			|  | 343 | +	
 | 
		
	
		
			
			|  | 344 | +	/* Determine final operation result */
 | 
		
	
		
			
			|  | 345 | +	err = ntohs ( error->errcode );
 | 
		
	
		
			
			|  | 346 | +	if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
 | 
		
	
		
			
			|  | 347 | +		rc = -tftp_errors[err];
 | 
		
	
		
			
			|  | 348 | +	if ( ! rc )
 | 
		
	
		
			
			|  | 349 | +		rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
 | 
		
	
		
			
			|  | 350 | +
 | 
		
	
		
			
			|  | 351 | +	/* Close TFTP session */
 | 
		
	
		
			
			|  | 352 | +	tftp_done ( tftp, rc );
 | 
		
	
		
			
			|  | 353 | +
 | 
		
	
		
			
			|  | 354 | +	return 0;
 | 
		
	
		
			
			|  | 355 | +}
 | 
		
	
		
			
			|  | 356 | +
 | 
		
	
		
			
			|  | 357 | +/**
 | 
		
	
		
			
			|  | 358 | + * Transmit data
 | 
		
	
		
			
			|  | 359 | + *
 | 
		
	
		
			
			|  | 360 | + * @v conn		UDP connection
 | 
		
	
		
			
			|  | 361 | + * @v buf		Temporary data buffer
 | 
		
	
		
			
			|  | 362 | + * @v len		Length of temporary data buffer
 | 
		
	
		
			
			|  | 363 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 364 | + */
 | 
		
	
		
			
			|  | 365 | +static int tftp_senddata ( struct udp_connection *conn,
 | 
		
	
		
			
			|  | 366 | +			   void *buf, size_t len ) {
 | 
		
	
		
			
			|  | 367 | +	struct tftp_session *tftp = 
 | 
		
	
		
			
			|  | 368 | +		container_of ( conn, struct tftp_session, udp );
 | 
		
	
		
			
			|  | 369 | +
 | 
		
	
		
			
			|  | 370 | +	if ( tftp->state < 0 ) {
 | 
		
	
		
			
			|  | 371 | +		return tftp_send_rrq ( tftp, buf, len );
 | 
		
	
		
			
			|  | 372 | +	} else {
 | 
		
	
		
			
			|  | 373 | +		return tftp_send_ack ( tftp );
 | 
		
	
		
			
			|  | 374 | +	}
 | 
		
	
		
			
			|  | 375 | +}
 | 
		
	
		
			
			|  | 376 | +
 | 
		
	
		
			
			|  | 377 | +/**
 | 
		
	
		
			
			|  | 378 | + * Receive new data
 | 
		
	
		
			
			|  | 379 | + *
 | 
		
	
		
			
			|  | 380 | + * @v udp		UDP connection
 | 
		
	
		
			
			|  | 381 | + * @v data		Received data
 | 
		
	
		
			
			|  | 382 | + * @v len		Length of received data
 | 
		
	
		
			
			|  | 383 | + * @v st_src		Partially-filled source address
 | 
		
	
		
			
			|  | 384 | + * @v st_dest		Partially-filled destination address
 | 
		
	
		
			
			|  | 385 | + */
 | 
		
	
		
			
			|  | 386 | +static int tftp_newdata ( struct udp_connection *conn, void *data, size_t len,
 | 
		
	
		
			
			|  | 387 | +			  struct sockaddr_tcpip *st_src __unused,
 | 
		
	
		
			
			|  | 388 | +			  struct sockaddr_tcpip *st_dest __unused ) {
 | 
		
	
		
			
			|  | 389 | +	struct tftp_session *tftp = 
 | 
		
	
		
			
			|  | 390 | +		container_of ( conn, struct tftp_session, udp );
 | 
		
	
		
			
			|  | 391 | +	struct tftp_common *common = data;
 | 
		
	
		
			
			|  | 392 | +	
 | 
		
	
		
			
			|  | 393 | +	if ( len < sizeof ( *common ) ) {
 | 
		
	
		
			
			|  | 394 | +		DBG ( "TFTP %p received underlength packet length %d\n",
 | 
		
	
		
			
			|  | 395 | +		      tftp, len );
 | 
		
	
		
			
			|  | 396 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 397 | +	}
 | 
		
	
		
			
			|  | 398 | +
 | 
		
	
		
			
			|  | 399 | +	/* Filter by TID.  Set TID on first response received */
 | 
		
	
		
			
			|  | 400 | +	if ( tftp->tid ) {
 | 
		
	
		
			
			|  | 401 | +		if ( tftp->tid != st_src->st_port ) {
 | 
		
	
		
			
			|  | 402 | +			DBG ( "TFTP %p received packet from wrong port "
 | 
		
	
		
			
			|  | 403 | +			      "(got %d, wanted %d)\n", tftp,
 | 
		
	
		
			
			|  | 404 | +			      ntohs ( st_src->st_port ), ntohs ( tftp->tid ) );
 | 
		
	
		
			
			|  | 405 | +			return -EINVAL;
 | 
		
	
		
			
			|  | 406 | +		}
 | 
		
	
		
			
			|  | 407 | +	} else {
 | 
		
	
		
			
			|  | 408 | +		tftp->tid = st_src->st_port;
 | 
		
	
		
			
			|  | 409 | +		DBG ( "TFTP %p using remote port %d\n", tftp,
 | 
		
	
		
			
			|  | 410 | +		      ntohs ( tftp->tid ) );
 | 
		
	
		
			
			|  | 411 | +		udp_connect_port ( &tftp->udp, tftp->tid );
 | 
		
	
		
			
			|  | 412 | +	}
 | 
		
	
		
			
			|  | 413 | +
 | 
		
	
		
			
			|  | 414 | +	/* Filter by source address */
 | 
		
	
		
			
			|  | 415 | +	if ( memcmp ( st_src, udp_peer ( &tftp->udp ),
 | 
		
	
		
			
			|  | 416 | +		      sizeof ( *st_src ) ) != 0 ) {
 | 
		
	
		
			
			|  | 417 | +		DBG ( "TFTP %p received packet from foreign source\n", tftp );
 | 
		
	
		
			
			|  | 418 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 419 | +	}
 | 
		
	
		
			
			|  | 420 | +
 | 
		
	
		
			
			|  | 421 | +	switch ( common->opcode ) {
 | 
		
	
		
			
			|  | 422 | +	case htons ( TFTP_OACK ):
 | 
		
	
		
			
			|  | 423 | +		return tftp_rx_oack ( tftp, data, len );
 | 
		
	
		
			
			|  | 424 | +	case htons ( TFTP_DATA ):
 | 
		
	
		
			
			|  | 425 | +		return tftp_rx_data ( tftp, data, len );
 | 
		
	
		
			
			|  | 426 | +	case htons ( TFTP_ERROR ):
 | 
		
	
		
			
			|  | 427 | +		return tftp_rx_error ( tftp, data, len );
 | 
		
	
		
			
			|  | 428 | +	default:
 | 
		
	
		
			
			|  | 429 | +		DBG ( "TFTP %p received strange packet type %d\n", tftp,
 | 
		
	
		
			
			|  | 430 | +		      ntohs ( common->opcode ) );
 | 
		
	
		
			
			|  | 431 | +		return -EINVAL;
 | 
		
	
		
			
			|  | 432 | +	};
 | 
		
	
		
			
			|  | 433 | +}
 | 
		
	
		
			
			|  | 434 | +
 | 
		
	
		
			
			|  | 435 | +/** TFTP UDP operations */
 | 
		
	
		
			
			|  | 436 | +static struct udp_operations tftp_udp_operations = {
 | 
		
	
		
			
			|  | 437 | +	.senddata = tftp_senddata,
 | 
		
	
		
			
			|  | 438 | +	.newdata = tftp_newdata,
 | 
		
	
		
			
			|  | 439 | +};
 | 
		
	
		
			
			|  | 440 | +
 | 
		
	
		
			
			|  | 441 | +/**
 | 
		
	
		
			
			|  | 442 | + * Initiate TFTP download
 | 
		
	
		
			
			|  | 443 | + *
 | 
		
	
		
			
			|  | 444 | + * @v tftp		TFTP session
 | 
		
	
		
			
			|  | 445 | + * @ret aop		Asynchronous operation
 | 
		
	
		
			
			|  | 446 | + */
 | 
		
	
		
			
			|  | 447 | +struct async_operation * tftp_get ( struct tftp_session *tftp ) {
 | 
		
	
		
			
			|  | 448 | +	int rc;
 | 
		
	
		
			
			|  | 449 | +
 | 
		
	
		
			
			|  | 450 | +	assert ( tftp->filename != NULL );
 | 
		
	
		
			
			|  | 451 | +	assert ( tftp->callback != NULL );
 | 
		
	
		
			
			|  | 452 | +	assert ( tftp->udp.peer.st_family != 0 );
 | 
		
	
		
			
			|  | 453 | +
 | 
		
	
		
			
			|  | 454 | +	/* Initialise TFTP session */
 | 
		
	
		
			
			|  | 455 | +	tftp->udp.udp_op = &tftp_udp_operations;
 | 
		
	
		
			
			|  | 456 | +	tftp->timer.expired = tftp_timer_expired;
 | 
		
	
		
			
			|  | 457 | +	tftp->state = -1;
 | 
		
	
		
			
			|  | 458 | +	tftp->blksize = TFTP_DEFAULT_BLKSIZE;
 | 
		
	
		
			
			|  | 459 | +
 | 
		
	
		
			
			|  | 460 | +	/* Open UDP connection */
 | 
		
	
		
			
			|  | 461 | +	if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) {
 | 
		
	
		
			
			|  | 462 | +		async_done ( &tftp->aop, rc );
 | 
		
	
		
			
			|  | 463 | +		goto out;
 | 
		
	
		
			
			|  | 464 | +	}
 | 
		
	
		
			
			|  | 465 | +
 | 
		
	
		
			
			|  | 466 | +	/* Transmit initial RRQ */
 | 
		
	
		
			
			|  | 467 | +	tftp_send_packet ( tftp );
 | 
		
	
		
			
			|  | 468 | +
 | 
		
	
		
			
			|  | 469 | + out:
 | 
		
	
		
			
			|  | 470 | +	return &tftp->aop;
 | 
		
	
		
			
			|  | 471 | +}
 |