| /* |
| * |
| * AT chat library with GLib integration |
| * |
| * Copyright (C) 2008-2010 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <glib.h> |
| |
| #include "gatsyntax.h" |
| |
| enum GSMV1_STATE { |
| GSMV1_STATE_IDLE = 0, |
| GSMV1_STATE_INITIAL_CR, |
| GSMV1_STATE_INITIAL_LF, |
| GSMV1_STATE_RESPONSE, |
| GSMV1_STATE_RESPONSE_STRING, |
| GSMV1_STATE_TERMINATOR_CR, |
| GSMV1_STATE_GUESS_MULTILINE_RESPONSE, |
| GSMV1_STATE_MULTILINE_RESPONSE, |
| GSMV1_STATE_MULTILINE_TERMINATOR_CR, |
| GSMV1_STATE_PDU_CHECK_EXTRA_CR, |
| GSMV1_STATE_PDU_CHECK_EXTRA_LF, |
| GSMV1_STATE_PDU, |
| GSMV1_STATE_PDU_CR, |
| GSMV1_STATE_PROMPT, |
| GSMV1_STATE_ECHO, |
| GSMV1_PPP_DATA, |
| }; |
| |
| enum GSM_PERMISSIVE_STATE { |
| GSM_PERMISSIVE_STATE_IDLE = 0, |
| GSM_PERMISSIVE_STATE_RESPONSE, |
| GSM_PERMISSIVE_STATE_RESPONSE_STRING, |
| GSM_PERMISSIVE_STATE_GUESS_PDU, |
| GSM_PERMISSIVE_STATE_PDU, |
| GSM_PERMISSIVE_STATE_PROMPT, |
| }; |
| |
| static void gsmv1_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint) |
| { |
| switch (hint) { |
| case G_AT_SYNTAX_EXPECT_PDU: |
| syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_CR; |
| break; |
| case G_AT_SYNTAX_EXPECT_MULTILINE: |
| syntax->state = GSMV1_STATE_GUESS_MULTILINE_RESPONSE; |
| break; |
| default: |
| break; |
| }; |
| } |
| |
| static GAtSyntaxResult gsmv1_feed(GAtSyntax *syntax, |
| const char *bytes, gsize *len) |
| { |
| gsize i = 0; |
| GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE; |
| |
| while (i < *len) { |
| char byte = bytes[i]; |
| |
| switch (syntax->state) { |
| case GSMV1_STATE_IDLE: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_INITIAL_CR; |
| else if (byte == '~') |
| syntax->state = GSMV1_PPP_DATA; |
| else |
| syntax->state = GSMV1_STATE_ECHO; |
| break; |
| |
| case GSMV1_STATE_INITIAL_CR: |
| if (byte == '\n') |
| syntax->state = GSMV1_STATE_INITIAL_LF; |
| else |
| syntax->state = GSMV1_STATE_ECHO; |
| break; |
| |
| case GSMV1_STATE_INITIAL_LF: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_TERMINATOR_CR; |
| else if (byte == '>') |
| syntax->state = GSMV1_STATE_PROMPT; |
| else if (byte == '"') |
| syntax->state = GSMV1_STATE_RESPONSE_STRING; |
| else |
| syntax->state = GSMV1_STATE_RESPONSE; |
| break; |
| |
| case GSMV1_STATE_RESPONSE: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_TERMINATOR_CR; |
| else if (byte == '"') |
| syntax->state = GSMV1_STATE_RESPONSE_STRING; |
| break; |
| |
| case GSMV1_STATE_RESPONSE_STRING: |
| if (byte == '"') |
| syntax->state = GSMV1_STATE_RESPONSE; |
| break; |
| |
| case GSMV1_STATE_TERMINATOR_CR: |
| syntax->state = GSMV1_STATE_IDLE; |
| |
| if (byte == '\n') { |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_LINE; |
| } else |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| |
| goto out; |
| |
| case GSMV1_STATE_GUESS_MULTILINE_RESPONSE: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_INITIAL_CR; |
| else |
| syntax->state = GSMV1_STATE_MULTILINE_RESPONSE; |
| break; |
| |
| case GSMV1_STATE_MULTILINE_RESPONSE: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_MULTILINE_TERMINATOR_CR; |
| break; |
| |
| case GSMV1_STATE_MULTILINE_TERMINATOR_CR: |
| syntax->state = GSMV1_STATE_IDLE; |
| |
| if (byte == '\n') { |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_MULTILINE; |
| } else |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| |
| goto out; |
| |
| /* Some 27.007 compliant modems still get this wrong. They |
| * insert an extra CRLF between the command and he PDU, |
| * in effect making them two separate lines. We try to |
| * handle this case gracefully |
| */ |
| case GSMV1_STATE_PDU_CHECK_EXTRA_CR: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_PDU_CHECK_EXTRA_LF; |
| else |
| syntax->state = GSMV1_STATE_PDU; |
| break; |
| |
| case GSMV1_STATE_PDU_CHECK_EXTRA_LF: |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| syntax->state = GSMV1_STATE_PDU; |
| |
| if (byte == '\n') |
| i += 1; |
| |
| goto out; |
| |
| case GSMV1_STATE_PDU: |
| if (byte == '\r') |
| syntax->state = GSMV1_STATE_PDU_CR; |
| break; |
| |
| case GSMV1_STATE_PDU_CR: |
| syntax->state = GSMV1_STATE_IDLE; |
| |
| if (byte == '\n') { |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_PDU; |
| } else |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| |
| goto out; |
| |
| case GSMV1_STATE_PROMPT: |
| if (byte == ' ') { |
| syntax->state = GSMV1_STATE_IDLE; |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_PROMPT; |
| goto out; |
| } |
| |
| syntax->state = GSMV1_STATE_RESPONSE; |
| return G_AT_SYNTAX_RESULT_UNSURE; |
| |
| case GSMV1_STATE_ECHO: |
| /* This handles the case of echo of the PDU terminated |
| * by CtrlZ character |
| */ |
| if (byte == 26 || byte == '\r') { |
| syntax->state = GSMV1_STATE_IDLE; |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| i += 1; |
| goto out; |
| } |
| |
| break; |
| |
| case GSMV1_PPP_DATA: |
| if (byte == '~') { |
| syntax->state = GSMV1_STATE_IDLE; |
| res = G_AT_SYNTAX_RESULT_UNRECOGNIZED; |
| i += 1; |
| goto out; |
| } |
| |
| break; |
| |
| default: |
| break; |
| }; |
| |
| i += 1; |
| } |
| |
| out: |
| *len = i; |
| return res; |
| } |
| |
| static void gsm_permissive_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint) |
| { |
| if (hint == G_AT_SYNTAX_EXPECT_PDU) |
| syntax->state = GSM_PERMISSIVE_STATE_GUESS_PDU; |
| } |
| |
| static GAtSyntaxResult gsm_permissive_feed(GAtSyntax *syntax, |
| const char *bytes, gsize *len) |
| { |
| gsize i = 0; |
| GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE; |
| |
| while (i < *len) { |
| char byte = bytes[i]; |
| |
| switch (syntax->state) { |
| case GSM_PERMISSIVE_STATE_IDLE: |
| if (byte == '\r' || byte == '\n') |
| /* ignore */; |
| else if (byte == '>') |
| syntax->state = GSM_PERMISSIVE_STATE_PROMPT; |
| else |
| syntax->state = GSM_PERMISSIVE_STATE_RESPONSE; |
| break; |
| |
| case GSM_PERMISSIVE_STATE_RESPONSE: |
| if (byte == '\r') { |
| syntax->state = GSM_PERMISSIVE_STATE_IDLE; |
| |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_LINE; |
| goto out; |
| } else if (byte == '"') |
| syntax->state = |
| GSM_PERMISSIVE_STATE_RESPONSE_STRING; |
| break; |
| |
| case GSM_PERMISSIVE_STATE_RESPONSE_STRING: |
| if (byte == '"') |
| syntax->state = GSM_PERMISSIVE_STATE_RESPONSE; |
| break; |
| |
| case GSM_PERMISSIVE_STATE_GUESS_PDU: |
| if (byte != '\r' && byte != '\n') |
| syntax->state = GSM_PERMISSIVE_STATE_PDU; |
| break; |
| |
| case GSM_PERMISSIVE_STATE_PDU: |
| if (byte == '\r') { |
| syntax->state = GSM_PERMISSIVE_STATE_IDLE; |
| |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_PDU; |
| goto out; |
| } |
| break; |
| |
| case GSM_PERMISSIVE_STATE_PROMPT: |
| if (byte == ' ') { |
| syntax->state = GSM_PERMISSIVE_STATE_IDLE; |
| i += 1; |
| res = G_AT_SYNTAX_RESULT_PROMPT; |
| goto out; |
| } |
| |
| syntax->state = GSM_PERMISSIVE_STATE_RESPONSE; |
| return G_AT_SYNTAX_RESULT_UNSURE; |
| |
| default: |
| break; |
| }; |
| |
| i += 1; |
| } |
| |
| out: |
| *len = i; |
| return res; |
| } |
| |
| GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed, |
| GAtSyntaxSetHintFunc hint, |
| int initial_state) |
| { |
| GAtSyntax *syntax; |
| |
| syntax = g_new0(GAtSyntax, 1); |
| |
| syntax->feed = feed; |
| syntax->set_hint = hint; |
| syntax->state = initial_state; |
| syntax->ref_count = 1; |
| |
| return syntax; |
| } |
| |
| |
| GAtSyntax *g_at_syntax_new_gsmv1(void) |
| { |
| return g_at_syntax_new_full(gsmv1_feed, gsmv1_hint, GSMV1_STATE_IDLE); |
| } |
| |
| GAtSyntax *g_at_syntax_new_gsm_permissive(void) |
| { |
| return g_at_syntax_new_full(gsm_permissive_feed, gsm_permissive_hint, |
| GSM_PERMISSIVE_STATE_IDLE); |
| } |
| |
| GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax) |
| { |
| if (syntax == NULL) |
| return NULL; |
| |
| g_atomic_int_inc(&syntax->ref_count); |
| |
| return syntax; |
| } |
| |
| void g_at_syntax_unref(GAtSyntax *syntax) |
| { |
| gboolean is_zero; |
| |
| if (syntax == NULL) |
| return; |
| |
| is_zero = g_atomic_int_dec_and_test(&syntax->ref_count); |
| |
| if (is_zero) |
| g_free(syntax); |
| } |