| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * OBEX library with GLib integration |
| * |
| * Copyright (C) 2011 Intel Corporation. All rights reserved. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "gobex/gobex.h" |
| #include "gobex/gobex-debug.h" |
| |
| #define FIRST_PACKET_TIMEOUT 60 |
| |
| static GSList *transfers = NULL; |
| |
| static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data); |
| |
| struct transfer { |
| guint id; |
| guint8 opcode; |
| |
| GObex *obex; |
| |
| guint req_id; |
| |
| guint put_id; |
| guint get_id; |
| guint abort_id; |
| |
| GObexDataProducer data_producer; |
| GObexDataConsumer data_consumer; |
| GObexFunc complete_func; |
| |
| gpointer user_data; |
| }; |
| |
| static void transfer_free(struct transfer *transfer) |
| { |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| transfers = g_slist_remove(transfers, transfer); |
| |
| if (transfer->req_id > 0) |
| g_obex_cancel_req(transfer->obex, transfer->req_id, TRUE); |
| |
| if (transfer->put_id > 0) |
| g_obex_remove_request_function(transfer->obex, |
| transfer->put_id); |
| |
| if (transfer->get_id > 0) |
| g_obex_remove_request_function(transfer->obex, |
| transfer->get_id); |
| |
| if (transfer->abort_id > 0) |
| g_obex_remove_request_function(transfer->obex, |
| transfer->abort_id); |
| |
| g_obex_unref(transfer->obex); |
| g_free(transfer); |
| } |
| |
| static struct transfer *find_transfer(guint id) |
| { |
| GSList *l; |
| |
| for (l = transfers; l != NULL; l = g_slist_next(l)) { |
| struct transfer *t = l->data; |
| if (t->id == id) |
| return t; |
| } |
| |
| return NULL; |
| } |
| |
| static void transfer_complete(struct transfer *transfer, GError *err) |
| { |
| guint id = transfer->id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id); |
| |
| if (err) { |
| /* No further tx must be performed */ |
| g_obex_drop_tx_queue(transfer->obex); |
| } |
| |
| transfer->complete_func(transfer->obex, err, transfer->user_data); |
| /* Check if the complete_func removed the transfer */ |
| if (find_transfer(id) == NULL) |
| return; |
| |
| transfer_free(transfer); |
| } |
| |
| static void transfer_abort_response(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| transfer->req_id = 0; |
| |
| /* Intentionally override error */ |
| err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, |
| "Operation was aborted"); |
| g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| |
| |
| static gssize put_get_data(void *buf, gsize len, gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GObexPacket *req; |
| GError *err = NULL; |
| gssize ret; |
| |
| ret = transfer->data_producer(buf, len, transfer->user_data); |
| if (ret == 0 || ret == -EAGAIN) |
| return ret; |
| |
| if (ret > 0) { |
| /* Check if SRM is active */ |
| if (!g_obex_srm_active(transfer->obex)) |
| return ret; |
| |
| /* Generate next packet */ |
| req = g_obex_packet_new(transfer->opcode, FALSE, |
| G_OBEX_HDR_INVALID); |
| g_obex_packet_add_body(req, put_get_data, transfer); |
| transfer->req_id = g_obex_send_req(transfer->obex, req, -1, |
| transfer_response, transfer, |
| &err); |
| goto done; |
| } |
| |
| transfer->req_id = g_obex_abort(transfer->obex, transfer_abort_response, |
| transfer, &err); |
| done: |
| if (err != NULL) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| |
| return ret; |
| } |
| |
| static gboolean handle_get_body(struct transfer *transfer, GObexPacket *rsp, |
| GError **err) |
| { |
| GObexHeader *body = g_obex_packet_get_body(rsp); |
| gboolean ret; |
| const guint8 *buf; |
| gsize len; |
| |
| if (body == NULL) |
| return TRUE; |
| |
| g_obex_header_get_bytes(body, &buf, &len); |
| if (len == 0) |
| return TRUE; |
| |
| ret = transfer->data_consumer(buf, len, transfer->user_data); |
| if (ret == FALSE) |
| g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, |
| "Data consumer callback failed"); |
| |
| return ret; |
| } |
| |
| static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GObexPacket *req; |
| gboolean rspcode, final; |
| guint id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| id = transfer->req_id; |
| transfer->req_id = 0; |
| |
| if (err != NULL) { |
| transfer_complete(transfer, err); |
| return; |
| } |
| |
| rspcode = g_obex_packet_get_operation(rsp, &final); |
| if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) { |
| err = g_error_new(G_OBEX_ERROR, rspcode, "%s", |
| g_obex_strerror(rspcode)); |
| goto failed; |
| } |
| |
| if (transfer->opcode == G_OBEX_OP_GET) { |
| handle_get_body(transfer, rsp, &err); |
| if (err != NULL) |
| goto failed; |
| } |
| |
| if (rspcode == G_OBEX_RSP_SUCCESS) { |
| transfer_complete(transfer, NULL); |
| return; |
| } |
| |
| if (transfer->opcode == G_OBEX_OP_PUT) { |
| req = g_obex_packet_new(transfer->opcode, FALSE, |
| G_OBEX_HDR_INVALID); |
| g_obex_packet_add_body(req, put_get_data, transfer); |
| } else if (!g_obex_srm_active(transfer->obex)) { |
| req = g_obex_packet_new(transfer->opcode, TRUE, |
| G_OBEX_HDR_INVALID); |
| } else { |
| /* Keep id since request still outstanting */ |
| transfer->req_id = id; |
| return; |
| } |
| |
| transfer->req_id = g_obex_send_req(obex, req, -1, transfer_response, |
| transfer, &err); |
| failed: |
| if (err != NULL) { |
| g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| } |
| |
| static struct transfer *transfer_new(GObex *obex, guint8 opcode, |
| GObexFunc complete_func, gpointer user_data) |
| { |
| static guint next_id = 1; |
| struct transfer *transfer; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p opcode %u", obex, opcode); |
| |
| transfer = g_new0(struct transfer, 1); |
| |
| transfer->id = next_id++; |
| transfer->opcode = opcode; |
| transfer->obex = g_obex_ref(obex); |
| transfer->complete_func = complete_func; |
| transfer->user_data = user_data; |
| |
| transfers = g_slist_append(transfers, transfer); |
| |
| return transfer; |
| } |
| |
| guint g_obex_put_req_pkt(GObex *obex, GObexPacket *req, |
| GObexDataProducer data_func, GObexFunc complete_func, |
| gpointer user_data, GError **err) |
| { |
| struct transfer *transfer; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_PUT) |
| return 0; |
| |
| transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data); |
| transfer->data_producer = data_func; |
| |
| g_obex_packet_add_body(req, put_get_data, transfer); |
| |
| transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, |
| transfer_response, transfer, err); |
| if (transfer->req_id == 0) { |
| transfer_free(transfer); |
| return 0; |
| } |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| return transfer->id; |
| } |
| |
| guint g_obex_put_req(GObex *obex, GObexDataProducer data_func, |
| GObexFunc complete_func, gpointer user_data, |
| GError **err, guint first_hdr_id, ...) |
| { |
| GObexPacket *req; |
| va_list args; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| va_start(args, first_hdr_id); |
| req = g_obex_packet_new_valist(G_OBEX_OP_PUT, FALSE, |
| first_hdr_id, args); |
| va_end(args); |
| |
| return g_obex_put_req_pkt(obex, req, data_func, complete_func, |
| user_data, err); |
| } |
| |
| static void transfer_abort_req(GObex *obex, GObexPacket *req, gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GObexPacket *rsp; |
| GError *err; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, |
| "Request was aborted"); |
| rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID); |
| g_obex_send(obex, rsp, NULL); |
| |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| |
| static guint8 put_get_bytes(struct transfer *transfer, GObexPacket *req) |
| { |
| GObexHeader *body; |
| gboolean final; |
| guint8 rsp; |
| const guint8 *buf; |
| gsize len; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| g_obex_packet_get_operation(req, &final); |
| if (final) |
| rsp = G_OBEX_RSP_SUCCESS; |
| else |
| rsp = G_OBEX_RSP_CONTINUE; |
| |
| body = g_obex_packet_get_body(req); |
| if (body == NULL) |
| return rsp; |
| |
| g_obex_header_get_bytes(body, &buf, &len); |
| if (len == 0) |
| return rsp; |
| |
| if (transfer->data_consumer(buf, len, transfer->user_data) == FALSE) |
| rsp = G_OBEX_RSP_FORBIDDEN; |
| |
| return rsp; |
| } |
| |
| static void transfer_put_req_first(struct transfer *transfer, GObexPacket *req, |
| guint8 first_hdr_id, va_list args) |
| { |
| GError *err = NULL; |
| GObexPacket *rsp; |
| guint8 rspcode; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| rspcode = put_get_bytes(transfer, req); |
| |
| rsp = g_obex_packet_new_valist(rspcode, TRUE, first_hdr_id, args); |
| |
| if (!g_obex_send(transfer->obex, rsp, &err)) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| return; |
| } |
| |
| if (rspcode != G_OBEX_RSP_CONTINUE) |
| transfer_complete(transfer, NULL); |
| } |
| |
| static void transfer_put_req(GObex *obex, GObexPacket *req, gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GError *err = NULL; |
| GObexPacket *rsp; |
| guint8 rspcode; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| rspcode = put_get_bytes(transfer, req); |
| |
| /* Don't send continue while SRM is active */ |
| if (g_obex_srm_active(transfer->obex) && |
| rspcode == G_OBEX_RSP_CONTINUE) |
| goto done; |
| |
| rsp = g_obex_packet_new(rspcode, TRUE, G_OBEX_HDR_INVALID); |
| |
| if (!g_obex_send(obex, rsp, &err)) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| return; |
| } |
| |
| done: |
| if (rspcode != G_OBEX_RSP_CONTINUE) |
| transfer_complete(transfer, NULL); |
| } |
| |
| guint g_obex_put_rsp(GObex *obex, GObexPacket *req, |
| GObexDataConsumer data_func, GObexFunc complete_func, |
| gpointer user_data, GError **err, |
| guint first_hdr_id, ...) |
| { |
| struct transfer *transfer; |
| va_list args; |
| guint id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data); |
| transfer->data_consumer = data_func; |
| |
| va_start(args, first_hdr_id); |
| transfer_put_req_first(transfer, req, first_hdr_id, args); |
| va_end(args); |
| if (!g_slist_find(transfers, transfer)) |
| return 0; |
| |
| id = g_obex_add_request_function(obex, G_OBEX_OP_PUT, transfer_put_req, |
| transfer); |
| transfer->put_id = id; |
| |
| id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT, |
| transfer_abort_req, transfer); |
| transfer->abort_id = id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| return transfer->id; |
| } |
| |
| guint g_obex_get_req_pkt(GObex *obex, GObexPacket *req, |
| GObexDataConsumer data_func, GObexFunc complete_func, |
| gpointer user_data, GError **err) |
| { |
| struct transfer *transfer; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_GET) |
| return 0; |
| |
| transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); |
| transfer->data_consumer = data_func; |
| transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, |
| transfer_response, transfer, err); |
| if (transfer->req_id == 0) { |
| transfer_free(transfer); |
| return 0; |
| } |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| return transfer->id; |
| } |
| |
| guint g_obex_get_req(GObex *obex, GObexDataConsumer data_func, |
| GObexFunc complete_func, gpointer user_data, |
| GError **err, guint first_hdr_id, ...) |
| { |
| struct transfer *transfer; |
| GObexPacket *req; |
| va_list args; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); |
| transfer->data_consumer = data_func; |
| |
| va_start(args, first_hdr_id); |
| req = g_obex_packet_new_valist(G_OBEX_OP_GET, TRUE, |
| first_hdr_id, args); |
| va_end(args); |
| |
| transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, |
| transfer_response, transfer, err); |
| if (transfer->req_id == 0) { |
| transfer_free(transfer); |
| return 0; |
| } |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| return transfer->id; |
| } |
| |
| static gssize get_get_data(void *buf, gsize len, gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GObexPacket *req, *rsp; |
| GError *err = NULL; |
| gssize ret; |
| guint8 op; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| ret = transfer->data_producer(buf, len, transfer->user_data); |
| if (ret > 0) { |
| if (!g_obex_srm_active(transfer->obex)) |
| return ret; |
| |
| /* Generate next response */ |
| rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, |
| G_OBEX_HDR_INVALID); |
| g_obex_packet_add_body(rsp, get_get_data, transfer); |
| |
| if (!g_obex_send(transfer->obex, rsp, &err)) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| |
| return ret; |
| } |
| |
| if (ret == -EAGAIN) |
| return ret; |
| |
| if (ret == 0) { |
| transfer_complete(transfer, NULL); |
| return ret; |
| } |
| |
| op = g_obex_errno_to_rsp(ret); |
| |
| req = g_obex_packet_new(op, TRUE, G_OBEX_HDR_INVALID); |
| g_obex_send(transfer->obex, req, NULL); |
| |
| err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, |
| "Data producer function failed"); |
| g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| |
| return ret; |
| } |
| |
| static gboolean transfer_get_req_first(struct transfer *transfer, |
| GObexPacket *rsp) |
| { |
| GError *err = NULL; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| g_obex_packet_add_body(rsp, get_get_data, transfer); |
| |
| if (!g_obex_send(transfer->obex, rsp, &err)) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void transfer_get_req(GObex *obex, GObexPacket *req, gpointer user_data) |
| { |
| struct transfer *transfer = user_data; |
| GError *err = NULL; |
| GObexPacket *rsp; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID); |
| g_obex_packet_add_body(rsp, get_get_data, transfer); |
| |
| if (!g_obex_send(obex, rsp, &err)) { |
| transfer_complete(transfer, err); |
| g_error_free(err); |
| } |
| } |
| |
| guint g_obex_get_rsp_pkt(GObex *obex, GObexPacket *rsp, |
| GObexDataProducer data_func, GObexFunc complete_func, |
| gpointer user_data, GError **err) |
| { |
| struct transfer *transfer; |
| guint id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); |
| transfer->data_producer = data_func; |
| |
| if (!transfer_get_req_first(transfer, rsp)) |
| return 0; |
| |
| if (!g_slist_find(transfers, transfer)) |
| return 0; |
| |
| id = g_obex_add_request_function(obex, G_OBEX_OP_GET, transfer_get_req, |
| transfer); |
| transfer->get_id = id; |
| |
| id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT, |
| transfer_abort_req, transfer); |
| transfer->abort_id = id; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); |
| |
| return transfer->id; |
| } |
| |
| guint g_obex_get_rsp(GObex *obex, GObexDataProducer data_func, |
| GObexFunc complete_func, gpointer user_data, |
| GError **err, guint first_hdr_id, ...) |
| { |
| GObexPacket *rsp; |
| va_list args; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); |
| |
| va_start(args, first_hdr_id); |
| rsp = g_obex_packet_new_valist(G_OBEX_RSP_CONTINUE, TRUE, |
| first_hdr_id, args); |
| va_end(args); |
| |
| return g_obex_get_rsp_pkt(obex, rsp, data_func, complete_func, |
| user_data, err); |
| } |
| |
| gboolean g_obex_cancel_transfer(guint id, GObexFunc complete_func, |
| gpointer user_data) |
| { |
| struct transfer *transfer = NULL; |
| gboolean ret = TRUE; |
| |
| g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id); |
| |
| transfer = find_transfer(id); |
| |
| if (transfer == NULL) |
| return FALSE; |
| |
| if (complete_func == NULL) |
| goto done; |
| |
| transfer->complete_func = complete_func; |
| transfer->user_data = user_data; |
| |
| if (!transfer->req_id) { |
| transfer->req_id = g_obex_abort(transfer->obex, |
| transfer_abort_response, |
| transfer, NULL); |
| if (transfer->req_id) |
| return TRUE; |
| } |
| |
| ret = g_obex_cancel_req(transfer->obex, transfer->req_id, FALSE); |
| if (ret) |
| return TRUE; |
| |
| done: |
| transfer_free(transfer); |
| return ret; |
| } |