// 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 #endif #include #include #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; }