packaging: Bump to 1.17
[platform/upstream/ofono.git] / gatchat / gatchat.c
1 /*
2  *
3  *  AT chat library with GLib integration
4  *
5  *  Copyright (C) 2008-2011  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 <unistd.h>
28 #include <string.h>
29 #include <assert.h>
30 #include <ctype.h>
31 #include <errno.h>
32
33 #include <glib.h>
34
35 #include "ringbuffer.h"
36 #include "gatchat.h"
37 #include "gatio.h"
38
39 /* #define WRITE_SCHEDULER_DEBUG 1 */
40
41 #define COMMAND_FLAG_EXPECT_PDU                 0x1
42 #define COMMAND_FLAG_EXPECT_SHORT_PROMPT        0x2
43
44 struct at_chat;
45 static void chat_wakeup_writer(struct at_chat *chat);
46
47 static const char *none_prefix[] = { NULL };
48
49 struct at_command {
50         char *cmd;
51         char **prefixes;
52         guint flags;
53         guint id;
54         guint gid;
55         GAtResultFunc callback;
56         GAtNotifyFunc listing;
57         gpointer user_data;
58         GDestroyNotify notify;
59 };
60
61 struct at_notify_node {
62         guint id;
63         guint gid;
64         GAtNotifyFunc callback;
65         gpointer user_data;
66         GDestroyNotify notify;
67         gboolean destroyed;
68 };
69
70 typedef gboolean (*node_remove_func)(struct at_notify_node *node,
71                                         gpointer user_data);
72
73 struct at_notify {
74         GSList *nodes;
75         gboolean pdu;
76 };
77
78 struct at_chat {
79         gint ref_count;                         /* Ref count */
80         guint next_cmd_id;                      /* Next command id */
81         guint next_notify_id;                   /* Next notify id */
82         guint next_gid;                         /* Next group id */
83         GAtIO *io;                              /* AT IO */
84         GQueue *command_queue;                  /* Command queue */
85         guint cmd_bytes_written;                /* bytes written from cmd */
86         GHashTable *notify_list;                /* List of notification reg */
87         GAtDisconnectFunc user_disconnect;      /* user disconnect func */
88         gpointer user_disconnect_data;          /* user disconnect data */
89         guint read_so_far;                      /* Number of bytes processed */
90         gboolean suspended;                     /* Are we suspended? */
91         GAtDebugFunc debugf;                    /* debugging output function */
92         gpointer debug_data;                    /* Data to pass to debug func */
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         gint timeout_source;
97         gdouble inactivity_time;                /* Period of inactivity */
98         guint wakeup_timeout;                   /* How long to wait for resp */
99         GTimer *wakeup_timer;                   /* Keep track of elapsed time */
100         GAtSyntax *syntax;
101         gboolean destroyed;                     /* Re-entrancy guard */
102         gboolean in_read_handler;               /* Re-entrancy guard */
103         gboolean in_notify;
104         GSList *terminator_list;                /* Non-standard terminator */
105         guint16 terminator_blacklist;           /* Blacklisted terinators */
106 };
107
108 struct _GAtChat {
109         gint ref_count;
110         struct at_chat *parent;
111         guint group;
112         GAtChat *slave;
113 };
114
115 struct terminator_info {
116         char *terminator;
117         int len;
118         gboolean success;
119 };
120
121 static gboolean node_is_destroyed(struct at_notify_node *node, gpointer user)
122 {
123         return node->destroyed;
124 }
125
126 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
127 {
128         const struct at_notify_node *node = a;
129         guint id = GPOINTER_TO_UINT(b);
130
131         if (node->id < id)
132                 return -1;
133
134         if (node->id > id)
135                 return 1;
136
137         return 0;
138 }
139
140 static void at_notify_node_destroy(gpointer data, gpointer user_data)
141 {
142         struct at_notify_node *node = data;
143
144         if (node->notify)
145                 node->notify(node->user_data);
146
147         g_free(node);
148 }
149
150 static void at_notify_destroy(gpointer user_data)
151 {
152         struct at_notify *notify = user_data;
153
154         g_slist_foreach(notify->nodes, at_notify_node_destroy, NULL);
155         g_slist_free(notify->nodes);
156         g_free(notify);
157 }
158
159 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
160 {
161         const struct at_command *command = a;
162         guint id = GPOINTER_TO_UINT(b);
163
164         if (command->id < id)
165                 return -1;
166
167         if (command->id > id)
168                 return 1;
169
170         return 0;
171 }
172
173 static gboolean at_chat_unregister_all(struct at_chat *chat,
174                                         gboolean mark_only,
175                                         node_remove_func func,
176                                         gpointer userdata)
177 {
178         GHashTableIter iter;
179         struct at_notify *notify;
180         struct at_notify_node *node;
181         gpointer key, value;
182         GSList *p;
183         GSList *c;
184         GSList *t;
185
186         if (chat->notify_list == NULL)
187                 return FALSE;
188
189         g_hash_table_iter_init(&iter, chat->notify_list);
190
191         while (g_hash_table_iter_next(&iter, &key, &value)) {
192                 notify = value;
193
194                 p = NULL;
195                 c = notify->nodes;
196
197                 while (c) {
198                         node = c->data;
199
200                         if (func(node, userdata) != TRUE) {
201                                 p = c;
202                                 c = c->next;
203                                 continue;
204                         }
205
206                         if (mark_only) {
207                                 node->destroyed = TRUE;
208                                 p = c;
209                                 c = c->next;
210                                 continue;
211                         }
212
213                         if (p)
214                                 p->next = c->next;
215                         else
216                                 notify->nodes = c->next;
217
218                         at_notify_node_destroy(node, NULL);
219
220                         t = c;
221                         c = c->next;
222                         g_slist_free_1(t);
223                 }
224
225                 if (notify->nodes == NULL)
226                         g_hash_table_iter_remove(&iter);
227         }
228
229         return TRUE;
230 }
231
232 static struct at_command *at_command_create(guint gid, const char *cmd,
233                                                 const char **prefix_list,
234                                                 guint flags,
235                                                 GAtNotifyFunc listing,
236                                                 GAtResultFunc func,
237                                                 gpointer user_data,
238                                                 GDestroyNotify notify,
239                                                 gboolean wakeup)
240 {
241         struct at_command *c;
242         gsize len;
243         char **prefixes = NULL;
244
245         if (prefix_list) {
246                 int num_prefixes = 0;
247                 int i;
248
249                 while (prefix_list[num_prefixes])
250                         num_prefixes += 1;
251
252                 prefixes = g_new(char *, num_prefixes + 1);
253
254                 for (i = 0; i < num_prefixes; i++)
255                         prefixes[i] = strdup(prefix_list[i]);
256
257                 prefixes[num_prefixes] = NULL;
258         }
259
260         c = g_try_new0(struct at_command, 1);
261         if (c == NULL)
262                 return 0;
263
264         len = strlen(cmd);
265         c->cmd = g_try_new(char, len + 2);
266         if (c->cmd == NULL) {
267                 g_free(c);
268                 return 0;
269         }
270
271         memcpy(c->cmd, cmd, len);
272
273         /* If we have embedded '\r' then this is a command expecting a prompt
274          * from the modem.  Embed Ctrl-Z at the very end automatically
275          */
276         if (wakeup == FALSE) {
277                 if (strchr(cmd, '\r'))
278                         c->cmd[len] = 26;
279                 else
280                         c->cmd[len] = '\r';
281
282                 len += 1;
283         }
284
285         c->cmd[len] = '\0';
286
287         c->gid = gid;
288         c->flags = flags;
289         c->prefixes = prefixes;
290         c->callback = func;
291         c->listing = listing;
292         c->user_data = user_data;
293         c->notify = notify;
294
295         return c;
296 }
297
298 static void at_command_destroy(struct at_command *cmd)
299 {
300         if (cmd->notify)
301                 cmd->notify(cmd->user_data);
302
303         g_strfreev(cmd->prefixes);
304         g_free(cmd->cmd);
305         g_free(cmd);
306 }
307
308 static void free_terminator(struct terminator_info *info)
309 {
310         g_free(info->terminator);
311         info->terminator = NULL;
312         g_free(info);
313         info = NULL;
314 }
315
316 static void chat_cleanup(struct at_chat *chat)
317 {
318         struct at_command *c;
319
320         /* Cleanup pending commands */
321         while ((c = g_queue_pop_head(chat->command_queue)))
322                 at_command_destroy(c);
323
324         g_queue_free(chat->command_queue);
325         chat->command_queue = NULL;
326
327         /* Cleanup any response lines we have pending */
328         g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
329         g_slist_free(chat->response_lines);
330         chat->response_lines = NULL;
331
332         /* Cleanup registered notifications */
333         g_hash_table_destroy(chat->notify_list);
334         chat->notify_list = NULL;
335
336         if (chat->pdu_notify) {
337                 g_free(chat->pdu_notify);
338                 chat->pdu_notify = NULL;
339         }
340
341         if (chat->wakeup) {
342                 g_free(chat->wakeup);
343                 chat->wakeup = NULL;
344         }
345
346         if (chat->wakeup_timer) {
347                 g_timer_destroy(chat->wakeup_timer);
348                 chat->wakeup_timer = 0;
349         }
350
351         if (chat->timeout_source) {
352                 g_source_remove(chat->timeout_source);
353                 chat->timeout_source = 0;
354         }
355
356         g_at_syntax_unref(chat->syntax);
357         chat->syntax = NULL;
358
359         if (chat->terminator_list) {
360                 g_slist_foreach(chat->terminator_list,
361                                         (GFunc)free_terminator, NULL);
362                 g_slist_free(chat->terminator_list);
363                 chat->terminator_list = NULL;
364         }
365 }
366
367 static void io_disconnect(gpointer user_data)
368 {
369         struct at_chat *chat = user_data;
370
371         chat_cleanup(chat);
372         g_at_io_unref(chat->io);
373         chat->io = NULL;
374
375         if (chat->user_disconnect)
376                 chat->user_disconnect(chat->user_disconnect_data);
377 }
378
379 static void at_notify_call_callback(gpointer data, gpointer user_data)
380 {
381         struct at_notify_node *node = data;
382         GAtResult *result = user_data;
383
384         node->callback(result, node->user_data);
385 }
386
387 static gboolean at_chat_match_notify(struct at_chat *chat, char *line)
388 {
389         GHashTableIter iter;
390         struct at_notify *notify;
391         gpointer key, value;
392         gboolean ret = FALSE;
393         GAtResult result;
394
395         g_hash_table_iter_init(&iter, chat->notify_list);
396         result.lines = 0;
397         result.final_or_pdu = 0;
398
399         chat->in_notify = TRUE;
400
401         while (g_hash_table_iter_next(&iter, &key, &value)) {
402                 notify = value;
403
404                 if (!g_str_has_prefix(line, key))
405                         continue;
406
407                 if (notify->pdu) {
408                         chat->pdu_notify = line;
409
410                         if (chat->syntax->set_hint)
411                                 chat->syntax->set_hint(chat->syntax,
412                                                         G_AT_SYNTAX_EXPECT_PDU);
413                         return TRUE;
414                 }
415
416                 if (result.lines == NULL)
417                         result.lines = g_slist_prepend(NULL, line);
418
419                 g_slist_foreach(notify->nodes, at_notify_call_callback,
420                                         &result);
421                 ret = TRUE;
422         }
423
424         chat->in_notify = FALSE;
425
426         if (ret) {
427                 g_slist_free(result.lines);
428                 g_free(line);
429
430                 at_chat_unregister_all(chat, FALSE, node_is_destroyed, NULL);
431         }
432
433         return ret;
434 }
435
436 static void at_chat_finish_command(struct at_chat *p, gboolean ok, char *final)
437 {
438         struct at_command *cmd = g_queue_pop_head(p->command_queue);
439         GSList *response_lines;
440
441         /* Cannot happen, but lets be paranoid */
442         if (cmd == NULL)
443                 return;
444
445         p->cmd_bytes_written = 0;
446
447         if (g_queue_peek_head(p->command_queue))
448                 chat_wakeup_writer(p);
449
450         response_lines = p->response_lines;
451         p->response_lines = NULL;
452
453         if (cmd->callback) {
454                 GAtResult result;
455
456                 response_lines = g_slist_reverse(response_lines);
457
458                 result.final_or_pdu = final;
459                 result.lines = response_lines;
460
461                 cmd->callback(ok, &result, cmd->user_data);
462         }
463
464         g_slist_foreach(response_lines, (GFunc)g_free, NULL);
465         g_slist_free(response_lines);
466
467         g_free(final);
468         at_command_destroy(cmd);
469 }
470
471 static struct terminator_info terminator_table[] = {
472         { "OK", -1, TRUE },
473         { "ERROR", -1, FALSE },
474         { "NO DIALTONE", -1, FALSE },
475         { "BUSY", -1, FALSE },
476         { "NO CARRIER", -1, FALSE },
477         { "CONNECT", 7, TRUE },
478         { "NO ANSWER", -1, FALSE },
479         { "+CMS ERROR:", 11, FALSE },
480         { "+CME ERROR:", 11, FALSE },
481         { "+EXT ERROR:", 11, FALSE }
482 };
483
484 static void at_chat_add_terminator(struct at_chat *chat, char *terminator,
485                                         int len, gboolean success)
486 {
487         struct terminator_info *info = g_new0(struct terminator_info, 1);
488         info->terminator = g_strdup(terminator);
489         info->len = len;
490         info->success = success;
491         chat->terminator_list = g_slist_prepend(chat->terminator_list, info);
492 }
493
494 static void at_chat_blacklist_terminator(struct at_chat *chat,
495                                                 GAtChatTerminator terminator)
496 {
497         chat->terminator_blacklist |= 1 << terminator;
498 }
499
500 static gboolean check_terminator(struct terminator_info *info, char *line)
501 {
502         if (info->len == -1 && !strcmp(line, info->terminator))
503                 return TRUE;
504
505         if (info->len > 0 && !strncmp(line, info->terminator, info->len))
506                 return TRUE;
507
508         return FALSE;
509 }
510
511 static gboolean at_chat_handle_command_response(struct at_chat *p,
512                                                         struct at_command *cmd,
513                                                         char *line)
514 {
515         int i;
516         int size = sizeof(terminator_table) / sizeof(struct terminator_info);
517         int hint;
518         GSList *l;
519
520         for (i = 0; i < size; i++) {
521                 struct terminator_info *info = &terminator_table[i];
522                 if (check_terminator(info, line) &&
523                                 (p->terminator_blacklist & 1 << i) == 0) {
524                         at_chat_finish_command(p, info->success, line);
525                         return TRUE;
526                 }
527         }
528
529         for (l = p->terminator_list; l; l = l->next) {
530                 struct terminator_info *info = l->data;
531                 if (check_terminator(info, line)) {
532                         at_chat_finish_command(p, info->success, line);
533                         return TRUE;
534                 }
535         }
536
537         if (cmd->prefixes) {
538                 int n;
539
540                 for (n = 0; cmd->prefixes[n]; n++)
541                         if (g_str_has_prefix(line, cmd->prefixes[n]))
542                                 goto out;
543
544                 return FALSE;
545         }
546
547 out:
548         if (cmd->listing && (cmd->flags & COMMAND_FLAG_EXPECT_PDU))
549                 hint = G_AT_SYNTAX_EXPECT_PDU;
550         else
551                 hint = G_AT_SYNTAX_EXPECT_MULTILINE;
552
553         if (p->syntax->set_hint)
554                 p->syntax->set_hint(p->syntax, hint);
555
556         if (cmd->listing && (cmd->flags & COMMAND_FLAG_EXPECT_PDU)) {
557                 p->pdu_notify = line;
558                 return TRUE;
559         }
560
561         if (cmd->listing) {
562                 GAtResult result;
563
564                 result.lines = g_slist_prepend(NULL, line);
565                 result.final_or_pdu = NULL;
566
567                 cmd->listing(&result, cmd->user_data);
568
569                 g_slist_free(result.lines);
570                 g_free(line);
571         } else
572                 p->response_lines = g_slist_prepend(p->response_lines, line);
573
574         return TRUE;
575 }
576
577 static void have_line(struct at_chat *p, char *str)
578 {
579         /* We're not going to copy terminal <CR><LF> */
580         struct at_command *cmd;
581
582         if (str == NULL)
583                 return;
584
585         /* Check for echo, this should not happen, but lets be paranoid */
586         if (!strncmp(str, "AT", 2))
587                 goto done;
588
589         cmd = g_queue_peek_head(p->command_queue);
590
591         if (cmd && p->cmd_bytes_written > 0) {
592                 char c = cmd->cmd[p->cmd_bytes_written - 1];
593
594                 /* We check that we have submitted a terminator, in which case
595                  * a command might have failed or completed successfully
596                  *
597                  * In the generic case, \r is at the end of the command, so we
598                  * know the entire command has been submitted.  In the case of
599                  * commands like CMGS, every \r or Ctrl-Z might result in a
600                  * final response from the modem, so we check this as well.
601                  */
602                 if ((c == '\r' || c == 26) &&
603                                 at_chat_handle_command_response(p, cmd, str))
604                         return;
605         }
606
607         if (at_chat_match_notify(p, str) == TRUE)
608                 return;
609
610 done:
611         /* No matches & no commands active, ignore line */
612         g_free(str);
613 }
614
615 static void have_notify_pdu(struct at_chat *p, char *pdu, GAtResult *result)
616 {
617         GHashTableIter iter;
618         struct at_notify *notify;
619         char *prefix;
620         gpointer key, value;
621         gboolean called = FALSE;
622
623         p->in_notify = TRUE;
624
625         g_hash_table_iter_init(&iter, p->notify_list);
626
627         while (g_hash_table_iter_next(&iter, &key, &value)) {
628                 prefix = key;
629                 notify = value;
630
631                 if (!g_str_has_prefix(p->pdu_notify, prefix))
632                         continue;
633
634                 if (!notify->pdu)
635                         continue;
636
637                 g_slist_foreach(notify->nodes, at_notify_call_callback, result);
638                 called = TRUE;
639         }
640
641         p->in_notify = FALSE;
642
643         if (called)
644                 at_chat_unregister_all(p, FALSE, node_is_destroyed, NULL);
645 }
646
647 static void have_pdu(struct at_chat *p, char *pdu)
648 {
649         struct at_command *cmd;
650         GAtResult result;
651         gboolean listing_pdu = FALSE;
652
653         if (pdu == NULL)
654                 goto error;
655
656         result.lines = g_slist_prepend(NULL, p->pdu_notify);
657         result.final_or_pdu = pdu;
658
659         cmd = g_queue_peek_head(p->command_queue);
660
661         if (cmd && (cmd->flags & COMMAND_FLAG_EXPECT_PDU) &&
662                         p->cmd_bytes_written > 0) {
663                 char c = cmd->cmd[p->cmd_bytes_written - 1];
664
665                 if (c == '\r')
666                         listing_pdu = TRUE;
667         }
668
669         if (listing_pdu) {
670                 cmd->listing(&result, cmd->user_data);
671
672                 if (p->syntax->set_hint)
673                         p->syntax->set_hint(p->syntax,
674                                                 G_AT_SYNTAX_EXPECT_MULTILINE);
675         } else
676                 have_notify_pdu(p, pdu, &result);
677
678         g_slist_free(result.lines);
679
680 error:
681         g_free(p->pdu_notify);
682         p->pdu_notify = NULL;
683
684         if (pdu)
685                 g_free(pdu);
686 }
687
688 static char *extract_line(struct at_chat *p, struct ring_buffer *rbuf)
689 {
690         unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
691         unsigned int pos = 0;
692         unsigned char *buf = ring_buffer_read_ptr(rbuf, pos);
693         gboolean in_string = FALSE;
694         int strip_front = 0;
695         int line_length = 0;
696         char *line;
697
698         while (pos < p->read_so_far) {
699                 if (in_string == FALSE && (*buf == '\r' || *buf == '\n')) {
700                         if (!line_length)
701                                 strip_front += 1;
702                         else
703                                 break;
704                 } else {
705                         if (*buf == '"')
706                                 in_string = !in_string;
707
708                         line_length += 1;
709                 }
710
711                 buf += 1;
712                 pos += 1;
713
714                 if (pos == wrap)
715                         buf = ring_buffer_read_ptr(rbuf, pos);
716         }
717
718         line = g_try_new(char, line_length + 1);
719         if (line == NULL) {
720                 ring_buffer_drain(rbuf, p->read_so_far);
721                 return NULL;
722         }
723
724         ring_buffer_drain(rbuf, strip_front);
725         ring_buffer_read(rbuf, line, line_length);
726         ring_buffer_drain(rbuf, p->read_so_far - strip_front - line_length);
727
728         line[line_length] = '\0';
729
730         return line;
731 }
732
733 static void new_bytes(struct ring_buffer *rbuf, gpointer user_data)
734 {
735         struct at_chat *p = user_data;
736         unsigned int len = ring_buffer_len(rbuf);
737         unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
738         unsigned char *buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
739
740         GAtSyntaxResult result;
741
742         p->in_read_handler = TRUE;
743
744         while (p->suspended == FALSE && (p->read_so_far < len)) {
745                 gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
746                 result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
747
748                 buf += rbytes;
749                 p->read_so_far += rbytes;
750
751                 if (p->read_so_far == wrap) {
752                         buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
753                         wrap = len;
754                 }
755
756                 if (result == G_AT_SYNTAX_RESULT_UNSURE)
757                         continue;
758
759                 switch (result) {
760                 case G_AT_SYNTAX_RESULT_LINE:
761                 case G_AT_SYNTAX_RESULT_MULTILINE:
762                         have_line(p, extract_line(p, rbuf));
763                         break;
764
765                 case G_AT_SYNTAX_RESULT_PDU:
766                         have_pdu(p, extract_line(p, rbuf));
767                         break;
768
769                 case G_AT_SYNTAX_RESULT_PROMPT:
770                         chat_wakeup_writer(p);
771                         ring_buffer_drain(rbuf, p->read_so_far);
772                         break;
773
774                 default:
775                         ring_buffer_drain(rbuf, p->read_so_far);
776                         break;
777                 }
778
779                 len -= p->read_so_far;
780                 wrap -= p->read_so_far;
781                 p->read_so_far = 0;
782         }
783
784         p->in_read_handler = FALSE;
785
786         if (p->destroyed)
787                 g_free(p);
788 }
789
790 static void wakeup_cb(gboolean ok, GAtResult *result, gpointer user_data)
791 {
792         struct at_chat *chat = user_data;
793
794         if (ok == FALSE)
795                 return;
796
797         if (chat->debugf)
798                 chat->debugf("Finally woke up the modem\n", chat->debug_data);
799
800         g_source_remove(chat->timeout_source);
801         chat->timeout_source = 0;
802 }
803
804 static gboolean wakeup_no_response(gpointer user_data)
805 {
806         struct at_chat *chat = user_data;
807         struct at_command *cmd = g_queue_peek_head(chat->command_queue);
808
809         if (chat->debugf)
810                 chat->debugf("Wakeup got no response\n", chat->debug_data);
811
812         if (cmd == NULL)
813                 return FALSE;
814
815         at_chat_finish_command(chat, FALSE, NULL);
816
817         cmd = at_command_create(0, chat->wakeup, none_prefix, 0,
818                                 NULL, wakeup_cb, chat, NULL, TRUE);
819         if (cmd == NULL) {
820                 chat->timeout_source = 0;
821                 return FALSE;
822         }
823
824         g_queue_push_head(chat->command_queue, cmd);
825
826         return TRUE;
827 }
828
829 static gboolean can_write_data(gpointer data)
830 {
831         struct at_chat *chat = data;
832         struct at_command *cmd;
833         gsize bytes_written;
834         gsize towrite;
835         gsize len;
836         char *cr;
837         gboolean wakeup_first = FALSE;
838 #ifdef WRITE_SCHEDULER_DEBUG
839         int limiter;
840 #endif
841
842         /* Grab the first command off the queue and write as
843          * much of it as we can
844          */
845         cmd = g_queue_peek_head(chat->command_queue);
846
847         /* For some reason command queue is empty, cancel write watcher */
848         if (cmd == NULL)
849                 return FALSE;
850
851         len = strlen(cmd->cmd);
852
853         /* For some reason write watcher fired, but we've already
854          * written the entire command out to the io channel,
855          * cancel write watcher
856          */
857         if (chat->cmd_bytes_written >= len)
858                 return FALSE;
859
860         if (chat->wakeup) {
861                 if (chat->wakeup_timer == NULL) {
862                         wakeup_first = TRUE;
863                         chat->wakeup_timer = g_timer_new();
864
865                 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
866                                 chat->inactivity_time)
867                         wakeup_first = TRUE;
868         }
869
870         if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
871                 cmd = at_command_create(0, chat->wakeup, none_prefix, 0,
872                                         NULL, wakeup_cb, chat, NULL, TRUE);
873                 if (cmd == NULL)
874                         return FALSE;
875
876                 g_queue_push_head(chat->command_queue, cmd);
877
878                 len = strlen(chat->wakeup);
879
880                 chat->timeout_source = g_timeout_add(chat->wakeup_timeout,
881                                                 wakeup_no_response, chat);
882         }
883
884         towrite = len - chat->cmd_bytes_written;
885
886         cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
887
888         if (cr)
889                 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
890
891 #ifdef WRITE_SCHEDULER_DEBUG
892         limiter = towrite;
893
894         if (limiter > 5)
895                 limiter = 5;
896 #endif
897
898         bytes_written = g_at_io_write(chat->io,
899                                         cmd->cmd + chat->cmd_bytes_written,
900 #ifdef WRITE_SCHEDULER_DEBUG
901                                         limiter
902 #else
903                                         towrite
904 #endif
905                                         );
906
907         if (bytes_written == 0)
908                 return FALSE;
909
910         chat->cmd_bytes_written += bytes_written;
911
912         if (bytes_written < towrite)
913                 return TRUE;
914
915         /*
916          * If we're expecting a short prompt, set the hint for all lines
917          * sent to the modem except the last
918          */
919         if ((cmd->flags & COMMAND_FLAG_EXPECT_SHORT_PROMPT) &&
920                         chat->cmd_bytes_written < len &&
921                         chat->syntax->set_hint)
922                 chat->syntax->set_hint(chat->syntax,
923                                         G_AT_SYNTAX_EXPECT_SHORT_PROMPT);
924
925         /* Full command submitted, update timer */
926         if (chat->wakeup_timer)
927                 g_timer_start(chat->wakeup_timer);
928
929         return FALSE;
930 }
931
932 static void chat_wakeup_writer(struct at_chat *chat)
933 {
934         g_at_io_set_write_handler(chat->io, can_write_data, chat);
935 }
936
937 static void at_chat_suspend(struct at_chat *chat)
938 {
939         chat->suspended = TRUE;
940
941         g_at_io_set_write_handler(chat->io, NULL, NULL);
942         g_at_io_set_read_handler(chat->io, NULL, NULL);
943         g_at_io_set_debug(chat->io, NULL, NULL);
944 }
945
946 static void at_chat_resume(struct at_chat *chat)
947 {
948         chat->suspended = FALSE;
949
950         if (g_at_io_get_channel(chat->io) == NULL) {
951                 io_disconnect(chat);
952                 return;
953         }
954
955         g_at_io_set_disconnect_function(chat->io, io_disconnect, chat);
956
957         g_at_io_set_debug(chat->io, chat->debugf, chat->debug_data);
958         g_at_io_set_read_handler(chat->io, new_bytes, chat);
959
960         if (g_queue_get_length(chat->command_queue) > 0)
961                 chat_wakeup_writer(chat);
962 }
963
964 static void at_chat_unref(struct at_chat *chat)
965 {
966         gboolean is_zero;
967
968         is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
969
970         if (is_zero == FALSE)
971                 return;
972
973         if (chat->io) {
974                 at_chat_suspend(chat);
975                 g_at_io_unref(chat->io);
976                 chat->io = NULL;
977                 chat_cleanup(chat);
978         }
979
980         if (chat->in_read_handler)
981                 chat->destroyed = TRUE;
982         else
983                 g_free(chat);
984 }
985
986 static gboolean at_chat_set_disconnect_function(struct at_chat *chat,
987                                                 GAtDisconnectFunc disconnect,
988                                                 gpointer user_data)
989 {
990         chat->user_disconnect = disconnect;
991         chat->user_disconnect_data = user_data;
992
993         return TRUE;
994 }
995
996 static gboolean at_chat_set_debug(struct at_chat *chat,
997                                         GAtDebugFunc func, gpointer user_data)
998 {
999
1000         chat->debugf = func;
1001         chat->debug_data = user_data;
1002
1003         if (chat->io)
1004                 g_at_io_set_debug(chat->io, func, user_data);
1005
1006         return TRUE;
1007 }
1008
1009 static gboolean at_chat_set_wakeup_command(struct at_chat *chat,
1010                                                 const char *cmd,
1011                                                 unsigned int timeout,
1012                                                 unsigned int msec)
1013 {
1014         if (chat->wakeup)
1015                 g_free(chat->wakeup);
1016
1017         chat->wakeup = g_strdup(cmd);
1018         chat->inactivity_time = (gdouble)msec / 1000;
1019         chat->wakeup_timeout = timeout;
1020
1021         return TRUE;
1022 }
1023
1024 static guint at_chat_send_common(struct at_chat *chat, guint gid,
1025                                         const char *cmd,
1026                                         const char **prefix_list,
1027                                         guint flags,
1028                                         GAtNotifyFunc listing,
1029                                         GAtResultFunc func,
1030                                         gpointer user_data,
1031                                         GDestroyNotify notify)
1032 {
1033         struct at_command *c;
1034
1035         if (chat == NULL || chat->command_queue == NULL)
1036                 return 0;
1037
1038         c = at_command_create(gid, cmd, prefix_list, flags, listing, func,
1039                                 user_data, notify, FALSE);
1040         if (c == NULL)
1041                 return 0;
1042
1043         c->id = chat->next_cmd_id++;
1044
1045         g_queue_push_tail(chat->command_queue, c);
1046
1047         if (g_queue_get_length(chat->command_queue) == 1)
1048                 chat_wakeup_writer(chat);
1049
1050         return c->id;
1051 }
1052
1053 static struct at_notify *at_notify_create(struct at_chat *chat,
1054                                                 const char *prefix,
1055                                                 gboolean pdu)
1056 {
1057         struct at_notify *notify;
1058         char *key;
1059
1060         key = g_strdup(prefix);
1061         if (key == NULL)
1062                 return 0;
1063
1064         notify = g_try_new0(struct at_notify, 1);
1065         if (notify == NULL) {
1066                 g_free(key);
1067                 return 0;
1068         }
1069
1070         notify->pdu = pdu;
1071
1072         g_hash_table_insert(chat->notify_list, key, notify);
1073
1074         return notify;
1075 }
1076
1077 static gboolean at_chat_cancel(struct at_chat *chat, guint group, guint id)
1078 {
1079         GList *l;
1080         struct at_command *c;
1081
1082         if (chat->command_queue == NULL)
1083                 return FALSE;
1084
1085         l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
1086                                 at_command_compare_by_id);
1087
1088         if (l == NULL)
1089                 return FALSE;
1090
1091         c = l->data;
1092
1093         if (c->gid != group)
1094                 return FALSE;
1095
1096         if (c == g_queue_peek_head(chat->command_queue) &&
1097                         chat->cmd_bytes_written > 0) {
1098                 /* We can't actually remove it since it is most likely
1099                  * already in progress, just null out the callback
1100                  * so it won't be called
1101                  */
1102                 c->callback = NULL;
1103         } else {
1104                 at_command_destroy(c);
1105                 g_queue_remove(chat->command_queue, c);
1106         }
1107
1108         return TRUE;
1109 }
1110
1111 static gboolean at_chat_cancel_group(struct at_chat *chat, guint group)
1112 {
1113         int n = 0;
1114         struct at_command *c;
1115
1116         if (chat->command_queue == NULL)
1117                 return FALSE;
1118
1119         while ((c = g_queue_peek_nth(chat->command_queue, n)) != NULL) {
1120                 if (c->id == 0 || c->gid != group) {
1121                         n += 1;
1122                         continue;
1123                 }
1124
1125                 if (n == 0 && chat->cmd_bytes_written > 0) {
1126                         c->callback = NULL;
1127                         n += 1;
1128                         continue;
1129                 }
1130
1131                 at_command_destroy(c);
1132                 g_queue_remove(chat->command_queue, c);
1133         }
1134
1135         return TRUE;
1136 }
1137
1138 static gpointer at_chat_get_userdata(struct at_chat *chat,
1139                                                 guint group, guint id)
1140 {
1141         GList *l;
1142         struct at_command *c;
1143
1144         if (chat->command_queue == NULL)
1145                 return NULL;
1146
1147         l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
1148                                 at_command_compare_by_id);
1149
1150         if (l == NULL)
1151                 return NULL;
1152
1153         c = l->data;
1154
1155         if (c->gid != group)
1156                 return NULL;
1157
1158         return c->user_data;
1159 }
1160
1161 static guint at_chat_register(struct at_chat *chat, guint group,
1162                                 const char *prefix, GAtNotifyFunc func,
1163                                 gboolean expect_pdu, gpointer user_data,
1164                                 GDestroyNotify destroy_notify)
1165 {
1166         struct at_notify *notify;
1167         struct at_notify_node *node;
1168
1169         if (chat->notify_list == NULL)
1170                 return 0;
1171
1172         if (func == NULL)
1173                 return 0;
1174
1175         if (prefix == NULL || strlen(prefix) == 0)
1176                 return 0;
1177
1178         notify = g_hash_table_lookup(chat->notify_list, prefix);
1179
1180         if (notify == NULL)
1181                 notify = at_notify_create(chat, prefix, expect_pdu);
1182
1183         if (notify == NULL || notify->pdu != expect_pdu)
1184                 return 0;
1185
1186         node = g_try_new0(struct at_notify_node, 1);
1187         if (node == NULL)
1188                 return 0;
1189
1190         node->id = chat->next_notify_id++;
1191         node->gid = group;
1192         node->callback = func;
1193         node->user_data = user_data;
1194         node->notify = destroy_notify;
1195
1196         notify->nodes = g_slist_prepend(notify->nodes, node);
1197
1198         return node->id;
1199 }
1200
1201 static gboolean at_chat_unregister(struct at_chat *chat, gboolean mark_only,
1202                                         guint group, guint id)
1203 {
1204         GHashTableIter iter;
1205         struct at_notify *notify;
1206         struct at_notify_node *node;
1207         gpointer key, value;
1208         GSList *l;
1209
1210         if (chat->notify_list == NULL)
1211                 return FALSE;
1212
1213         g_hash_table_iter_init(&iter, chat->notify_list);
1214
1215         while (g_hash_table_iter_next(&iter, &key, &value)) {
1216                 notify = value;
1217
1218                 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1219                                         at_notify_node_compare_by_id);
1220
1221                 if (l == NULL)
1222                         continue;
1223
1224                 node = l->data;
1225
1226                 if (node->gid != group)
1227                         return FALSE;
1228
1229                 if (mark_only) {
1230                         node->destroyed = TRUE;
1231                         return TRUE;
1232                 }
1233
1234                 at_notify_node_destroy(node, NULL);
1235                 notify->nodes = g_slist_remove(notify->nodes, node);
1236
1237                 if (notify->nodes == NULL)
1238                         g_hash_table_iter_remove(&iter);
1239
1240                 return TRUE;
1241         }
1242
1243         return FALSE;
1244 }
1245
1246 static gboolean node_compare_by_group(struct at_notify_node *node,
1247                                         gpointer userdata)
1248 {
1249         guint group = GPOINTER_TO_UINT(userdata);
1250
1251         if (node->gid == group)
1252                 return TRUE;
1253
1254         return FALSE;
1255 }
1256
1257 static struct at_chat *create_chat(GIOChannel *channel, GIOFlags flags,
1258                                         GAtSyntax *syntax)
1259 {
1260         struct at_chat *chat;
1261
1262         if (channel == NULL)
1263                 return NULL;
1264
1265         if (syntax == NULL)
1266                 return NULL;
1267
1268         chat = g_try_new0(struct at_chat, 1);
1269         if (chat == NULL)
1270                 return chat;
1271
1272         chat->ref_count = 1;
1273         chat->next_cmd_id = 1;
1274         chat->next_notify_id = 1;
1275         chat->debugf = NULL;
1276
1277         if (flags & G_IO_FLAG_NONBLOCK)
1278                 chat->io = g_at_io_new(channel);
1279         else
1280                 chat->io = g_at_io_new_blocking(channel);
1281
1282         if (chat->io == NULL)
1283                 goto error;
1284
1285         g_at_io_set_disconnect_function(chat->io, io_disconnect, chat);
1286
1287         chat->command_queue = g_queue_new();
1288         if (chat->command_queue == NULL)
1289                 goto error;
1290
1291         chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
1292                                                 g_free, at_notify_destroy);
1293
1294         g_at_io_set_read_handler(chat->io, new_bytes, chat);
1295
1296         chat->syntax = g_at_syntax_ref(syntax);
1297
1298         return chat;
1299
1300 error:
1301         g_at_io_unref(chat->io);
1302
1303         if (chat->command_queue)
1304                 g_queue_free(chat->command_queue);
1305
1306         if (chat->notify_list)
1307                 g_hash_table_destroy(chat->notify_list);
1308
1309         g_free(chat);
1310         return NULL;
1311 }
1312
1313 static GAtChat *g_at_chat_new_common(GIOChannel *channel, GIOFlags flags,
1314                                         GAtSyntax *syntax)
1315 {
1316         GAtChat *chat;
1317
1318         chat = g_try_new0(GAtChat, 1);
1319         if (chat == NULL)
1320                 return NULL;
1321
1322         chat->parent = create_chat(channel, flags, syntax);
1323         if (chat->parent == NULL) {
1324                 g_free(chat);
1325                 return NULL;
1326         }
1327
1328         chat->group = chat->parent->next_gid++;
1329         chat->ref_count = 1;
1330
1331         return chat;
1332 }
1333
1334 GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax)
1335 {
1336         return g_at_chat_new_common(channel, G_IO_FLAG_NONBLOCK, syntax);
1337 }
1338
1339 GAtChat *g_at_chat_new_blocking(GIOChannel *channel, GAtSyntax *syntax)
1340 {
1341         return g_at_chat_new_common(channel, 0, syntax);
1342 }
1343
1344 GAtChat *g_at_chat_clone(GAtChat *clone)
1345 {
1346         GAtChat *chat;
1347
1348         if (clone == NULL)
1349                 return NULL;
1350
1351         chat = g_try_new0(GAtChat, 1);
1352         if (chat == NULL)
1353                 return NULL;
1354
1355         chat->parent = clone->parent;
1356         chat->group = chat->parent->next_gid++;
1357         chat->ref_count = 1;
1358         g_atomic_int_inc(&chat->parent->ref_count);
1359
1360         if (clone->slave != NULL)
1361                 chat->slave = g_at_chat_clone(clone->slave);
1362
1363         return chat;
1364 }
1365
1366 GAtChat *g_at_chat_set_slave(GAtChat *chat, GAtChat *slave)
1367 {
1368         if (chat == NULL)
1369                 return NULL;
1370
1371         if (chat->slave != NULL)
1372                 g_at_chat_unref(chat->slave);
1373
1374         if (slave != NULL)
1375                 chat->slave = g_at_chat_ref(slave);
1376         else
1377                 chat->slave = NULL;
1378
1379         return chat->slave;
1380 }
1381
1382 GAtChat *g_at_chat_get_slave(GAtChat *chat)
1383 {
1384         if (chat == NULL)
1385                 return NULL;
1386
1387         return chat->slave;
1388 }
1389
1390 GIOChannel *g_at_chat_get_channel(GAtChat *chat)
1391 {
1392         if (chat == NULL || chat->parent->io == NULL)
1393                 return NULL;
1394
1395         return g_at_io_get_channel(chat->parent->io);
1396 }
1397
1398 GAtIO *g_at_chat_get_io(GAtChat *chat)
1399 {
1400         if (chat == NULL)
1401                 return NULL;
1402
1403         return chat->parent->io;
1404 }
1405
1406 GAtChat *g_at_chat_ref(GAtChat *chat)
1407 {
1408         if (chat == NULL)
1409                 return NULL;
1410
1411         g_atomic_int_inc(&chat->ref_count);
1412
1413         return chat;
1414 }
1415
1416 void g_at_chat_suspend(GAtChat *chat)
1417 {
1418         if (chat == NULL)
1419                 return;
1420
1421         at_chat_suspend(chat->parent);
1422 }
1423
1424 void g_at_chat_resume(GAtChat *chat)
1425 {
1426         if (chat == NULL)
1427                 return;
1428
1429         at_chat_resume(chat->parent);
1430 }
1431
1432 void g_at_chat_unref(GAtChat *chat)
1433 {
1434         gboolean is_zero;
1435
1436         if (chat == NULL)
1437                 return;
1438
1439         is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
1440
1441         if (is_zero == FALSE)
1442                 return;
1443
1444         if (chat->slave != NULL)
1445                 g_at_chat_unref(chat->slave);
1446
1447         at_chat_cancel_group(chat->parent, chat->group);
1448         g_at_chat_unregister_all(chat);
1449         at_chat_unref(chat->parent);
1450
1451         g_free(chat);
1452 }
1453
1454 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
1455                         GAtDisconnectFunc disconnect, gpointer user_data)
1456 {
1457         if (chat == NULL || chat->group != 0)
1458                 return FALSE;
1459
1460         return at_chat_set_disconnect_function(chat->parent, disconnect,
1461                                                 user_data);
1462 }
1463
1464 gboolean g_at_chat_set_debug(GAtChat *chat,
1465                                 GAtDebugFunc func, gpointer user_data)
1466 {
1467
1468         if (chat == NULL || chat->group != 0)
1469                 return FALSE;
1470
1471         return at_chat_set_debug(chat->parent, func, user_data);
1472 }
1473
1474 void g_at_chat_add_terminator(GAtChat *chat, char *terminator,
1475                                         int len, gboolean success)
1476 {
1477         if (chat == NULL || chat->group != 0)
1478                 return;
1479
1480         at_chat_add_terminator(chat->parent, terminator, len, success);
1481 }
1482
1483 void g_at_chat_blacklist_terminator(GAtChat *chat,
1484                                                 GAtChatTerminator terminator)
1485 {
1486         if (chat == NULL || chat->group != 0)
1487                 return;
1488
1489         at_chat_blacklist_terminator(chat->parent, terminator);
1490 }
1491
1492 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1493                                         unsigned int timeout, unsigned int msec)
1494 {
1495         if (chat == NULL || chat->group != 0)
1496                 return FALSE;
1497
1498         return at_chat_set_wakeup_command(chat->parent, cmd, timeout, msec);
1499 }
1500
1501 guint g_at_chat_send(GAtChat *chat, const char *cmd,
1502                         const char **prefix_list, GAtResultFunc func,
1503                         gpointer user_data, GDestroyNotify notify)
1504 {
1505         return at_chat_send_common(chat->parent, chat->group,
1506                                         cmd, prefix_list, 0, NULL,
1507                                         func, user_data, notify);
1508 }
1509
1510 guint g_at_chat_send_listing(GAtChat *chat, const char *cmd,
1511                                 const char **prefix_list,
1512                                 GAtNotifyFunc listing, GAtResultFunc func,
1513                                 gpointer user_data, GDestroyNotify notify)
1514 {
1515         if (listing == NULL)
1516                 return 0;
1517
1518         return at_chat_send_common(chat->parent, chat->group,
1519                                         cmd, prefix_list, 0,
1520                                         listing, func, user_data, notify);
1521 }
1522
1523 guint g_at_chat_send_pdu_listing(GAtChat *chat, const char *cmd,
1524                                 const char **prefix_list,
1525                                 GAtNotifyFunc listing, GAtResultFunc func,
1526                                 gpointer user_data, GDestroyNotify notify)
1527 {
1528         if (listing == NULL)
1529                 return 0;
1530
1531         return at_chat_send_common(chat->parent, chat->group,
1532                                         cmd, prefix_list,
1533                                         COMMAND_FLAG_EXPECT_PDU,
1534                                         listing, func, user_data, notify);
1535 }
1536
1537 guint g_at_chat_send_and_expect_short_prompt(GAtChat *chat, const char *cmd,
1538                                                 const char **prefix_list,
1539                                                 GAtResultFunc func,
1540                                                 gpointer user_data,
1541                                                 GDestroyNotify notify)
1542 {
1543         return at_chat_send_common(chat->parent, chat->group,
1544                                         cmd, prefix_list,
1545                                         COMMAND_FLAG_EXPECT_SHORT_PROMPT,
1546                                         NULL, func, user_data, notify);
1547 }
1548
1549 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
1550 {
1551         /* We use id 0 for wakeup commands */
1552         if (chat == NULL || id == 0)
1553                 return FALSE;
1554
1555         return at_chat_cancel(chat->parent, chat->group, id);
1556 }
1557
1558 gboolean g_at_chat_cancel_all(GAtChat *chat)
1559 {
1560         if (chat == NULL)
1561                 return FALSE;
1562
1563         return at_chat_cancel_group(chat->parent, chat->group);
1564 }
1565
1566 gpointer g_at_chat_get_userdata(GAtChat *chat, guint id)
1567 {
1568         if (chat == NULL)
1569                 return NULL;
1570
1571         return at_chat_get_userdata(chat->parent, chat->group, id);
1572 }
1573
1574 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1575                                 GAtNotifyFunc func, gboolean expect_pdu,
1576                                 gpointer user_data,
1577                                 GDestroyNotify destroy_notify)
1578 {
1579         if (chat == NULL)
1580                 return 0;
1581
1582         return at_chat_register(chat->parent, chat->group, prefix,
1583                                 func, expect_pdu, user_data, destroy_notify);
1584 }
1585
1586 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1587 {
1588         if (chat == NULL)
1589                 return FALSE;
1590
1591         return at_chat_unregister(chat->parent, chat->parent->in_notify,
1592                                         chat->group, id);
1593 }
1594
1595 gboolean g_at_chat_unregister_all(GAtChat *chat)
1596 {
1597         if (chat == NULL)
1598                 return FALSE;
1599
1600         return at_chat_unregister_all(chat->parent,
1601                                         chat->parent->in_notify,
1602                                         node_compare_by_group,
1603                                         GUINT_TO_POINTER(chat->group));
1604 }