/*
 * dctpSend.c -- support routines for sending DCCP packets.  For dccp-tp internal
 * code only.
 *
 * Copyright (C) 2008 Tom Phelan
 *
 * This file is part of dccp-tp.
 *
 * Dccp-tp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Dccp-tp 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with dccp-tp.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Documentation and source code for dccp-tp is available at
 * http://www.phelan-4.com/dccp-tp/.
 */

#include "dctpCore.h"
#include "dctpInternal.h"
#include "dctpSupport.h"

/*
 * Prototypes for static functions
 */
static void sendNATv4Packet(dctpSocket *sock, dctpPacket *pkt);
static void sendPacket(dctpSocket *sock, dctpPacket *pkt, uint_t encap);

static char *resetErrorText[] = {
    "Unspecified",
    "Closed",
    "Aborted",
    "No Connection",
    "Packet Error",
    "Option Error",
    "Mandatory Error",
    "Connection Refused",
    "Bad Service Code",
    "Too Busy",
    "Bad Init Cookie",
    "Aggression Penalty"
};
#define DCTPRESET_MAXERRORTEXTCODE 11

/*
 * dctpiSendReset -- send a reset packet.  One of sock or ipkt may be NULL,
 * but not both.  If code within RFC 4340 defined range, a text description
 * will also be included.
 */
void dctpiSendReset(dctpSocket *sock, dctpPacket *ipkt, uint_t code,
		    uint_t data1, uint_t data2, uint_t data3, uint_t encap) {
    dctpPacket *rpkt;
    uint_t textlen;

    textlen = (code <= DCTPRESET_MAXERRORTEXTCODE) ? dctpoStrlen(resetErrorText[code]) : 0;
    if ((rpkt = dctpoPacketMalloc(textlen)) == NULL) {
	/* No memory, give up */
	return;
    }
    if (textlen &&
	((rpkt = dctpoPacketCopyTo(rpkt, 0, resetErrorText[code], textlen)) == NULL)) {
	/* Shouldn't happen because we allocated it big enough, but... */
	return;
    }
    rpkt->chdr.type = DCTPPKT_RESET;
    rpkt->chdr.ccval = 0;
    rpkt->chdr.hasAck = 1;
    rpkt->chdr.hasSC = 0;
    rpkt->chdr.shortSeq = 0;
    rpkt->chdr.hasReset = 1;
    rpkt->chdr.rcode = code;
    rpkt->chdr.rdata1 = data1;
    rpkt->chdr.rdata2 = data2;
    rpkt->chdr.rdata3 = data3;
    if ((sock == NULL) || (code == DCTPRESET_NOCONNECTION) || (code == DCTPRESET_PACKETERROR)) {
	/* Get header info from received packet */
	if (!ipkt) {
	    dctpoLog(DCTPLOG_ERR, "Reset code requires input packet, but none given\n");
	    dctpoPacketFree(rpkt);
	    return;
	}
	rpkt->chdr.sport = ipkt->chdr.dport;
	rpkt->chdr.dport = ipkt->chdr.sport;
	if (!ipkt->chdr.hasAck) {
	    rpkt->chdr.seqno = 0;
	} else {
	    rpkt->chdr.seqno = (ipkt->chdr.ackno + 1) & DCTP_48BITMASK;
	}
	rpkt->chdr.ackno = ipkt->chdr.seqno;
    } else {
	/* Get header info from sock */
	rpkt->chdr.sport = sock->localAddr.sa_port;
	rpkt->chdr.dport = sock->remoteAddr.sa_port;
    }
    sendPacket(sock, rpkt, encap);
}

/*
 * Add options to a packet about to head out the door
 */
