1696 lines
37 KiB
C
1696 lines
37 KiB
C
/*
|
|
*
|
|
* OBEX library with GLib integration
|
|
*
|
|
* Copyright (C) 2011 Intel Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "gobex.h"
|
|
#include "gobex-debug.h"
|
|
|
|
#define G_OBEX_DEFAULT_MTU 4096
|
|
#define G_OBEX_MINIMUM_MTU 255
|
|
#define G_OBEX_MAXIMUM_MTU 65535
|
|
|
|
#define G_OBEX_DEFAULT_TIMEOUT 10
|
|
#define G_OBEX_ABORT_TIMEOUT 5
|
|
|
|
#define G_OBEX_OP_NONE 0xff
|
|
|
|
#define FINAL_BIT 0x80
|
|
|
|
#define CONNID_INVALID 0xffffffff
|
|
|
|
/* Challenge request */
|
|
#define NONCE_TAG 0x00
|
|
#define NONCE_LEN 16
|
|
|
|
/* Challenge response */
|
|
#define DIGEST_TAG 0x00
|
|
|
|
guint gobex_debug = 0;
|
|
|
|
struct srm_config {
|
|
guint8 op;
|
|
gboolean enabled;
|
|
guint8 srm;
|
|
guint8 srmp;
|
|
gboolean outgoing;
|
|
};
|
|
|
|
struct _GObex {
|
|
int ref_count;
|
|
GIOChannel *io;
|
|
guint io_source;
|
|
|
|
gboolean (*read) (GObex *obex, GError **err);
|
|
gboolean (*write) (GObex *obex, GError **err);
|
|
|
|
guint8 *rx_buf;
|
|
size_t rx_data;
|
|
guint16 rx_pkt_len;
|
|
guint8 rx_last_op;
|
|
|
|
guint8 *tx_buf;
|
|
size_t tx_data;
|
|
size_t tx_sent;
|
|
|
|
gboolean suspended;
|
|
gboolean use_srm;
|
|
|
|
struct srm_config *srm;
|
|
|
|
guint write_source;
|
|
|
|
gssize io_rx_mtu;
|
|
gssize io_tx_mtu;
|
|
|
|
guint16 rx_mtu;
|
|
guint16 tx_mtu;
|
|
|
|
guint32 conn_id;
|
|
GObexApparam *authchal;
|
|
|
|
GQueue *tx_queue;
|
|
|
|
GSList *req_handlers;
|
|
|
|
GObexFunc disconn_func;
|
|
gpointer disconn_func_data;
|
|
|
|
struct pending_pkt *pending_req;
|
|
};
|
|
|
|
struct pending_pkt {
|
|
guint id;
|
|
GObex *obex;
|
|
GObexPacket *pkt;
|
|
guint timeout;
|
|
guint timeout_id;
|
|
GObexResponseFunc rsp_func;
|
|
gpointer rsp_data;
|
|
gboolean cancelled;
|
|
gboolean suspended;
|
|
gboolean authenticating;
|
|
};
|
|
|
|
struct req_handler {
|
|
guint id;
|
|
guint8 opcode;
|
|
GObexRequestFunc func;
|
|
gpointer user_data;
|
|
};
|
|
|
|
struct connect_data {
|
|
guint8 version;
|
|
guint8 flags;
|
|
guint16 mtu;
|
|
} __attribute__ ((packed));
|
|
|
|
struct setpath_data {
|
|
guint8 flags;
|
|
guint8 constants;
|
|
} __attribute__ ((packed));
|
|
|
|
static struct error_code {
|
|
guint8 code;
|
|
const char *name;
|
|
} obex_errors[] = {
|
|
{ G_OBEX_RSP_CONTINUE, "Continue" },
|
|
{ G_OBEX_RSP_SUCCESS, "Success" },
|
|
{ G_OBEX_RSP_CREATED, "Created" },
|
|
{ G_OBEX_RSP_ACCEPTED, "Accepted" },
|
|
{ G_OBEX_RSP_NON_AUTHORITATIVE, "Non Authoritative" },
|
|
{ G_OBEX_RSP_NO_CONTENT, "No Content" },
|
|
{ G_OBEX_RSP_RESET_CONTENT, "Reset Content" },
|
|
{ G_OBEX_RSP_PARTIAL_CONTENT, "Partial Content" },
|
|
{ G_OBEX_RSP_MULTIPLE_CHOICES, "Multiple Choices" },
|
|
{ G_OBEX_RSP_MOVED_PERMANENTLY, "Moved Permanently" },
|
|
{ G_OBEX_RSP_MOVED_TEMPORARILY, "Moved Temporarily" },
|
|
{ G_OBEX_RSP_SEE_OTHER, "See Other" },
|
|
{ G_OBEX_RSP_NOT_MODIFIED, "Not Modified" },
|
|
{ G_OBEX_RSP_USE_PROXY, "Use Proxy" },
|
|
{ G_OBEX_RSP_BAD_REQUEST, "Bad Request" },
|
|
{ G_OBEX_RSP_UNAUTHORIZED, "Unauthorized" },
|
|
{ G_OBEX_RSP_PAYMENT_REQUIRED, "Payment Required" },
|
|
{ G_OBEX_RSP_FORBIDDEN, "Forbidden" },
|
|
{ G_OBEX_RSP_NOT_FOUND, "Not Found" },
|
|
{ G_OBEX_RSP_METHOD_NOT_ALLOWED, "Method Not Allowed" },
|
|
{ G_OBEX_RSP_NOT_ACCEPTABLE, "Not Acceptable" },
|
|
{ G_OBEX_RSP_PROXY_AUTH_REQUIRED, "Proxy Authentication Required" },
|
|
{ G_OBEX_RSP_REQUEST_TIME_OUT, "Request Time Out" },
|
|
{ G_OBEX_RSP_CONFLICT, "Conflict" },
|
|
{ G_OBEX_RSP_GONE, "Gone" },
|
|
{ G_OBEX_RSP_LENGTH_REQUIRED, "Length Required" },
|
|
{ G_OBEX_RSP_PRECONDITION_FAILED, "Precondition Failed" },
|
|
{ G_OBEX_RSP_REQ_ENTITY_TOO_LARGE, "Request Entity Too Large" },
|
|
{ G_OBEX_RSP_REQ_URL_TOO_LARGE, "Request URL Too Large" },
|
|
{ G_OBEX_RSP_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" },
|
|
{ G_OBEX_RSP_INTERNAL_SERVER_ERROR, "Internal Server Error" },
|
|
{ G_OBEX_RSP_NOT_IMPLEMENTED, "Not Implemented" },
|
|
{ G_OBEX_RSP_BAD_GATEWAY, "Bad Gateway" },
|
|
{ G_OBEX_RSP_SERVICE_UNAVAILABLE, "Service Unavailable" },
|
|
{ G_OBEX_RSP_GATEWAY_TIMEOUT, "Gateway Timeout" },
|
|
{ G_OBEX_RSP_VERSION_NOT_SUPPORTED, "Version Not Supported" },
|
|
{ G_OBEX_RSP_DATABASE_FULL, "Database Full" },
|
|
{ G_OBEX_RSP_DATABASE_LOCKED, "Database Locked" },
|
|
{ 0x00, NULL }
|
|
};
|
|
|
|
const char *g_obex_strerror(guint8 err_code)
|
|
{
|
|
struct error_code *error;
|
|
|
|
for (error = obex_errors; error->name != NULL; error++) {
|
|
if (error->code == err_code)
|
|
return error->name;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
static ssize_t req_header_offset(guint8 opcode)
|
|
{
|
|
switch (opcode) {
|
|
case G_OBEX_OP_CONNECT:
|
|
return sizeof(struct connect_data);
|
|
case G_OBEX_OP_SETPATH:
|
|
return sizeof(struct setpath_data);
|
|
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:
|
|
case G_OBEX_OP_ACTION:
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
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:
|
|
case G_OBEX_OP_ACTION:
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void pending_pkt_free(struct pending_pkt *p)
|
|
{
|
|
if (p->obex != NULL)
|
|
g_obex_unref(p->obex);
|
|
|
|
if (p->timeout_id > 0)
|
|
g_source_remove(p->timeout_id);
|
|
|
|
g_obex_packet_free(p->pkt);
|
|
|
|
g_free(p);
|
|
}
|
|
|
|
static gboolean req_timeout(gpointer user_data)
|
|
{
|
|
GObex *obex = user_data;
|
|
struct pending_pkt *p = obex->pending_req;
|
|
GError *err;
|
|
|
|
g_assert(p != NULL);
|
|
|
|
obex->pending_req = NULL;
|
|
|
|
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_TIMEOUT,
|
|
"Timed out waiting for response");
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
|
|
|
|
if (p->rsp_func)
|
|
p->rsp_func(obex, err, NULL, p->rsp_data);
|
|
|
|
g_error_free(err);
|
|
pending_pkt_free(p);
|
|
|
|
p->timeout_id = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean write_stream(GObex *obex, GError **err)
|
|
{
|
|
GIOStatus status;
|
|
gsize bytes_written;
|
|
char *buf;
|
|
|
|
buf = (char *) &obex->tx_buf[obex->tx_sent];
|
|
status = g_io_channel_write_chars(obex->io, buf, obex->tx_data,
|
|
&bytes_written, err);
|
|
if (status != G_IO_STATUS_NORMAL)
|
|
return FALSE;
|
|
|
|
g_obex_dump(G_OBEX_DEBUG_DATA, "<", buf, bytes_written);
|
|
|
|
obex->tx_sent += bytes_written;
|
|
obex->tx_data -= bytes_written;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean write_packet(GObex *obex, GError **err)
|
|
{
|
|
GIOStatus status;
|
|
gsize bytes_written;
|
|
char *buf;
|
|
|
|
buf = (char *) &obex->tx_buf[obex->tx_sent];
|
|
status = g_io_channel_write_chars(obex->io, buf, obex->tx_data,
|
|
&bytes_written, err);
|
|
if (status != G_IO_STATUS_NORMAL)
|
|
return FALSE;
|
|
|
|
if (bytes_written != obex->tx_data)
|
|
return FALSE;
|
|
|
|
g_obex_dump(G_OBEX_DEBUG_DATA, "<", buf, bytes_written);
|
|
|
|
obex->tx_sent += bytes_written;
|
|
obex->tx_data -= bytes_written;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void set_srmp(GObex *obex, guint8 srmp, gboolean outgoing)
|
|
{
|
|
struct srm_config *config = obex->srm;
|
|
|
|
if (config == NULL)
|
|
return;
|
|
|
|
/* Dont't reset if direction doesn't match */
|
|
if (srmp > G_OBEX_SRMP_NEXT_WAIT && config->outgoing != outgoing)
|
|
return;
|
|
|
|
config->srmp = srmp;
|
|
config->outgoing = outgoing;
|
|
}
|
|
|
|
static void set_srm(GObex *obex, guint8 op, guint8 srm)
|
|
{
|
|
struct srm_config *config = obex->srm;
|
|
gboolean enable;
|
|
|
|
if (config == NULL) {
|
|
if (srm == G_OBEX_SRM_DISABLE)
|
|
return;
|
|
|
|
config = g_new0(struct srm_config, 1);
|
|
config->op = op;
|
|
config->srm = srm;
|
|
obex->srm = config;
|
|
return;
|
|
}
|
|
|
|
/* Indicate response, treat it as request */
|
|
if (config->srm == G_OBEX_SRM_INDICATE) {
|
|
if (srm != G_OBEX_SRM_ENABLE)
|
|
goto done;
|
|
config->srm = srm;
|
|
return;
|
|
}
|
|
|
|
enable = (srm == G_OBEX_SRM_ENABLE);
|
|
if (config->enabled == enable)
|
|
goto done;
|
|
|
|
config->enabled = enable;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "SRM %s", config->enabled ?
|
|
"Enabled" : "Disabled");
|
|
|
|
done:
|
|
if (config->enabled)
|
|
return;
|
|
|
|
g_free(obex->srm);
|
|
obex->srm = NULL;
|
|
}
|
|
|
|
static gboolean g_obex_srm_enabled(GObex *obex)
|
|
{
|
|
if (!obex->use_srm)
|
|
return FALSE;
|
|
|
|
if (obex->srm == NULL)
|
|
return FALSE;
|
|
|
|
return obex->srm->enabled;
|
|
}
|
|
|
|
static void check_srm_final(GObex *obex, guint8 op)
|
|
{
|
|
if (!g_obex_srm_enabled(obex))
|
|
return;
|
|
|
|
switch (obex->srm->op) {
|
|
case G_OBEX_OP_CONNECT:
|
|
return;
|
|
default:
|
|
if (op <= G_OBEX_RSP_CONTINUE)
|
|
return;
|
|
}
|
|
|
|
set_srm(obex, op, G_OBEX_SRM_DISABLE);
|
|
}
|
|
|
|
static void setup_srm(GObex *obex, GObexPacket *pkt, gboolean outgoing)
|
|
{
|
|
GObexHeader *hdr;
|
|
guint8 op;
|
|
gboolean final;
|
|
|
|
if (!obex->use_srm)
|
|
return;
|
|
|
|
op = g_obex_packet_get_operation(pkt, &final);
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM);
|
|
if (hdr != NULL) {
|
|
guint8 srm;
|
|
g_obex_header_get_uint8(hdr, &srm);
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "srm 0x%02x", srm);
|
|
set_srm(obex, op, srm);
|
|
} else if (!g_obex_srm_enabled(obex))
|
|
set_srm(obex, op, G_OBEX_SRM_DISABLE);
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRMP);
|
|
if (hdr != NULL) {
|
|
guint8 srmp;
|
|
g_obex_header_get_uint8(hdr, &srmp);
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "srmp 0x%02x", srmp);
|
|
set_srmp(obex, srmp, outgoing);
|
|
} else if (obex->pending_req && obex->pending_req->suspended)
|
|
g_obex_packet_add_uint8(pkt, G_OBEX_HDR_SRMP, G_OBEX_SRMP_WAIT);
|
|
else
|
|
set_srmp(obex, -1, outgoing);
|
|
|
|
if (final)
|
|
check_srm_final(obex, op);
|
|
}
|
|
|
|
static gboolean write_data(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
GObex *obex = user_data;
|
|
|
|
if (cond & G_IO_NVAL)
|
|
return FALSE;
|
|
|
|
if (cond & (G_IO_HUP | G_IO_ERR))
|
|
goto stop_tx;
|
|
|
|
if (obex->tx_data == 0) {
|
|
struct pending_pkt *p = g_queue_pop_head(obex->tx_queue);
|
|
ssize_t len;
|
|
|
|
if (p == NULL)
|
|
goto stop_tx;
|
|
|
|
setup_srm(obex, p->pkt, TRUE);
|
|
|
|
if (g_obex_srm_enabled(obex))
|
|
goto encode;
|
|
|
|
/* Can't send a request while there's a pending one */
|
|
if (obex->pending_req && p->id > 0) {
|
|
g_queue_push_head(obex->tx_queue, p);
|
|
goto stop_tx;
|
|
}
|
|
|
|
encode:
|
|
len = g_obex_packet_encode(p->pkt, obex->tx_buf, obex->tx_mtu);
|
|
if (len == -EAGAIN) {
|
|
g_queue_push_head(obex->tx_queue, p);
|
|
g_obex_suspend(obex);
|
|
goto stop_tx;
|
|
}
|
|
|
|
if (len < 0) {
|
|
pending_pkt_free(p);
|
|
goto done;
|
|
}
|
|
|
|
if (p->id > 0) {
|
|
if (obex->pending_req != NULL)
|
|
pending_pkt_free(obex->pending_req);
|
|
obex->pending_req = p;
|
|
p->timeout_id = g_timeout_add_seconds(p->timeout,
|
|
req_timeout, obex);
|
|
} else {
|
|
/* During packet encode final bit can be set */
|
|
if (obex->tx_buf[0] & FINAL_BIT)
|
|
check_srm_final(obex,
|
|
obex->tx_buf[0] & ~FINAL_BIT);
|
|
pending_pkt_free(p);
|
|
}
|
|
|
|
obex->tx_data = len;
|
|
obex->tx_sent = 0;
|
|
}
|
|
|
|
if (obex->suspended) {
|
|
obex->write_source = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
if (!obex->write(obex, NULL))
|
|
goto stop_tx;
|
|
|
|
done:
|
|
if (obex->tx_data > 0 || g_queue_get_length(obex->tx_queue) > 0)
|
|
return TRUE;
|
|
|
|
stop_tx:
|
|
obex->rx_last_op = G_OBEX_OP_NONE;
|
|
obex->tx_data = 0;
|
|
obex->write_source = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
static void enable_tx(GObex *obex)
|
|
{
|
|
GIOCondition cond;
|
|
|
|
if (obex->suspended)
|
|
return;
|
|
|
|
if (obex->write_source > 0)
|
|
return;
|
|
|
|
cond = G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
obex->write_source = g_io_add_watch(obex->io, cond, write_data, obex);
|
|
}
|
|
|
|
static gboolean g_obex_send_internal(GObex *obex, struct pending_pkt *p,
|
|
GError **err)
|
|
{
|
|
|
|
if (obex->io == NULL) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_DISCONNECTED,
|
|
"The transport is not connected");
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message);
|
|
return FALSE;
|
|
}
|
|
|
|
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);
|
|
|
|
if (obex->pending_req == NULL || p->id == 0)
|
|
enable_tx(obex);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void init_connect_data(GObex *obex, struct connect_data *data)
|
|
{
|
|
guint16 u16;
|
|
|
|
memset(data, 0, sizeof(*data));
|
|
|
|
data->version = 0x10;
|
|
data->flags = 0;
|
|
|
|
u16 = g_htons(obex->rx_mtu);
|
|
memcpy(&data->mtu, &u16, sizeof(u16));
|
|
}
|
|
|
|
static guint8 *digest_response(const guint8 *nonce)
|
|
{
|
|
GChecksum *md5;
|
|
guint8 *result;
|
|
gsize size;
|
|
|
|
result = g_new0(guint8, NONCE_LEN);
|
|
|
|
md5 = g_checksum_new(G_CHECKSUM_MD5);
|
|
if (md5 == NULL)
|
|
return result;
|
|
|
|
g_checksum_update(md5, nonce, NONCE_LEN);
|
|
g_checksum_update(md5, (guint8 *) ":BlueZ", 6);
|
|
|
|
size = NONCE_LEN;
|
|
g_checksum_get_digest(md5, result, &size);
|
|
|
|
g_checksum_free(md5);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void prepare_auth_rsp(GObex *obex, GObexPacket *rsp)
|
|
{
|
|
GObexHeader *hdr;
|
|
GObexApparam *authrsp;
|
|
const guint8 *nonce;
|
|
guint8 *result;
|
|
gsize len;
|
|
|
|
/* Check if client is already responding to authentication challenge */
|
|
hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_AUTHRESP);
|
|
if (hdr)
|
|
goto done;
|
|
|
|
if (!g_obex_apparam_get_bytes(obex->authchal, NONCE_TAG, &nonce, &len))
|
|
goto done;
|
|
|
|
if (len != NONCE_LEN)
|
|
goto done;
|
|
|
|
result = digest_response(nonce);
|
|
authrsp = g_obex_apparam_set_bytes(NULL, DIGEST_TAG, result, NONCE_LEN);
|
|
|
|
hdr = g_obex_header_new_tag(G_OBEX_HDR_AUTHRESP, authrsp);
|
|
g_obex_packet_add_header(rsp, hdr);
|
|
|
|
g_obex_apparam_free(authrsp);
|
|
g_free(result);
|
|
|
|
done:
|
|
g_obex_apparam_free(obex->authchal);
|
|
obex->authchal = NULL;
|
|
}
|
|
|
|
static void prepare_connect_rsp(GObex *obex, GObexPacket *rsp)
|
|
{
|
|
GObexHeader *hdr;
|
|
struct connect_data data;
|
|
static guint32 next_connid = 1;
|
|
|
|
init_connect_data(obex, &data);
|
|
g_obex_packet_set_data(rsp, &data, sizeof(data), G_OBEX_DATA_COPY);
|
|
|
|
hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_CONNECTION);
|
|
if (hdr) {
|
|
g_obex_header_get_uint32(hdr, &obex->conn_id);
|
|
goto done;
|
|
}
|
|
|
|
obex->conn_id = next_connid++;
|
|
|
|
hdr = g_obex_header_new_uint32(G_OBEX_HDR_CONNECTION, obex->conn_id);
|
|
g_obex_packet_prepend_header(rsp, hdr);
|
|
|
|
done:
|
|
if (obex->authchal)
|
|
prepare_auth_rsp(obex, rsp);
|
|
}
|
|
|
|
static void prepare_srm_rsp(GObex *obex, GObexPacket *pkt)
|
|
{
|
|
GObexHeader *hdr;
|
|
|
|
if (!obex->use_srm || obex->srm == NULL)
|
|
return;
|
|
|
|
if (obex->srm->enabled)
|
|
return;
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM);
|
|
if (hdr != NULL)
|
|
return;
|
|
|
|
hdr = g_obex_header_new_uint8(G_OBEX_HDR_SRM, G_OBEX_SRM_ENABLE);
|
|
g_obex_packet_prepend_header(pkt, hdr);
|
|
}
|
|
|
|
gboolean g_obex_send(GObex *obex, GObexPacket *pkt, GError **err)
|
|
{
|
|
struct pending_pkt *p;
|
|
gboolean ret;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
if (obex == NULL || pkt == NULL) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_INVALID_ARGS,
|
|
"Invalid arguments");
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (obex->rx_last_op) {
|
|
case G_OBEX_OP_CONNECT:
|
|
prepare_connect_rsp(obex, pkt);
|
|
break;
|
|
case G_OBEX_OP_GET:
|
|
case G_OBEX_OP_PUT:
|
|
prepare_srm_rsp(obex, pkt);
|
|
break;
|
|
}
|
|
|
|
p = g_new0(struct pending_pkt, 1);
|
|
p->pkt = pkt;
|
|
|
|
ret = g_obex_send_internal(obex, p, err);
|
|
if (ret == FALSE)
|
|
pending_pkt_free(p);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void prepare_srm_req(GObex *obex, GObexPacket *pkt)
|
|
{
|
|
GObexHeader *hdr;
|
|
|
|
if (!obex->use_srm)
|
|
return;
|
|
|
|
if (obex->srm != NULL && obex->srm->enabled)
|
|
return;
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM);
|
|
if (hdr != NULL)
|
|
return;
|
|
|
|
hdr = g_obex_header_new_uint8(G_OBEX_HDR_SRM, G_OBEX_SRM_ENABLE);
|
|
g_obex_packet_prepend_header(pkt, hdr);
|
|
}
|
|
|
|
guint g_obex_send_req(GObex *obex, GObexPacket *req, int timeout,
|
|
GObexResponseFunc func, gpointer user_data,
|
|
GError **err)
|
|
{
|
|
GObexHeader *hdr;
|
|
struct pending_pkt *p;
|
|
static guint id = 1;
|
|
guint8 op;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
op = g_obex_packet_get_operation(req, NULL);
|
|
if (op == G_OBEX_OP_PUT || op == G_OBEX_OP_GET) {
|
|
/* Only enable SRM automatically for GET and PUT */
|
|
prepare_srm_req(obex, req);
|
|
}
|
|
|
|
if (obex->conn_id == CONNID_INVALID)
|
|
goto create_pending;
|
|
|
|
if (obex->rx_last_op == G_OBEX_RSP_CONTINUE)
|
|
goto create_pending;
|
|
|
|
if (g_obex_srm_enabled(obex) && obex->pending_req != NULL)
|
|
goto create_pending;
|
|
|
|
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_CONNECTION);
|
|
if (hdr != NULL)
|
|
goto create_pending;
|
|
|
|
hdr = g_obex_header_new_uint32(G_OBEX_HDR_CONNECTION, obex->conn_id);
|
|
g_obex_packet_prepend_header(req, hdr);
|
|
|
|
create_pending:
|
|
p = g_new0(struct pending_pkt, 1);
|
|
|
|
p->pkt = req;
|
|
p->id = id++;
|
|
p->rsp_func = func;
|
|
p->rsp_data = user_data;
|
|
|
|
if (timeout < 0)
|
|
p->timeout = G_OBEX_DEFAULT_TIMEOUT;
|
|
else
|
|
p->timeout = timeout;
|
|
|
|
if (!g_obex_send_internal(obex, p, err)) {
|
|
pending_pkt_free(p);
|
|
return 0;
|
|
}
|
|
|
|
return p->id;
|
|
}
|
|
|
|
static int pending_pkt_cmp(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct pending_pkt *p = a;
|
|
guint id = GPOINTER_TO_UINT(b);
|
|
|
|
return (p->id - id);
|
|
}
|
|
|
|
static gboolean pending_req_abort(GObex *obex, GError **err)
|
|
{
|
|
struct pending_pkt *p = obex->pending_req;
|
|
GObexPacket *req;
|
|
|
|
if (p->cancelled)
|
|
return TRUE;
|
|
|
|
p->cancelled = TRUE;
|
|
|
|
if (p->timeout_id > 0)
|
|
g_source_remove(p->timeout_id);
|
|
|
|
p->timeout = G_OBEX_ABORT_TIMEOUT;
|
|
p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout, obex);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_ABORT, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
return g_obex_send(obex, req, 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;
|
|
}
|
|
|
|
if (remove_callback)
|
|
obex->pending_req->rsp_func = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
match = g_queue_find_custom(obex->tx_queue, GUINT_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;
|
|
}
|
|
|
|
gboolean g_obex_send_rsp(GObex *obex, guint8 rspcode, GError **err,
|
|
guint8 first_hdr_type, ...)
|
|
{
|
|
GObexPacket *rsp;
|
|
va_list args;
|
|
|
|
va_start(args, first_hdr_type);
|
|
rsp = g_obex_packet_new_valist(rspcode, TRUE, first_hdr_type, args);
|
|
va_end(args);
|
|
|
|
return g_obex_send(obex, rsp, err);
|
|
}
|
|
|
|
void g_obex_set_disconnect_function(GObex *obex, GObexFunc func,
|
|
gpointer user_data)
|
|
{
|
|
obex->disconn_func = func;
|
|
obex->disconn_func_data = user_data;
|
|
}
|
|
|
|
static int req_handler_cmpop(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct req_handler *handler = a;
|
|
guint opcode = GPOINTER_TO_UINT(b);
|
|
|
|
return (int) handler->opcode - (int) opcode;
|
|
}
|
|
|
|
static int req_handler_cmpid(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct req_handler *handler = a;
|
|
guint id = GPOINTER_TO_UINT(b);
|
|
|
|
return (int) handler->id - (int) id;
|
|
}
|
|
|
|
guint g_obex_add_request_function(GObex *obex, guint8 opcode,
|
|
GObexRequestFunc func,
|
|
gpointer user_data)
|
|
{
|
|
struct req_handler *handler;
|
|
static guint next_id = 1;
|
|
|
|
handler = g_new0(struct req_handler, 1);
|
|
handler->id = next_id++;
|
|
handler->opcode = opcode;
|
|
handler->func = func;
|
|
handler->user_data = user_data;
|
|
|
|
obex->req_handlers = g_slist_prepend(obex->req_handlers, handler);
|
|
|
|
return handler->id;
|
|
}
|
|
|
|
gboolean g_obex_remove_request_function(GObex *obex, guint id)
|
|
{
|
|
struct req_handler *handler;
|
|
GSList *match;
|
|
|
|
match = g_slist_find_custom(obex->req_handlers, GUINT_TO_POINTER(id),
|
|
req_handler_cmpid);
|
|
if (match == NULL)
|
|
return FALSE;
|
|
|
|
handler = match->data;
|
|
|
|
obex->req_handlers = g_slist_delete_link(obex->req_handlers, match);
|
|
g_free(handler);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void g_obex_srm_suspend(GObex *obex)
|
|
{
|
|
struct pending_pkt *p = obex->pending_req;
|
|
GObexPacket *req;
|
|
|
|
if (p->timeout_id > 0) {
|
|
g_source_remove(p->timeout_id);
|
|
p->timeout_id = 0;
|
|
}
|
|
|
|
p->suspended = TRUE;
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_GET, TRUE,
|
|
G_OBEX_HDR_SRMP, G_OBEX_SRMP_WAIT,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
g_obex_send(obex, req, NULL);
|
|
}
|
|
|
|
void g_obex_suspend(GObex *obex)
|
|
{
|
|
struct pending_pkt *req = obex->pending_req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
if (!g_obex_srm_active(obex) || !req)
|
|
goto done;
|
|
|
|
/* Send SRMP wait in case of GET */
|
|
if (g_obex_packet_get_operation(req->pkt, NULL) == G_OBEX_OP_GET) {
|
|
g_obex_srm_suspend(obex);
|
|
return;
|
|
}
|
|
|
|
done:
|
|
obex->suspended = TRUE;
|
|
|
|
if (obex->write_source > 0) {
|
|
g_source_remove(obex->write_source);
|
|
obex->write_source = 0;
|
|
}
|
|
}
|
|
|
|
static void g_obex_srm_resume(GObex *obex)
|
|
{
|
|
struct pending_pkt *p = obex->pending_req;
|
|
GObexPacket *req;
|
|
|
|
p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout, obex);
|
|
p->suspended = FALSE;
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
g_obex_send(obex, req, NULL);
|
|
}
|
|
|
|
void g_obex_resume(GObex *obex)
|
|
{
|
|
struct pending_pkt *req = obex->pending_req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
obex->suspended = FALSE;
|
|
|
|
if (g_obex_srm_active(obex) || !req)
|
|
goto done;
|
|
|
|
if (g_obex_packet_get_operation(req->pkt, NULL) == G_OBEX_OP_GET)
|
|
g_obex_srm_resume(obex);
|
|
|
|
done:
|
|
if (g_queue_get_length(obex->tx_queue) > 0 || obex->tx_data > 0)
|
|
enable_tx(obex);
|
|
}
|
|
|
|
gboolean g_obex_srm_active(GObex *obex)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (!g_obex_srm_enabled(obex))
|
|
goto done;
|
|
|
|
if (obex->srm->srmp <= G_OBEX_SRMP_NEXT_WAIT)
|
|
goto done;
|
|
|
|
ret = TRUE;
|
|
done:
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "%s", ret ? "yes" : "no");
|
|
return ret;
|
|
}
|
|
|
|
static void auth_challenge(GObex *obex)
|
|
{
|
|
struct pending_pkt *p = obex->pending_req;
|
|
|
|
if (p->authenticating)
|
|
return;
|
|
|
|
p->authenticating = TRUE;
|
|
|
|
prepare_auth_rsp(obex, p->pkt);
|
|
|
|
/* Remove it as pending and add it back to the queue so it gets sent
|
|
* again */
|
|
if (p->timeout_id > 0) {
|
|
g_source_remove(p->timeout_id);
|
|
p->timeout_id = 0;
|
|
}
|
|
obex->pending_req = NULL;
|
|
g_obex_send_internal(obex, p, NULL);
|
|
}
|
|
|
|
static void parse_connect_data(GObex *obex, GObexPacket *pkt)
|
|
{
|
|
const struct connect_data *data;
|
|
GObexHeader *hdr;
|
|
guint16 u16;
|
|
size_t data_len;
|
|
|
|
data = g_obex_packet_get_data(pkt, &data_len);
|
|
if (data == NULL || data_len != sizeof(*data))
|
|
return;
|
|
|
|
memcpy(&u16, &data->mtu, sizeof(u16));
|
|
|
|
obex->tx_mtu = g_ntohs(u16);
|
|
if (obex->io_tx_mtu > 0 && obex->tx_mtu > obex->io_tx_mtu)
|
|
obex->tx_mtu = obex->io_tx_mtu;
|
|
obex->tx_buf = g_realloc(obex->tx_buf, obex->tx_mtu);
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_CONNECTION);
|
|
if (hdr)
|
|
g_obex_header_get_uint32(hdr, &obex->conn_id);
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_AUTHCHAL);
|
|
if (hdr)
|
|
obex->authchal = g_obex_header_get_apparam(hdr);
|
|
}
|
|
|
|
static gboolean parse_response(GObex *obex, GObexPacket *rsp)
|
|
{
|
|
struct pending_pkt *p = obex->pending_req;
|
|
guint8 opcode, rspcode;
|
|
gboolean final;
|
|
|
|
rspcode = g_obex_packet_get_operation(rsp, &final);
|
|
|
|
opcode = g_obex_packet_get_operation(p->pkt, NULL);
|
|
if (opcode == G_OBEX_OP_CONNECT) {
|
|
parse_connect_data(obex, rsp);
|
|
if (rspcode == G_OBEX_RSP_UNAUTHORIZED && obex->authchal)
|
|
auth_challenge(obex);
|
|
}
|
|
|
|
setup_srm(obex, rsp, FALSE);
|
|
|
|
if (!g_obex_srm_enabled(obex))
|
|
return final;
|
|
|
|
/*
|
|
* Resposes have final bit set but in case of GET with SRM
|
|
* we should not remove the request since the remote side will
|
|
* continue sending responses until the transfer is finished
|
|
*/
|
|
if (opcode == G_OBEX_OP_GET && rspcode == G_OBEX_RSP_CONTINUE) {
|
|
if (p->timeout_id > 0)
|
|
g_source_remove(p->timeout_id);
|
|
|
|
p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout,
|
|
obex);
|
|
return FALSE;
|
|
}
|
|
|
|
return final;
|
|
}
|
|
|
|
static void handle_response(GObex *obex, GError *err, GObexPacket *rsp)
|
|
{
|
|
struct pending_pkt *p;
|
|
gboolean disconn = err ? TRUE : FALSE, final_rsp = TRUE;
|
|
|
|
if (rsp != NULL)
|
|
final_rsp = parse_response(obex, rsp);
|
|
|
|
if (!obex->pending_req)
|
|
return;
|
|
|
|
p = obex->pending_req;
|
|
|
|
/* Reset if final so it can no longer be cancelled */
|
|
if (final_rsp)
|
|
obex->pending_req = NULL;
|
|
|
|
if (p->cancelled)
|
|
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
|
|
"The operation was cancelled");
|
|
|
|
if (err)
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
|
|
|
|
if (p->rsp_func) {
|
|
p->rsp_func(obex, err, rsp, p->rsp_data);
|
|
|
|
/* Check if user callback removed the request */
|
|
if (!final_rsp && p != obex->pending_req)
|
|
return;
|
|
}
|
|
|
|
if (p->cancelled)
|
|
g_error_free(err);
|
|
|
|
if (final_rsp)
|
|
pending_pkt_free(p);
|
|
|
|
if (!disconn && g_queue_get_length(obex->tx_queue) > 0)
|
|
enable_tx(obex);
|
|
}
|
|
|
|
static gboolean check_connid(GObex *obex, GObexPacket *pkt)
|
|
{
|
|
GObexHeader *hdr;
|
|
guint32 id;
|
|
|
|
if (obex->conn_id == CONNID_INVALID)
|
|
return TRUE;
|
|
|
|
hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_CONNECTION);
|
|
if (hdr == NULL)
|
|
return TRUE;
|
|
|
|
g_obex_header_get_uint32(hdr, &id);
|
|
|
|
return obex->conn_id == id;
|
|
}
|
|
|
|
static int parse_request(GObex *obex, GObexPacket *req)
|
|
{
|
|
guint8 op;
|
|
gboolean final;
|
|
|
|
op = g_obex_packet_get_operation(req, &final);
|
|
switch (op) {
|
|
case G_OBEX_OP_CONNECT:
|
|
parse_connect_data(obex, req);
|
|
break;
|
|
case G_OBEX_OP_ABORT:
|
|
break;
|
|
default:
|
|
if (check_connid(obex, req))
|
|
break;
|
|
|
|
return -G_OBEX_RSP_SERVICE_UNAVAILABLE;
|
|
}
|
|
|
|
setup_srm(obex, req, FALSE);
|
|
|
|
return op;
|
|
}
|
|
|
|
static void handle_request(GObex *obex, GObexPacket *req)
|
|
{
|
|
GSList *match;
|
|
int op;
|
|
|
|
op = parse_request(obex, req);
|
|
if (op < 0)
|
|
goto fail;
|
|
|
|
match = g_slist_find_custom(obex->req_handlers, GUINT_TO_POINTER(op),
|
|
req_handler_cmpop);
|
|
if (match) {
|
|
struct req_handler *handler = match->data;
|
|
handler->func(obex, req, handler->user_data);
|
|
return;
|
|
}
|
|
|
|
op = -G_OBEX_RSP_NOT_IMPLEMENTED;
|
|
|
|
fail:
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", g_obex_strerror(-op));
|
|
g_obex_send_rsp(obex, -op, NULL, G_OBEX_HDR_INVALID);
|
|
}
|
|
|
|
static gboolean read_stream(GObex *obex, GError **err)
|
|
{
|
|
GIOChannel *io = obex->io;
|
|
GIOStatus status;
|
|
gsize rbytes, toread;
|
|
guint16 u16;
|
|
char *buf;
|
|
|
|
if (obex->rx_data >= 3)
|
|
goto read_body;
|
|
|
|
rbytes = 0;
|
|
toread = 3 - obex->rx_data;
|
|
buf = (char *) &obex->rx_buf[obex->rx_data];
|
|
|
|
status = g_io_channel_read_chars(io, buf, toread, &rbytes, NULL);
|
|
if (status != G_IO_STATUS_NORMAL)
|
|
return TRUE;
|
|
|
|
obex->rx_data += rbytes;
|
|
if (obex->rx_data < 3)
|
|
goto done;
|
|
|
|
memcpy(&u16, &buf[1], sizeof(u16));
|
|
obex->rx_pkt_len = g_ntohs(u16);
|
|
|
|
if (obex->rx_pkt_len > obex->rx_mtu) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"Too big incoming packet");
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message);
|
|
return FALSE;
|
|
}
|
|
|
|
read_body:
|
|
if (obex->rx_data >= obex->rx_pkt_len)
|
|
goto done;
|
|
|
|
do {
|
|
toread = obex->rx_pkt_len - obex->rx_data;
|
|
buf = (char *) &obex->rx_buf[obex->rx_data];
|
|
|
|
status = g_io_channel_read_chars(io, buf, toread, &rbytes, NULL);
|
|
if (status != G_IO_STATUS_NORMAL)
|
|
goto done;
|
|
|
|
obex->rx_data += rbytes;
|
|
} while (rbytes > 0 && obex->rx_data < obex->rx_pkt_len);
|
|
|
|
done:
|
|
g_obex_dump(G_OBEX_DEBUG_DATA, ">", obex->rx_buf, obex->rx_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean read_packet(GObex *obex, GError **err)
|
|
{
|
|
GIOChannel *io = obex->io;
|
|
GError *read_err = NULL;
|
|
GIOStatus status;
|
|
gsize rbytes;
|
|
guint16 u16;
|
|
|
|
if (obex->rx_data > 0) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"RX buffer not empty before reading packet");
|
|
goto fail;
|
|
}
|
|
|
|
status = g_io_channel_read_chars(io, (char *) obex->rx_buf,
|
|
obex->rx_mtu, &rbytes, &read_err);
|
|
if (status != G_IO_STATUS_NORMAL) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"Unable to read data: %s", read_err->message);
|
|
g_error_free(read_err);
|
|
goto fail;
|
|
}
|
|
|
|
obex->rx_data += rbytes;
|
|
|
|
if (rbytes < 3) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"Incomplete packet received");
|
|
goto fail;
|
|
}
|
|
|
|
memcpy(&u16, &obex->rx_buf[1], sizeof(u16));
|
|
obex->rx_pkt_len = g_ntohs(u16);
|
|
|
|
if (obex->rx_pkt_len != rbytes) {
|
|
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"Data size doesn't match packet size (%zu != %u)",
|
|
rbytes, obex->rx_pkt_len);
|
|
return FALSE;
|
|
}
|
|
|
|
g_obex_dump(G_OBEX_DEBUG_DATA, ">", obex->rx_buf, obex->rx_data);
|
|
|
|
return TRUE;
|
|
fail:
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean incoming_data(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
GObex *obex = user_data;
|
|
GObexPacket *pkt;
|
|
ssize_t header_offset;
|
|
GError *err = NULL;
|
|
guint8 opcode;
|
|
|
|
if (cond & G_IO_NVAL)
|
|
return FALSE;
|
|
|
|
if (cond & (G_IO_HUP | G_IO_ERR)) {
|
|
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_DISCONNECTED,
|
|
"Transport got disconnected");
|
|
goto failed;
|
|
}
|
|
|
|
if (!obex->read(obex, &err))
|
|
goto failed;
|
|
|
|
if (obex->rx_data < 3 || obex->rx_data < obex->rx_pkt_len)
|
|
return TRUE;
|
|
|
|
obex->rx_last_op = obex->rx_buf[0] & ~FINAL_BIT;
|
|
|
|
if (obex->pending_req) {
|
|
struct pending_pkt *p = obex->pending_req;
|
|
opcode = g_obex_packet_get_operation(p->pkt, NULL);
|
|
header_offset = rsp_header_offset(opcode);
|
|
} else {
|
|
opcode = obex->rx_last_op;
|
|
/* Unexpected response -- fail silently */
|
|
if (opcode > 0x1f && opcode != G_OBEX_OP_ABORT) {
|
|
obex->rx_data = 0;
|
|
return TRUE;
|
|
}
|
|
header_offset = req_header_offset(opcode);
|
|
}
|
|
|
|
if (header_offset < 0) {
|
|
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR,
|
|
"Unknown header offset for opcode 0x%02x",
|
|
opcode);
|
|
goto failed;
|
|
}
|
|
|
|
pkt = g_obex_packet_decode(obex->rx_buf, obex->rx_data, header_offset,
|
|
G_OBEX_DATA_REF, &err);
|
|
if (pkt == NULL)
|
|
goto failed;
|
|
|
|
/* Protect against user callback freeing the object */
|
|
g_obex_ref(obex);
|
|
|
|
if (obex->pending_req)
|
|
handle_response(obex, NULL, pkt);
|
|
else
|
|
handle_request(obex, pkt);
|
|
|
|
obex->rx_data = 0;
|
|
|
|
g_obex_unref(obex);
|
|
|
|
if (err != NULL)
|
|
g_error_free(err);
|
|
|
|
if (pkt != NULL)
|
|
g_obex_packet_free(pkt);
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
if (err)
|
|
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
|
|
|
|
g_io_channel_unref(obex->io);
|
|
obex->io = NULL;
|
|
obex->io_source = 0;
|
|
obex->rx_data = 0;
|
|
|
|
/* Protect against user callback freeing the object */
|
|
g_obex_ref(obex);
|
|
|
|
if (obex->pending_req)
|
|
handle_response(obex, err, NULL);
|
|
|
|
if (obex->disconn_func)
|
|
obex->disconn_func(obex, err, obex->disconn_func_data);
|
|
|
|
g_obex_unref(obex);
|
|
|
|
g_error_free(err);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GDebugKey keys[] = {
|
|
{ "error", G_OBEX_DEBUG_ERROR },
|
|
{ "command", G_OBEX_DEBUG_COMMAND },
|
|
{ "transfer", G_OBEX_DEBUG_TRANSFER },
|
|
{ "header", G_OBEX_DEBUG_HEADER },
|
|
{ "packet", G_OBEX_DEBUG_PACKET },
|
|
{ "data", G_OBEX_DEBUG_DATA },
|
|
{ "apparam", G_OBEX_DEBUG_APPARAM },
|
|
};
|
|
|
|
GObex *g_obex_new(GIOChannel *io, GObexTransportType transport_type,
|
|
gssize io_rx_mtu, gssize io_tx_mtu)
|
|
{
|
|
GObex *obex;
|
|
GIOCondition cond;
|
|
|
|
if (gobex_debug == 0) {
|
|
const char *env = g_getenv("GOBEX_DEBUG");
|
|
|
|
if (env) {
|
|
gobex_debug = g_parse_debug_string(env, keys, 7);
|
|
g_setenv("G_MESSAGES_DEBUG", "gobex", FALSE);
|
|
} else
|
|
gobex_debug = G_OBEX_DEBUG_NONE;
|
|
}
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "");
|
|
|
|
if (io == NULL)
|
|
return NULL;
|
|
|
|
if (io_rx_mtu >= 0 && io_rx_mtu < G_OBEX_MINIMUM_MTU)
|
|
return NULL;
|
|
|
|
if (io_tx_mtu >= 0 && io_tx_mtu < G_OBEX_MINIMUM_MTU)
|
|
return NULL;
|
|
|
|
obex = g_new0(GObex, 1);
|
|
|
|
obex->io = g_io_channel_ref(io);
|
|
obex->ref_count = 1;
|
|
obex->conn_id = CONNID_INVALID;
|
|
obex->rx_last_op = G_OBEX_OP_NONE;
|
|
|
|
obex->io_rx_mtu = io_rx_mtu;
|
|
obex->io_tx_mtu = io_tx_mtu;
|
|
|
|
if (io_rx_mtu > G_OBEX_MAXIMUM_MTU)
|
|
obex->rx_mtu = G_OBEX_MAXIMUM_MTU;
|
|
else if (io_rx_mtu < G_OBEX_MINIMUM_MTU)
|
|
obex->rx_mtu = G_OBEX_DEFAULT_MTU;
|
|
else
|
|
obex->rx_mtu = io_rx_mtu;
|
|
|
|
obex->tx_mtu = G_OBEX_MINIMUM_MTU;
|
|
|
|
obex->tx_queue = g_queue_new();
|
|
obex->rx_buf = g_malloc(obex->rx_mtu);
|
|
obex->tx_buf = g_malloc(obex->tx_mtu);
|
|
|
|
switch (transport_type) {
|
|
case G_OBEX_TRANSPORT_STREAM:
|
|
obex->read = read_stream;
|
|
obex->write = write_stream;
|
|
break;
|
|
case G_OBEX_TRANSPORT_PACKET:
|
|
obex->use_srm = TRUE;
|
|
obex->read = read_packet;
|
|
obex->write = write_packet;
|
|
break;
|
|
default:
|
|
g_obex_unref(obex);
|
|
return NULL;
|
|
}
|
|
|
|
g_io_channel_set_encoding(io, NULL, NULL);
|
|
g_io_channel_set_buffered(io, FALSE);
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
obex->io_source = g_io_add_watch(io, cond, incoming_data, obex);
|
|
|
|
return obex;
|
|
}
|
|
|
|
GObex *g_obex_ref(GObex *obex)
|
|
{
|
|
int refs;
|
|
|
|
if (obex == NULL)
|
|
return NULL;
|
|
|
|
refs = __sync_add_and_fetch(&obex->ref_count, 1);
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "ref %u", refs);
|
|
|
|
return obex;
|
|
}
|
|
|
|
void g_obex_unref(GObex *obex)
|
|
{
|
|
int refs;
|
|
|
|
refs = __sync_sub_and_fetch(&obex->ref_count, 1);
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "ref %u", refs);
|
|
|
|
if (refs > 0)
|
|
return;
|
|
|
|
g_slist_free_full(obex->req_handlers, g_free);
|
|
|
|
g_queue_foreach(obex->tx_queue, (GFunc) pending_pkt_free, NULL);
|
|
g_queue_free(obex->tx_queue);
|
|
|
|
if (obex->io != NULL)
|
|
g_io_channel_unref(obex->io);
|
|
|
|
if (obex->io_source > 0)
|
|
g_source_remove(obex->io_source);
|
|
|
|
if (obex->write_source > 0)
|
|
g_source_remove(obex->write_source);
|
|
|
|
g_free(obex->rx_buf);
|
|
g_free(obex->tx_buf);
|
|
g_free(obex->srm);
|
|
|
|
if (obex->pending_req)
|
|
pending_pkt_free(obex->pending_req);
|
|
|
|
if (obex->authchal)
|
|
g_obex_apparam_free(obex->authchal);
|
|
|
|
g_free(obex);
|
|
}
|
|
|
|
/* Higher level functions */
|
|
|
|
guint g_obex_connect(GObex *obex, GObexResponseFunc func, gpointer user_data,
|
|
GError **err, guint8 first_hdr_id, ...)
|
|
{
|
|
GObexPacket *req;
|
|
struct connect_data data;
|
|
va_list args;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "");
|
|
|
|
va_start(args, first_hdr_id);
|
|
req = g_obex_packet_new_valist(G_OBEX_OP_CONNECT, TRUE,
|
|
first_hdr_id, args);
|
|
va_end(args);
|
|
|
|
init_connect_data(obex, &data);
|
|
g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_disconnect(GObex *obex, GObexResponseFunc func, gpointer user_data,
|
|
GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "");
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_DISCONNECT, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_setpath(GObex *obex, const char *path, GObexResponseFunc func,
|
|
gpointer user_data, GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
struct setpath_data data;
|
|
const char *folder;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_SETPATH, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
if (path != NULL && strncmp("..", path, 2) == 0) {
|
|
data.flags = 0x03;
|
|
folder = (path[2] == '/') ? &path[3] : NULL;
|
|
} else {
|
|
data.flags = 0x02;
|
|
folder = path;
|
|
}
|
|
|
|
if (folder != NULL) {
|
|
GObexHeader *hdr;
|
|
hdr = g_obex_header_new_unicode(G_OBEX_HDR_NAME, folder);
|
|
g_obex_packet_add_header(req, hdr);
|
|
}
|
|
|
|
g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_mkdir(GObex *obex, const char *path, GObexResponseFunc func,
|
|
gpointer user_data, GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
struct setpath_data data;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_SETPATH, TRUE, G_OBEX_HDR_NAME, path,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_delete(GObex *obex, const char *name, GObexResponseFunc func,
|
|
gpointer user_data, GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_PUT, TRUE, G_OBEX_HDR_NAME, name,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_copy(GObex *obex, const char *name, const char *dest,
|
|
GObexResponseFunc func, gpointer user_data,
|
|
GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_ACTION, TRUE,
|
|
G_OBEX_HDR_ACTION, G_OBEX_ACTION_COPY,
|
|
G_OBEX_HDR_NAME, name,
|
|
G_OBEX_HDR_DESTNAME, dest,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint g_obex_move(GObex *obex, const char *name, const char *dest,
|
|
GObexResponseFunc func, gpointer user_data,
|
|
GError **err)
|
|
{
|
|
GObexPacket *req;
|
|
|
|
g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id);
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_ACTION, TRUE,
|
|
G_OBEX_HDR_ACTION, G_OBEX_ACTION_MOVE,
|
|
G_OBEX_HDR_NAME, name,
|
|
G_OBEX_HDR_DESTNAME, dest,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
return g_obex_send_req(obex, req, -1, func, user_data, err);
|
|
}
|
|
|
|
guint8 g_obex_errno_to_rsp(int err)
|
|
{
|
|
switch (err) {
|
|
case 0:
|
|
return G_OBEX_RSP_SUCCESS;
|
|
case -EPERM:
|
|
case -EACCES:
|
|
return G_OBEX_RSP_FORBIDDEN;
|
|
case -ENOENT:
|
|
return G_OBEX_RSP_NOT_FOUND;
|
|
case -EINVAL:
|
|
case -EBADR:
|
|
return G_OBEX_RSP_BAD_REQUEST;
|
|
case -EFAULT:
|
|
return G_OBEX_RSP_SERVICE_UNAVAILABLE;
|
|
case -ENOSYS:
|
|
return G_OBEX_RSP_NOT_IMPLEMENTED;
|
|
case -ENOTEMPTY:
|
|
case -EEXIST:
|
|
return G_OBEX_RSP_PRECONDITION_FAILED;
|
|
default:
|
|
return G_OBEX_RSP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
}
|