Fix multiline responses in GAtChat
[connman] / gatchat / gatchat.c
1 /*
2  *
3  *  AT chat library with GLib integration
4  *
5  *  Copyright (C) 2008-2009  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <assert.h>
29
30 #include <glib.h>
31
32 #include "ringbuffer.h"
33 #include "gatresult.h"
34 #include "gatchat.h"
35
36 /* #define WRITE_SCHEDULER_DEBUG 1 */
37
38 static void g_at_chat_wakeup_writer(GAtChat *chat);
39
40 enum chat_state {
41         PARSER_STATE_IDLE = 0,
42         PARSER_STATE_INITIAL_CR,
43         PARSER_STATE_INITIAL_LF,
44         PARSER_STATE_RESPONSE,
45         PARSER_STATE_TERMINATOR_CR,
46         PARSER_STATE_RESPONSE_COMPLETE,
47         PARSER_STATE_GUESS_MULTILINE_RESPONSE,
48         PARSER_STATE_PDU,
49         PARSER_STATE_PDU_CR,
50         PARSER_STATE_PDU_COMPLETE,
51         PARSER_STATE_PROMPT,
52         PARSER_STATE_PROMPT_COMPLETE
53 };
54
55 struct at_command {
56         char *cmd;
57         char **prefixes;
58         guint id;
59         GAtResultFunc callback;
60         gpointer user_data;
61         GDestroyNotify notify;
62 };
63
64 struct at_notify_node {
65         guint id;
66         GAtNotifyFunc callback;
67         gpointer user_data;
68         GDestroyNotify notify;
69 };
70
71 struct at_notify {
72         GSList *nodes;
73         gboolean pdu;
74 };
75
76 struct _GAtChat {
77         gint ref_count;                         /* Ref count */
78         guint next_cmd_id;                      /* Next command id */
79         guint next_notify_id;                   /* Next notify id */
80         guint read_watch;                       /* GSource read id, 0 if none */
81         guint write_watch;                      /* GSource write id, 0 if none */
82         GIOChannel *channel;                    /* channel */
83         GQueue *command_queue;                  /* Command queue */
84         guint cmd_bytes_written;                /* bytes written from cmd */
85         GHashTable *notify_list;                /* List of notification reg */
86         GAtDisconnectFunc user_disconnect;      /* user disconnect func */
87         gpointer user_disconnect_data;          /* user disconnect data */
88         struct ring_buffer *buf;                /* Current read buffer */
89         guint read_so_far;                      /* Number of bytes processed */
90         gboolean disconnecting;                 /* Whether we're disconnecting */
91         enum chat_state state;          /* Current chat state */
92         int flags;
93         char *pdu_notify;                       /* Unsolicited Resp w/ PDU */
94         GSList *response_lines;                 /* char * lines of the response */
95         char *wakeup;                           /* command sent to wakeup modem */
96         gdouble inactivity_time;                /* Period of inactivity */
97         guint wakeup_timeout;                   /* How long to wait for resp */
98         GTimer *wakeup_timer;                   /* Keep track of elapsed time */
99 };
100
101 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
102 {
103         const struct at_notify_node *node = a;
104         guint id = GPOINTER_TO_UINT(b);
105
106         if (node->id < id)
107                 return -1;
108
109         if (node->id > id)
110                 return 1;
111
112         return 0;
113 }
114
115 static void at_notify_node_destroy(struct at_notify_node *node)
116 {
117         if (node->notify)
118                 node->notify(node->user_data);
119
120         g_free(node);
121 }
122
123 static void at_notify_destroy(struct at_notify *notify)
124 {
125         g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
126         g_free(notify);
127 }
128
129 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
130 {
131         const struct at_command *command = a;
132         guint id = GPOINTER_TO_UINT(b);
133
134         if (command->id < id)
135                 return -1;
136
137         if (command->id > id)
138                 return 1;
139
140         return 0;
141 }
142
143 static struct at_command *at_command_create(const char *cmd,
144                                                 const char **prefix_list,
145                                                 GAtResultFunc func,
146                                                 gpointer user_data,
147                                                 GDestroyNotify notify)
148 {
149         struct at_command *c;
150         gsize len;
151         char **prefixes = NULL;
152
153         if (prefix_list) {
154                 int num_prefixes = 0;
155                 int i;
156
157                 while (prefix_list[num_prefixes])
158                         num_prefixes += 1;
159
160                 prefixes = g_new(char *, num_prefixes + 1);
161
162                 for (i = 0; i < num_prefixes; i++)
163                         prefixes[i] = strdup(prefix_list[i]);
164
165                 prefixes[num_prefixes] = NULL;
166         }
167
168         c = g_try_new0(struct at_command, 1);
169
170         if (!c)
171                 return 0;
172
173         len = strlen(cmd);
174         c->cmd = g_try_new(char, len + 2);
175
176         if (!c->cmd) {
177                 g_free(c);
178                 return 0;
179         }
180
181         memcpy(c->cmd, cmd, len);
182
183         /* If we have embedded '\r' then this is a command expecting a prompt
184          * from the modem.  Embed Ctrl-Z at the very end automatically
185          */
186         if (strchr(cmd, '\r'))
187                 c->cmd[len] = 26;
188         else
189                 c->cmd[len] = '\r';
190
191         c->cmd[len+1] = '\0';
192
193         c->prefixes = prefixes;
194         c->callback = func;
195         c->user_data = user_data;
196         c->notify = notify;
197
198         return c;
199 }
200
201 static void at_command_destroy(struct at_command *cmd)
202 {
203         if (cmd->notify)
204                 cmd->notify(cmd->user_data);
205
206         g_strfreev(cmd->prefixes);
207         g_free(cmd->cmd);
208         g_free(cmd);
209 }
210
211 static void g_at_chat_cleanup(GAtChat *chat)
212 {
213         struct at_command *c;
214
215         ring_buffer_free(chat->buf);
216         chat->buf = NULL;
217
218         /* Cleanup pending commands */
219         while ((c = g_queue_pop_head(chat->command_queue)))
220                 at_command_destroy(c);
221
222         g_queue_free(chat->command_queue);
223         chat->command_queue = NULL;
224
225         /* Cleanup any response lines we have pending */
226         g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
227         g_slist_free(chat->response_lines);
228         chat->response_lines = NULL;
229
230         /* Cleanup registered notifications */
231         g_hash_table_destroy(chat->notify_list);
232         chat->notify_list = NULL;
233
234         if (chat->pdu_notify) {
235                 g_free(chat->pdu_notify);
236                 chat->pdu_notify = NULL;
237         }
238
239         if (chat->wakeup) {
240                 g_free(chat->wakeup);
241                 chat->wakeup = NULL;
242         }
243
244         if (chat->wakeup_timer) {
245                 g_timer_destroy(chat->wakeup_timer);
246                 chat->wakeup_timer = 0;
247         }
248 }
249
250 static void read_watcher_destroy_notify(GAtChat *chat)
251 {
252         chat->read_watch = 0;
253
254         if (chat->disconnecting)
255                 return;
256
257         chat->channel = NULL;
258
259         g_at_chat_cleanup(chat);
260
261         if (chat->user_disconnect)
262                 chat->user_disconnect(chat->user_disconnect_data);
263 }
264
265 static void write_watcher_destroy_notify(GAtChat *chat)
266 {
267         chat->write_watch = 0;
268 }
269
270 static void at_notify_call_callback(gpointer data, gpointer user_data)
271 {
272         struct at_notify_node *node = data;
273         GAtResult *result = user_data;
274
275         node->callback(result, node->user_data);
276 }
277
278 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
279 {
280         GHashTableIter iter;
281         struct at_notify *notify;
282         char *prefix;
283         gpointer key, value;
284         gboolean ret = FALSE;
285         GAtResult result;
286
287         g_hash_table_iter_init(&iter, chat->notify_list);
288         result.lines = 0;
289         result.final_or_pdu = 0;
290
291         while (g_hash_table_iter_next(&iter, &key, &value)) {
292                 prefix = key;
293                 notify = value;
294
295                 if (!g_str_has_prefix(line, key))
296                         continue;
297
298                 if (notify->pdu) {
299                         chat->pdu_notify = line;
300                         chat->state = PARSER_STATE_PDU;
301                         return TRUE;
302                 }
303
304                 if (!result.lines)
305                         result.lines = g_slist_prepend(NULL, line);
306
307                 g_slist_foreach(notify->nodes, at_notify_call_callback,
308                                         &result);
309                 ret = TRUE;
310         }
311
312         if (ret) {
313                 g_slist_free(result.lines);
314                 g_free(line);
315                 chat->state = PARSER_STATE_IDLE;
316         }
317
318         return ret;
319 }
320
321 static void g_at_chat_finish_command(GAtChat *p, gboolean ok,
322                                                 char *final)
323 {
324         struct at_command *cmd = g_queue_pop_head(p->command_queue);
325
326         /* Cannot happen, but lets be paranoid */
327         if (!cmd)
328                 return;
329
330         if (cmd->callback) {
331                 GAtResult result;
332
333                 p->response_lines = g_slist_reverse(p->response_lines);
334
335                 result.final_or_pdu = final;
336                 result.lines = p->response_lines;
337
338                 cmd->callback(ok, &result, cmd->user_data);
339         }
340
341         g_slist_foreach(p->response_lines, (GFunc)g_free, NULL);
342         g_slist_free(p->response_lines);
343         p->response_lines = NULL;
344
345         g_free(final);
346
347         at_command_destroy(cmd);
348
349         p->cmd_bytes_written = 0;
350
351         if (g_queue_peek_head(p->command_queue))
352                 g_at_chat_wakeup_writer(p);
353 }
354
355 struct terminator_info {
356         const char *terminator;
357         int len;
358         gboolean success;
359 };
360
361 static struct terminator_info terminator_table[] = {
362         { "OK", -1, TRUE },
363         { "ERROR", -1, FALSE },
364         { "NO DIALTONE", -1, FALSE },
365         { "BUSY", -1, FALSE },
366         { "NO CARRIER", -1, FALSE },
367         { "CONNECT", -1, TRUE },
368         { "NO ANSWER", -1, FALSE },
369         { "+CMS ERROR:", 11, FALSE },
370         { "+CME ERROR:", 11, FALSE },
371         { "+EXT ERROR:", 11, FALSE }
372 };
373
374 static gboolean g_at_chat_handle_command_response(GAtChat *p,
375                                                         struct at_command *cmd,
376                                                         char *line)
377 {
378         int i;
379         int size = sizeof(terminator_table) / sizeof(struct terminator_info);
380
381         p->state = PARSER_STATE_IDLE;
382
383         for (i = 0; i < size; i++) {
384                 struct terminator_info *info = &terminator_table[i];
385
386                 if (info->len == -1 && !strcmp(line, info->terminator)) {
387                         g_at_chat_finish_command(p, info->success, line);
388                         return TRUE;
389                 }
390
391                 if (info->len > 0 &&
392                         !strncmp(line, info->terminator, info->len)) {
393                         g_at_chat_finish_command(p, info->success, line);
394                         return TRUE;
395                 }
396         }
397
398         if (cmd->prefixes) {
399                 int i;
400
401                 for (i = 0; cmd->prefixes[i]; i++)
402                         if (g_str_has_prefix(line, cmd->prefixes[i]))
403                                 goto out;
404
405                 return FALSE;
406         }
407
408 out:
409         if (!(p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
410                 p->state = PARSER_STATE_GUESS_MULTILINE_RESPONSE;
411
412         p->response_lines = g_slist_prepend(p->response_lines,
413                                                 line);
414
415         return TRUE;
416 }
417
418 static void have_line(GAtChat *p)
419 {
420         /* We're not going to copy terminal <CR><LF> */
421         unsigned int len = p->read_so_far - 2;
422         char *str;
423         struct at_command *cmd;
424
425         /* If we have preceding <CR><LF> modify the len */
426         if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
427                 len -= 2;
428
429         /* Make sure we have terminal null */
430         str = g_try_new(char, len + 1);
431
432         if (!str) {
433                 ring_buffer_drain(p->buf, p->read_so_far);
434                 return;
435         }
436
437         if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
438                 ring_buffer_drain(p->buf, 2);
439         ring_buffer_read(p->buf, str, len);
440         ring_buffer_drain(p->buf, 2);
441
442         str[len] = '\0';
443
444         /* Check for echo, this should not happen, but lets be paranoid */
445         if (!strncmp(str, "AT", 2) == TRUE)
446                 goto done;
447
448         cmd = g_queue_peek_head(p->command_queue);
449
450         if (cmd && p->cmd_bytes_written == strlen(cmd->cmd) &&
451                 g_at_chat_handle_command_response(p, cmd, str))
452                 return;
453
454         if (g_at_chat_match_notify(p, str) == TRUE)
455                 return;
456
457 done:
458         /* No matches & no commands active, ignore line */
459         g_free(str);
460         p->state = PARSER_STATE_IDLE;
461 }
462
463 static void have_pdu(GAtChat *p)
464 {
465         unsigned int len = p->read_so_far - 2;
466         char *pdu;
467         GHashTableIter iter;
468         struct at_notify *notify;
469         char *prefix;
470         gpointer key, value;
471         GAtResult result;
472
473         pdu = g_try_new(char, len + 1);
474
475         if (!pdu) {
476                 ring_buffer_drain(p->buf, p->read_so_far);
477                 goto out;
478         }
479
480         ring_buffer_read(p->buf, pdu, len);
481         ring_buffer_drain(p->buf, 2);
482
483         pdu[len] = '\0';
484
485         result.lines = g_slist_prepend(NULL, p->pdu_notify);
486         result.final_or_pdu = pdu;
487
488         g_hash_table_iter_init(&iter, p->notify_list);
489
490         while (g_hash_table_iter_next(&iter, &key, &value)) {
491                 prefix = key;
492                 notify = value;
493
494                 if (!g_str_has_prefix(p->pdu_notify, prefix))
495                         continue;
496
497                 if (!notify->pdu)
498                         continue;
499
500                 g_slist_foreach(notify->nodes, at_notify_call_callback,
501                                         &result);
502         }
503
504         g_slist_free(result.lines);
505
506 out:
507         g_free(p->pdu_notify);
508         p->pdu_notify = NULL;
509
510         if (pdu)
511                 g_free(pdu);
512
513         p->state = PARSER_STATE_IDLE;
514 }
515
516 static inline void parse_char(GAtChat *chat, char byte)
517 {
518         switch (chat->state) {
519         case PARSER_STATE_IDLE:
520                 if (byte == '\r')
521                         chat->state = PARSER_STATE_INITIAL_CR;
522                 else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) {
523                         if (byte == '>')
524                                 chat->state = PARSER_STATE_PROMPT;
525                         else
526                                 chat->state = PARSER_STATE_RESPONSE;
527                 }
528                 break;
529
530         case PARSER_STATE_INITIAL_CR:
531                 if (byte == '\n')
532                         chat->state = PARSER_STATE_INITIAL_LF;
533                 else if (byte != '\r' && /* Echo & no <CR><LF>?! */
534                         (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
535                         chat->state = PARSER_STATE_RESPONSE;
536                 else if (byte != '\r')
537                         chat->state = PARSER_STATE_IDLE;
538                 break;
539
540         case PARSER_STATE_INITIAL_LF:
541                 if (byte == '\r')
542                         chat->state = PARSER_STATE_TERMINATOR_CR;
543                 else if (byte == '>')
544                         chat->state = PARSER_STATE_PROMPT;
545                 else
546                         chat->state = PARSER_STATE_RESPONSE;
547                 break;
548
549         case PARSER_STATE_RESPONSE:
550                 if (byte == '\r')
551                         chat->state = PARSER_STATE_TERMINATOR_CR;
552                 break;
553
554         case PARSER_STATE_TERMINATOR_CR:
555                 if (byte == '\n')
556                         chat->state = PARSER_STATE_RESPONSE_COMPLETE;
557                 else
558                         chat->state = PARSER_STATE_IDLE;
559                 break;
560
561         case PARSER_STATE_GUESS_MULTILINE_RESPONSE:
562                 if (byte == '\r')
563                         chat->state = PARSER_STATE_INITIAL_CR;
564                 else
565                         chat->state = PARSER_STATE_RESPONSE;
566                 break;
567
568         case PARSER_STATE_PDU:
569                 if (byte == '\r')
570                         chat->state = PARSER_STATE_PDU_CR;
571                 break;
572
573         case PARSER_STATE_PDU_CR:
574                 if (byte == '\n')
575                         chat->state = PARSER_STATE_PDU_COMPLETE;
576                 break;
577
578         case PARSER_STATE_PROMPT:
579                 if (byte == ' ')
580                         chat->state = PARSER_STATE_PROMPT_COMPLETE;
581                 else
582                         chat->state = PARSER_STATE_RESPONSE;
583
584         case PARSER_STATE_RESPONSE_COMPLETE:
585         case PARSER_STATE_PDU_COMPLETE:
586         default:
587                 /* This really shouldn't happen */
588                 assert(TRUE);
589                 return;
590         }
591 }
592
593 static void new_bytes(GAtChat *p)
594 {
595         unsigned int len = ring_buffer_len(p->buf);
596         unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
597         unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
598
599         while (p->read_so_far < len) {
600                 parse_char(p, *buf);
601
602                 buf += 1;
603                 p->read_so_far += 1;
604
605                 if (p->read_so_far == wrap) {
606                         buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
607                         wrap = len;
608                 }
609
610                 if (p->state == PARSER_STATE_RESPONSE_COMPLETE) {
611                         len -= p->read_so_far;
612                         wrap -= p->read_so_far;
613
614                         have_line(p);
615
616                         p->read_so_far = 0;
617                 } else if (p->state == PARSER_STATE_PDU_COMPLETE) {
618                         len -= p->read_so_far;
619                         wrap -= p->read_so_far;
620
621                         have_pdu(p);
622
623                         p->read_so_far = 0;
624                 } else if (p->state == PARSER_STATE_INITIAL_CR) {
625                         len -= p->read_so_far - 1;
626                         wrap -= p->read_so_far - 1;
627
628                         ring_buffer_drain(p->buf, p->read_so_far - 1);
629
630                         p->read_so_far = 1;
631                 } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
632                         len -= p->read_so_far;
633                         wrap -= p->read_so_far;
634
635                         g_at_chat_wakeup_writer(p);
636
637                         ring_buffer_drain(p->buf, p->read_so_far);
638
639                         p->read_so_far = 0;
640                 }
641         }
642
643         if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
644                 ring_buffer_drain(p->buf, p->read_so_far);
645                 p->read_so_far = 0;
646         }
647 }
648
649 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
650                                 gpointer data)
651 {
652         unsigned char *buf;
653         GAtChat *chat = data;
654         GIOError err;
655         gsize rbytes;
656         gsize toread;
657         gsize total_read = 0;
658
659         if (cond & G_IO_NVAL)
660                 return FALSE;
661
662         /* Regardless of condition, try to read all the data available */
663         do {
664                 rbytes = 0;
665
666                 toread = ring_buffer_avail_no_wrap(chat->buf);
667
668                 /* We're going to start overflowing the buffer
669                  * this cannot happen under normal circumstances, so probably
670                  * the channel is getting garbage, drop off
671                  */
672                 if (toread == 0) {
673                         if (chat->state == PARSER_STATE_RESPONSE)
674                                 return FALSE;
675
676                         err = G_IO_ERROR_AGAIN;
677                         break;
678                 }
679
680                 buf = ring_buffer_write_ptr(chat->buf);
681
682                 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
683
684                 total_read += rbytes;
685
686                 if (rbytes > 0)
687                         ring_buffer_write_advance(chat->buf, rbytes);
688
689         } while (err == G_IO_ERROR_NONE && rbytes > 0);
690
691         if (total_read > 0)
692                 new_bytes(chat);
693
694         if (cond & (G_IO_HUP | G_IO_ERR))
695                 return FALSE;
696
697         if (err == G_IO_ERROR_NONE && rbytes == 0)
698                 return FALSE;
699
700         if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
701                 return FALSE;
702
703         return TRUE;
704 }
705
706 static gboolean wakeup_no_response(gpointer user)
707 {
708         GAtChat *chat = user;
709
710         g_at_chat_finish_command(chat, FALSE, NULL);
711
712         return FALSE;
713 }
714
715 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
716                                 gpointer data)
717 {
718         GAtChat *chat = data;
719         struct at_command *cmd;
720         GIOError err;
721         gsize bytes_written;
722         gsize towrite;
723         gsize len;
724         char *cr;
725         gboolean wakeup_first = FALSE;
726 #ifdef WRITE_SCHEDULER_DEBUG
727         int limiter;
728 #endif
729
730         if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
731                 return FALSE;
732
733         /* Grab the first command off the queue and write as
734          * much of it as we can
735          */
736         cmd = g_queue_peek_head(chat->command_queue);
737
738         /* For some reason command queue is empty, cancel write watcher */
739         if (cmd == NULL)
740                 return FALSE;
741
742         len = strlen(cmd->cmd);
743
744         /* For some reason write watcher fired, but we've already
745          * written the entire command out to the io channel,
746          * cancel write watcher
747          */
748         if (chat->cmd_bytes_written >= len)
749                 return FALSE;
750
751         if (chat->wakeup) {
752                 if (!chat->wakeup_timer) {
753                         wakeup_first = TRUE;
754                         chat->wakeup_timer = g_timer_new();
755
756                 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
757                                 chat->inactivity_time)
758                         wakeup_first = TRUE;
759         }
760
761         if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
762                 cmd = at_command_create(chat->wakeup, NULL, NULL, NULL, NULL);
763
764                 if (!cmd)
765                         return FALSE;
766
767                 g_queue_push_head(chat->command_queue, cmd);
768
769                 len = strlen(chat->wakeup);
770
771                 g_timeout_add(chat->wakeup_timeout, wakeup_no_response,
772                                 chat);
773         }
774
775         towrite = len - chat->cmd_bytes_written;
776
777         cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
778
779         if (cr)
780                 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
781
782 #ifdef WRITE_SCHEDULER_DEBUG
783         limiter = towrite;
784
785         if (limiter > 5)
786                 limiter = 5;
787 #endif
788
789         err = g_io_channel_write(chat->channel,
790                         cmd->cmd + chat->cmd_bytes_written,
791 #ifdef WRITE_SCHEDULER_DEBUG
792                         limiter,
793 #else
794                         towrite,
795 #endif
796                         &bytes_written);
797
798         if (err != G_IO_ERROR_NONE) {
799                 g_at_chat_shutdown(chat);
800                 return FALSE;
801         }
802
803         chat->cmd_bytes_written += bytes_written;
804
805         if (bytes_written < towrite)
806                 return TRUE;
807
808         /* Full command submitted, update timer */
809         if (chat->wakeup_timer)
810                 g_timer_start(chat->wakeup_timer);
811
812         return FALSE;
813 }
814
815 static void g_at_chat_wakeup_writer(GAtChat *chat)
816 {
817         if (chat->write_watch != 0)
818                 return;
819
820         chat->write_watch = g_io_add_watch_full(chat->channel,
821                                 G_PRIORITY_DEFAULT,
822                                 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
823                                 can_write_data, chat,
824                                 (GDestroyNotify)write_watcher_destroy_notify);
825 }
826
827 GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
828 {
829         GAtChat *chat;
830         GIOFlags io_flags;
831
832         if (!channel)
833                 return NULL;
834
835         chat = g_try_new0(GAtChat, 1);
836
837         if (!chat)
838                 return chat;
839
840         chat->next_cmd_id = 1;
841         chat->next_notify_id = 1;
842         chat->flags = flags;
843
844         chat->buf = ring_buffer_new(4096);
845
846         if (!chat->buf)
847                 goto error;
848
849         chat->command_queue = g_queue_new();
850
851         if (!chat->command_queue)
852                 goto error;
853
854         chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
855                                 g_free, (GDestroyNotify)at_notify_destroy);
856
857         if (g_io_channel_set_encoding(channel, NULL, NULL) !=
858                         G_IO_STATUS_NORMAL)
859                 goto error;
860
861         io_flags = g_io_channel_get_flags(channel);
862
863         io_flags |= G_IO_FLAG_NONBLOCK;
864
865         if (g_io_channel_set_flags(channel, io_flags, NULL) !=
866                         G_IO_STATUS_NORMAL)
867                 goto error;
868
869         g_io_channel_set_close_on_unref(channel, TRUE);
870
871         chat->channel = channel;
872         chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
873                                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
874                                 received_data, chat,
875                                 (GDestroyNotify)read_watcher_destroy_notify);
876
877         return chat;
878
879 error:
880         if (chat->buf)
881                 ring_buffer_free(chat->buf);
882
883         if (chat->command_queue)
884                 g_queue_free(chat->command_queue);
885
886         if (chat->notify_list)
887                 g_hash_table_destroy(chat->notify_list);
888
889         g_free(chat);
890         return NULL;
891 }
892
893 GAtChat *g_at_chat_ref(GAtChat *chat)
894 {
895         if (chat == NULL)
896                 return NULL;
897
898         g_atomic_int_inc(&chat->ref_count);
899
900         return chat;
901 }
902
903 void g_at_chat_unref(GAtChat *chat)
904 {
905         gboolean is_zero;
906
907         if (chat == NULL)
908                 return;
909
910         is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
911
912         if (is_zero) {
913                 g_at_chat_shutdown(chat);
914
915                 g_at_chat_cleanup(chat);
916                 g_free(chat);
917         }
918 }
919
920 gboolean g_at_chat_shutdown(GAtChat *chat)
921 {
922         if (chat->channel == NULL)
923                 return FALSE;
924
925         chat->disconnecting = TRUE;
926
927         if (chat->read_watch)
928                 g_source_remove(chat->read_watch);
929
930         if (chat->write_watch)
931                 g_source_remove(chat->write_watch);
932
933         return TRUE;
934 }
935
936 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
937                         GAtDisconnectFunc disconnect, gpointer user_data)
938 {
939         if (chat == NULL)
940                 return FALSE;
941
942         chat->user_disconnect = disconnect;
943         chat->user_disconnect_data = user_data;
944
945         return TRUE;
946 }
947
948 guint g_at_chat_send(GAtChat *chat, const char *cmd,
949                         const char **prefix_list, GAtResultFunc func,
950                         gpointer user_data, GDestroyNotify notify)
951 {
952         struct at_command *c;
953
954         if (chat == NULL || chat->command_queue == NULL)
955                 return 0;
956
957         c = at_command_create(cmd, prefix_list, func, user_data, notify);
958
959         if (!c)
960                 return 0;
961
962         c->id = chat->next_cmd_id++;
963
964         g_queue_push_tail(chat->command_queue, c);
965
966         if (g_queue_get_length(chat->command_queue) == 1)
967                 g_at_chat_wakeup_writer(chat);
968
969         return c->id;
970 }
971
972 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
973 {
974         GList *l;
975
976         if (chat == NULL || chat->command_queue == NULL)
977                 return FALSE;
978
979         l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
980                                 at_command_compare_by_id);
981
982         if (!l)
983                 return FALSE;
984
985         if (l == g_queue_peek_head(chat->command_queue)) {
986                 struct at_command *c = l->data;
987
988                 /* We can't actually remove it since it is most likely
989                  * already in progress, just null out the callback
990                  * so it won't be called
991                  */
992                 c->callback = NULL;
993         } else {
994                 at_command_destroy(l->data);
995                 g_queue_remove(chat->command_queue, l->data);
996         }
997
998         return TRUE;
999 }
1000
1001 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
1002                                                 gboolean pdu)
1003 {
1004         struct at_notify *notify;
1005         char *key;
1006
1007         key = g_strdup(prefix);
1008
1009         if (!key)
1010                 return 0;
1011
1012         notify = g_try_new0(struct at_notify, 1);
1013
1014         if (!notify) {
1015                 g_free(key);
1016                 return 0;
1017         }
1018
1019         notify->pdu = pdu;
1020
1021         g_hash_table_insert(chat->notify_list, key, notify);
1022
1023         return notify;
1024 }
1025
1026 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1027                                 GAtNotifyFunc func, gboolean expect_pdu,
1028                                 gpointer user_data,
1029                                 GDestroyNotify destroy_notify)
1030 {
1031         struct at_notify *notify;
1032         struct at_notify_node *node;
1033
1034         if (chat == NULL || chat->notify_list == NULL)
1035                 return 0;
1036
1037         if (func == NULL)
1038                 return 0;
1039
1040         if (prefix == NULL || strlen(prefix) == 0)
1041                 return 0;
1042
1043         notify = g_hash_table_lookup(chat->notify_list, prefix);
1044
1045         if (!notify)
1046                 notify = at_notify_create(chat, prefix, expect_pdu);
1047
1048         if (!notify || notify->pdu != expect_pdu)
1049                 return 0;
1050
1051         node = g_try_new0(struct at_notify_node, 1);
1052
1053         if (!node)
1054                 return 0;
1055
1056         node->id = chat->next_notify_id++;
1057         node->callback = func;
1058         node->user_data = user_data;
1059         node->notify = destroy_notify;
1060
1061         notify->nodes = g_slist_prepend(notify->nodes, node);
1062
1063         return node->id;
1064 }
1065
1066 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1067 {
1068         GHashTableIter iter;
1069         struct at_notify *notify;
1070         char *prefix;
1071         gpointer key, value;
1072         GSList *l;
1073
1074         if (chat == NULL || chat->notify_list == NULL)
1075                 return FALSE;
1076
1077         g_hash_table_iter_init(&iter, chat->notify_list);
1078
1079         while (g_hash_table_iter_next(&iter, &key, &value)) {
1080                 prefix = key;
1081                 notify = value;
1082
1083                 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1084                                         at_notify_node_compare_by_id);
1085
1086                 if (!l)
1087                         continue;
1088
1089                 at_notify_node_destroy(l->data);
1090                 notify->nodes = g_slist_remove(notify->nodes, l->data);
1091
1092                 if (notify->nodes == NULL)
1093                         g_hash_table_iter_remove(&iter);
1094
1095                 return TRUE;
1096         }
1097
1098         return TRUE;
1099 }
1100
1101 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1102                                         unsigned int timeout, unsigned int msec)
1103 {
1104         if (chat == NULL)
1105                 return FALSE;
1106
1107         if (chat->wakeup)
1108                 g_free(chat->wakeup);
1109
1110         chat->wakeup = g_strdup(cmd);
1111         chat->inactivity_time = (gdouble)msec / 1000;
1112         chat->wakeup_timeout = timeout;
1113
1114         return TRUE;
1115 }