new venti library.
diff --git a/src/libventi/client.c b/src/libventi/client.c
new file mode 100644
index 0000000..c16fe48
--- /dev/null
+++ b/src/libventi/client.c
@@ -0,0 +1,151 @@
+#include <u.h>
+#include <libc.h>
+#include <venti.h>
+
+static int
+vtfcallrpc(VtConn *z, VtFcall *ou, VtFcall *in)
+{
+	Packet *p;
+
+	p = vtfcallpack(ou);
+	if(p == nil)
+		return -1;
+	if((p = vtrpc(z, p)) == nil)
+		return -1;
+	if(vtfcallunpack(in, p) < 0){
+		packetfree(p);
+		return -1;
+	}
+	if(in->type == VtRerror){
+		werrstr(in->error);
+		vtfcallclear(in);
+		packetfree(p);
+		return -1;
+	}
+	if(in->type != ou->type+1){
+		werrstr("type mismatch: sent %c%d got %c%d",
+			"TR"[ou->type&1], ou->type>>1,
+			"TR"[in->type&1], in->type>>1);
+		vtfcallclear(in);
+		packetfree(p);
+		return -1;
+	}
+	packetfree(p);
+	return 0;
+}
+
+int
+vthello(VtConn *z)
+{
+	VtFcall tx, rx;
+
+	memset(&tx, 0, sizeof tx);
+	tx.type = VtThello;
+	tx.version = z->version;
+	tx.uid = z->uid;
+	if(tx.uid == nil)
+		tx.uid = "anonymous";
+	if(vtfcallrpc(z, &tx, &rx) < 0)
+		return -1;
+	z->sid = rx.sid;
+	rx.sid = 0;
+	vtfcallclear(&rx);
+	return 0;
+}
+
+Packet*
+vtreadpacket(VtConn *z, uchar score[VtScoreSize], uint type, int n)
+{
+	VtFcall tx, rx;
+
+	memset(&tx, 0, sizeof tx);
+	tx.type = VtTread;
+	tx.dtype = type;
+	tx.count = n;
+	memmove(tx.score, score, VtScoreSize);
+	if(vtfcallrpc(z, &tx, &rx) < 0)
+		return nil;
+	if(packetsize(rx.data) > n){
+		werrstr("read returned too much data");
+		packetfree(rx.data);
+		return nil;
+	}
+	packetsha1(rx.data, tx.score);
+	if(memcmp(score, tx.score, VtScoreSize) != 0){
+		werrstr("read asked for %V got %V", score, tx.score);
+		packetfree(rx.data);
+		return nil;
+	}
+
+	return rx.data;
+}
+
+int
+vtread(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n)
+{
+	int nn;
+	Packet *p;
+
+	if((p = vtreadpacket(z, score, type, n)) == nil)
+		return -1;
+	nn = packetsize(p);
+	if(packetconsume(p, buf, nn) < 0)
+		abort();
+	return nn;
+}
+
+int
+vtwritepacket(VtConn *z, uchar score[VtScoreSize], uint type, Packet *p)
+{
+	VtFcall tx, rx;
+
+	tx.type = VtTwrite;
+	tx.dtype = type;
+	tx.data = p;
+	packetsha1(p, score);
+	if(vtfcallrpc(z, &tx, &rx) < 0)
+		return -1;
+	if(memcmp(score, rx.score, VtScoreSize) != 0){
+		werrstr("sha1 hash mismatch: want %V got %V", score, rx.score);
+		return -1;
+	}
+	return 0;
+}
+
+int
+vtwrite(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n)
+{
+	Packet *p;
+
+	p = packetforeign(buf, n, nil, nil);
+	return vtwritepacket(z, score, type, p);
+}
+
+int
+vtsync(VtConn *z)
+{
+	VtFcall tx, rx;
+
+	tx.type = VtTsync;
+	return vtfcallrpc(z, &tx, &rx);
+}
+
+int
+vtping(VtConn *z)
+{
+	VtFcall tx, rx;
+
+	tx.type = VtTping;
+	return vtfcallrpc(z, &tx, &rx);
+}
+
+int
+vtconnect(VtConn *z)
+{
+	if(vtversion(z) < 0)
+		return -1;
+	if(vthello(z) < 0)
+		return -1;
+	return 0;
+}
+