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