/*
 * TFTP data output backend
 */

#include <string.h>
#include <stdio.h>
#include <syslinux/pxe.h>
#include <syslinux/config.h>
#include <netinet/in.h>
#include <sys/times.h>
#include "upload_backend.h"

enum tftp_opcode {
    TFTP_RRQ	= 1,
    TFTP_WRQ	= 2,
    TFTP_DATA	= 3,
    TFTP_ACK	= 4,
    TFTP_ERROR	= 5,
};

struct tftp_error {
	uint16_t opcode;
	uint16_t errcode;
	char errmsg[0];
} __attribute__ (( packed ));

struct tftp_state {
    uint32_t my_ip;
    uint32_t srv_ip;
    uint32_t srv_gw;
    uint16_t my_port;
    uint16_t srv_port;
    uint16_t seq;
};

const char *tftp_string_error_message[]={
"",
"File not found",
"Access Denied",
"Disk Full",
"Illegal Operation",
"Unknown Transfert ID",
"File already exists",
"Unknown User",
"Negociation failed",
"Unable to resolve hostname", // not in RFC
"Unable to connect", // not in RFC
"No Error",
};

#define RCV_BUF	2048

static int send_ack_packet(struct tftp_state *tftp,
			   const void *pkt, size_t len)
{
    com32sys_t ireg, oreg;
    t_PXENV_UDP_WRITE *uw;
    t_PXENV_UDP_READ  *ur;
    clock_t start;
    static const clock_t timeouts[] = {
	2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31,
	37, 44, 53, 64, 77, 92, 110, 132, 159, 191, 229, 0
    };
    const clock_t *timeout;
    int err = -1;

    uw = lmalloc(sizeof *uw + len);
    ur = lmalloc(sizeof *ur + RCV_BUF);

    memset(&ireg, 0, sizeof ireg);
    ireg.eax.w[0] = 0x0009;

    for (timeout = timeouts ; *timeout ; timeout++) {
	memset(uw, 0, sizeof *uw);
	memcpy(uw+1, pkt, len);
	uw->ip = tftp->srv_ip;
	uw->gw = tftp->srv_gw;
	uw->src_port = tftp->my_port;
	uw->dst_port = tftp->srv_port ? tftp->srv_port : htons(69);
	uw->buffer_size = len;
	uw->buffer = FAR_PTR(uw+1);

	ireg.ebx.w[0] = PXENV_UDP_WRITE;
	ireg.es = SEG(uw);
	ireg.edi.w[0] = OFFS(uw);

	__intcall(0x22, &ireg, &oreg);

	start = times(NULL);

	do {
	    memset(ur, 0, sizeof *ur);
	    ur->src_ip = tftp->srv_ip;
	    ur->dest_ip = tftp->my_ip;
	    ur->s_port = tftp->srv_port;
	    ur->d_port = tftp->my_port;
	    ur->buffer_size = RCV_BUF;
	    ur->buffer = FAR_PTR(ur+1);

	    ireg.ebx.w[0] = PXENV_UDP_READ;
	    ireg.es = SEG(ur);
	    ireg.edi.w[0] = OFFS(ur);
	    __intcall(0x22, &ireg, &oreg);

	    if (!(oreg.eflags.l & EFLAGS_CF) &&
		ur->status == PXENV_STATUS_SUCCESS &&
		tftp->srv_ip == ur->src_ip &&
		(tftp->srv_port == 0 ||
		 tftp->srv_port == ur->s_port)) {
		uint16_t *xb = (uint16_t *)(ur+1);
		if (ntohs(xb[0]) == TFTP_ACK &&
		    ntohs(xb[1]) == tftp->seq) {
		    tftp->srv_port = ur->s_port;
		    err = TFTP_OK;		/* All good! */
		    goto done;
		} else if (ntohs(xb[0]) == TFTP_ERROR) {
		    struct tftp_error *te = (struct tftp_error *)(ur+1);
		    if (te->errcode == TFTP_ERR_UNKNOWN_ERROR) {
		    	tftp_string_error_message[TFTP_ERR_UNKNOWN_ERROR]=strdup(te->errmsg);
		    }
 		    err=-ntohs(te->errcode); // Return the associated error code
		    goto done;
		}
	    }
	} while ((clock_t)(times(NULL) - start) < *timeout);
    }

done:
    lfree(ur);
    lfree(uw);

    return err;
}

static int upload_tftp_write(struct upload_backend *be)
{
    static uint16_t local_port = 0x4000;
    struct tftp_state tftp;
    char buffer[512+4+6];
    int nlen;
    int err=TFTP_OK;
    const union syslinux_derivative_info *sdi =
	syslinux_derivative_info();
    const char *data = be->outbuf;
    size_t len = be->zbytes;
    size_t chunk;

    tftp.my_ip    = sdi->pxe.myip;
    tftp.my_port  = htons(local_port++);
    tftp.srv_gw   = ((tftp.srv_ip ^ tftp.my_ip) & sdi->pxe.ipinfo->netmask)
	? sdi->pxe.ipinfo->gateway : 0;
    tftp.srv_port = 0;
    tftp.seq      = 0;

    if (be->argv[1]) {
	tftp.srv_ip   = pxe_dns(be->argv[1]);
	if (!tftp.srv_ip) {
//	    printf("\nUnable to resolve hostname: %s\n", be->argv[1]);
	    return -TFTP_ERR_UNABLE_TO_RESOLVE;
	}
    } else {
	tftp.srv_ip   = sdi->pxe.ipinfo->serverip;
	if (!tftp.srv_ip) {
//	    printf("\nNo server IP address\n");
	    return -TFTP_ERR_UNABLE_TO_CONNECT;
	}
    }

/*    printf("server %u.%u.%u.%u... ",
	   ((uint8_t *)&tftp.srv_ip)[0],
	   ((uint8_t *)&tftp.srv_ip)[1],
	   ((uint8_t *)&tftp.srv_ip)[2],
	   ((uint8_t *)&tftp.srv_ip)[3]);*/

    buffer[0] = 0;
    buffer[1] = TFTP_WRQ;
    nlen = strlcpy(buffer+2, be->argv[0], 512);
    memcpy(buffer+3+nlen, "octet", 6);

    if ((err=send_ack_packet(&tftp, buffer, 2+nlen+1+6))!=TFTP_OK)
	return err;

    do {
	chunk = len >= 512 ? 512 : len;

	buffer[1] = TFTP_DATA;
	*((uint16_t *)(buffer+2)) = htons(++tftp.seq);
	memcpy(buffer+4, data, chunk);
	data += chunk;
	len -= chunk;

	if ((err=send_ack_packet(&tftp, buffer, chunk+4))!=TFTP_OK)
	    return err;
    } while (chunk == 512);

    return TFTP_OK;
}

struct upload_backend upload_tftp = {
    .name       = "tftp",
    .helpmsg    = "filename [tftp_server]",
    .minargs    = 1,
    .write      = upload_tftp_write,
};
