/*
 *  ether_windows.cpp - Ethernet device driver
 *
 *  Basilisk II (C) 1997-2008 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 <process.h>
#include <windowsx.h>
#include <winioctl.h>
#include <ctype.h>

#include "cpu_emulation.h"
#include "main.h"
#include "macos_util.h"
#include "prefs.h"
#include "user_strings.h"
#include "ether.h"
#include "ether_defs.h"
#include "b2ether/multiopt.h"
#include "b2ether/inc/b2ether_hl.h"
#include "ether_windows.h"
#include "router/router.h"
#include "util_windows.h"
// somehow util_windows undefines min
#define min(x,y) ((x) < (y) ? (x) : (y))
#include "libslirp.h"
#include "ctl.h"

// Define to let the slirp library determine the right timeout for select()
#define USE_SLIRP_TIMEOUT 1


#define DEBUG 0
#define MONITOR 0

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

#include "debug.h"


// Ethernet device types
enum {
	NET_IF_B2ETHER,
	NET_IF_ROUTER,
	NET_IF_SLIRP,
	NET_IF_TAP,
	NET_IF_FAKE,
};

// TAP-Win32 constants
#define TAP_VERSION_MIN_MAJOR 7
#define TAP_VERSION_MIN_MINOR 1

#define TAP_CONTROL_CODE(request, method) \
		CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS)

#define TAP_IOCTL_GET_MAC				TAP_CONTROL_CODE (1, METHOD_BUFFERED)
#define TAP_IOCTL_GET_VERSION			TAP_CONTROL_CODE (2, METHOD_BUFFERED)
#define TAP_IOCTL_GET_MTU				TAP_CONTROL_CODE (3, METHOD_BUFFERED)
#define TAP_IOCTL_GET_INFO				TAP_CONTROL_CODE (4, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_POINT_TO_POINT	TAP_CONTROL_CODE (5, METHOD_BUFFERED)
#define TAP_IOCTL_SET_MEDIA_STATUS		TAP_CONTROL_CODE (6, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_DHCP_MASQ		TAP_CONTROL_CODE (7, METHOD_BUFFERED)
#define TAP_IOCTL_GET_LOG_LINE			TAP_CONTROL_CODE (8, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_DHCP_SET_OPT	TAP_CONTROL_CODE (9, METHOD_BUFFERED)

#define OLD_TAP_CONTROL_CODE(request, method) \
		CTL_CODE (FILE_DEVICE_PHYSICAL_NETCARD | 8000, request, method, FILE_ANY_ACCESS)

#define OLD_TAP_IOCTL_GET_VERSION		OLD_TAP_CONTROL_CODE (3, METHOD_BUFFERED)

// Options
bool ether_use_permanent = true;
static int16 ether_multi_mode = ETHER_MULTICAST_MAC;

// Global variables
HANDLE ether_th;
unsigned int ether_tid;
HANDLE ether_th1;
HANDLE ether_th2;
static int net_if_type = -1;	// Ethernet device type
#ifdef SHEEPSHAVER
static bool net_open = false;	// Flag: initialization succeeded, network device open
uint8 ether_addr[6];			// Our Ethernet address

#else
const bool ether_driver_opened = true;  // Flag: Driver is open on MacOS side
                                        // so ether.h layer is ready for
                                        // calls.
                                        // B2 doesn't provide this
                                        // but also calls don't need it
                           
#endif

// These are protected by queue_csection
// Controls transfer for read thread to feed thread
static CRITICAL_SECTION queue_csection;
typedef struct _win_queue_t {
	uint8 *buf;
	int sz;
} win_queue_t;
#define MAX_QUEUE_ITEMS 1024
static win_queue_t queue[MAX_QUEUE_ITEMS];
static int queue_head = 0;
static int queue_inx = 0;
static bool wait_request = true;



// Read thread protected packet pool
static CRITICAL_SECTION fetch_csection;
// Some people use pools as large as 64.
#define PACKET_POOL_COUNT 10
static LPPACKET packets[PACKET_POOL_COUNT];
static bool wait_request2 = false;



// Write thread packet queue
static CRITICAL_SECTION send_csection;
static LPPACKET send_queue = 0;


// Write thread free packet pool
static CRITICAL_SECTION wpool_csection;
static LPPACKET write_packet_pool = 0;

// Calling slirp functions from multiple threads concurrently is unsafe; guard it
static CRITICAL_SECTION slirp_single_call_csection;


// Try to deal with echos. Protected by fetch_csection.
// The code should be moved to the driver. No need to lift
// the echo packets to the application level.
// MAX_ECHO must be a power of two.
#define MAX_ECHO (1<<2)
static int echo_count = 0;
typedef uint8 echo_t[1514];
static echo_t pending_packet[MAX_ECHO];
static int pending_packet_sz[MAX_ECHO];


// List of attached protocols
struct NetProtocol {
	NetProtocol *next;
	uint16 type;
	uint32 handler;
};

static NetProtocol *prot_list = NULL;


static LPADAPTER fd = 0;
static bool thread_active = false;
static bool thread_active_1 = false;
static bool thread_active_2 = false;
static bool thread_active_3 = false;
static HANDLE int_ack = 0;
static HANDLE int_sig = 0;
static HANDLE int_sig2 = 0;
static HANDLE int_send_now = 0;

// Prototypes
static LPADAPTER tap_open_adapter(LPCTSTR dev_name);
static void tap_close_adapter(LPADAPTER fd);
static bool tap_check_version(LPADAPTER fd);
static bool tap_set_status(LPADAPTER fd, ULONG status);
static bool tap_get_mac(LPADAPTER fd, LPBYTE addr);
static bool tap_receive_packet(LPADAPTER fd, LPPACKET lpPacket, BOOLEAN Sync);
static bool tap_send_packet(LPADAPTER fd, LPPACKET lpPacket, BOOLEAN Sync, BOOLEAN recycle);
static unsigned int WINAPI slirp_receive_func(void *arg);
static unsigned int WINAPI ether_thread_feed_int(void *arg);
static unsigned int WINAPI ether_thread_get_packets_nt(void *arg);
static unsigned int WINAPI ether_thread_write_packets(void *arg);
static void init_queue(void);
static void final_queue(void);
static bool allocate_read_packets(void);
static void free_read_packets(void);
static void free_write_packets(void);
static int16 ether_do_add_multicast(uint8 *addr);
static int16 ether_do_del_multicast(uint8 *addr);
static int16 ether_do_write(uint32 arg);
static void ether_do_interrupt(void);
static void slirp_add_redirs();
static int slirp_add_redir(const char *redir_str);


/*
 *  Find protocol in list
 */

