server: handle negotiation lines

A bit of scaffolding for handling the lines from the client telling us
about its commits.
diff --git a/src/server.c b/src/server.c
index 047f8d2..7667267 100644
--- a/src/server.c
+++ b/src/server.c
@@ -29,6 +29,8 @@
 	if (server == NULL)
 		return;
 
+	git_array_clear(server->wants);
+	git_array_clear(server->common);
 	git__free(server->path);
 	git__free(server);
 }
@@ -98,6 +100,50 @@
 	return error;
 }
 
+int git_server__negotiation(git_server *server, git_pkt *_pkt)
+{
+	git_oid *id, *have_id;
+	git_pkt_have_want *pkt;
+	git_odb *odb = NULL;
+	int error;
+
+	if (_pkt->type != GIT_PKT_HAVE && _pkt->type != GIT_PKT_WANT) {
+		giterr_set(GITERR_NET, "invalid pkt for negotiation");
+		return -1;
+	}
+
+	pkt = (git_pkt_have_want *) _pkt;
+
+	if (pkt->type == GIT_PKT_WANT) {
+		id = git_array_alloc(server->wants);
+		GITERR_CHECK_ALLOC(id);
+
+		git_oid_cpy(id, &pkt->id);
+		return 0;
+	}
+
+	/* we know it's a 'have', so we check to see if it's common */
+	have_id = &pkt->id;
+	if ((error = git_repository_odb(&odb, server->repo)) < 0)
+		return error;
+
+	if ((error = git_odb_exists(odb, have_id)) < 0)
+		goto cleanup;
+
+	if (error == 1) {
+		error = 0;
+		id = git_array_alloc(server->common);
+		GITERR_CHECK_ALLOC(id);
+
+		git_oid_cpy(id, &pkt->id);
+	}
+
+cleanup:
+	git_odb_free(odb);
+
+	return error;
+}
+
 int git_server_run(git_server *server)
 {
 	/* 65535 is the max size of a pkt frame */
diff --git a/src/server.h b/src/server.h
index eff5dd4..943eaa2 100644
--- a/src/server.h
+++ b/src/server.h
@@ -9,6 +9,9 @@
 
 #include "common.h"
 #include "transports/smart.h"
+#include "array.h"
+
+typedef git_array_t(git_oid) git_oid_array;
 
 struct git_server {
 	enum git_request_type type;
@@ -16,12 +19,15 @@
 	gitno_socket s;
 	int rpc;
 	char *path;
+	git_oid_array common;
+	git_oid_array wants;
 };
 
 extern int git_server_new(git_server **out, git_repository *repo, int fd);
 extern void git_server_free(git_server *server);
 extern int git_server__handle_request(git_server *server, git_pkt *pkt);
 extern int git_server__ls(git_buf *out, git_server *server);
+extern int git_server__negotiation(git_server *server, git_pkt *_pkt);
 
 #endif
 
diff --git a/tests/network/protocol/negotiation.c b/tests/network/protocol/negotiation.c
new file mode 100644
index 0000000..0fa76a2
--- /dev/null
+++ b/tests/network/protocol/negotiation.c
@@ -0,0 +1,40 @@
+#include "clar_libgit2.h"
+#include "transports/smart.h"
+
+static git_pkt *pkt;
+
+void test_network_protocol_negotiation__cleanup(void)
+{
+	git_pkt_free(pkt);
+	pkt = NULL;
+}
+
+void test_network_protocol_negotiation__have(void)
+{
+	const char buf[] = "0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n";
+	const char *rest;
+	git_oid id;
+	git_pkt_have_want *ppkt;
+
+	git_oid_fromstr(&id, "7e47fe2bd8d01d481f44d7af0531bd93d3b21c01");
+
+	cl_git_pass(git_pkt_parse_line(&pkt, buf, &rest, sizeof(buf)));
+	cl_assert_equal_i(GIT_PKT_HAVE, pkt->type);
+	ppkt = (git_pkt_have_want *) pkt;
+	cl_assert(!git_oid_cmp(&id, &ppkt->id));
+}
+
+void test_network_protocol_negotiation__want(void)
+{
+	const char buf[] = "0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n";
+	const char *rest;
+	git_oid id;
+	git_pkt_have_want *ppkt;
+
+	git_oid_fromstr(&id, "7e47fe2bd8d01d481f44d7af0531bd93d3b21c01");
+
+	cl_git_pass(git_pkt_parse_line(&pkt, buf, &rest, sizeof(buf)));
+	cl_assert_equal_i(GIT_PKT_WANT, pkt->type);
+	ppkt = (git_pkt_have_want *) pkt;
+	cl_assert(!git_oid_cmp(&id, &ppkt->id));
+}
diff --git a/tests/network/server.c b/tests/network/server.c
index 4131955..4fc81cb 100644
--- a/tests/network/server.c
+++ b/tests/network/server.c
@@ -81,3 +81,36 @@
 
 	git_buf_free(&listing);
 }
+
+void test_network_server__want(void)
+{
+	const char buf[] = "0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n";
+	const char *rest;
+
+	cl_git_pass(git_pkt_parse_line(&g_pkt, buf, &rest, sizeof(buf)));
+	cl_git_pass(git_server_new(&g_server, g_repo, 0));
+	cl_git_pass(git_server__negotiation(g_server, g_pkt));
+	cl_assert_equal_i(1, git_array_size(g_server->wants));
+}
+
+void test_network_server__have_no_common(void)
+{
+	const char buf[] = "0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n";
+	const char *rest;
+
+	cl_git_pass(git_pkt_parse_line(&g_pkt, buf, &rest, sizeof(buf)));
+	cl_git_pass(git_server_new(&g_server, g_repo, 0));
+	cl_git_pass(git_server__negotiation(g_server, g_pkt));
+	cl_assert_equal_i(0, git_array_size(g_server->common));
+}
+
+void test_network_server__have_common(void)
+{
+	const char buf[] = "0032have a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n";
+	const char *rest;
+
+	cl_git_pass(git_pkt_parse_line(&g_pkt, buf, &rest, sizeof(buf)));
+	cl_git_pass(git_server_new(&g_server, g_repo, 0));
+	cl_git_pass(git_server__negotiation(g_server, g_pkt));
+	cl_assert_equal_i(1, git_array_size(g_server->common));
+}