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