static void addOptions(dctpSocket *sock, dctpPacket *pkt) {
    uint8_t options[DCTPOPTION_MAXLEN];
    uint_t i, oplen = 0;
    uint64_t etime;

    dctpiAddFeatures(sock, pkt, options, &oplen);
    /* Add empty confirms for bad received changes */
    for (i = 0; (i < DCTPSOCK_MAXEMPTYCONFIRMS) && (sock->emptyConfirms[i] != 0); i += 2) {
	options[oplen++] = sock->emptyConfirms[i];
	sock->emptyConfirms[i] = 0;
	options[oplen++] = 3;
	options[oplen++] = sock->emptyConfirms[i + 1];
    }
    /* Let the CCIDs add what they need */
    if (sock->ccidSenderFuncs) sock->ccidSenderFuncs->addOptions(sock, pkt, options, &oplen);
    if (sock->ccidRcvrFuncs) sock->ccidRcvrFuncs->addOptions(sock, pkt, options, &oplen);
    if (sock->pendingTimestampEcho) {
	/* Received timestamp, send echo */
	options[oplen++] = DCTPOPTION_TIMESTAMPECHO;
	etime = (dctpoNow() - sock->pendingTimestampRcvd)/10;
	if (etime <= 0xffff) {
	    options[oplen++] = DCTPOPTION_TIMESTAMPECHOMEDLEN;
	    *((uint32_t *)&options[oplen]) = dctpoHtonl((uint32_t)etime);
	    oplen += sizeof(uint32_t);
	    *((uint16_t *)&options[oplen]) = dctpoHtons((uint16_t)etime);
	    oplen += sizeof(uint16_t);
	} else {
	    options[oplen++] = DCTPOPTION_TIMESTAMPECHOLONGLEN;
	    *((uint32_t *)&options[oplen]) = dctpoHtonl((uint32_t)etime);
	    oplen += sizeof(uint32_t);
	    *((uint32_t *)&options[oplen]) = dctpoHtonl((uint32_t)etime);
	    oplen += sizeof(uint32_t);
	}
	sock->pendingTimestampEcho = 0;
    }
    /* See if we have a slow receiver */
    if (sock->rpktqlen > (sock->mrpktqlen/2)) {
	options[oplen++] = DCTPOPTION_SLOWRECEIVER;
    }
    /* Pad options to 32-bit boundary */
    while ((oplen & 3) != 0) {
	options[oplen++] = DCTPOPTION_PADDING;
    }
    if (oplen != 0) {
	dctpoPacketExtendDccpHdr(pkt, oplen);
	dctpoMemcpy(pkt->dccphdr, options, oplen);
	pkt->chdr.doffset += oplen;
    }
}

/*
 * Send a packet out the door.  All packet types come through here; no
 * packet should go out the door through another routine.
 */
static void sendPacket(dctpSocket *sock, dctpPacket *pkt, uint_t encap) {
    uint64_t tawl;

    if (sock) {
	/* Fix up sequence and ack numbers */
	pkt->chdr.seqno = sock->gss;
	sock->gss = dctpiSeqNoAdd(sock->gss, 1);
	sock->awh = sock->gss;
	tawl = dctpiSeqNoSub(sock->gss, sock->localFeatsCurr.seqWindow - 1);
	if (sock->awlinit) {
	    if (dctpiCmpSeqNo(tawl, sock->awl) > 0) {
		sock->awlinit = 0;
		sock->awl = tawl;
	    }
	} else {
	    sock->awl = tawl;
	}
	if (pkt->chdr.hasAck && (pkt->chdr.type != DCTPPKT_SYNC) &&
	    (pkt->chdr.type != DCTPPKT_SYNCACK)) {
	    pkt->chdr.ackno = sock->gsr;
	    sock->gas = pkt->chdr.ackno;
	} else if (sock->useDataAck && (pkt->chdr.type == DCTPPKT_DATA)) {
	    if (dctpiCmpSeqNo(sock->gsr, sock->gas) > 0) {
		pkt->chdr.type = DCTPPKT_DATAACK;
		pkt->chdr.hasAck = 1;
		pkt->chdr.ackno = sock->gsr;
		sock->gas = sock->gsr;
	    }
	}
	addOptions(sock, pkt);
	if (sock->ccidSenderFuncs && (sock->ccidSenderFuncs->xmtPkt(sock, pkt) < 0)) {
	    dctpoPacketFree(pkt);
	    dctpiCloseConnection(sock, NULL);
	    return;
	}
    }
    /* Send with proper encap */
    switch (encap) {
    case DCTPENCAP_NAT:
	sendNATv4Packet(sock, pkt);
	break;
    case DCTPENCAP_RAW:
    case DCTPENCAP_NAT6:
    case DCTPENCAP_RAW6:
    default:
	dctpoLog(DCTPLOG_ERR, "sendPacket: bad encap %d, packet not sent\n", encap);
    }
    dctpoPacketFree(pkt);
}

