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