Diff between aafd18d9d3537ade447c3cc071e44a845aa28b01 and 8005be3b8a0146f5231f6c34824b344545504931

Changed Files

File Additions Deletions Status
gobex/gobex.c +105 -0 modified
unit/test-gobex.c +112 -5 modified

Full Patch

diff --git a/gobex/gobex.c b/gobex/gobex.c
index 80a8bc4..485b91d 100644
--- a/gobex/gobex.c
+++ b/gobex/gobex.c
@@ -21,6 +21,7 @@
 
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 
 #include "gobex.h"
 
@@ -77,6 +78,12 @@ struct _GObex {
 	size_t rx_data;
 	guint16 rx_pkt_len;
 
+	guint8 *tx_buf;
+	size_t tx_data;
+	size_t tx_sent;
+
+	guint write_source;
+
 	guint16 rx_mtu;
 	guint16 tx_mtu;
 
@@ -536,13 +543,108 @@ failed:
 	return NULL;
 }
 
+static ssize_t g_obex_packet_encode(GObexPacket *pkt, uint8_t *buf, size_t len)
+{
+	size_t count;
+	guint16 pkt_len, u16;
+	GSList *l;
+
+	pkt_len = 3 + pkt->data_len + pkt->hlen;
+
+	if (pkt_len > len)
+		return -ENOBUFS;
+
+	buf[0] = pkt->opcode;
+	if (pkt->final)
+		buf[0] |= G_OBEX_FINAL;
+
+	u16 = g_htons(pkt_len);
+	memcpy(&buf[1], &u16, sizeof(u16));
+
+	if (pkt->data_len > 0) {
+		if (pkt->data_policy == G_OBEX_DATA_REF)
+			memcpy(&buf[3], pkt->data.buf_ref, pkt->data_len);
+		else
+			memcpy(&buf[3], pkt->data.buf, pkt->data_len);
+	}
+
+	count = 3 + pkt->data_len;
+
+	for (l = pkt->headers; l != NULL; l = g_slist_next(l)) {
+		GObexHeader *hdr = l->data;
+		count += g_obex_header_encode(hdr, buf + count, len - count);
+	}
+
+	g_assert_cmpuint(count, ==, pkt_len);
+
+	return count;
+}
+
+static gboolean write_data(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	GObex *obex = user_data;
+	GIOStatus status;
+	gsize bytes_written;
+	gchar *buf;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_HUP | G_IO_ERR))
+		goto done;
+
+	if (obex->tx_data == 0) {
+		GObexPacket *pkt = g_queue_pop_head(obex->req_queue);
+		ssize_t len;
+
+		if (pkt == NULL)
+			goto done;
+
+		len = g_obex_packet_encode(pkt, obex->tx_buf, obex->tx_mtu);
+
+		g_obex_packet_free(pkt);
+
+		if (len < 0)
+			goto done;
+
+		obex->tx_data = len;
+		obex->tx_sent = 0;
+	}
+
+	buf = (gchar *) &obex->tx_buf[obex->tx_sent];
+	status = g_io_channel_write_chars(io, buf, obex->tx_data,
+							&bytes_written, NULL);
+	if (status != G_IO_STATUS_NORMAL)
+		goto done;
+
+	obex->tx_sent += bytes_written;
+	obex->tx_data -= bytes_written;
+
+	if (obex->tx_data > 0 || g_queue_get_length(obex->req_queue) > 0)
+		return TRUE;
+
+done:
+	obex->tx_data = 0;
+	obex->write_source = 0;
+	return FALSE;
+}
+
 gboolean g_obex_send(GObex *obex, GObexPacket *pkt)
 {
+	GIOCondition cond;
+
 	if (obex == NULL || pkt == NULL)
 		return FALSE;
 
 	g_queue_push_tail(obex->req_queue, pkt);
 
+	if (g_queue_get_length(obex->req_queue) > 1)
+		return TRUE;
+
+	cond = G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
+	obex->write_source = g_io_add_watch(obex->io, cond, write_data, obex);
+
 	return TRUE;
 }
 
@@ -658,8 +760,10 @@ GObex *g_obex_new(GIOChannel *io)
 	obex->tx_mtu = G_OBEX_MINIMUM_MTU;
 	obex->req_queue = g_queue_new();
 	obex->rx_buf = g_malloc(obex->rx_mtu);
+	obex->tx_buf = g_malloc(obex->tx_mtu);
 
 	g_io_channel_set_encoding(io, NULL, NULL);
