From fd01c4044910bd8fa591084fd7084c4ae9a3e491 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 27 Jun 2011 16:17:04 +0300 Subject: [PATCH] gobex: Add basic response handling --- gobex/gobex.c | 143 +++++++++++++++++++++++++++++++++++++++++----- gobex/gobex.h | 10 ++++ unit/test-gobex.c | 107 +++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 14 deletions(-) diff --git a/gobex/gobex.c b/gobex/gobex.c index 485b91d5d..3e06d86f8 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 5e3612fbc..cc56cdf22 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 ff46b8e1c..54257d529 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(); -- 2.47.3