From ad811dd51eaee735446d7b53a37cdc1c3d029717 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Thu, 30 Jun 2011 22:57:51 +0300 Subject: [PATCH] gobex: Add support for on-demand headers --- gobex/gobex-header.c | 38 ++++++++ gobex/gobex-header.h | 6 ++ gobex/gobex-packet.c | 14 ++- unit/test-gobex-header.c | 29 ++++++ unit/test-gobex-packet.c | 40 ++++++++ unit/test-gobex.c | 206 ++++++++++++++++++++++++++++----------- 6 files changed, 266 insertions(+), 67 deletions(-) diff --git a/gobex/gobex-header.c b/gobex/gobex-header.c index ff24589f4..0ef5e240f 100644 --- a/gobex/gobex-header.c +++ b/gobex/gobex-header.c @@ -43,6 +43,9 @@ struct _GObexHeader { guint8 u8; guint32 u32; } v; + + GObexHeaderDataFunc get_data; + gpointer get_data_data; }; static glong utf8_to_utf16(gunichar2 **utf16, const char *utf8) { @@ -80,6 +83,20 @@ static const guint8 *get_bytes(void *to, const guint8 *from, gsize count) return (from + count); } +static gsize get_data(GObexHeader *header, guint8 *buf, gsize len) +{ + guint16 u16; + + header->vlen = header->get_data(header, buf + 2, len - 2, + header->get_data_data); + + header->hlen = header->vlen + 3; + u16 = g_htons(header->hlen); + memcpy(buf, &u16, sizeof(u16)); + + return header->hlen; +} + gsize g_obex_header_encode(GObexHeader *header, void *buf, gsize buf_len) { guint8 *ptr = buf; @@ -105,6 +122,9 @@ gsize g_obex_header_encode(GObexHeader *header, void *buf, gsize buf_len) g_free(utf16); break; case G_OBEX_HDR_TYPE_BYTES: + if (header->get_data) + return get_data(header, ptr, buf_len - 1); + u16 = g_htons(header->hlen); ptr = put_bytes(ptr, &u16, sizeof(u16)); if (header->extdata) @@ -362,6 +382,24 @@ GObexHeader *g_obex_header_new_bytes(guint8 id, void *data, gsize len, return header; } +GObexHeader *g_obex_header_new_on_demand(guint8 id, GObexHeaderDataFunc func, + gpointer user_data) +{ + GObexHeader *header; + + if (G_OBEX_HDR_TYPE(id) != G_OBEX_HDR_TYPE_BYTES) + return NULL; + + header = g_new0(GObexHeader, 1); + + header->id = id; + header->hlen = 3; + header->get_data = func; + header->get_data_data = user_data; + + return header; +} + GObexHeader *g_obex_header_new_uint8(guint8 id, guint8 val) { GObexHeader *header; diff --git a/gobex/gobex-header.h b/gobex/gobex-header.h index 1df7f148d..e7f16e5e0 100644 --- a/gobex/gobex-header.h +++ b/gobex/gobex-header.h @@ -55,6 +55,9 @@ typedef struct _GObexHeader GObexHeader; +typedef guint16 (*GObexHeaderDataFunc) (GObexHeader *header, void *buf, + gsize len, gpointer user_data); + gboolean g_obex_header_get_unicode(GObexHeader *header, const char **str); gboolean g_obex_header_get_bytes(GObexHeader *header, const guint8 **val, gsize *len); @@ -64,6 +67,9 @@ gboolean g_obex_header_get_uint32(GObexHeader *header, guint32 *val); GObexHeader *g_obex_header_new_unicode(guint8 id, const char *str); GObexHeader *g_obex_header_new_bytes(guint8 id, void *data, gsize len, GObexDataPolicy data_policy); +GObexHeader *g_obex_header_new_on_demand(guint8 id, + GObexHeaderDataFunc func, + gpointer user_data); GObexHeader *g_obex_header_new_uint8(guint8 id, guint8 val); GObexHeader *g_obex_header_new_uint32(guint8 id, guint32 val); diff --git a/gobex/gobex-packet.c b/gobex/gobex-packet.c index a74b129b1..89f14e4e3 100644 --- a/gobex/gobex-packet.c +++ b/gobex/gobex-packet.c @@ -237,21 +237,16 @@ failed: gssize g_obex_packet_encode(GObexPacket *pkt, guint8 *buf, gsize len) { gsize count; - guint16 pkt_len, u16; + guint16 u16; GSList *l; - pkt_len = 3 + pkt->data_len + pkt->hlen; - - if (pkt_len > len) + if (3 + pkt->data_len + pkt->hlen > len) return -ENOBUFS; buf[0] = pkt->opcode; if (pkt->final) buf[0] |= FINAL_BIT; - 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); @@ -263,10 +258,13 @@ gssize g_obex_packet_encode(GObexPacket *pkt, guint8 *buf, gsize len) for (l = pkt->headers; l != NULL; l = g_slist_next(l)) { GObexHeader *hdr = l->data; + if (count >= len) + return -ENOBUFS; count += g_obex_header_encode(hdr, buf + count, len - count); } - g_assert_cmpuint(count, ==, pkt_len); + u16 = g_htons(count); + memcpy(&buf[1], &u16, sizeof(u16)); return count; } diff --git a/unit/test-gobex-header.c b/unit/test-gobex-header.c index d2dd99a40..61536e268 100644 --- a/unit/test-gobex-header.c +++ b/unit/test-gobex-header.c @@ -20,6 +20,7 @@ */ #include +#include #include @@ -129,6 +130,32 @@ static void test_header_uint32(void) g_obex_header_free(header); } +static guint16 get_body_data(GObexHeader *header, void *buf, gsize len, + gpointer user_data) +{ + uint8_t body_data[] = { 1, 2, 3, 4 }; + + memcpy(buf, body_data, sizeof(body_data)); + + return sizeof(body_data); +} + +static void test_header_on_demand(void) +{ + GObexHeader *header; + uint8_t buf[1024]; + size_t len; + + header = g_obex_header_new_on_demand(G_OBEX_HDR_ID_BODY, + get_body_data, NULL); + + len = g_obex_header_encode(header, buf, sizeof(buf)); + + assert_memequal(hdr_body, sizeof(hdr_body), buf, len); + + g_obex_header_free(header); +} + static GObexHeader *parse_and_encode(uint8_t *buf, size_t buf_len) { GObexHeader *header; @@ -457,6 +484,8 @@ int main(int argc, char *argv[]) g_test_add_func("/gobex/test_header_uint8", test_header_uint8); g_test_add_func("/gobex/test_header_uint32", test_header_uint32); + g_test_add_func("/gobex/test_header_on_demand", test_header_on_demand); + g_test_run(); return 0; diff --git a/unit/test-gobex-packet.c b/unit/test-gobex-packet.c index afc8adf45..464382a2c 100644 --- a/unit/test-gobex-packet.c +++ b/unit/test-gobex-packet.c @@ -20,6 +20,7 @@ */ #include +#include #include @@ -31,6 +32,9 @@ static uint8_t pkt_connect[] = { G_OBEX_OP_CONNECT, 0x00, 0x0c, 0x00, 0x05, 0xab, 0xcd }; static uint8_t pkt_put_action[] = { G_OBEX_OP_PUT, 0x00, 0x05, G_OBEX_HDR_ID_ACTION, 0xab }; +static uint8_t pkt_put_body[] = { G_OBEX_OP_PUT, 0x00, 0x0a, + G_OBEX_HDR_ID_BODY, 0x00, 0x07, + 1, 2, 3, 4 }; static uint8_t pkt_put[] = { G_OBEX_OP_PUT, 0x00, 0x03 }; static uint8_t pkt_nval_len[] = { G_OBEX_OP_PUT, 0xab, 0xcd, 0x12 }; @@ -140,6 +144,40 @@ static void test_decode_encode(void) g_obex_packet_free(pkt); } +static guint16 get_body_data(GObexHeader *header, void *buf, gsize len, + gpointer user_data) +{ + uint8_t data[] = { 1, 2, 3, 4 }; + + memcpy(buf, data, sizeof(data)); + + return sizeof(data); +} + +static void test_encode_on_demand(void) +{ + GObexPacket *pkt; + GObexHeader *hdr; + uint8_t buf[255]; + gssize len; + + pkt = g_obex_packet_new(G_OBEX_OP_PUT, FALSE); + + hdr = g_obex_header_new_on_demand(G_OBEX_HDR_ID_BODY, + get_body_data, NULL); + g_obex_packet_add_header(pkt, hdr); + + len = g_obex_packet_encode(pkt, buf, sizeof(buf)); + if (len < 0) { + g_printerr("Encoding failed: %s\n", g_strerror(-len)); + g_assert_not_reached(); + } + + assert_memequal(pkt_put_body, sizeof(pkt_put_body), buf, len); + + g_obex_packet_free(pkt); +} + int main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); @@ -155,6 +193,8 @@ int main(int argc, char *argv[]) g_test_add_func("/gobex/test_encode_pkt", test_decode_encode); + g_test_add_func("/gobex/test_encode_on_demand", test_encode_on_demand); + g_test_run(); return 0; diff --git a/unit/test-gobex.c b/unit/test-gobex.c index 13a06687d..2d8630ece 100644 --- a/unit/test-gobex.c +++ b/unit/test-gobex.c @@ -43,6 +43,9 @@ static uint8_t pkt_nval_connect_rsp[] = { 0x10 | FINAL_BIT, 0x00, 0x05, 0x10, 0x00, }; static uint8_t pkt_abort_rsp[] = { 0x90, 0x00, 0x03 }; static uint8_t pkt_nval_short_rsp[] = { 0x10 | FINAL_BIT, 0x12 }; +static uint8_t pkt_put_body[] = { G_OBEX_OP_PUT, 0x00, 0x0a, + G_OBEX_HDR_ID_BODY, 0x00, 0x07, + 1, 2, 3, 4 }; static gboolean test_timeout(gpointer user_data) { @@ -58,49 +61,6 @@ 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 GObex *create_gobex(int fd, GObexTransportType transport_type, gboolean close_on_unref) { @@ -272,25 +232,18 @@ static gboolean send_nothing(GIOChannel *io, GIOCondition cond, return FALSE; } -static void send_connect(GObexResponseFunc rsp_func, GIOFunc send_rsp_func, - gint req_timeout, int transport_type) +static void send_req(GObexPacket *req, GObexResponseFunc rsp_func, + GIOFunc send_rsp_func, gint req_timeout, + int transport_type) { - guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 }; GError *gerr = NULL; GIOChannel *io; GIOCondition cond; - GObexPacket *req; guint io_id, timer_id, test_time; GObex *obex; create_endpoints(&obex, &io, transport_type); - 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, req_timeout, rsp_func, &gerr, &gerr); g_assert_no_error(gerr); @@ -319,6 +272,21 @@ static void send_connect(GObexResponseFunc rsp_func, GIOFunc send_rsp_func, g_assert_no_error(gerr); } +static void send_connect(GObexResponseFunc rsp_func, GIOFunc send_rsp_func, + gint req_timeout, int transport_type) +{ + GObexPacket *req; + guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 }; + + 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); + + send_req(req, rsp_func, send_rsp_func, req_timeout, transport_type); +} + static void test_send_connect_req_stream(void) { send_connect(connect_rsp, send_connect_rsp, -1, SOCK_STREAM); @@ -498,10 +466,56 @@ static void test_cancel_req_delay_pkt(void) test_cancel_req_delay(SOCK_SEQPACKET); } +struct rcv_buf_info { + GError *err; + const guint8 *buf; + gsize len; +}; + +static gboolean rcv_data(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + struct rcv_buf_info *r = user_data; + GIOStatus status; + gsize rbytes; + char buf[255]; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_set_error(&r->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(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED, + "Reading data failed with status %d", status); + goto done; + } + + if (rbytes != r->len) { + g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED, + "Got %zu bytes instead of %zu", + rbytes, sizeof(pkt_connect_req)); + dump_bufs(r->buf, r->len, buf, rbytes); + goto done; + } + + if (memcmp(buf, r->buf, rbytes) != 0) { + g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED, + "Mismatch with received data"); + dump_bufs(r->buf, r->len, buf, rbytes); + goto done; + } + +done: + g_main_loop_quit(mainloop); + return FALSE; +} + static void test_send_connect(int transport_type) { guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 }; - GError *gerr = NULL; + struct rcv_buf_info r; GIOChannel *io; GIOCondition cond; GObexPacket *req; @@ -510,20 +524,24 @@ static void test_send_connect(int transport_type) create_endpoints(&obex, &io, transport_type); + r.err = NULL; + r.buf = pkt_connect_req; + r.len = sizeof(pkt_connect_req); + 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, &gerr); - g_assert_no_error(gerr); + g_obex_send(obex, req, &r.err); + g_assert_no_error(r.err); 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); + io_id = g_io_add_watch(io, cond, rcv_data, &r); mainloop = g_main_loop_new(NULL, FALSE); - timer_id = g_timeout_add_seconds(1, test_timeout, &gerr); + timer_id = g_timeout_add_seconds(1, test_timeout, &r.err); g_main_loop_run(mainloop); @@ -535,7 +553,7 @@ static void test_send_connect(int transport_type) g_source_remove(io_id); g_obex_unref(obex); - g_assert_no_error(gerr); + g_assert_no_error(r.err); } static void test_send_connect_stream(void) @@ -548,6 +566,71 @@ static void test_send_connect_pkt(void) test_send_connect(SOCK_SEQPACKET); } +static guint16 get_body_data(GObexHeader *header, void *buf, gsize len, + gpointer user_data) +{ + uint8_t data[] = { 1, 2, 3, 4 }; + + memcpy(buf, data, sizeof(data)); + + return sizeof(data); +} + +static void test_send_on_demand(int transport_type) +{ + struct rcv_buf_info r; + GIOChannel *io; + GIOCondition cond; + GObexPacket *req; + GObexHeader *hdr; + guint io_id, timer_id; + GObex *obex; + + create_endpoints(&obex, &io, transport_type); + + r.err = NULL; + r.buf = pkt_put_body; + r.len = sizeof(pkt_put_body); + + req = g_obex_packet_new(G_OBEX_OP_PUT, FALSE); + + hdr = g_obex_header_new_on_demand(G_OBEX_HDR_ID_BODY, + get_body_data, NULL); + g_obex_packet_add_header(req, hdr); + + g_obex_send(obex, req, &r.err); + g_assert_no_error(r.err); + + cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + io_id = g_io_add_watch(io, cond, rcv_data, &r); + + mainloop = g_main_loop_new(NULL, FALSE); + + timer_id = g_timeout_add_seconds(1, test_timeout, &r.err); + + 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(r.err); +} + +static void test_send_on_demand_stream(void) +{ + test_send_on_demand(SOCK_STREAM); +} + +static void test_send_on_demand_pkt(void) +{ + test_send_on_demand(SOCK_SEQPACKET); +} + static void handle_connect_event(GObex *obex, GError *err, GObexPacket *pkt, gpointer user_data) { @@ -703,6 +786,10 @@ int main(int argc, char *argv[]) test_send_connect_stream); g_test_add_func("/gobex/test_send_connect_pkt", test_send_connect_pkt); + g_test_add_func("/gobex/test_send_on_demand_stream", + test_send_on_demand_stream); + g_test_add_func("/gobex/test_send_on_demand_pkt", + test_send_on_demand_pkt); g_test_add_func("/gobex/test_send_connect_req_stream", test_send_connect_req_stream); g_test_add_func("/gobex/test_send_connect_req_pkt", @@ -718,6 +805,7 @@ int main(int argc, char *argv[]) g_test_add_func("/gobex/test_send_connect_req_timeout_pkt", test_send_connect_req_timeout_pkt); + g_test_add_func("/gobex/test_cancel_req_immediate", test_cancel_req_immediate); g_test_add_func("/gobex/test_cancel_req_delay_stream", -- 2.47.3