Diff between 0b92685e8be8fe03481c131fe77985e65f3d67e2 and fd01c4044910bd8fa591084fd7084c4ae9a3e491

Changed Files

File Additions Deletions Status
gobex/gobex.c +130 -13 modified
gobex/gobex.h +10 -0 modified
unit/test-gobex.c +106 -1 modified

Full Patch

diff --git a/gobex/gobex.c b/gobex/gobex.c
index 485b91d..3e06d86 100644
--- a/gobex/gobex.c
+++ b/gobex/gobex.c
@@ -67,6 +67,10 @@ struct _GObexPacket {
 
 	size_t hlen;		/* Length of all encoded headers */
 	GSList *headers;
+
+	guint id;
+	GObexResponseFunc rsp_func;
+	gpointer rsp_data;
 };
 
 struct _GObex {
@@ -91,6 +95,15 @@ struct _GObex {
 
 	GObexRequestFunc req_func;
 	gpointer req_func_data;
+
+	struct pending_req *pending_req;
+};
+
+struct pending_req {
+	guint id;
+	guint8 opcode;
+	GObexResponseFunc rsp_func;
+	gpointer rsp_data;
 };
 
 struct connect_data {
@@ -378,6 +391,19 @@ GObexHeader *g_obex_header_uint32(guint8 id, guint32 val)
 	return header;
 }
 
+guint g_obex_packet_set_response_function(GObexPacket *pkt,
+							GObexResponseFunc func,
+							gpointer user_data)
+{
+	static guint next_id = 1;
+
+	pkt->rsp_func = func;
+	pkt->rsp_data = user_data;
+	pkt->id = next_id++;
+
+	return pkt->id;
+}
+
 guint8 g_obex_packet_get_operation(GObexPacket *pkt, gboolean *final)
 {
 	if (final)
@@ -448,7 +474,7 @@ void g_obex_packet_free(GObexPacket *pkt)
 	g_free(pkt);
 }
 
-static ssize_t get_header_offset(guint8 opcode)
+static ssize_t req_header_offset(guint8 opcode)
 {
 	switch (opcode) {
 	case G_OBEX_OP_CONNECT:
@@ -466,6 +492,23 @@ static ssize_t get_header_offset(guint8 opcode)
 	}
 }
 
+static ssize_t rsp_header_offset(guint8 opcode)
+{
+	switch (opcode) {
+	case G_OBEX_OP_CONNECT:
+		return sizeof(struct connect_data);
+	case G_OBEX_OP_SETPATH:
+	case G_OBEX_OP_DISCONNECT:
+	case G_OBEX_OP_PUT:
+	case G_OBEX_OP_GET:
+	case G_OBEX_OP_SESSION:
+	case G_OBEX_OP_ABORT:
+		return 0;
+	default:
+		return -1;
+	}
+}
+
 static gboolean parse_headers(GObexPacket *pkt, const void *data, size_t len,
 						GObexDataPolicy data_policy)
 {
@@ -489,12 +532,12 @@ static gboolean parse_headers(GObexPacket *pkt, const void *data, size_t len,
 }
 
 GObexPacket *g_obex_packet_decode(const void *data, size_t len,
+						size_t header_offset,
 						GObexDataPolicy data_policy)
 {
 	const guint8 *buf = data;
 	guint16 packet_len;
 	guint8 opcode;
-	ssize_t header_offset;
 	GObexPacket *pkt;
 	gboolean final;
 
@@ -511,16 +554,12 @@ GObexPacket *g_obex_packet_decode(const void *data, size_t len,
 	final = (opcode & G_OBEX_FINAL) ? TRUE : FALSE;
 	opcode &= ~G_OBEX_FINAL;
 
-	header_offset = get_header_offset(opcode);
-	if (header_offset < 0)
-		return NULL;
-
 	pkt = g_obex_packet_new(opcode, final);
 
 	if (header_offset == 0)
 		goto headers;
 
-	if (3 + header_offset < (ssize_t) len)
+	if (3 + header_offset < len)
 		goto failed;
 
 	if (data_policy == G_OBEX_DATA_INHERIT)
@@ -580,6 +619,25 @@ static ssize_t g_obex_packet_encode(GObexPacket *pkt, uint8_t *buf, size_t len)
 	return count;
 }
 
+static void pending_req_free(struct pending_req *req)
+{
+	g_free(req);
+}
+
+static struct pending_req *pending_req_new(GObexPacket *pkt)
+{
+	struct pending_req *req;
+
+	req = g_new0(struct pending_req, 1);
+
+	req->id = pkt->id;
+	req->rsp_func = pkt->rsp_func;
+	req->rsp_data = pkt->rsp_data;
+	req->opcode = pkt->opcode;
+
+	return req;
+}
+
 static gboolean write_data(GIOChannel *io, GIOCondition cond,
 							gpointer user_data)
 {
@@ -601,12 +659,20 @@ static gboolean write_data(GIOChannel *io, GIOCondition cond,
 		if (pkt == NULL)
 			goto done;
 
+		/* Can't send a request while there's a pending one */
+		if (obex->pending_req && pkt->id > 0)
+			goto done;
+
 		len = g_obex_packet_encode(pkt, obex->tx_buf, obex->tx_mtu);
+		if (len < 0) {
+			g_obex_packet_free(pkt);
+			goto done;
+		}
 
-		g_obex_packet_free(pkt);
+		if (pkt->id > 0)
+			obex->pending_req = pending_req_new(pkt);
 
-		if (len < 0)
-			goto done;
+		g_obex_packet_free(pkt);
 
 		obex->tx_data = len;
 		obex->tx_sent = 0;
@@ -648,6 +714,24 @@ gboolean g_obex_send(GObex *obex, GObexPacket *pkt)
 	return TRUE;
 }
 
+guint g_obex_send_req(GObex *obex, GObexPacket *req, GObexResponseFunc func,
+							gpointer user_data)
+{
+	guint id;
+
+	id = g_obex_packet_set_response_function(req, func, user_data);
+
+	if (!g_obex_send(obex, req))
+		return 0;
+
+	return id;
+}
+
+gboolean g_obex_cancel_req(GObex *obex, guint req_id)
+{
+	return TRUE;
+}
+
 void g_obex_set_request_function(GObex *obex, GObexRequestFunc func,
 							gpointer user_data)
 {
@@ -655,10 +739,29 @@ void g_obex_set_request_function(GObex *obex, GObexRequestFunc func,
 	obex->req_func_data = user_data;
 }
 
-static gboolean g_obex_handle_packet(GObex *obex, GObexPacket *pkt)
+static void handle_response(GObex *obex, GObexPacket *rsp)
+{
+	struct pending_req *req = obex->pending_req;
+
+	if (req->rsp_func)
+		req->rsp_func(obex, NULL, rsp, req->rsp_data);
+
+	pending_req_free(req);
+	obex->pending_req = NULL;
+}
+
+static void handle_request(GObex *obex, GObexPacket *req)
 {
 	if (obex->req_func)
-		obex->req_func(obex, pkt, obex->req_func_data);
+		obex->req_func(obex, req, obex->req_func_data);
+}
+
+static gboolean g_obex_handle_packet(GObex *obex, GObexPacket *pkt)
+{
+	if (obex->pending_req)
+		handle_response(obex, pkt);
+	else
+		handle_request(obex, pkt);
 
 	return TRUE;
 }
@@ -712,6 +815,7 @@ static gboolean incoming_data(GIOChannel *io, GIOCondition cond,
 {
 	GObex *obex = user_data;
 	GObexPacket *pkt;
+	ssize_t header_offset;
 
 	if (cond & G_IO_NVAL)
 		return FALSE;
@@ -724,7 +828,17 @@ static gboolean incoming_data(GIOChannel *io, GIOCondition cond,
 	if (obex->rx_data < 3 || obex->rx_data < obex->rx_pkt_len)
 		return TRUE;
 
-	pkt = g_obex_packet_decode(obex->rx_buf, obex->rx_data,
+	if (obex->pending_req)
+		header_offset = rsp_header_offset(obex->pending_req->opcode);
+	else {
+		guint8 opcode = obex->rx_buf[0] & ~G_OBEX_FINAL;
+		header_offset = req_header_offset(opcode);
+	}
+
+	if (header_offset < 0)
+		goto failed;
+
+	pkt = g_obex_packet_decode(obex->rx_buf, obex->rx_data, header_offset,
 							G_OBEX_DATA_REF);
 	if (pkt == NULL) {
 		/* FIXME: Handle decoding error */
@@ -800,5 +914,8 @@ void g_obex_unref(GObex *obex)
 	g_free(obex->rx_buf);
 	g_free(obex->tx_buf);
 
+	if (obex->pending_req)
+		pending_req_free(obex->pending_req);
+
 	g_free(obex);
 }
diff --git a/gobex/gobex.h b/gobex/gobex.h
index 5e3612f..cc56cdf 100644
--- a/gobex/gobex.h
+++ b/gobex/gobex.h
@@ -73,6 +73,8 @@ typedef struct _GObexHeader GObexHeader;
 
 typedef void (*GObexRequestFunc) (GObex *obex, GObexPacket *req,
 							gpointer user_data);
+typedef void (*GObexResponseFunc) (GObex *obex, GError *err, GObexPacket *rsp,
+							gpointer user_data);
 
 GObexHeader *g_obex_header_unicode(guint8 id, const char *str);
 GObexHeader *g_obex_header_bytes(guint8 id, void *data, size_t len,
@@ -85,6 +87,9 @@ GObexHeader *g_obex_header_decode(const void *data, size_t len,
 				GObexDataPolicy data_policy, size_t *parsed);
 void g_obex_header_free(GObexHeader *header);
 
+guint g_obex_packet_set_response_function(GObexPacket *pkt,
+							GObexResponseFunc func,
+							gpointer user_data);
 guint8 g_obex_packet_get_operation(GObexPacket *pkt, gboolean *final);
 gboolean g_obex_packet_add_header(GObexPacket *pkt, GObexHeader *header);
 gboolean g_obex_packet_set_data(GObexPacket *pkt, const void *data, size_t len,
@@ -93,10 +98,15 @@ GObexPacket *g_obex_packet_new(guint8 opcode, gboolean final);
 void g_obex_packet_free(GObexPacket *pkt);
 
 GObexPacket *g_obex_packet_decode(const void *data, size_t len,
+						size_t header_offset,
 						GObexDataPolicy data_policy);
 
 gboolean g_obex_send(GObex *obex, GObexPacket *pkt);
 
+guint g_obex_send_req(GObex *obex, GObexPacket *req, GObexResponseFunc func,
+							gpointer user_data);
+gboolean g_obex_cancel_req(GObex *obex, guint req_id);
+
 void g_obex_set_request_function(GObex *obex, GObexRequestFunc func,
 							gpointer user_data);
 
diff --git a/unit/test-gobex.c b/unit/test-gobex.c
index ff46b8e..54257d5 100644
--- a/unit/test-gobex.c
+++ b/unit/test-gobex.c
@@ -34,6 +34,8 @@ static GMainLoop *mainloop = NULL;
 
 static uint8_t pkt_connect_req[] = { G_OBEX_OP_CONNECT | FINAL_BIT,
 					0x00, 0x07, 0x10, 0x00, 0x10, 0x00 };
+static uint8_t pkt_connect_rsp[] = { 0x10 | FINAL_BIT, 0x00, 0x07,
+					0x10, 0x00, 0x10, 0x00 };
 
 static uint8_t hdr_connid[] = { G_OBEX_HDR_ID_CONNECTION, 1, 2, 3, 4 };
 static uint8_t hdr_name_ascii[] = { G_OBEX_HDR_ID_NAME, 0x00, 0x0b,
@@ -173,6 +175,107 @@ static void create_endpoints(GObex **obex, GIOChannel **io, int sock_type)
 	g_io_channel_set_close_on_unref(*io, TRUE);
 }
 
+static void connect_rsp(GObex *obex, GError *err, GObexPacket *rsp,
+							gpointer user_data)
+{
+	guint8 rsp_code;
+	gboolean final;
+	GError **test_err = user_data;
+
+	if (err != NULL) {
+		g_assert(*test_err == NULL);
+		*test_err = g_error_copy(err);
+		goto done;
+	}
+
+	rsp_code = g_obex_packet_get_operation(rsp, &final);
+	if (rsp_code != 0x10) {
+		g_set_error(test_err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+				"Unexpected response 0x%02x", rsp_code);
+		goto done;
+	}
+
+	if (!final) {
+		g_set_error(test_err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+				"Connect response didn't have final bit");
+		goto done;
+	}
+
+done:
+	g_main_loop_quit(mainloop);
+}
+
+static gboolean send_connect_rsp(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	GError **err = user_data;
+	gsize bytes_written, rbytes;
+	char buf[255];
+	GIOStatus status;
+
+	status = g_io_channel_read_chars(io, buf, sizeof(buf), &rbytes, NULL);
+	if (status != G_IO_STATUS_NORMAL) {
+		g_set_error(err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+					"read failed with status %d", status);
+		goto failed;
+	}
+
+	g_io_channel_write_chars(io, (gchar *) pkt_connect_rsp,
+					sizeof(pkt_connect_rsp),
+					&bytes_written, NULL);
+	if (bytes_written != sizeof(pkt_connect_rsp)) {
+		g_set_error(err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+						"Unable to write to socket");
+		goto failed;
+	}
+
+	return FALSE;
+
+failed:
+	g_main_loop_quit(mainloop);
+	return FALSE;
+}
+
+static void test_send_req_stream(void)
+{
+	guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 };
+	GError *gerr = NULL;
+	GIOChannel *io;
+	GIOCondition cond;
+	GObexPacket *req;
+	guint io_id, timer_id;
+	GObex *obex;
+
+	create_endpoints(&obex, &io, SOCK_STREAM);
+
+	req = g_obex_packet_new(G_OBEX_OP_CONNECT, TRUE);
+	g_assert(req != NULL);
+
+	g_obex_packet_set_data(req, connect_data, sizeof(connect_data),
+							G_OBEX_DATA_REF);
+
+	g_obex_send_req(obex, req, connect_rsp, &gerr);
+
+	cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
+	io_id = g_io_add_watch(io, cond, send_connect_rsp, &gerr);
+
+	mainloop = g_main_loop_new(NULL, FALSE);
+
+	timer_id = g_timeout_add_seconds(1, test_timeout, &gerr);
+
+	g_main_loop_run(mainloop);
+
+	g_main_loop_unref(mainloop);
+	mainloop = NULL;
+
+	g_source_remove(timer_id);
+	g_io_channel_unref(io);
+	g_source_remove(io_id);
+	g_obex_unref(obex);
+
+	g_assert_no_error(gerr);
+}
+
 static void test_send_connect_stream(void)
 {
 	guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 };
@@ -353,7 +456,7 @@ static void test_decode_pkt(void)
 	GObexPacket *pkt;
 	uint8_t buf[] = { G_OBEX_OP_PUT, 0x00, 0x03 };
 
-	pkt = g_obex_packet_decode(buf, sizeof(buf), G_OBEX_DATA_REF);
+	pkt = g_obex_packet_decode(buf, sizeof(buf), 0, G_OBEX_DATA_REF);
 	g_assert(pkt != NULL);
 
 	g_obex_packet_free(pkt);
@@ -627,6 +730,8 @@ int main(int argc, char *argv[])
 						test_recv_connect_stream);
 	g_test_add_func("/gobex/test_send_connect_stream",
 						test_send_connect_stream);
+	g_test_add_func("/gobex/test_send_req_stream",
+						test_send_req_stream);
 
 	g_test_run();