+	g_io_channel_set_buffered(io, FALSE);
 	cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
 	obex->io_source = g_io_add_watch(io, cond, incoming_data, obex);
 
@@ -694,6 +798,7 @@ void g_obex_unref(GObex *obex)
 		g_source_remove(obex->io_source);
 
 	g_free(obex->rx_buf);
+	g_free(obex->tx_buf);
 
 	g_free(obex);
 }
diff --git a/unit/test-gobex.c b/unit/test-gobex.c
index 99170af..368a602 100644
--- a/unit/test-gobex.c
+++ b/unit/test-gobex.c
@@ -76,15 +76,20 @@ static void dump_bytes(uint8_t *buf, size_t buf_len)
 	g_printerr("\n");
 }
 
-static void assert_memequal(void *mem1, size_t len1, void *mem2, size_t len2)
+static void dump_bufs(void *mem1, size_t len1, void *mem2, size_t len2)
 {
-	if (len1 == len2 && memcmp(mem1, mem2, len1) == 0)
-		return;
-
 	g_printerr("\nExpected: ");
 	dump_bytes(mem1, len1);
 	g_printerr("Got:      ");
 	dump_bytes(mem2, len2);
+}
+
+static void assert_memequal(void *mem1, size_t len1, void *mem2, size_t len2)
+{
+	if (len1 == len2 && memcmp(mem1, mem2, len1) == 0)
+		return;
+
+	dump_bufs(mem1, len1, mem2, len2);
 
 	g_assert(0);
 }
@@ -103,6 +108,98 @@ static gboolean test_timeout(gpointer user_data)
 	return FALSE;
 }
 
+static gboolean handle_connect_data(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	GError **err = user_data;
+	GIOStatus status;
+	gsize rbytes;
+	char buf[255];
+
+	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		g_set_error(err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+				"Unexpected condition %d on socket", cond);
+		goto done;
+	}
+
+	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,
+				"Reading data failed with status %d", status);
+		goto done;
+	}
+
+	if (rbytes != sizeof(pkt_connect_req)) {
+		g_set_error(err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+				"Got %zu bytes instead of %zu",
+				rbytes, sizeof(pkt_connect_req));
+		dump_bufs(pkt_connect_req, sizeof(pkt_connect_req),
+								buf, rbytes);
+		goto done;
+	}
+
+	if (memcmp(buf, pkt_connect_req, rbytes) != 0) {
+		g_set_error(err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+				"Mismatch with received data");
+		dump_bufs(pkt_connect_req, sizeof(pkt_connect_req),
+								buf, rbytes);
+		goto done;
+	}
+
+done:
+	g_main_loop_quit(mainloop);
+	return FALSE;
+}
+
+static void test_send_connect_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;
+	int sv[2];
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sv) < 0) {
+		g_printerr("socketpair: %s", strerror(errno));
+		abort();
+	}
+
+	obex = create_gobex(sv[0]);
+	g_assert(obex != NULL);
+
+	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(obex, req);
+
+	io = g_io_channel_unix_new(sv[1]);
+	g_io_channel_set_encoding(io, NULL, NULL);
+	g_io_channel_set_buffered(io, FALSE);
+	cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
+	io_id = g_io_add_watch(io, cond, handle_connect_data, &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 handle_connect_request(GObex *obex, GObexPacket *pkt,
 							gpointer user_data)
 {
@@ -123,6 +220,7 @@ static void handle_connect_request(GObex *obex, GObexPacket *pkt,
 static void test_recv_connect_stream(void)
 {
 	GError *gerr = NULL;
+	guint timer_id;
 	GObex *obex;
 	ssize_t err;
 	int sv[2];
@@ -142,10 +240,17 @@ static void test_recv_connect_stream(void)
 
 	mainloop = g_main_loop_new(NULL, FALSE);
 
-	g_timeout_add_seconds(1, test_timeout, &gerr);
+	timer_id = g_timeout_add_seconds(1, test_timeout, &gerr);
 
 	g_main_loop_run(mainloop);
 
+	g_source_remove(timer_id);
+	g_obex_unref(obex);
+
+	g_main_loop_unref(mainloop);
+	mainloop = NULL;
+
+
 	g_assert_no_error(gerr);
 }
 
@@ -510,6 +615,8 @@ int main(int argc, char *argv[])
 
 	g_test_add_func("/gobex/test_recv_connect_stream",
 						test_recv_connect_stream);
+	g_test_add_func("/gobex/test_send_connect_stream",
+						test_send_connect_stream);
 
 	g_test_run();