/*
 * Create NATv4 header for packet and send it
 */
static void sendNATv4Packet(dctpSocket *sock, dctpPacket *pkt) {
    uint_t hdrsize = 12;
    uint8_t *hdr;

    if (pkt->chdr.hasAck) hdrsize += 8;
    if (pkt->chdr.hasSC) hdrsize += 4;
    if (pkt->chdr.hasReset) hdrsize += 4;
    if ((pkt = dctpoPacketExtendDccpHdr(pkt, hdrsize)) == NULL) {
	dctpoLog(DCTPLOG_INFO, "sendNATv4Packet: can't add dccp header, dropping packet\n");
	return;
    }
    pkt->chdr.doffset += hdrsize;
    hdr = pkt->dccphdr;
    *((uint16_t *)hdr) = pkt->chdr.sport;
    hdr += 2;
    *((uint16_t *)hdr) = pkt->chdr.dport;
    hdr += 2;
    *hdr++ = pkt->chdr.doffset >> 2;
    *hdr++ = pkt->chdr.type | (pkt->chdr.ccval << 4);
    *((uint16_t *)hdr) = dctpoHtons((uint16_t)(pkt->chdr.seqno >> 32));
    hdr += 2;
    *((uint32_t *)hdr) = dctpoHtonl((uint32_t)(pkt->chdr.seqno));
    hdr += 4;
    if (pkt->chdr.hasAck) {
	*((uint16_t *)hdr) = 0;
	hdr += 2;
	*((uint16_t *)hdr) = dctpoHtons((uint16_t)(pkt->chdr.ackno >> 32));
	hdr += 2;
	*((uint32_t *)hdr) = dctpoHtonl((uint32_t)(pkt->chdr.ackno));
	hdr += 4;
    }
    if (pkt->chdr.hasSC) {
	*((uint32_t *)hdr) = pkt->chdr.scode;
	hdr += 4;
    }
    if (pkt->chdr.hasReset) {
	*hdr++ = pkt->chdr.rcode;
	*hdr++ = pkt->chdr.rdata1;
	*hdr++ = pkt->chdr.rdata2;
	*hdr = pkt->chdr.rdata3;
    }
    /* UDP will add the UDP/IP header */
    dctpoNatV4Xmt(sock, pkt);
}

/*
 * dctpiSendResponse -- send a response packet.  If upkt is not NULL and has
 * user data, the response will include that data.  It's the caller's
 * responsibility to free upkt.
 */
