ofono/gatchat/gatsyntax.c

429 lines
9.4 KiB
C

/*
*
* AT chat library with GLib integration
*
* Copyright (C) 2008-2011 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_STATE_PPP_DATA,
GSMV1_STATE_SHORT_PROMPT,
GSMV1_STATE_SHORT_PROMPT_CR,
};
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,
GSM_PERMISSIVE_STATE_GUESS_SHORT_PROMPT,
GSM_PERMISSIVE_STATE_SHORT_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;
case G_AT_SYNTAX_EXPECT_SHORT_PROMPT:
syntax->state = GSMV1_STATE_SHORT_PROMPT;
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_STATE_PPP_DATA;
else
syntax->state = GSMV1_STATE_ECHO;
break;
case GSMV1_STATE_INITIAL_CR:
if (byte == '\n')
syntax->state = GSMV1_STATE_INITIAL_LF;
else if (byte == '\r') {
syntax->state = GSMV1_STATE_IDLE;
return G_AT_SYNTAX_RESULT_UNRECOGNIZED;
} 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_STATE_PPP_DATA:
if (byte == '~') {
syntax->state = GSMV1_STATE_IDLE;
res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
i += 1;
goto out;
}
break;
case GSMV1_STATE_SHORT_PROMPT:
if (byte == '\r')
syntax->state = GSMV1_STATE_SHORT_PROMPT_CR;
else
syntax->state = GSMV1_STATE_ECHO;
break;
case GSMV1_STATE_SHORT_PROMPT_CR:
if (byte == '\n') {
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;
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;
else if (hint == G_AT_SYNTAX_EXPECT_SHORT_PROMPT)
syntax->state = GSM_PERMISSIVE_STATE_GUESS_SHORT_PROMPT;
}
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 if (byte == '"')
syntax->state =
GSM_PERMISSIVE_STATE_RESPONSE_STRING;
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;
else if (byte == '\r') {
syntax->state = GSM_PERMISSIVE_STATE_IDLE;
i += 1;
res = G_AT_SYNTAX_RESULT_LINE;
goto out;
}
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;
case GSM_PERMISSIVE_STATE_GUESS_SHORT_PROMPT:
if (byte == '\n')
/* ignore */;
else if (byte == '\r')
syntax->state =
GSM_PERMISSIVE_STATE_SHORT_PROMPT;
else
syntax->state = GSM_PERMISSIVE_STATE_RESPONSE;
break;
case GSM_PERMISSIVE_STATE_SHORT_PROMPT:
if (byte == '\n') {
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);
}