diff options
Diffstat (limited to 'gisi/client.c')
-rw-r--r-- | gisi/client.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/gisi/client.c b/gisi/client.c new file mode 100644 index 00000000..24b89ecd --- /dev/null +++ b/gisi/client.c @@ -0,0 +1,360 @@ +/* + * This file is part of oFono - Open Source Telephony + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <errno.h> +#include <glib.h> + +#include "socket.h" +#include "client.h" + +struct _GIsiClient { + uint8_t resource; + + /* Requests */ + int fd; + guint source; + uint8_t prev[256], next[256]; + guint timeout[256]; + GIsiResponseFunc func[256]; + void *data[256]; + + /* Indications */ + struct { + int fd; + guint source; + uint16_t count; + GIsiIndicationFunc func[256]; + void *data[256]; + } ind; +}; + +static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond, + gpointer data); +static gboolean g_isi_timeout(gpointer data); + +static inline GIsiRequest *g_isi_req(GIsiClient *cl, uint8_t id) +{ + return (GIsiRequest *)(((uint8_t *)(void *)cl) + id); +} + +static inline uint8_t g_isi_id(void *ptr) +{ + return ((uintptr_t)ptr) & 255; +} + +static inline GIsiClient *g_isi_cl(void *ptr) +{ + return (GIsiClient *)(((uintptr_t)ptr) & ~255); +} + +/** + * Create an ISI client. + * @param resource Phonet resource ID for the client + * @return NULL on error (see errno), an isi_client pointer on success, + */ +GIsiClient *g_isi_client_create(uint8_t resource) +{ + void *ptr; + GIsiClient *cl; + GIOChannel *channel; + unsigned i; + + if (G_UNLIKELY(posix_memalign(&ptr, 256, sizeof(*cl)))) + abort(); + cl = ptr; + cl->resource = resource; + memset(cl->timeout, 0, sizeof(cl->timeout)); + for (i = 0; i < 256; i++) { + cl->data[i] = cl->ind.data[i] = NULL; + cl->func[i] = NULL; + cl->ind.func[i] = NULL; + } + cl->ind.count = 0; + + /* Reserve 0 as head of available IDs, and 255 as head of busy ones */ + cl->prev[0] = 254; + for (i = 0; i < 254; i++) { + cl->next[i] = i + 1; + cl->prev[i + 1] = i; + } + cl->next[254] = 0; + cl->prev[255] = cl->next[255] = 255; + + channel = phonet_new(resource); + if (channel == NULL) { + free(cl); + return NULL; + } + cl->fd = g_io_channel_unix_get_fd(channel); + cl->source = g_io_add_watch(channel, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + g_isi_callback, cl); + g_io_channel_unref(channel); + return cl; +} + +/** + * Destroys an ISI client, cancels all pending transactions and subscriptions. + * @param client client to destroy + */ +void g_isi_client_destroy(GIsiClient *client) +{ + unsigned id; + + g_source_remove(client->source); + for (id = 0; id < 256; id++) + if (client->timeout[id] > 0) + g_source_remove(client->timeout[id]); + if (client->ind.count > 0) + g_source_remove(client->ind.source); + free(client); +} + +/** + * Make an ISI request and register a callback to process the response(s) to + * the resulting transaction. + * @param cl ISI client (from g_isi_client_create()) + * @param buf pointer to request payload + * @param len request payload byte length + * @param cb callback to process response(s) + * @param opaque data for the callback + */ +GIsiRequest *g_isi_request_make(GIsiClient *cl, const void *__restrict buf, + size_t len, unsigned timeout, + GIsiResponseFunc cb, void *opaque) +{ + struct iovec iov[2]; + ssize_t ret; + uint8_t id = cl->next[0]; + + if (id == 0) { + errno = EBUSY; + return NULL; + } + if (cb == NULL) { + errno = EINVAL; + return NULL; + } + iov[0].iov_base = &id; + iov[0].iov_len = 1; + iov[1].iov_base = (void *)buf; + iov[1].iov_len = len; + ret = writev(cl->fd, iov, sizeof(iov) / sizeof(iov[0])); + if (ret == -1) + return NULL; + if (ret != (ssize_t)(len + 2)) { + errno = EMSGSIZE; + return NULL; + } + + cl->func[id] = cb; + cl->data[id] = opaque; + + /* Remove transaction from available list */ + cl->next[0] = cl->next[id]; + cl->prev[cl->next[id]] = 0; + /* Insert into busy list */ + cl->next[id] = cl->next[255]; + cl->prev[cl->next[id]] = id; + cl->next[255] = id; + cl->prev[id] = 255; + + if (timeout > 0) + cl->timeout[id] = g_timeout_add_seconds(timeout, + g_isi_timeout, cl); + else + cl->timeout[id] = 0; + return g_isi_req(cl, id); +} + +/** + * Cancels a pending request, i.e. stop waiting for responses and cancels the + * timeout. + * @param req request to cancel + */ +void g_isi_request_cancel(GIsiRequest *req) +{ + GIsiClient *cl = g_isi_cl(req); + uint8_t id = g_isi_id(req); + + cl->func[id] = NULL; + cl->data[id] = NULL; + + /* Remove transaction from pending circular list */ + cl->prev[cl->next[id]] = cl->prev[id]; + cl->next[cl->prev[id]] = cl->next[id]; + /* Insert transaction into available circular list */ + cl->prev[id] = cl->prev[0]; + cl->prev[0] = id; + cl->next[id] = 0; + cl->next[cl->prev[id]] = id; + + if (cl->timeout[id] > 0) { + g_source_remove(cl->timeout[id]); + cl->timeout[id] = 0; + } +} + +#define PN_COMMGR 0x10 +#define PNS_SUBSCRIBED_RESOURCES_IND 0x10 + +static int g_isi_indication_init(GIsiClient *cl) +{ + uint8_t msg[] = { + 0, PNS_SUBSCRIBED_RESOURCES_IND, 1, cl->resource, + }; + GIOChannel *channel = phonet_new(PN_COMMGR); + + if (channel == NULL) + return errno; + /* Send subscribe indication */ + cl->ind.fd = g_io_channel_unix_get_fd(channel); + send(cl->ind.fd, msg, 4, 0); + cl->ind.source = g_io_add_watch(channel, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + g_isi_callback, cl); + return 0; +} + +static void g_isi_indication_deinit(GIsiClient *client) +{ + uint8_t msg[] = { + 0, PNS_SUBSCRIBED_RESOURCES_IND, 0, + }; + + /* Send empty subscribe indication */ + send(client->ind.fd, msg, 3, 0); + g_source_remove(client->ind.source); +} + +/** + * Subscribe to a given indication type for the resource that an ISI client + * is associated with. If the same type was already subscrived, the old + * subscription is overriden. + * @param cl ISI client (fomr g_isi_client_create()) + * @param type indication type + * @param cb callback to process received indications + * @param data data for the callback + * @return 0 on success, a system error code otherwise. + */ +int g_isi_subscribe(GIsiClient *cl, uint8_t type, + GIsiIndicationFunc cb, void *data) +{ + if (cb == NULL) + return EINVAL; + + if (cl->ind.func[type] == NULL) { + if (cl->ind.count == 0) { + int ret = g_isi_indication_init(cl); + if (ret) + return ret; + } + cl->ind.count++; + } + cl->ind.func[type] = cb; + cl->ind.data[type] = data; + return 0; +} + +/** + * Unsubscribe from a given indication type. + * @param client ISI client (from g_isi_client_create()) + * @param type indication type. + */ +void g_isi_unsubscribe(GIsiClient *client, uint8_t type) +{ + /* Unsubscribe */ + if (client->ind.func[type] == NULL) + return; + client->ind.func[type] = NULL; + if (--client->ind.count == 0) + g_isi_indication_deinit(client); +} + +/* Data callback for both responses and indications */ +static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond, + gpointer data) +{ + GIsiClient *cl = data; + int fd = g_io_channel_unix_get_fd(channel); + bool indication = (fd != cl->fd); + int len; + + if (cond & (G_IO_NVAL|G_IO_HUP)) { + g_warning("Unexpected event on Phonet channel %p", channel); + return FALSE; + } + + len = phonet_peek_length(channel); + { + uint32_t buf[(len + 3) / 4]; + uint16_t obj; + uint8_t res, id; + + len = phonet_read(channel, buf, len, &obj, &res); + if (len < 2 || res != cl->resource) + return TRUE; + memcpy(&id, buf, 1); /* Transaction ID or indication type */ + if (indication) { + if (cl->ind.func[id] == NULL) + return TRUE; /* Unsubscribed indication */ + cl->ind.func[id](cl, buf + 1, len - 1, obj, + cl->ind.data[id]); + } else { + if (cl->func[id] == NULL) + return TRUE; /* Bad transaction ID */ + if ((cl->func[id])(cl, buf + 1, len - 1, obj, + cl->data[id])) + g_isi_request_cancel(g_isi_req(cl, id)); + } + } + return TRUE; +} + +static gboolean g_isi_timeout(gpointer data) +{ + GIsiRequest *req = data; + GIsiClient *cl = g_isi_cl(req); + uint8_t id = g_isi_id(req); + + assert(cl->func[id]); + (cl->func[id])(cl, NULL, 0, 0, cl->data[id]); + g_isi_request_cancel(req); + return FALSE; +} + +int g_isi_client_error(const GIsiClient *client) +{ /* The only possible error at the moment */ + return ETIMEDOUT; +} |