void dctpiSendResponse(dctpSocket *sock, dctpPacket *upkt) {
    dctpPacket *pkt;

    pkt = dctpoPacketMalloc(upkt ? upkt->appdatalen : 0);
    if (pkt) {
	if (upkt) dctpoPacketCopyData(pkt, upkt);
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_RESPONSE;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 1;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	pkt->chdr.scode = sock->scode;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiSendRequest -- send a request packet.  If upkt is not NULL and has
 * user data, the request will include that data.  It's the caller's
 * responsibility to free upkt.
 */
void dctpiSendRequest(dctpSocket *sock, dctpPacket *upkt) {
    dctpPacket *pkt;

    pkt = dctpoPacketMalloc(upkt ? upkt->appdatalen : 0);
    if (pkt) {
	if (upkt) dctpoPacketCopyData(pkt, upkt);
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_REQUEST;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 0;
	pkt->chdr.hasSC = 1;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	pkt->chdr.scode = sock->scode;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiRequestTimeout -- process timeout in REQUEST state
 */
void dctpiRequestTimeout(void *arg) {
    dctpSocket *sock = (dctpSocket *)arg;

    dctpoLog(DCTPLOG_DEBUG, "RequestTimeout: socket 0x%x, server %d, request timeout\n",
	     (uint_t)sock, sock->server);
    dctpoSocketLock(sock);
    if (sock->destroyed) {
	dctpoSocketUnlock(sock);
	return;
    }
    if (sock->timeval < DCTP_MAXREQUESTTIMEOUT) {
	/* Retry */
	sock->timeval *= 2;
	if (sock->timeval > DCTP_MAXREQUESTTIMEOUT) {
	    sock->timeval = DCTP_MAXREQUESTTIMEOUT;
	}
	dctpiSendRequest(sock, NULL);
	dctpoStartTimer(&(sock->timer), sock->timeval, dctpiRequestTimeout, sock);
    } else {
	/* Failed, abort */
	sock->state = DCTPSOCK_CLOSED;
	dctpiSendReset(sock, NULL, DCTPRESET_ABORTED, 0, 0, 0, sock->encap);
	dctpoSocketSignal(sock);
    }
    dctpoSocketUnlock(sock);
    return;
}

/*
 * dctpiSendAck -- send an ack packet
 */
void dctpiSendAck(dctpSocket *sock) {
    dctpPacket *pkt;

    if ((pkt = dctpoPacketMalloc(0)) != NULL) {
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_ACK;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiSendClosereq -- send closerequest packet
 */
void dctpiSendClosereq(dctpSocket *sock) {
    dctpPacket *pkt;

    if ((pkt = dctpoPacketMalloc(0)) != NULL) {
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_CLOSEREQ;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiSendClose -- send close packet
 */
void dctpiSendClose(dctpSocket *sock) {
    dctpPacket *pkt;

    if ((pkt = dctpoPacketMalloc(0)) != NULL) {
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_CLOSE;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiXmtPacket -- send the first data queued on the socket as a data or
 * dataack packet.
 */
int dctpiXmtPacket(dctpSocket *sock) {
    dctpPacket *pkt;

    if (sock->firstXpkt && sock->ccidSenderFuncs &&
	(sock->ccidSenderFuncs->dataXmtOK(sock, sock->firstXpkt->appdatalen) >= 0)) {
	pkt = sock->firstXpkt;
	if ((sock->firstXpkt = pkt->nextPkt) == NULL) {
	    sock->lastXpkt = NULL;
	}
	--sock->xpktqlen;
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_DATA;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 0;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	sendPacket(sock, pkt, sock->encap);
	return(0);
    } else {
	return(-1);
    }
}

/*
 * dctpiSendSync -- send a sync packet
 */
void dctpiSendSync(dctpSocket *sock, uint64_t ackno) {
    dctpPacket *pkt;

    if ((pkt = dctpoPacketMalloc(0)) != NULL) {
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_SYNC;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	pkt->chdr.ackno = ackno;
	sendPacket(sock, pkt, sock->encap);
    }
}

/*
 * dctpiSendSyncAck -- send a syncack packet
 */
void dctpiSendSyncAck(dctpSocket *sock, uint64_t ackno) {
    dctpPacket *pkt;

    if ((pkt = dctpoPacketMalloc(0)) != NULL) {
	pkt->chdr.sport = sock->localAddr.sa_port;
	pkt->chdr.dport = sock->remoteAddr.sa_port;
	pkt->chdr.type = DCTPPKT_SYNCACK;
	pkt->chdr.ccval = 0;
	pkt->chdr.hasAck = 1;
	pkt->chdr.hasSC = 0;
	pkt->chdr.shortSeq = 0;
	pkt->chdr.hasReset = 0;
	pkt->chdr.ackno = ackno;
	sendPacket(sock, pkt, sock->encap);
    }
}

