#include <windows.h>
#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/ns.h>
#include <9pm/devip.h>

#undef listen
#undef accept
#undef bind

#pragma comment(lib, "wsock32.lib")


static long
netread(Conv *c, void *a, long n)
{
	long r;

	r = recv(c->sfd, a, n, 0);
	if(r < 0){
		pm_oserror();
		nexterror();
	}
	return r;
}

static long
netwrite(Conv *c, void *a, long n)
{
	long r;

	r = send(c->sfd, a, n, 0);
	if(r < 0){
		pm_oserror();
		nexterror();
	}
	return r;
}

static void
netclone(Conv *c)
{
	int fd, one, type;

	if(c->p->name[0] == 't')
		type = SOCK_STREAM;
	else
		type = SOCK_DGRAM;

	fd = socket(AF_INET, type, 0);
	if(fd < 0){
		pm_oserror();
		werrstr("devip clone (socket): %r");
		nexterror();
	}
	if(type == SOCK_STREAM){
		one = 1;
		setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
	}
	c->sfd = fd;
}

static void
netconnect(Conv *c)
{
	struct sockaddr_in sin;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	hnputs(&sin.sin_port, c->rport);
	hnputl(&sin.sin_addr.s_addr, c->raddr);
	if(connect(c->sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0){
		pm_oserror();
		werrstr("devip connect: %r");
		nexterror();
	}
}

/*
 * I don't know if this is right.  -rsc
 */
static void
netannounce(Conv *c)
{
	if(listen(c->sfd, 5) < 0){
		pm_oserror();
		werrstr("devip announce (listen): %r");
		nexterror();
	}
}

/*
 * I don't know if this is right.  -rsc
 */
static void
netlisten(Conv *cv, Conv *lcv)
{
	int nfd, len;
	struct sockaddr_in sin;

	len = sizeof(sin);
	nfd = accept(lcv->sfd, (struct sockaddr*)&sin, &len);
	if(nfd < 0){
		pm_oserror();
		werrstr("devip listen (accept): %r");
		nexterror();
	}

	if(sin.sin_family != AF_INET || len != sizeof(sin)){
		closesocket(nfd);
		error("accepted socket not AF_INET");
	}

	cv->raddr = nhgetl(&sin.sin_addr.s_addr);
	cv->rport = nhgets(&sin.sin_port);
	cv->sfd = nfd;
}

static void
netclose(Conv *c)
{
	closesocket(c->sfd);
}

Proto udpproto = {
	"udp",
	netread,
	netwrite,
	netclone,
	netconnect,
	netannounce,
	netlisten,
	netclose,
	30,
};

Proto tcpproto = {
	"tcp",
	netread,
	netwrite,
	netclone,
	netconnect,
	netannounce,
	netlisten,
	netclose,
	30,
};

void
ipinit(void (*inst)(Proto*))
{
	WSADATA wasdat;

	if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
		sysfatal("no winsock.dll");

	inst(&tcpproto);
	inst(&udpproto);
}
