/*
 *  udp.cpp - ip router
 *
 *  Basilisk II (C) 1997-2001 Christian Bauer
 *
 *  Windows platform specific code copyright (C) Lauri Pesonen
 *
 *  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 "sysdeps.h"
#include "cpu_emulation.h"
#include "prefs.h"
#include "ether_windows.h"
#include "ether.h"
#include "router.h"
#include "router_types.h"
#include "dynsockets.h"
#include "ipsocket.h"
#include "iphelp.h"
#include "udp.h"
#include "dump.h"


#if DEBUG
#pragma optimize("",off)
#endif

#include "debug.h"


void CALLBACK udp_read_completion(
	DWORD error,
	DWORD bytes_read,
	LPWSAOVERLAPPED lpOverlapped,
	DWORD flags
)
{
  D(bug("udp_read_completion(error=0x%x, bytes_read=%d, flags=0x%x)\r\n", error, bytes_read, flags));

	socket_t *cmpl = (socket_t *)lpOverlapped->hEvent;

	// It's not easy to know whether empty upd datagrams should be passed along. doh.
	if(error == 0 && bytes_read > 0) {

		if(bytes_read > 1460) {
		  D(bug("discarding oversized udp packet, size = \r\n", bytes_read));
		} else {
			struct sockaddr_in name;
			int namelen = sizeof(name);
			memset( &name, 0, sizeof(name) );
			if( _getsockname( cmpl->s, (struct sockaddr *)&name, &namelen ) == SOCKET_ERROR ) {
				D(bug("_getsockname() failed, error=%d\r\n", _WSAGetLastError() ));
			} else {
				D(bug("_getsockname(): port=%d\r\n", ntohs(name.sin_port) ));
			}

			int udp_size = sizeof(udp_t) + bytes_read;
			udp_t *udp = (udp_t *)malloc( udp_size );
			if(udp) {
				mac_t *mac = (mac_t *)udp;
				ip_t *ip = (ip_t *)udp;

				// Build MAC
				// memcpy( udp->ip.mac.dest, cmpl->mac_src, 6 );
				memcpy( mac->dest, ether_addr, 6 );
				memcpy( mac->src, router_mac_addr, 6 );
				mac->type = htons(mac_type_ip4);

				// Build IP
				ip->version = 4;
				ip->header_len = 5;
				ip->tos = 0;
				ip->total_len = htons(sizeof(udp_t) - sizeof(mac_t) + bytes_read); // no options
				ip->ident = htons(next_ip_ident_number++); // htons() might be a macro... but does not really matter here.
				ip->flags_n_frag_offset = 0;
				ip->ttl = 128; // one hop actually!
				ip->proto = ip_proto_udp;
				ip->src = htonl(cmpl->ip_dest);
				ip->dest = htonl(cmpl->ip_src);
				make_ip4_checksum( (ip_t *)udp );

				// Copy payload (used by UDP checksum)
				memcpy( (char *)udp + sizeof(udp_t), cmpl->buffers[0].buf, bytes_read );

				// Build UDP
				udp->src_port = htons(cmpl->dest_port);
				udp->dest_port = htons(cmpl->src_port);
				udp->msg_len = htons(sizeof(udp_t) - sizeof(ip_t) + bytes_read); // no options
				make_udp_checksum( udp );

				dump_bytes( (uint8 *)udp, udp_size );

				enqueue_packet( (uint8 *)udp, udp_size );
				free(udp);
			}
		}
	}

	if(!is_router_shutting_down && cmpl->s != INVALID_SOCKET && cmpl->b_recfrom()) {
		cmpl->socket_ttl = GetTickCount() + 60000L;
	} else {
		delete_socket( cmpl );
	}
}

void write_udp( udp_t *udp, int len )
{
	if( len < sizeof(udp_t) ) {
	  D(bug("Too small udp packet(%d), dropped\r\n", len));
		return;
	}

	uint16 src_port = ntohs(udp->src_port);
	uint16 dest_port = ntohs(udp->dest_port);

	BOOL ok = true;

	socket_t *cmpl = find_socket( src_port, dest_port, IPPROTO_UDP );

	BOOL old_socket_found = cmpl != 0;

	if(!cmpl) {
		cmpl = new socket_t(IPPROTO_UDP);
		if(cmpl) {
			cmpl->s = _socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
			if(cmpl->s == INVALID_SOCKET) {
				delete cmpl;
				cmpl = 0;
				ok = false;
			} else {
				cmpl->src_port = src_port;
				cmpl->dest_port = dest_port;
				add_socket( cmpl );
			}
		} else {
			ok = false;
		}
	}

	if(ok) {
		cmpl->src_port = src_port;
		cmpl->dest_port = dest_port;
		cmpl->ip_src = ntohl(udp->ip.src);
		cmpl->ip_dest = ntohl(udp->ip.dest);

		struct sockaddr_in to;
		memset( &to, 0, sizeof(to) );
		to.sin_family = AF_INET;
		to.sin_port = udp->dest_port;
		to.sin_addr.s_addr = udp->ip.dest;

		char *data = (char *)udp + sizeof(udp_t);
		int dlen = len - sizeof(udp_t);

		// ttl changed, update checksum
		make_udp_checksum( udp );

		cmpl->set_ttl( udp->ip.ttl );

		bool please_close = true;
		/*
			Note that broadcast messages fill fail, no setsockopt(SO_BROADCAST).
			That's exactly what I want.
		*/
		if(SOCKET_ERROR != _sendto( cmpl->s, data, dlen, 0, (struct sockaddr *)&to, sizeof(to) )) {
			if(old_socket_found) {
				// This socket is not overlapped.
				please_close = false;
			} else {
				if(cmpl->b_recfrom()) please_close = false;
			}
			cmpl->socket_ttl = GetTickCount() + 60000L;
		} else {
			int socket_error = _WSAGetLastError();
			D(bug("_sendto() completed with error %d\r\n", socket_error));
			// TODO: check this out: error_winsock_2_icmp() uses router_ip_address
			// as source ip; but it's probably allright
			error_winsock_2_icmp( socket_error, (ip_t *)udp, len );
		}
		if(please_close) {
			delete_socket(cmpl);
		}
	}
}

void init_udp()
{
}

void final_udp()
{
}
