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