27
27
#include <stdint.h>
28
28
#include <string.h>
29
29
#include <stdlib.h>
31
#include <sys/types.h>
32
#include <sys/socket.h>
39
33
#include "client.h"
41
#define PN_COMMGR 0x10
42
#define PNS_SUBSCRIBED_RESOURCES_IND 0x10
44
static const struct sockaddr_pn commgr = {
45
.spn_family = AF_PHONET,
46
.spn_resource = PN_COMMGR,
50
unsigned int id; /* don't move, see g_isi_cmp */
53
GIsiResponseFunc func;
55
GDestroyNotify notify;
58
struct _GIsiIndication {
59
unsigned int type; /* don't move, see g_isi_cmp */
60
GIsiIndicationFunc func;
63
typedef struct _GIsiIndication GIsiIndication;
65
35
struct _GIsiClient {
79
unsigned int last; /* last used transaction ID */
92
GIsiDebugFunc debug_func;
96
static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond,
98
static gboolean g_isi_timeout(gpointer data);
100
static void g_isi_vdebug(const struct iovec *__restrict iov,
101
size_t iovlen, size_t total_len,
102
GIsiDebugFunc func, void *data)
104
uint8_t debug[total_len];
105
uint8_t *ptr = debug;
108
for (i = 0; i < iovlen; i++) {
109
memcpy(ptr, iov[i].iov_base, iov[i].iov_len);
110
ptr += iov[i].iov_len;
113
func(debug, total_len, data);
117
static int g_isi_cmp(const void *a, const void *b)
119
const unsigned int *ua = (const unsigned int *)a;
120
const unsigned int *ub = (const unsigned int *)b;
126
* Create an ISI client.
127
* @param resource PhoNet resource ID for the client
128
* @return NULL on error (see errno), a GIsiClient pointer on success,
41
uint8_t g_isi_client_resource(GIsiClient *client)
43
return client != NULL ? client->resource : 0;
46
GIsiModem *g_isi_client_modem(GIsiClient *client)
48
return client != NULL ? client->modem : NULL;
130
51
GIsiClient *g_isi_client_create(GIsiModem *modem, uint8_t resource)
132
53
GIsiClient *client;
135
60
client = g_try_new0(GIsiClient, 1);
66
client->timeout = G_ISI_CLIENT_DEFAULT_TIMEOUT;
141
67
client->resource = resource;
142
client->version.major = -1;
143
client->version.minor = -1;
144
68
client->modem = modem;
146
client->debug_func = NULL;
148
client->reqs.last = 0;
149
client->reqs.pending = NULL;
151
client->inds.count = 0;
152
client->inds.subs = NULL;
154
channel = phonet_new(modem, resource);
159
client->reqs.fd = g_io_channel_unix_get_fd(channel);
160
client->reqs.source = g_io_add_watch(channel,
161
G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
162
g_isi_callback, client);
163
g_io_channel_unref(channel);
169
* Set the ISI resource version of @a client.
170
* @param client client for the resource
171
* @param major ISI major version
172
* @param minor ISI minor version
174
void g_isi_version_set(GIsiClient *client, int major, int minor)
179
client->version.major = major;
180
client->version.minor = minor;
184
* Returns the ISI major version of the resource associated with @a
186
* @param client client for the resource
187
* @return major version, -1 if not available
189
int g_isi_version_major(GIsiClient *client)
191
return client ? client->version.major : -1;
195
* Returns the ISI minor version of the resource associated with @a
197
* @param client client for the resource
198
* @return minor version, -1 if not available
200
int g_isi_version_minor(GIsiClient *client)
202
return client ? client->version.minor : -1;
206
* Set the server object for the resource associated with @a
208
* @param client client for the resource
209
* @param server object
211
void g_isi_server_object_set(GIsiClient *client, uint16_t obj)
216
client->server_obj = obj;
220
* Returns the server object for the the resource associated with @a
222
* @param client client for the resource
223
* @return server object
225
uint8_t g_isi_server_object(GIsiClient *client)
227
return client ? client->server_obj : 0;
231
* Returns the resource associated with @a client
232
* @param client client for the resource
233
* @return PhoNet resource ID for the client
235
uint8_t g_isi_client_resource(GIsiClient *client)
237
return client ? client->resource : 0;
241
* Set a debugging function for @a client. This function will be
242
* called whenever an ISI protocol message is sent or received.
243
* @param client client to debug
244
* @param func debug function
245
* @param opaque user data
247
void g_isi_client_set_debug(GIsiClient *client, GIsiDebugFunc func,
253
client->debug_func = func;
254
client->debug_data = opaque;
257
static void g_isi_cleanup_req(void *data)
259
GIsiRequest *req = data;
264
/* Finalize any pending requests */
265
req->client->error = ESHUTDOWN;
267
req->func(req->client, NULL, 0, 0, req->data);
268
req->client->error = 0;
271
req->notify(req->data);
273
if (req->timeout > 0)
274
g_source_remove(req->timeout);
279
static void g_isi_cleanup_ind(void *data)
281
GIsiIndication *ind = data;
290
* Destroys an ISI client, cancels all pending transactions and subscriptions.
291
* @param client client to destroy (may be NULL)
73
void g_isi_client_reset(GIsiClient *client)
75
g_isi_remove_pending_by_owner(client->modem, client->resource, client);
293
78
void g_isi_client_destroy(GIsiClient *client)
298
tdestroy(client->reqs.pending, g_isi_cleanup_req);
299
if (client->reqs.source > 0)
300
g_source_remove(client->reqs.source);
302
tdestroy(client->inds.subs, g_isi_cleanup_ind);
303
client->inds.subs = NULL;
304
client->inds.count = 0;
305
g_isi_commit_subscriptions(client);
306
if (client->inds.source > 0)
307
g_source_remove(client->inds.source);
83
g_isi_client_reset(client);
313
* Make an ISI request and register a callback to process the response(s) to
314
* the resulting transaction.
315
* @param cl ISI client (from g_isi_client_create())
316
* @param buf pointer to request payload
317
* @param len request payload byte length
318
* @param timeout timeout in seconds
319
* @param cb callback to process response(s)
320
* @param opaque data for the callback
322
GIsiRequest *g_isi_request_make(GIsiClient *client, const void *__restrict buf,
323
size_t len, unsigned timeout,
324
GIsiResponseFunc cb, void *opaque)
326
return g_isi_send(client, buf, len, timeout, cb, opaque, NULL);
330
* Make an ISI request and register a callback to process the response(s) to
331
* the resulting transaction.
332
* @param cl ISI client (from g_isi_client_create())
333
* @param iov scatter-gather array to the request payload
334
* @param iovlen number of vectors in the scatter-gather array
335
* @param timeout timeout in seconds
336
* @param cb callback to process response(s)
337
* @param opaque data for the callback
339
GIsiRequest *g_isi_request_vmake(GIsiClient *client, const struct iovec *iov,
340
size_t iovlen, unsigned timeout,
341
GIsiResponseFunc func, void *opaque)
343
return g_isi_vsend(client, iov, iovlen, timeout, func, opaque, NULL);
347
* Send an ISI request to a specific Phonet address and register a callback
348
* to process the response(s) to the resulting transaction.
350
* @param client ISI client (from g_isi_client_create())
351
* @param dst Phonet destination address
352
* @param buf pointer to request payload
353
* @param len request payload byte length
354
* @param timeout timeout in seconds
355
* @param cb callback to process response(s)
356
* @param opaque data for the callback
357
* @param notify finalizer function for the @a opaque data (may be NULL)
360
* A pointer to a newly created GIsiRequest.
363
* If an error occurs, @a errno is set accordingly and a NULL pointer is
366
GIsiRequest *g_isi_sendto(GIsiClient *client,
367
struct sockaddr_pn *dst,
87
void g_isi_client_set_timeout(GIsiClient *client, unsigned timeout)
92
client->timeout = timeout;
95
gboolean g_isi_client_send(GIsiClient *client,
96
const void *__restrict msg, size_t len,
97
GIsiNotifyFunc notify, void *data,
98
GDestroyNotify destroy)
102
op = g_isi_request_send(client->modem, client->resource, msg, len,
103
client->timeout, notify, data, destroy);
105
g_isi_pending_set_owner(op, client);
110
gboolean g_isi_client_send_with_timeout(GIsiClient *client,
368
111
const void *__restrict buf, size_t len,
369
112
unsigned timeout,
370
GIsiResponseFunc cb, void *opaque,
371
GDestroyNotify notify)
373
const struct iovec iov = {
374
.iov_base = (void *)buf,
378
return g_isi_vsendto(client, dst, &iov, 1, timeout, cb, opaque, notify);
383
* Send an ISI request and register a callback to process the response(s) to
384
* the resulting transaction.
386
* @param cl ISI client (from g_isi_client_create())
387
* @param buf pointer to request payload
388
* @param len request payload byte length
389
* @param timeout timeout in seconds
390
* @param cb callback to process response(s)
391
* @param opaque data for the callback
392
* @param notify finalizer function for the @a opaque data (may be NULL)
395
* A pointer to a newly created GIsiRequest.
398
* If an error occurs, @a errno is set accordingly and a NULL pointer is
401
GIsiRequest *g_isi_send(GIsiClient *client,
402
const void *__restrict buf, size_t len,
404
GIsiResponseFunc cb, void *opaque,
405
GDestroyNotify notify)
407
const struct iovec iov = {
408
.iov_base = (void *)buf,
412
return g_isi_vsend(client, &iov, 1, timeout, cb, opaque, notify);
417
* Send an ISI request to a specific Phonet address and register a callback
418
* to process the response(s) to the resulting transaction.
420
* @param client ISI client (from g_isi_client_create())
421
* @param dst Phonet destination address
422
* @param iov scatter-gather array to the request payload
423
* @param iovlen number of vectors in the scatter-gather array
424
* @param timeout timeout in seconds
425
* @param cb callback to process response(s)
426
* @param opaque data for the callback
427
* @param notify finalizer function for the @a opaque data (may be NULL)
430
* A pointer to a newly created GIsiRequest.
433
* If an error occurs, @a errno is set accordingly and a NULL pointer is
436
GIsiRequest *g_isi_vsendto(GIsiClient *client,
437
struct sockaddr_pn *dst,
438
const struct iovec *__restrict iov,
439
size_t iovlen, unsigned timeout,
440
GIsiResponseFunc cb, void *opaque,
441
GDestroyNotify notify)
443
struct iovec _iov[1 + iovlen];
444
struct msghdr msg = {
445
.msg_name = (void *)dst,
446
.msg_namelen = sizeof(*dst),
448
.msg_iovlen = 1 + iovlen,
458
GIsiRequest *req = NULL;
466
key = 1 + ((client->reqs.last + 1) % 255);
469
req = g_try_new0(GIsiRequest, 1);
475
req->client = client;
479
req->notify = notify;
481
old = tsearch(req, &client->reqs.pending, g_isi_cmp);
490
old = tfind(&key, &client->reqs.pending, g_isi_cmp);
493
/* FIXME: perhaps retry with randomized access after
494
* initial miss. Although if the rate at which
495
* requests are sent is so high that the transaction
496
* ID wraps it's likely there is something wrong and
497
* we might as well fail here. */
503
_iov[0].iov_base = &id;
506
for (i = 0, len = 1; i < iovlen; i++) {
507
_iov[1 + i] = iov[i];
508
len += iov[i].iov_len;
511
if (client->debug_func)
512
g_isi_vdebug(iov, iovlen, len - 1, client->debug_func,
515
ret = sendmsg(client->reqs.fd, &msg, MSG_NOSIGNAL);
519
if (ret != (ssize_t)len) {
525
req->timeout = g_timeout_add_seconds(timeout, g_isi_timeout,
527
client->reqs.last = key;
531
tdelete(req, &client->reqs.pending, g_isi_cmp);
538
* Send an ISI request and register a callback to process the response(s) to
539
* the resulting transaction.
541
* @param cl ISI client (from g_isi_client_create())
542
* @param iov scatter-gather array to the request payload
543
* @param iovlen number of vectors in the scatter-gather array
544
* @param timeout timeout in seconds
545
* @param cb callback to process response(s)
546
* @param opaque data for the callback
547
* @param notify finalizer function for the @a opaque data (may be NULL)
550
* A pointer to a newly created GIsiRequest.
553
* If an error occurs, @a errno is set accordingly and a NULL pointer is
556
GIsiRequest *g_isi_vsend(GIsiClient *client,
557
const struct iovec *__restrict iov,
558
size_t iovlen, unsigned timeout,
559
GIsiResponseFunc cb, void *opaque,
560
GDestroyNotify notify)
562
struct sockaddr_pn dst = {
563
.spn_family = AF_PHONET,
571
dst.spn_resource = client->resource;
573
return g_isi_vsendto(client, &dst, iov, iovlen, timeout,
578
* Cancels a pending request, i.e. stop waiting for responses and cancels the
580
* @param req request to cancel
582
void g_isi_request_cancel(GIsiRequest *req)
587
if (req->timeout > 0)
588
g_source_remove(req->timeout);
590
tdelete(req, &req->client->reqs.pending, g_isi_cmp);
593
req->notify(req->data);
598
static uint8_t *__msg;
599
static void build_subscribe_msg(const void *nodep,
603
GIsiIndication *ind = *(GIsiIndication **)nodep;
604
uint8_t res = ind->type >> 8;
609
if (__msg[2] && res == __msg[2+__msg[2]])
612
__msg[2+__msg[2]] = res;
620
* Subscribe indications from the modem.
621
* @param client ISI client (from g_isi_client_create())
622
* @return 0 on success, a system error code otherwise.
624
int g_isi_commit_subscriptions(GIsiClient *client)
627
uint8_t msg[3+256] = {
628
0, PNS_SUBSCRIBED_RESOURCES_IND,
635
if (!client->inds.source) {
636
if (client->inds.count == 0)
639
channel = phonet_new(client->modem, PN_COMMGR);
643
client->inds.fd = g_io_channel_unix_get_fd(channel);
645
client->inds.source = g_io_add_watch(channel,
648
g_isi_callback, client);
650
g_io_channel_unref(channel);
654
twalk(client->inds.subs, build_subscribe_msg);
656
/* Subscribe by sending an indication */
657
sendto(client->inds.fd, msg, 3+msg[2], MSG_NOSIGNAL, (void *)&commgr,
663
* Add subscription for a given indication type from the given resource.
664
* If the same type was already subscribed, the old subscription
665
* is overriden. Subscriptions for newly added resources do not become
666
* effective until g_isi_commit_subscriptions() has been called.
667
* @param client ISI client (from g_isi_client_create())
668
* @param res resource id
669
* @param type indication type
670
* @param cb callback to process received indications
671
* @param data data for the callback
672
* @return 0 on success, a system error code otherwise.
674
int g_isi_add_subscription(GIsiClient *client, uint8_t res, uint8_t type,
675
GIsiIndicationFunc cb, void *data)
678
GIsiIndication **old;
680
if (client == NULL || cb == NULL)
683
ind = g_try_new0(GIsiIndication, 1);
687
ind->type = (res << 8) | type;
689
old = tsearch(ind, &client->inds.subs, g_isi_cmp);
695
/* FIXME: This overrides any existing subscription. We should
696
* enable multiple subscriptions to a single indication in
697
* order to allow efficient client sharing. */
702
client->inds.count++;
711
* Subscribe to a given indication type for the resource that an ISI client
712
* is associated with. If the same type was already subscribed, the old
713
* subscription is overriden. For multiple subscriptions,
714
* g_isi_add_subcription() and g_isi_commit_subscriptions() should be used
716
* @param cl ISI client (from g_isi_client_create())
717
* @param type indication type
718
* @param cb callback to process received indications
719
* @param data data for the callback
720
* @return 0 on success, a system error code otherwise.
722
int g_isi_subscribe(GIsiClient *client, uint8_t type,
723
GIsiIndicationFunc cb, void *data)
730
ret = g_isi_add_subscription(client, client->resource, type, cb, data);
734
return g_isi_commit_subscriptions(client);
738
* Remove subscription for a given indication type from the given resource.
739
* g_isi_commit_subcsriptions() should be called after modifications to
740
* cancel unnecessary resource subscriptions from the modem.
741
* @param client ISI client (from g_isi_client_create())
742
* @param res resource id
743
* @param type indication type
745
void g_isi_remove_subscription(GIsiClient *client, uint8_t res, uint8_t type)
749
unsigned int id = (res << 8) | type;
754
ret = tfind(&id, &client->inds.subs, g_isi_cmp);
758
ind = *(GIsiIndication **)ret;
759
tdelete(ind, &client->inds.subs, g_isi_cmp);
760
client->inds.count--;
765
* Unsubscribe from a given indication type. For removing multiple
766
* subscriptions, g_isi_remove_subcription() and
767
* g_isi_commit_subscriptions() should be used instead.
768
* @param client ISI client (from g_isi_client_create())
769
* @param type indication type.
771
void g_isi_unsubscribe(GIsiClient *client, uint8_t type)
776
g_isi_remove_subscription(client, client->resource, type);
777
g_isi_commit_subscriptions(client);
780
static void g_isi_dispatch_indication(GIsiClient *client, uint8_t res,
781
uint16_t obj, uint8_t *msg,
786
unsigned type = (res << 8) | msg[0];
788
ret = tfind(&type, &client->inds.subs, g_isi_cmp);
792
ind = *(GIsiIndication **)ret;
795
ind->func(client, msg, len, obj, ind->data);
798
static void g_isi_dispatch_response(GIsiClient *client, uint8_t res,
799
uint16_t obj, uint8_t *msg,
804
unsigned id = msg[0];
806
ret = tfind(&id, &client->reqs.pending, g_isi_cmp);
808
/* This could either be an unsolicited response, which
809
* we will ignore, or an incoming request, which we
810
* handle just like an incoming indication */
811
g_isi_dispatch_indication(client, res, obj, msg + 1, len - 1);
815
req = *(GIsiRequest **)ret;
817
if (!req->func || req->func(client, msg + 1, len - 1, obj, req->data))
818
g_isi_request_cancel(req);
821
/* Data callback for both responses and indications */
822
static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond,
825
GIsiClient *client = data;
826
int fd = g_io_channel_unix_get_fd(channel);
829
if (cond & (G_IO_NVAL|G_IO_HUP)) {
830
g_warning("Unexpected event on Phonet channel %p", channel);
834
len = phonet_peek_length(channel);
837
uint32_t buf[(len + 3) / 4];
842
len = phonet_read(channel, buf, len, &obj, &res);
846
msg = (uint8_t *)buf;
848
if (client->debug_func)
849
client->debug_func(msg + 1, len - 1,
852
if (fd == client->reqs.fd)
853
g_isi_dispatch_response(client, res, obj, msg, len);
855
/* Transaction field at first byte is
856
* discarded with indications */
857
g_isi_dispatch_indication(client, res, obj, msg + 1,
863
static gboolean g_isi_timeout(gpointer data)
865
GIsiRequest *req = data;
867
req->client->error = ETIMEDOUT;
869
req->func(req->client, NULL, 0, 0, req->data);
870
req->client->error = 0;
872
g_isi_request_cancel(req);
876
int g_isi_client_error(const GIsiClient *client)
878
return -client->error;
113
GIsiNotifyFunc notify, void *data,
114
GDestroyNotify destroy)
118
op = g_isi_request_send(client->modem, client->resource, buf, len,
119
timeout, notify, data, destroy);
121
g_isi_pending_set_owner(op, client);
126
gboolean g_isi_client_vsend(GIsiClient *client,
127
const struct iovec *iov, size_t iovlen,
128
GIsiNotifyFunc notify, void *data,
129
GDestroyNotify destroy)
133
op = g_isi_request_vsend(client->modem, client->resource, iov, iovlen,
134
client->timeout, notify, data, destroy);
136
g_isi_pending_set_owner(op, client);
141
gboolean g_isi_client_vsend_with_timeout(GIsiClient *client,
142
const struct iovec *__restrict iov,
143
size_t iovlen, unsigned timeout,
144
GIsiNotifyFunc notify, void *data,
145
GDestroyNotify destroy)
149
op = g_isi_request_vsend(client->modem, client->resource, iov, iovlen,
150
timeout, notify, data, destroy);
152
g_isi_pending_set_owner(op, client);
157
gboolean g_isi_client_ind_subscribe(GIsiClient *client, uint8_t type,
158
GIsiNotifyFunc notify, void *data)
162
op = g_isi_ind_subscribe(client->modem, client->resource, type,
165
g_isi_pending_set_owner(op, client);
170
gboolean g_isi_client_ntf_subscribe(GIsiClient *client, uint8_t type,
171
GIsiNotifyFunc notify, void *data)
175
op = g_isi_ntf_subscribe(client->modem, client->resource, type,
178
g_isi_pending_set_owner(op, client);
183
gboolean g_isi_client_verify(GIsiClient *client, GIsiNotifyFunc notify,
184
void *data, GDestroyNotify destroy)
188
op = g_isi_resource_ping(client->modem, client->resource,
189
notify, data, destroy);
191
g_isi_pending_set_owner(op, client);