struct pending_pkt {
guint id;
+ GObex *obex;
GObexPacket *pkt;
guint timeout;
guint timeout_id;
GObexResponseFunc rsp_func;
gpointer rsp_data;
+ gboolean cancelled;
};
struct connect_data {
static void pending_pkt_free(struct pending_pkt *p)
{
+ if (p->obex != NULL)
+ g_obex_unref(p->obex);
g_obex_packet_free(p->pkt);
g_free(p);
}
return FALSE;
}
- g_queue_push_tail(obex->tx_queue, p);
-
- if (g_queue_get_length(obex->tx_queue) > 1)
- return TRUE;
-
- if (p->id > 0 && obex->pending_req != NULL)
- return TRUE;
+ if (g_obex_packet_get_operation(p->pkt, NULL) == G_OBEX_OP_ABORT)
+ g_queue_push_head(obex->tx_queue, p);
+ else
+ g_queue_push_tail(obex->tx_queue, p);
- enable_tx(obex);
+ if (obex->pending_req == NULL || p->id == 0)
+ enable_tx(obex);
return TRUE;
}
return p->id;
}
-gboolean g_obex_cancel_req(GObex *obex, guint req_id)
+static gint pending_pkt_cmp(gconstpointer a, gconstpointer b)
{
+ const struct pending_pkt *p = a;
+ guint id = GPOINTER_TO_INT(b);
+
+ return (p->id - id);
+}
+
+static gboolean pending_req_abort(GObex *obex, GError **err)
+{
+ GObexPacket *pkt;
+
+ if (obex->pending_req->cancelled)
+ return TRUE;
+
+ obex->pending_req->cancelled = TRUE;
+
+ pkt = g_obex_packet_new(G_OBEX_OP_ABORT, TRUE);
+
+ return g_obex_send(obex, pkt, err);
+}
+
+static gboolean cancel_complete(gpointer user_data)
+{
+ struct pending_pkt *p = user_data;
+ GObex *obex = p->obex;
+ GError *err;
+
+ g_assert(p->rsp_func != NULL);
+
+ err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
+ "The request was cancelled");
+ p->rsp_func(obex, err, NULL, p->rsp_data);
+
+ g_error_free(err);
+
+ pending_pkt_free(p);
+
+ return FALSE;
+}
+
+gboolean g_obex_cancel_req(GObex *obex, guint req_id, gboolean remove_callback)
+{
+ GList *match;
+ struct pending_pkt *p;
+
+ if (obex->pending_req && obex->pending_req->id == req_id) {
+ if (!pending_req_abort(obex, NULL)) {
+ p = obex->pending_req;
+ obex->pending_req = NULL;
+ goto immediate_completion;
+ }
+
+ return TRUE;
+ }
+
+ match = g_queue_find_custom(obex->tx_queue, GINT_TO_POINTER(req_id),
+ pending_pkt_cmp);
+ if (match == NULL)
+ return FALSE;
+
+ p = match->data;
+
+ g_queue_delete_link(obex->tx_queue, match);
+
+immediate_completion:
+ p->cancelled = TRUE;
+ p->obex = g_obex_ref(obex);
+
+ if (remove_callback || p->rsp_func == NULL)
+ pending_pkt_free(p);
+ else
+ g_idle_add(cancel_complete, p);
+
return TRUE;
}
parse_connect_data(obex, rsp);
}
+ if (p->cancelled)
+ err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
+ "The operation was cancelled");
+
if (p->rsp_func)
p->rsp_func(obex, err, rsp, p->rsp_data);
+ if (p->cancelled)
+ g_error_free(err);
+
pending_pkt_free(p);
obex->pending_req = NULL;
0x10, 0x00, 0x10, 0x00 };
static uint8_t pkt_nval_connect_rsp[] = { 0x10 | FINAL_BIT, 0x00, 0x05,
0x10, 0x00, };
+static uint8_t pkt_abort_rsp[] = { 0x90, 0x00, 0x03 };
static gboolean test_timeout(gpointer user_data)
{
*obex = create_gobex(sv[0], transport_type, TRUE);
g_assert(*obex != NULL);
+ if (io == NULL) {
+ close(sv[1]);
+ return;
+ }
+
*io = g_io_channel_unix_new(sv[1]);
g_assert(*io != NULL);
send_connect(timeout_rsp, send_nothing, 1);
}
+struct req_info {
+ GObex *obex;
+ guint id;
+ GError *err;
+};
+
+static void req_done(GObex *obex, GError *err, GObexPacket *rsp,
+ gpointer user_data)
+{
+ struct req_info *r = user_data;
+
+ if (!g_error_matches(err, G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED))
+ g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+ "Did not get expected cancelled error");
+
+ g_main_loop_quit(mainloop);
+}
+
+static void test_cancel_req_immediate(void)
+{
+ GObexPacket *req;
+ struct req_info r;
+ gboolean ret;
+
+ create_endpoints(&r.obex, NULL, SOCK_STREAM);
+
+ r.err = NULL;
+
+ req = g_obex_packet_new(G_OBEX_OP_PUT, TRUE);
+ r.id = g_obex_send_req(r.obex, req, -1, req_done, &r, &r.err);
+ g_assert_no_error(r.err);
+ g_assert(r.id != 0);
+
+ ret = g_obex_cancel_req(r.obex, r.id, FALSE);
+ g_assert(ret == TRUE);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+
+ g_main_loop_run(mainloop);
+
+ g_assert_no_error(r.err);
+
+ g_obex_unref(r.obex);
+ g_main_loop_unref(mainloop);
+}
+
+static gboolean cancel_server(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
+{
+ struct req_info *r = user_data;
+ GIOStatus status;
+ gsize bytes_written, rbytes;
+ char buf[255];
+
+ 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 failed;
+ }
+
+ if (rbytes < 3) {
+ g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+ "Not enough data from socket");
+ goto failed;
+ }
+
+ if ((uint8_t) buf[0] == (G_OBEX_OP_PUT | FINAL_BIT)) {
+ if (!g_obex_cancel_req(r->obex, r->id, FALSE)) {
+ g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+ "Cancelling request failed");
+ goto failed;
+ }
+ return TRUE;
+ }
+
+ if ((uint8_t) buf[0] != (G_OBEX_OP_ABORT | FINAL_BIT)) {
+ g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+ "Neither Put nor Abort packet received");
+ goto failed;
+ }
+
+ g_io_channel_write_chars(io, (gchar *) pkt_abort_rsp,
+ sizeof(pkt_abort_rsp), &bytes_written, NULL);
+ if (bytes_written != sizeof(pkt_abort_rsp)) {
+ g_set_error(&r->err, TEST_ERROR, TEST_ERROR_UNEXPECTED,
+ "Unable to write to socket");
+ goto failed;
+ }
+
+ return TRUE;
+
+failed:
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+static void test_cancel_req_delay(void)
+{
+ GIOChannel *io;
+ guint io_id, timer_id;
+ struct req_info r;
+ GObexPacket *req;
+ GIOCondition cond;
+
+ create_endpoints(&r.obex, &io, SOCK_STREAM);
+
+ r.err = NULL;
+
+ req = g_obex_packet_new(G_OBEX_OP_PUT, TRUE);
+ r.id = g_obex_send_req(r.obex, req, -1, req_done, &r, &r.err);
+ g_assert_no_error(r.err);
+ g_assert(r.id != 0);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+
+ cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
+ io_id = g_io_add_watch(io, cond, cancel_server, &r);
+
+ timer_id = g_timeout_add_seconds(2, test_timeout, &r.err);
+
+ g_main_loop_run(mainloop);
+
+ g_assert_no_error(r.err);
+
+ g_source_remove(timer_id);
+ g_io_channel_unref(io);
+ g_source_remove(io_id);
+ g_obex_unref(r.obex);
+ g_main_loop_unref(mainloop);
+}
+
static void test_send_connect_stream(void)
{
guint8 connect_data[] = { 0x10, 0x00, 0x10, 0x00 };
g_test_add_func("/gobex/test_send_connect_req_timeout_stream",
test_send_connect_req_timeout_stream);
+ g_test_add_func("/gobex/test_cancel_req_immediate",
+ test_cancel_req_immediate);
+ g_test_add_func("/gobex/test_cancel_req_delay",
+ test_cancel_req_delay);
+
g_test_run();
return 0;