shithub: neoventi

ref: f31b4ce3b34f7191f7cfdae7b3d88e8e27e6b179
dir: /server.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <libsec.h>
#include "neoventi.h"

static void vtsend(VtConn conn, char *buf, u16int size, u8int tag, int drop);

// Handles an error on `conn` handling client request `tbuf`
// Only the tag must be preserved in the buffer
static void
vterr(VtConn conn, char *tbuf, char *msg, ...)
{
	u16int len;
	va_list args;
	va_start(args, msg);
	msg = vsmprint(msg, args);
	werrstr(msg);
	free(msg);
	va_end(args);
	if(tbuf != nil){
		len = snprint(tbuf+6, 0x10000, "neoventi: %r");
		U16PUT(tbuf+4, len);
		vtsend(conn, tbuf, len+4, VtRerror, 1);
	}
	longjmp(conn.bounce, 1);
}

static void
vthangup(VtConn conn)
{
	longjmp(conn.bounce, 2);
}

// Convenience function: reads a venti packet from conn into buf
static void
vtrecv(VtConn conn, char *buf)
{
	switch(read(conn.fd, buf, MaxPacketSize)){
	case 0:
		vthangup(conn);
	case 1:
		vterr(conn, nil, "received a single byte for message size");
	default:
		return;
	}
}

static void
vtversion(VtConn conn, char *buf)
{
	
	long n, i;
	// Response is one line of unknown size; discard bytes until EOL
	if(fprint(conn.fd, "venti-02-neoventi\n") == 18){
		for(;;){
			n = read(conn.fd, buf, 1);
			if(n != 1)
				break;
			if(buf[0] == '\n')
				return;
		}
	}
fail:
	// If the handshake fails, make it clear there's a problem and give up
	fprint(conn.fd, "FUCK OFF\n");
	vterr(conn, nil, "handshake failed");
}

static void
vtsend(VtConn conn, char *buf, u16int size, u8int type, int drop)
{
	U16PUT(buf, size);
	buf[2] = type;
	// +2 because we need to send the u16 size as well!
	if(write(conn.fd, buf, size+2) != size+2){
		if(drop)
			fprint(2, "failed to submit error packet: %r\n");
		else
			vterr(conn, buf, "failed to write packet: %r");
	}
}

static void
vtread(VtConn conn, char *buf)
{
	VtAddress addr;
	if(!vtreadlookup((u8int*)buf + 4, &addr))
		vterr(conn, buf, "read error: %r");
	if(!readclump((uchar*)buf+4, addr))
		vterr(conn, buf, "clump read failed: %r");
	vtsend(conn, buf, addr.size+2, VtRread, 0);
}

static void
vtwrite(VtConn conn, char *buf, u16int len)
{
	u8int score[20];
	uchar buf2[MaxPacketSize];
	VtAddress addr;
	sha1((uchar*)buf+8, len, score, nil);
	if(vtreadlookup(score, &addr)){
		if(addr.size != len)
			vterr(conn, buf, "hash collision detected, addr size mismatch");
		if(!readclump(buf2, addr))
			vterr(conn, buf, "clump read failed: %r");
		if(memcmp(buf2, buf+8, len) != 0)
			vterr(conn, buf, "hash collision detected, memcmp divergence on write: len %d, buf %s\n", len, buf2);
	} else {
		if(!vtwriteclump(buf+8, len, score))
			vterr(conn, buf, "data insertion failed: %r");
	}
	memcpy(buf+4, score, 20);
	vtsend(conn, buf, 22, VtRwrite, 0);
}

static int
vtconnhandle(VtConn conn, char *buf, u16int len)
{
	switch(buf[2]){
	case VtTread:
		vtread(conn, buf);
		return 1;
	case VtTwrite:
		vtwrite(conn, buf, len-6);
		return 1;
	case VtTgoodbye:
		vthangup(conn);
	case VtTsync:
		return 1;
	default:
		vterr(conn, buf, "TODO unimplemented request type %d", buf[2]);
	}
	return 0;
}

static void
vthello(VtConn conn, char *buf)
{
	vtversion(conn, buf);
	vtrecv(conn, buf);
	if(buf[2] != VtThello)
		vterr(conn, buf, "received message before hello: %d", buf[2]);
	if(buf[4] != 0 || buf[5] != 2 || buf[6] != '0' || buf[7] != '2')
		vterr(conn, buf, "unsupported protocol version requested in Thello: %d %d %d %d", buf[4], buf[5], buf[6], buf[7]);
	buf[6] = 'n';
	buf[7] = 'o';
	vtsend(conn, buf, 8, VtRhello, 0);
}

static void
vtloop(VtConn conn, char *packetbuf)
{
	long n;
	u16int sz, len;
	for(;;){
		n = 0;
		while(n < 2){
			// Can read 2 bytes even if we've got one; packet must be at least
			// 3 bytes anyways.
			sz = read(conn.fd, packetbuf+n, 2);
			if(sz <= 0)
				vthangup(conn);
			n += sz;
		}
		len = U16GET((uchar*)packetbuf);
		while(n < len){
			sz = read(conn.fd, packetbuf+n, len+2-n);
			if(sz <= 0)
				vthangup(conn);
			n += sz;
		}
		vtconnhandle(conn, packetbuf, len);
	}
}
static void
handleproc(void *fd)
{
	char buf[MaxPacketSize];
	VtConn conn;
	conn.fd = (int)(usize)fd;
	switch(setjmp(conn.bounce)){
	case 0:
		vthello(conn, buf);
		vtloop(conn, buf);
		break;
	case 1:
		fprint(2, "abandoning client: %r\n");
		break;
	case 2:
		// Deliberate disconnect, don't log.
		break;
	default:
		sysfatal("internal error: unexpected bounce code");
	}
	close(conn.fd);
}

static void
handle(int ctl, char *dir)
{
	int fd = accept(ctl, dir);
	if(fd < 0){
		fprint(2, "failed to accept connection\n");
		return;
	}
	proccreate(handleproc, (void*)fd, 512*1024);
}

void
serve(char *addr)
{
	char adir[NETPATHLEN], dir[NETPATHLEN];
	int fd, ctl;
	fd = announce(addr, adir);
	if(fd < 0)
		sysfatal("%r");
	procsetname("neoventi/server");
	for(ctl = listen(adir, dir); ctl >= 0; ctl = listen(adir, dir)){
		handle(ctl, dir);
		close(ctl);
	}
	fprint(2, "server has died\n");
}