Framework to support non-standard terminator
[platform/upstream/connman.git] / gatchat / gatsyntax.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 <glib.h>
27
28 #include "gatsyntax.h"
29
30 enum GSMV1_STATE {
31         GSMV1_STATE_IDLE = 0,
32         GSMV1_STATE_INITIAL_CR,
33         GSMV1_STATE_INITIAL_LF,
34         GSMV1_STATE_RESPONSE,
35         GSMV1_STATE_TERMINATOR_CR,
36         GSMV1_STATE_GUESS_MULTILINE_RESPONSE,
37         GSMV1_STATE_MULTILINE_RESPONSE,
38         GSMV1_STATE_MULTILINE_TERMINATOR_CR,
39         GSMV1_STATE_PDU_CHECK_EXTRA_CR,
40         GSMV1_STATE_PDU_CHECK_EXTRA_LF,
41         GSMV1_STATE_PDU,
42         GSMV1_STATE_PDU_CR,
43         GSMV1_STATE_PROMPT,
44         GSMV1_STATE_GARBAGE,
45         GSMV1_STATE_GARBAGE_CHECK_LF,
46 };
47
48 enum GSM_PERMISSIVE_STATE {
49         GSM_PERMISSIVE_STATE_IDLE = 0,
50         GSM_PERMISSIVE_STATE_RESPONSE,
51         GSM_PERMISSIVE_STATE_GUESS_PDU,
52         GSM_PERMISSIVE_STATE_PDU,
53         GSM_PERMISSIVE_STATE_PROMPT,
54 };
55
56 static void gsmv1_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint)
57 {
58         switch (hint) {
59         case G_AT_SYNTAX_EXPECT_PDU:
60                 syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_CR;
61                 break;
62         case G_AT_SYNTAX_EXPECT_MULTILINE:
63                 syntax->state = GSMV1_STATE_GUESS_MULTILINE_RESPONSE;
64                 break;
65         default:
66                 break;
67         };
68 }
69
70 static GAtSyntaxResult gsmv1_feed(GAtSyntax *syntax,
71                                         const char *bytes, gsize *len)
72 {
73         gsize i = 0;
74         GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE;
75
76         while (i < *len) {
77                 char byte = bytes[i];
78
79                 switch (syntax->state) {
80                 case GSMV1_STATE_IDLE:
81                         if (byte == '\r')
82                                 syntax->state = GSMV1_STATE_INITIAL_CR;
83                         else
84                                 syntax->state = GSMV1_STATE_GARBAGE;
85                         break;
86
87                 case GSMV1_STATE_INITIAL_CR:
88                         if (byte == '\n')
89                                 syntax->state = GSMV1_STATE_INITIAL_LF;
90                         else
91                                 syntax->state = GSMV1_STATE_GARBAGE;
92                         break;
93
94                 case GSMV1_STATE_INITIAL_LF:
95                         if (byte == '\r')
96                                 syntax->state = GSMV1_STATE_TERMINATOR_CR;
97                         else if (byte == '>')
98                                 syntax->state = GSMV1_STATE_PROMPT;
99                         else
100                                 syntax->state = GSMV1_STATE_RESPONSE;
101                         break;
102
103                 case GSMV1_STATE_RESPONSE:
104                         if (byte == '\r')
105                                 syntax->state = GSMV1_STATE_TERMINATOR_CR;
106                         break;
107
108                 case GSMV1_STATE_TERMINATOR_CR:
109                         syntax->state = GSMV1_STATE_IDLE;
110
111                         if (byte == '\n') {
112                                 i += 1;
113                                 res = G_AT_SYNTAX_RESULT_LINE;
114                         } else
115                                 res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
116
117                         goto out;
118
119                 case GSMV1_STATE_GUESS_MULTILINE_RESPONSE:
120                         if (byte == '\r')
121                                 syntax->state = GSMV1_STATE_INITIAL_CR;
122                         else
123                                 syntax->state = GSMV1_STATE_MULTILINE_RESPONSE;
124                         break;
125
126                 case GSMV1_STATE_MULTILINE_RESPONSE:
127                         if (byte == '\r')
128                                 syntax->state = GSMV1_STATE_MULTILINE_TERMINATOR_CR;
129                         break;
130
131                 case GSMV1_STATE_MULTILINE_TERMINATOR_CR:
132                         syntax->state = GSMV1_STATE_IDLE;
133
134                         if (byte == '\n') {
135                                 i += 1;
136                                 res = G_AT_SYNTAX_RESULT_MULTILINE;
137                         } else
138                                 res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
139
140                         goto out;
141
142                 /* Some 27.007 compliant modems still get this wrong.  They
143                  * insert an extra CRLF between the command and he PDU,
144                  * in effect making them two separate lines.  We try to
145                  * handle this case gracefully
146                  */
147                 case GSMV1_STATE_PDU_CHECK_EXTRA_CR:
148                         if (byte == '\r')
149                                 syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_LF;
150                         else
151                                 syntax->state = GSMV1_STATE_PDU;
152                         break;
153
154                 case GSMV1_STATE_PDU_CHECK_EXTRA_LF:
155                         res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
156                         syntax->state = GSMV1_STATE_PDU;
157
158                         if (byte == '\n')
159                                 i += 1;
160
161                         goto out;
162
163                 case GSMV1_STATE_PDU:
164                         if (byte == '\r')
165                                 syntax->state = GSMV1_STATE_PDU_CR;
166                         break;
167
168                 case GSMV1_STATE_PDU_CR:
169                         syntax->state = GSMV1_STATE_IDLE;
170
171                         if (byte == '\n') {
172                                 i += 1;
173                                 res = G_AT_SYNTAX_RESULT_PDU;
174                         } else
175                                 res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
176
177                         goto out;
178
179                 case GSMV1_STATE_PROMPT:
180                         if (byte == ' ') {
181                                 syntax->state = GSMV1_STATE_IDLE;
182                                 i += 1;
183                                 res = G_AT_SYNTAX_RESULT_PROMPT;
184                                 goto out;
185                         }
186
187                         syntax->state = GSMV1_STATE_RESPONSE;
188                         return G_AT_SYNTAX_RESULT_UNSURE;
189
190                 case GSMV1_STATE_GARBAGE:
191                         if (byte == '\r')
192                                 syntax->state = GSMV1_STATE_GARBAGE_CHECK_LF;
193                         /* This handles the case of echo of the PDU terminated
194                          * by CtrlZ character
195                          */
196                         else if (byte == 26) {
197                                 syntax->state = GSMV1_STATE_IDLE;
198                                 res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
199                                 i += 1;
200                                 goto out;
201                         }
202
203                         break;
204
205                 case GSMV1_STATE_GARBAGE_CHECK_LF:
206                         syntax->state = GSMV1_STATE_IDLE;
207                         res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
208
209                         if (byte == '\n')
210                                 i += 1;
211
212                         goto out;
213
214                 default:
215                         break;
216                 };
217
218                 i += 1;
219         }
220
221 out:
222         *len = i;
223         return res;
224 }
225
226 static void gsm_permissive_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint)
227 {
228         if (hint == G_AT_SYNTAX_EXPECT_PDU)
229                 syntax->state = GSM_PERMISSIVE_STATE_GUESS_PDU;
230 }
231
232 static GAtSyntaxResult gsm_permissive_feed(GAtSyntax *syntax,
233                                                 const char *bytes, gsize *len)
234 {
235         gsize i = 0;
236         GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE;
237
238         while (i < *len) {
239                 char byte = bytes[i];
240
241                 switch (syntax->state) {
242                 case GSM_PERMISSIVE_STATE_IDLE:
243                         if (byte == '\r' || byte == '\n')
244                                 /* ignore */;
245                         else if (byte == '>')
246                                 syntax->state = GSM_PERMISSIVE_STATE_PROMPT;
247                         else
248                                 syntax->state = GSM_PERMISSIVE_STATE_RESPONSE;
249                         break;
250
251                 case GSM_PERMISSIVE_STATE_RESPONSE:
252                         if (byte == '\r') {
253                                 syntax->state = GSM_PERMISSIVE_STATE_IDLE;
254
255                                 i += 1;
256                                 res = G_AT_SYNTAX_RESULT_LINE;
257                                 goto out;
258                         }
259                         break;
260
261                 case GSM_PERMISSIVE_STATE_GUESS_PDU:
262                         if (byte != '\r' && byte != '\n')
263                                 syntax->state = GSM_PERMISSIVE_STATE_PDU;
264                         break;
265
266                 case GSM_PERMISSIVE_STATE_PDU:
267                         if (byte == '\r') {
268                                 syntax->state = GSM_PERMISSIVE_STATE_IDLE;
269
270                                 i += 1;
271                                 res = G_AT_SYNTAX_RESULT_PDU;
272                                 goto out;
273                         }
274                         break;
275
276                 case GSM_PERMISSIVE_STATE_PROMPT:
277                         if (byte == ' ') {
278                                 syntax->state = GSM_PERMISSIVE_STATE_IDLE;
279                                 i += 1;
280                                 res = G_AT_SYNTAX_RESULT_PROMPT;
281                                 goto out;
282                         }
283
284                         syntax->state = GSM_PERMISSIVE_STATE_RESPONSE;
285                         return G_AT_SYNTAX_RESULT_UNSURE;
286
287                 default:
288                         break;
289                 };
290
291                 i += 1;
292         }
293
294 out:
295         *len = i;
296         return res;
297 }
298
299 GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed,
300                                         GAtSyntaxSetHintFunc hint,
301                                         int initial_state)
302 {
303         GAtSyntax *syntax;
304
305         syntax = g_new0(GAtSyntax, 1);
306
307         syntax->feed = feed;
308         syntax->set_hint = hint;
309         syntax->state = initial_state;
310         syntax->ref_count = 1;
311
312         return syntax;
313 }
314
315
316 GAtSyntax *g_at_syntax_new_gsmv1()
317 {
318         return g_at_syntax_new_full(gsmv1_feed, gsmv1_hint, GSMV1_STATE_IDLE);
319 }
320
321 GAtSyntax *g_at_syntax_new_gsm_permissive()
322 {
323         return g_at_syntax_new_full(gsm_permissive_feed, gsm_permissive_hint,
324                                         GSM_PERMISSIVE_STATE_IDLE);
325 }
326
327 GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax)
328 {
329         if (syntax == NULL)
330                 return NULL;
331
332         g_atomic_int_inc(&syntax->ref_count);
333
334         return syntax;
335 }
336
337 void g_at_syntax_unref(GAtSyntax *syntax)
338 {
339         gboolean is_zero;
340
341         if (syntax == NULL)
342                 return;
343
344         is_zero = g_atomic_int_dec_and_test(&syntax->ref_count);
345
346         if (is_zero)
347                 g_free(syntax);
348 }