static NetProtocol *find_protocol(uint16 type)
{
	// All 802.2 types are the same
	if (type <= 1500)
		type = 0;

	// Search list (we could use hashing here but there are usually only three
	// handlers installed: 0x0000 for AppleTalk and 0x0800/0x0806 for TCP/IP)
	NetProtocol *p = prot_list;
	while (p) {
		if (p->type == type)
			return p;
		p = p->next;
	}
	return NULL;
}


/*
 *  Initialization
 */

bool ether_init(void)
{
	TCHAR buf[256];

	// Do nothing if no Ethernet device specified
	const char *name = PrefsFindString("ether");
	if (name == NULL)
		return false;

	ether_multi_mode = PrefsFindInt32("ethermulticastmode");
	ether_use_permanent = PrefsFindBool("etherpermanentaddress");

	// Determine Ethernet device type
	net_if_type = -1;
	if (PrefsFindBool("routerenabled") || strcmp(name, "router") == 0)
		net_if_type = NET_IF_ROUTER;
	else if (strcmp(name, "slirp") == 0)
		net_if_type = NET_IF_SLIRP;
	else if (strcmp(name, "tap") == 0)
		net_if_type = NET_IF_TAP;
	else
		net_if_type = NET_IF_B2ETHER;

	// Initialize NAT-Router
	if (net_if_type == NET_IF_ROUTER) {
		if (!router_init())
			net_if_type = NET_IF_FAKE;
	}

	// Initialize slirp library
	if (net_if_type == NET_IF_SLIRP) {
		if (slirp_init() < 0) {
			WarningAlert(GetString(STR_SLIRP_NO_DNS_FOUND_WARN));
			return false;
		}
		slirp_add_redirs();
	}

	// Open ethernet device
	decltype(tstr(std::declval<const char*>())) dev_name;
	switch (net_if_type) {
	case NET_IF_B2ETHER:
		dev_name = tstr(PrefsFindString("etherguid"));
		if (dev_name == NULL || strcmp(name, "b2ether") != 0)
			dev_name = tstr(name);
		break;
	case NET_IF_TAP:
		dev_name = tstr(PrefsFindString("etherguid"));
		break;
	}
	if (net_if_type == NET_IF_B2ETHER) {
		if (dev_name == NULL) {
			WarningAlert("No ethernet device GUID specified. Ethernet is not available.");
			goto open_error;
		}

		fd = PacketOpenAdapter( dev_name.get(), ether_multi_mode );
		if (!fd) {
			_sntprintf(buf, lengthof(buf), TEXT("Could not open ethernet adapter %s."), dev_name.get());
			WarningAlert(buf);
			goto open_error;
		}

		// Get Ethernet address
		if(!PacketGetMAC(fd,ether_addr,ether_use_permanent)) {
			_sntprintf(buf, lengthof(buf), TEXT("Could not get hardware address of device %s. Ethernet is not available."), dev_name.get());
			WarningAlert(buf);
			goto open_error;
		}
		D(bug("Real ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));

		const char *ether_fake_address;
		ether_fake_address = PrefsFindString("etherfakeaddress");
		if(ether_fake_address && strlen(ether_fake_address) == 12) {
			char sm[10];
			strcpy( sm, "0x00" );
			for( int i=0; i<6; i++ ) {
				sm[2] = ether_fake_address[i*2];
				sm[3] = ether_fake_address[i*2+1];
				ether_addr[i] = (uint8)strtoul(sm,0,0);
			}
			D(bug("Fake ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
		}
	}
	else if (net_if_type == NET_IF_TAP) {
		if (dev_name == NULL) {
			WarningAlert("No ethernet device GUID specified. Ethernet is not available.");
			goto open_error;
		}

		fd = tap_open_adapter(dev_name.get());
		if (!fd) {
			_sntprintf(buf, lengthof(buf), TEXT("Could not open ethernet adapter %s."), dev_name.get());
			WarningAlert(buf);
			goto open_error;
		}

		if (!tap_check_version(fd)) {
			_sntprintf(buf, lengthof(buf), TEXT("Minimal TAP-Win32 version supported is %d.%d."), TAP_VERSION_MIN_MAJOR, TAP_VERSION_MIN_MINOR);
			WarningAlert(buf);
			goto open_error;
		}

		if (!tap_set_status(fd, true)) {
			WarningAlert("Could not set media status to connected.");
			goto open_error;
		}

		if (!tap_get_mac(fd, ether_addr)) {
			_sntprintf(buf, lengthof(buf), TEXT("Could not get hardware address of device %s. Ethernet is not available."), dev_name.get());
			WarningAlert(buf);
			goto open_error;
		}
		D(bug("Real ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));

		const char *ether_fake_address;
		ether_fake_address = PrefsFindString("etherfakeaddress");
		if (ether_fake_address && strlen(ether_fake_address) == 12) {
			char sm[10];
			strcpy( sm, "0x00" );
			for( int i=0; i<6; i++ ) {
				sm[2] = ether_fake_address[i*2];
				sm[3] = ether_fake_address[i*2+1];
				ether_addr[i] = (uint8)strtoul(sm,0,0);
			}
		}
#if 1
		/*
		  If we bridge the underlying ethernet connection and the TAP
		  device altogether, we have to use a fake address.
		 */
		else {
			ether_addr[0] = 0x52;
			ether_addr[1] = 0x54;
			ether_addr[2] = 0x00;
		}
#endif
		D(bug("Fake ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
	}
	else if (net_if_type == NET_IF_SLIRP) {
		ether_addr[0] = 0x52;
		ether_addr[1] = 0x54;
		ether_addr[2] = 0x00;
		ether_addr[3] = 0x12;
		ether_addr[4] = 0x34;
		ether_addr[5] = 0x56;
		D(bug("Ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
	}
	else {
		memcpy( ether_addr, router_mac_addr, 6 );
		D(bug("Fake ethernet address (same as router) %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
	}

	// Start packet reception thread
	int_ack = CreateSemaphore( 0, 0, 1, NULL);
	if(!int_ack) {
		WarningAlert("WARNING: Cannot create int_ack semaphore");
		goto open_error;
	}

	// nonsignaled
	int_sig = CreateSemaphore( 0, 0, 1, NULL);
	if(!int_sig) {
		WarningAlert("WARNING: Cannot create int_sig semaphore");
		goto open_error;
	}

	int_sig2 = CreateSemaphore( 0, 0, 1, NULL);
	if(!int_sig2) {
		WarningAlert("WARNING: Cannot create int_sig2 semaphore");
		goto open_error;
	}

	int_send_now = CreateSemaphore( 0, 0, 1, NULL);
	if(!int_send_now) {
		WarningAlert("WARNING: Cannot create int_send_now semaphore");
		goto open_error;
	}

	init_queue();

	if(!allocate_read_packets()) goto open_error;

	// No need to enter wait state if we can avoid it.
	// These all terminate fast.

	InitializeCriticalSectionAndSpinCount( &fetch_csection, 5000 );
	InitializeCriticalSectionAndSpinCount( &queue_csection, 5000 );
	InitializeCriticalSectionAndSpinCount( &send_csection, 5000 );
	InitializeCriticalSectionAndSpinCount( &wpool_csection, 5000 );

	InitializeCriticalSection( &slirp_single_call_csection );

	ether_th = (HANDLE)_beginthreadex( 0, 0, ether_thread_feed_int, 0, 0, &ether_tid );
	if (!ether_th) {
		D(bug("Failed to create ethernet thread\n"));
		goto open_error;
	}
	thread_active = true;

	unsigned int dummy;
	unsigned int (WINAPI *receive_func)(void *);
	switch (net_if_type) {
	case NET_IF_SLIRP:
	  receive_func = slirp_receive_func;
	  break;
	default:
	  receive_func = ether_thread_get_packets_nt;
	  break;
	}
	ether_th2 = (HANDLE)_beginthreadex( 0, 0, receive_func, 0, 0, &dummy );
	ether_th1 = (HANDLE)_beginthreadex( 0, 0, ether_thread_write_packets, 0, 0, &dummy );

	// Everything OK
	return true;

 open_error:
	if (thread_active) {
		TerminateThread(ether_th,0);
		ether_th = 0;
		if (int_ack)
			CloseHandle(int_ack);
		int_ack = 0;
		if(int_sig)
			CloseHandle(int_sig);
		int_sig = 0;
		if(int_sig2)
			CloseHandle(int_sig2);
		int_sig2 = 0;
		if(int_send_now)
			CloseHandle(int_send_now);
		int_send_now = 0;
		thread_active = false;
	}
	if (fd) {
		switch (net_if_type) {
		case NET_IF_B2ETHER:
			PacketCloseAdapter(fd);
			break;
		case NET_IF_TAP:
			tap_close_adapter(fd);
			break;
		}
		fd = 0;
	}
	return false;
}


/*
 *  Deinitialization
 */

void ether_exit(void)
{
	D(bug("EtherExit\n"));

	// Stop reception thread
	thread_active = false;

	if(int_ack) ReleaseSemaphore(int_ack,1,NULL);
	if(int_sig) ReleaseSemaphore(int_sig,1,NULL);
	if(int_sig2) ReleaseSemaphore(int_sig2,1,NULL);
	if(int_send_now) ReleaseSemaphore(int_send_now,1,NULL);

	D(bug("CancelIO if needed\n"));
	if (fd && fd->hFile)
		CancelIo(fd->hFile);

	// Wait max 2 secs to shut down pending io. After that, kill them.
	D(bug("Wait delay\n"));
	for( int i=0; i<10; i++ ) {
		if(!thread_active_1 && !thread_active_2 && !thread_active_3) break;
		Sleep(200);
	}

	if(thread_active_1) {
		D(bug("Ether killing ether_th1\n"));
		if(ether_th1) TerminateThread(ether_th1,0);
		thread_active_1 = false;
	}
	if(thread_active_2) {
		D(bug("Ether killing ether_th2\n"));
		if(ether_th2) TerminateThread(ether_th2,0);
		thread_active_2 = false;
	}
	if(thread_active_3) {
		D(bug("Ether killing thread\n"));
		if(ether_th) TerminateThread(ether_th,0);
		thread_active_3 = false;
	}

	ether_th1 = 0;
	ether_th2 = 0;
	ether_th = 0;

	D(bug("Closing semaphores\n"));
	if(int_ack) {
		CloseHandle(int_ack);
		int_ack = 0;
	}
	if(int_sig) {
		CloseHandle(int_sig);
		int_sig = 0;
	}
	if(int_sig2) {
		CloseHandle(int_sig2);
		int_sig2 = 0;
	}
	if(int_send_now) {
		CloseHandle(int_send_now);
		int_send_now = 0;
	}

	// Close ethernet device
	if (fd) {
		switch (net_if_type) {
		case NET_IF_B2ETHER:
			PacketCloseAdapter(fd);
			break;
		case NET_IF_TAP:
			tap_close_adapter(fd);
			break;
		}
		fd = 0;
	}

	// Remove all protocols
	D(bug("Removing protocols\n"));
	NetProtocol *p = prot_list;
	while (p) {
		NetProtocol *next = p->next;
		delete p;
		p = next;
	}
	prot_list = 0;

	D(bug("Deleting sections\n"));
	DeleteCriticalSection( &fetch_csection );
	DeleteCriticalSection( &queue_csection );
	DeleteCriticalSection( &send_csection );
	DeleteCriticalSection( &wpool_csection );
	DeleteCriticalSection( &slirp_single_call_csection );

	D(bug("Freeing read packets\n"));
	free_read_packets();

	D(bug("Freeing write packets\n"));
	free_write_packets();

	D(bug("Finalizing queue\n"));
	final_queue();

	if (net_if_type == NET_IF_ROUTER) {
		D(bug("Stopping router\n"));
		router_final();
	}

	D(bug("EtherExit done\n"));
}


/*
 *  Glue around low-level implementation
 */

#ifdef SHEEPSHAVER
// Error codes
enum {
	eMultiErr		= -91,
	eLenErr			= -92,
	lapProtErr		= -94,
	excessCollsns	= -95
};

// Initialize ethernet
void EtherInit(void)
{
	net_open = false;

	// Do nothing if the user disabled the network
	if (PrefsFindBool("nonet"))
		return;

	net_open = ether_init();
}

// Exit ethernet
void EtherExit(void)
{
	ether_exit();
	net_open = false;
}

// Get ethernet hardware address
void AO_get_ethernet_address(uint32 arg)
{
	uint8 *addr = Mac2HostAddr(arg);
	if (net_open)
		OTCopy48BitAddress(ether_addr, addr);
	else {
		addr[0] = 0x12;
		addr[1] = 0x34;
		addr[2] = 0x56;
		addr[3] = 0x78;
		addr[4] = 0x9a;
		addr[5] = 0xbc;
	}
	D(bug("AO_get_ethernet_address: got address %02x%02x%02x%02x%02x%02x\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]));
}

// Add multicast address
void AO_enable_multicast(uint32 addr)
{
	if (net_open)
		ether_do_add_multicast(Mac2HostAddr(addr));
}

// Disable multicast address
void AO_disable_multicast(uint32 addr)
{
	if (net_open)
		ether_do_del_multicast(Mac2HostAddr(addr));
}

// Transmit one packet
void AO_transmit_packet(uint32 mp)
{
	if (net_open) {
		switch (ether_do_write(mp)) {
		case noErr:
			num_tx_packets++;
			break;
		case excessCollsns:
			num_tx_buffer_full++;
			break;
		}
	}
}

// Copy packet data from message block to linear buffer
static inline int ether_arg_to_buffer(uint32 mp, uint8 *p)
{
	return ether_msgb_to_buffer(mp, p);
}

// Ethernet interrupt
void EtherIRQ(void)
{
	D(bug("EtherIRQ\n"));
	num_ether_irq++;

	OTEnterInterrupt();
	ether_do_interrupt();
	OTLeaveInterrupt();

	// Acknowledge interrupt to reception thread
	D(bug(" EtherIRQ done\n"));
	ReleaseSemaphore(int_ack,1,NULL);
}
#else
// Add multicast address
int16 ether_add_multicast(uint32 pb)
{
	return ether_do_add_multicast(Mac2HostAddr(pb + eMultiAddr));
}

// Disable multicast address
int16 ether_del_multicast(uint32 pb)
{
	return ether_do_del_multicast(Mac2HostAddr(pb + eMultiAddr));
}

// Transmit one packet
int16 ether_write(uint32 wds)
{
	return ether_do_write(wds);
}

// Copy packet data from WDS to linear buffer
static inline int ether_arg_to_buffer(uint32 wds, uint8 *p)
{
	return ether_wds_to_buffer(wds, p);
}

// Dispatch packet to protocol handler
static void ether_dispatch_packet(uint32 packet, uint32 length)
{
	// Get packet type
	uint16 type = ReadMacInt16(packet + 12);

	// Look for protocol
	NetProtocol *prot = find_protocol(type);
	if (prot == NULL)
		return;

	// No default handler
	if (prot->handler == 0)
		return;

	// Copy header to RHA
	Mac2Mac_memcpy(ether_data + ed_RHA, packet, 14);
	D(bug(" header %08lx%04lx %08lx%04lx %04lx\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));

	// Call protocol handler
	M68kRegisters r;
	r.d[0] = type;								// Packet type
	r.d[1] = length - 14;						// Remaining packet length (without header, for ReadPacket)
	r.a[0] = packet + 14;						// Pointer to packet (Mac address, for ReadPacket)
	r.a[3] = ether_data + ed_RHA + 14;			// Pointer behind header in RHA
	r.a[4] = ether_data + ed_ReadPacket;		// Pointer to ReadPacket/ReadRest routines
	D(bug(" calling protocol handler %08lx, type %08lx, length %08lx, data %08lx, rha %08lx, read_packet %08lx\n", prot->handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
	Execute68k(prot->handler, &r);
}

// Ethernet interrupt
void EtherInterrupt(void)
{
	D(bug("EtherIRQ\n"));
	ether_do_interrupt();

	// Acknowledge interrupt to reception thread
	D(bug(" EtherIRQ done\n"));
	ReleaseSemaphore(int_ack,1,NULL);
}
#endif


/*
 *  Reset
 */

void ether_reset(void)
{
	D(bug("EtherReset\n"));

	// Remove all protocols
	NetProtocol *p = prot_list;
	while (p) {
		NetProtocol *next = p->next;
		delete p;
		p = next;
	}
	prot_list = NULL;
}


/*
 *  Add multicast address
 */

static int16 ether_do_add_multicast(uint8 *addr)
{
	D(bug("ether_add_multicast\n"));

	// We wouldn't need to do this
	// if(ether_multi_mode != ETHER_MULTICAST_MAC) return noErr;

	switch (net_if_type) {
	case NET_IF_B2ETHER:
		if (!PacketAddMulticast( fd, addr)) {
			D(bug("WARNING: couldn't enable multicast address\n"));
			return eMultiErr;
		}
	default:
		D(bug("ether_add_multicast: noErr\n"));
		return noErr;
	}
}


/*
 *  Delete multicast address
 */

int16 ether_do_del_multicast(uint8 *addr)
{
	D(bug("ether_del_multicast\n"));

	// We wouldn't need to do this
	// if(ether_multi_mode != ETHER_MULTICAST_MAC) return noErr;

	switch (net_if_type) {
	case NET_IF_B2ETHER:
		if (!PacketDelMulticast( fd, addr)) {
			D(bug("WARNING: couldn't disable multicast address\n"));
			return eMultiErr;
		}
	default:
		return noErr;
	}
}


/*
 *  Attach protocol handler
 */

int16 ether_attach_ph(uint16 type, uint32 handler)
{
	D(bug("ether_attach_ph type=0x%x, handler=0x%x\n",(int)type,handler));

	// Already attached?
	NetProtocol *p = find_protocol(type);
	if (p != NULL) {
		D(bug("ether_attach_ph: lapProtErr\n"));
		return lapProtErr;
	} else {
		// No, create and attach
		p = new NetProtocol;
		p->next = prot_list;
		p->type = type;
		p->handler = handler;
		prot_list = p;
		D(bug("ether_attach_ph: noErr\n"));
		return noErr;
	}
}


/*
 *  Detach protocol handler
 */

int16 ether_detach_ph(uint16 type)
{
	D(bug("ether_detach_ph type=%08lx\n",(int)type));

	NetProtocol *p = find_protocol(type);
	if (p != NULL) {
		NetProtocol *previous = 0;
		NetProtocol *q = prot_list;
		while(q) {
			if (q == p) {
				if(previous) {
					previous->next = q->next;
				} else {
					prot_list = q->next;
				}
				delete p;
				return noErr;
			}
			previous = q;
			q = q->next;
		}
	}
	return lapProtErr;
}

#if MONITOR
static void dump_packet( uint8 *packet, int length )
{
	char buf[1000], sm[10];

	*buf = 0;

	if(length > 256) length = 256;

	for (int i=0; i<length; i++) {
		sprintf(sm," %02x", (int)packet[i]);
		strcat( buf, sm );
	}
	strcat( buf, "\n" );
	bug(buf);
}
#endif


/*
 *  Transmit raw ethernet packet
 */

static void insert_send_queue( LPPACKET Packet )
{
	EnterCriticalSection( &send_csection );
	Packet->next = 0;
	if(send_queue) {
		LPPACKET p = send_queue;
		// The queue is short. It would be larger overhead to double-link it.
		while(p->next) p = p->next;
		p->next = Packet;
	} else {
		send_queue = Packet;
	}
	LeaveCriticalSection( &send_csection );
}

static LPPACKET get_send_head( void )
{
	LPPACKET Packet = 0;

	EnterCriticalSection( &send_csection );
	if(send_queue) {
		Packet = send_queue;
		send_queue = send_queue->next;
	}
	LeaveCriticalSection( &send_csection );

	return Packet;
}

static int get_write_packet_pool_sz( void )
{
	LPPACKET t = write_packet_pool;
	int sz = 0;

	while(t) {
		t = t->next;
		sz++;
	}
	return(sz);
}

static void free_write_packets( void )
{
	LPPACKET next;
	int i = 0;
	while(write_packet_pool) {
		next = write_packet_pool->next;
		D(bug("Freeing write packet %ld\n",++i));
		PacketFreePacket(write_packet_pool);
		write_packet_pool = next;
	}
}

void recycle_write_packet( LPPACKET Packet )
{
	EnterCriticalSection( &wpool_csection );
	Packet->next = write_packet_pool;
	write_packet_pool = Packet;
	D(bug("Pool size after recycling = %ld\n",get_write_packet_pool_sz()));
	LeaveCriticalSection( &wpool_csection );
}

static LPPACKET get_write_packet( UINT len )
{
	LPPACKET Packet = 0;

	EnterCriticalSection( &wpool_csection );
	if(write_packet_pool) {
		Packet = write_packet_pool;
		write_packet_pool = write_packet_pool->next;
		Packet->OverLapped.Offset = 0;
		Packet->OverLapped.OffsetHigh = 0;
		Packet->Length = len;
		Packet->BytesReceived	= 0;
		Packet->bIoComplete	= FALSE;
		Packet->free = TRUE;
		Packet->next = 0;
		// actually an auto-reset event.
		if(Packet->OverLapped.hEvent) ResetEvent(Packet->OverLapped.hEvent);
	} else {
		Packet = PacketAllocatePacket(fd,len);
	}

	D(bug("Pool size after get wr packet = %ld\n",get_write_packet_pool_sz()));

	LeaveCriticalSection( &wpool_csection );

	return Packet;
}

unsigned int WINAPI ether_thread_write_packets(void *arg)
{
	LPPACKET Packet;

	thread_active_1 = true;

	D(bug("ether_thread_write_packets start\n"));

	while(thread_active) {
		// must be alertable, otherwise write completion is never called
		WaitForSingleObjectEx(int_send_now,INFINITE,TRUE);
		while( thread_active && (Packet = get_send_head()) != 0 ) {
			switch (net_if_type) {
			case NET_IF_ROUTER:
				if(router_write_packet((uint8 *)Packet->Buffer, Packet->Length)) {
					Packet->bIoComplete = TRUE;
					recycle_write_packet(Packet);
				}
				break;
			case NET_IF_FAKE:
				Packet->bIoComplete = TRUE;
				recycle_write_packet(Packet);
				break;
			case NET_IF_B2ETHER:
				if(!PacketSendPacket( fd, Packet, FALSE, TRUE )) {
					// already recycled if async
				}
				break;
			case NET_IF_TAP:
				if (!tap_send_packet(fd, Packet, FALSE, TRUE)) {
					// already recycled if async
				}
				break;
			case NET_IF_SLIRP:
				EnterCriticalSection( &slirp_single_call_csection );
				slirp_input((uint8 *)Packet->Buffer, Packet->Length);
				LeaveCriticalSection( &slirp_single_call_csection );
				Packet->bIoComplete = TRUE;
				recycle_write_packet(Packet);
				break;
			}
		}
	}

	D(bug("ether_thread_write_packets exit\n"));

	thread_active_1 = false;

	return(0);
}

static BOOL write_packet( uint8 *packet, int len )
{
	LPPACKET Packet;

	D(bug("write_packet\n"));

	Packet = get_write_packet(len);
	if(Packet) {
		memcpy( Packet->Buffer, packet, len );

		EnterCriticalSection( &fetch_csection );
		pending_packet_sz[echo_count] = min(sizeof(pending_packet),len);
		memcpy( pending_packet[echo_count], packet, pending_packet_sz[echo_count] );
		echo_count = (echo_count+1) & (~(MAX_ECHO-1));
		LeaveCriticalSection( &fetch_csection );

		insert_send_queue( Packet );

		ReleaseSemaphore(int_send_now,1,NULL);
		return(TRUE);
	} else {
		return(FALSE);
	}
}

static int16 ether_do_write(uint32 arg)
{
	D(bug("ether_write\n"));

	// Copy packet to buffer
	uint8 packet[1514], *p = packet;
	int len = ether_arg_to_buffer(arg, p);

	if(len > 1514) {
		D(bug("illegal packet length: %d\n",len));
		return eLenErr;
	} else {
#if MONITOR
		bug("Sending Ethernet packet (%d bytes):\n",(int)len);
		dump_packet( packet, len );
#endif
	}

	// Transmit packet
	if (!write_packet(packet, len)) {
		D(bug("WARNING: couldn't transmit packet\n"));
		return excessCollsns;
	} else {
		// It's up to the protocol drivers to do the error checking. Even if the
		// i/o completion routine returns ok, there can be errors, so there is
		// no point to wait for write completion and try to make some sense of the
		// possible error codes.
		return noErr;
	}
}


static void init_queue(void)
{
	queue_inx = 0;
	queue_head = 0;

	for( int i=0; i<MAX_QUEUE_ITEMS; i++ ) {
		queue[i].buf = (uint8 *)malloc( 1514 );
		queue[i].sz = 0;
	}
}

static void final_queue(void)
{
	for( int i=0; i<MAX_QUEUE_ITEMS; i++ ) {
		if(queue[i].buf) free(queue[i].buf);
	}
}

void enqueue_packet( const uint8 *buf, int sz )
{
	EnterCriticalSection( &queue_csection );
	if(queue[queue_inx].sz > 0) {
		D(bug("ethernet queue full, packet dropped\n"));
	} else {
		if(sz > 1514) sz = 1514;
		queue[queue_inx].sz = sz;
		memcpy( queue[queue_inx].buf, buf, sz );
		queue_inx++;
		if(queue_inx >= MAX_QUEUE_ITEMS) queue_inx = 0;
		if(wait_request) {
			wait_request = false;
			ReleaseSemaphore(int_sig,1,NULL);
		}
	}
	LeaveCriticalSection( &queue_csection );
}

static int dequeue_packet( uint8 *buf )
{
	int sz;

	if(!thread_active) return(0);

	EnterCriticalSection( &queue_csection );
	sz = queue[queue_head].sz;
	if(sz > 0) {
		memcpy( buf, queue[queue_head].buf, sz );
		queue[queue_head].sz = 0;
		queue_head++;
		if(queue_head >= MAX_QUEUE_ITEMS) queue_head = 0;
	}
	LeaveCriticalSection( &queue_csection );
	return(sz);
}

static void trigger_queue(void)
{
	EnterCriticalSection( &queue_csection );
	if( queue[queue_head].sz > 0 ) {
		D(bug(" packet received, triggering Ethernet interrupt\n"));
		SetInterruptFlag(INTFLAG_ETHER);
		TriggerInterrupt();
		// of course can't wait here.
	}
	LeaveCriticalSection( &queue_csection );
}

static bool set_wait_request(void)
{
	bool result;
	EnterCriticalSection( &queue_csection );
	if(queue[queue_head].sz) {
		result = true;
	} else {
		result = false;
		wait_request = true;
	}
	LeaveCriticalSection( &queue_csection );
	return(result);
}


/*
 *  TAP-Win32 glue
 */

static LPADAPTER tap_open_adapter(LPCTSTR dev_name)
{
	fd = (LPADAPTER)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(*fd));
	if (fd == NULL)
		return NULL;

	TCHAR dev_path[MAX_PATH];
	_sntprintf(dev_path, lengthof(dev_path),
			 TEXT("\\\\.\\Global\\%s.tap"), dev_name);

	HANDLE handle = CreateFile(
		dev_path,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
		NULL);
	if (handle == NULL || handle == INVALID_HANDLE_VALUE)
		return NULL;

	fd->hFile = handle;
	return fd;
}

static void tap_close_adapter(LPADAPTER fd)
{
	if (fd) {
		if (fd->hFile) {
			tap_set_status(fd, false);
			CloseHandle(fd->hFile);
		}
		GlobalFreePtr(fd);
	}
}

static bool tap_check_version(LPADAPTER fd)
{
	ULONG len;
	ULONG info[3] = { 0, };

	if (!DeviceIoControl(fd->hFile, TAP_IOCTL_GET_VERSION,
						 &info, sizeof(info),
						 &info, sizeof(info), &len, NULL)
		&& !DeviceIoControl(fd->hFile, OLD_TAP_IOCTL_GET_VERSION,
							&info, sizeof(info),
							&info, sizeof(info), &len, NULL))
		return false;

	if (info[0] > TAP_VERSION_MIN_MAJOR)
		return true;
	if (info[0] == TAP_VERSION_MIN_MAJOR && info[1] >= TAP_VERSION_MIN_MINOR)
		return true;

	return false;
}

static bool tap_set_status(LPADAPTER fd, ULONG status)
{
	DWORD len = 0;
	return DeviceIoControl(fd->hFile, TAP_IOCTL_SET_MEDIA_STATUS,
						   &status, sizeof (status),
						   &status, sizeof (status), &len, NULL) != FALSE;
}

static bool tap_get_mac(LPADAPTER fd, LPBYTE addr)
{
	DWORD len = 0;
	return DeviceIoControl(fd->hFile, TAP_IOCTL_GET_MAC,
						   addr, 6,
						   addr, 6, &len, NULL) != FALSE;
}

static VOID CALLBACK tap_write_completion(
	DWORD dwErrorCode,
	DWORD dwNumberOfBytesTransfered,
	LPOVERLAPPED lpOverLapped
	)
{
	LPPACKET lpPacket = CONTAINING_RECORD(lpOverLapped, PACKET, OverLapped);

	lpPacket->bIoComplete = TRUE;
	recycle_write_packet(lpPacket);
}

static bool tap_send_packet(
	LPADAPTER fd,
	LPPACKET lpPacket,
	BOOLEAN Sync,
	BOOLEAN RecyclingAllowed)
{
	BOOLEAN Result;

	lpPacket->OverLapped.Offset = 0;
	lpPacket->OverLapped.OffsetHigh = 0;
	lpPacket->bIoComplete = FALSE;

	if (Sync) {
		Result = WriteFile(fd->hFile,
						   lpPacket->Buffer,
						   lpPacket->Length,
						   &lpPacket->BytesReceived,
						   &lpPacket->OverLapped);
		if (Result) {
			GetOverlappedResult(fd->hFile,
								&lpPacket->OverLapped,
								&lpPacket->BytesReceived,
								TRUE);
		}
		lpPacket->bIoComplete = TRUE;
		if (RecyclingAllowed)
			PacketFreePacket(lpPacket);
	}
	else {
		Result = WriteFileEx(fd->hFile,
							 lpPacket->Buffer,
							 lpPacket->Length,
							 &lpPacket->OverLapped,
							 tap_write_completion);

		if (!Result && RecyclingAllowed)
			recycle_write_packet(lpPacket);
	}

	return Result != FALSE;
}

static bool tap_receive_packet(LPADAPTER fd, LPPACKET lpPacket, BOOLEAN Sync)
{
	BOOLEAN Result;

	lpPacket->OverLapped.Offset = 0;
	lpPacket->OverLapped.OffsetHigh = 0;
	lpPacket->bIoComplete = FALSE;

	if (Sync) {
		Result = ReadFile(fd->hFile,
						  lpPacket->Buffer,
						  lpPacket->Length,
						  &lpPacket->BytesReceived,
						  &lpPacket->OverLapped);
		if (Result) {
			Result = GetOverlappedResult(fd->hFile,
										 &lpPacket->OverLapped,
										 &lpPacket->BytesReceived,
										 TRUE);
			if (Result)
				lpPacket->bIoComplete = TRUE;
			else
				lpPacket->free = TRUE;
		}
	}
	else {
		Result = ReadFileEx(fd->hFile,
							lpPacket->Buffer,
							lpPacket->Length,
							&lpPacket->OverLapped,
							packet_read_completion);

		if (!Result)
			lpPacket->BytesReceived = 0;
	}

	return Result != FALSE;
}


/*
 *  SLIRP output buffer glue
 */

int slirp_can_output(void)
{
	return 1;
}

void slirp_output(const uint8 *packet, int len)
{
	enqueue_packet(packet, len);
}

unsigned int WINAPI slirp_receive_func(void *arg)
{
	D(bug("slirp_receive_func\n"));
	thread_active_2 = true;

	while (thread_active) {
		// Wait for packets to arrive
		fd_set rfds, wfds, xfds;
		int nfds, ret, timeout;

		// ... in the output queue
		nfds = -1;
		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		FD_ZERO(&xfds);
		EnterCriticalSection( &slirp_single_call_csection );
		timeout = slirp_select_fill(&nfds, &rfds, &wfds, &xfds);
		LeaveCriticalSection( &slirp_single_call_csection );
#if ! USE_SLIRP_TIMEOUT
		timeout = 10000;
#endif
		if (nfds < 0) {
			/* Windows does not honour the timeout if there is not
			   descriptor to wait for */
			Delay_usec(timeout);
			ret = 0;
		}
		else {
			struct timeval tv;
			tv.tv_sec = 0;
			tv.tv_usec = timeout;
			ret = select(0, &rfds, &wfds, &xfds, &tv);
		}
		if (ret >= 0) {
			EnterCriticalSection( &slirp_single_call_csection );
			slirp_select_poll(&rfds, &wfds, &xfds);
			LeaveCriticalSection( &slirp_single_call_csection );
		}
	}

	D(bug("slirp_receive_func exit\n"));
	thread_active_2 = false;
	return 0;
}

const uint8 ether_broadcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
const uint8 appletalk_broadcast_addr[6] = { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF };
const uint8 appletalk_zone_multicast_prefix[5] = { 0x09, 0x00, 0x07, 0x00, 0x00 };

/*
 *  Packet reception threads
 */

VOID CALLBACK packet_read_completion(
	DWORD dwErrorCode,
	DWORD dwNumberOfBytesTransfered,
	LPOVERLAPPED lpOverlapped
	)
{
	EnterCriticalSection( &fetch_csection );

	LPPACKET lpPacket = CONTAINING_RECORD(lpOverlapped,PACKET,OverLapped);

	D(bug("packet_read_completion bytes=%d, error code=%d\n",dwNumberOfBytesTransfered,dwErrorCode));

	if(thread_active && !dwErrorCode) {
		int count = min(dwNumberOfBytesTransfered,1514);
		if(count) {
			int j = echo_count;
			for(int i=MAX_ECHO; i; i--) {
				j--;
				if(j < 0) j = MAX_ECHO-1;
				if(count == pending_packet_sz[j] &&
				   memcmp(pending_packet[j],lpPacket->Buffer,count) == 0)
				{
					D(bug("packet_read_completion discarding own packet.\n"));
					dwNumberOfBytesTransfered = 0;

					j = (j+1) & (~(MAX_ECHO-1));
					if(j != echo_count) {
						D(bug("Wow, this fix made some good after all...\n"));
					}

					break;
				}
			}
			// XXX drop packets that we don't care about
			if (net_if_type == NET_IF_TAP) {
				if (memcmp((LPBYTE)lpPacket->Buffer, ether_addr, 6) != 0 &&
					memcmp((LPBYTE)lpPacket->Buffer, ether_broadcast_addr, 6) != 0 &&
					memcmp((LPBYTE)lpPacket->Buffer, appletalk_broadcast_addr, 6) != 0 &&
					memcmp((LPBYTE)lpPacket->Buffer, appletalk_zone_multicast_prefix, 5) != 0
					) {
					dwNumberOfBytesTransfered = 0;
				}
			}
			if(dwNumberOfBytesTransfered) {
				if(net_if_type != NET_IF_ROUTER || !router_read_packet((uint8 *)lpPacket->Buffer, dwNumberOfBytesTransfered)) {
					enqueue_packet( (LPBYTE)lpPacket->Buffer, dwNumberOfBytesTransfered );
				}
			}
		}
	}

	// actually an auto-reset event.
	if(lpPacket->OverLapped.hEvent) ResetEvent(lpPacket->OverLapped.hEvent);

	lpPacket->free = TRUE;
	lpPacket->bIoComplete = TRUE;

	if(wait_request2) {
		wait_request2 = false;
		ReleaseSemaphore(int_sig2,1,NULL);
	}

	LeaveCriticalSection( &fetch_csection );
}

static BOOL has_no_completed_io(void)
{
	BOOL result = TRUE;

	EnterCriticalSection( &fetch_csection );

	for( int i=0; i<PACKET_POOL_COUNT; i++ ) {
		if(packets[i]->bIoComplete) {
			result = FALSE;
			break;
		}
	}
	if(result) wait_request2 = true;

	LeaveCriticalSection( &fetch_csection );
	return(result);
}

static bool allocate_read_packets(void)
{
	for( int i=0; i<PACKET_POOL_COUNT; i++ ) {
		packets[i] = PacketAllocatePacket(fd,1514);
		if(!packets[i]) {
			D(bug("allocate_read_packets: out of memory\n"));
			return(false);
		}
	}
	return(true);
}

static void free_read_packets(void)
{
	for( int i=0; i<PACKET_POOL_COUNT; i++ ) {
		PacketFreePacket(packets[i]);
	}
}

unsigned int WINAPI ether_thread_get_packets_nt(void *arg)
{
	static uint8 packet[1514];
	int i, packet_sz = 0;

	thread_active_2 = true;

	D(bug("ether_thread_get_packets_nt start\n"));

	// Wait for packets to arrive.
	// Obey the golden rules; keep the reads pending.
	while(thread_active) {

		if(net_if_type == NET_IF_B2ETHER || net_if_type == NET_IF_TAP) {
			D(bug("Pending reads\n"));
			for( i=0; thread_active && i<PACKET_POOL_COUNT; i++ ) {
				if(packets[i]->free) {
					packets[i]->free = FALSE;
					BOOLEAN Result;
					switch (net_if_type) {
					case NET_IF_B2ETHER:
						Result = PacketReceivePacket(fd, packets[i], FALSE);
						break;
					case NET_IF_TAP:
						Result = tap_receive_packet(fd, packets[i], FALSE);
						break;
					}
					if (Result) {
						if(packets[i]->bIoComplete) {
							D(bug("Early io completion...\n"));
							packet_read_completion(
								ERROR_SUCCESS,
								packets[i]->BytesReceived,
								&packets[i]->OverLapped
								);
						}
					} else {
						packets[i]->free = TRUE;
					}
				}
			}
		}

		if(thread_active && has_no_completed_io()) {
			D(bug("Waiting for int_sig2\n"));
			// "problem": awakens twice in a row. Fix if you increase the pool size.
			WaitForSingleObjectEx(int_sig2,INFINITE,TRUE);
		}
	}

	D(bug("ether_thread_get_packets_nt exit\n"));

	thread_active_2 = false;

	return 0;
}

unsigned int WINAPI ether_thread_feed_int(void *arg)
{
	bool looping;

	thread_active_3 = true;

	D(bug("ether_thread_feed_int start\n"));

	while(thread_active) {
		D(bug("Waiting for int_sig\n"));
		WaitForSingleObject(int_sig,INFINITE);
		// Looping this way to avoid a race condition.
		D(bug("Triggering\n"));
		looping = true;
		while(thread_active && looping) {
			if (ether_driver_opened) {
				trigger_queue();
				// Wait for interrupt acknowledge by EtherInterrupt()
				WaitForSingleObject(int_ack,INFINITE);
				if(thread_active) looping = set_wait_request();
			} else {
				// Ether driver is closed on the MacOS side
				// ether.h calls in this case are undefined
				Delay_usec(20000);
			}
		}
		D(bug("Queue empty.\n"));
	}

	D(bug("ether_thread_feed_int exit\n"));

	thread_active_3 = false;

	return 0;
}


/*
 *  Ethernet interrupt - activate deferred tasks to call IODone or protocol handlers
 */

static void ether_do_interrupt(void)
{
	// Call protocol handler for received packets
	EthernetPacket ether_packet;
	uint32 packet = ether_packet.addr();
	ssize_t length;
	for (;;) {

		// Read packet from Ethernet device
		length = dequeue_packet(Mac2HostAddr(packet));
		if (length < 14)
			break;

#if MONITOR
		bug("Receiving Ethernet packet (%d bytes):\n",(int)length);
		dump_packet( Mac2HostAddr(packet), length );
#endif

		// Dispatch packet
		ether_dispatch_packet(packet, length);
	}
}

// Helper function for port forwarding
static int get_str_sep(char *buf, int buf_size, const char **pp, int sep)
{
	const char *p, *p1;
	int len;
	p = *pp;
	p1 = strchr(p, sep);
	if (!p1)
		return -1;
	len = p1 - p;
	p1++;
	if (buf_size > 0) {
		if (len > buf_size - 1)
			len = buf_size - 1;
		memcpy(buf, p, len);
		buf[len] = '\0';
	}
	*pp = p1;
	return 0;
}

// Set up port forwarding for slirp
static void slirp_add_redirs()
{
	int index = 0;
	const char *str;
	while ((str = PrefsFindString("redir", index++)) != NULL) {
		slirp_add_redir(str);
	}
}

// Add a port forward/redirection for slirp
static int slirp_add_redir(const char *redir_str)
{
	// code adapted from qemu source
	struct in_addr guest_addr = {0};
	int host_port, guest_port;
	const char *p;
	char buf[256];
	int is_udp;
	char *end;
	char str[256];

	p = redir_str;
	if (!p || get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
		goto fail_syntax;
	}
	if (!strcmp(buf, "tcp") || buf[0] == '\0') {
		is_udp = 0;
	} else if (!strcmp(buf, "udp")) {
		is_udp = 1;
	} else {
		goto fail_syntax;
	}

	if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
		goto fail_syntax;
	}
	host_port = strtol(buf, &end, 0);
	if (*end != '\0' || host_port < 1 || host_port > 65535) {
		goto fail_syntax;
	}

	if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
		goto fail_syntax;
	}
	// 0.0.0.0 doesn't seem to work, so default to a client address
	// if none is specified
	if (buf[0] == '\0' ?
			!inet_pton(AF_INET, CTL_LOCAL, &guest_addr) :
			!inet_pton(AF_INET, buf, &guest_addr)) {
		goto fail_syntax;
	}

	guest_port = strtol(p, &end, 0);
	if (*end != '\0' || guest_port < 1 || guest_port > 65535) {
		goto fail_syntax;
	}

	if (slirp_redir(is_udp, host_port, guest_addr, guest_port) < 0) {
		sprintf(str, "could not set up host forwarding rule '%s'", redir_str);
		WarningAlert(str);
		return -1;
	}
	return 0;

 fail_syntax:
	sprintf(str, "invalid host forwarding rule '%s'", redir_str);
	WarningAlert(str);
	return -1;
}
#if DEBUG
#pragma optimize("",on)
#endif
