Skip to content

Commit

Permalink
Make AT command parser more robust
Browse files Browse the repository at this point in the history
This change improves the robustness of the AT command parser against
noise or arbitrary (non-AT) data. The new version can detect the AT
prefix in the middle of arbitrary input, e.g., the data generated when
the port is initially configured with the wrong baud rate.

This is more in-line with the original algorithm from the Hayes AT
command interface which detects AT command sequences in arbitrary data
stream. The Hayes approach uses a guard interface to only detect AT
commands after a short period of inactivity. We do not implement this
here since our AT command interface only supports AT commands, not
arbitrary data.
  • Loading branch information
janakj committed Apr 5, 2022
1 parent da331b5 commit ad78c0b
Showing 1 changed file with 118 additions and 74 deletions.
192 changes: 118 additions & 74 deletions src/atci.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@
#include "halt.h"
#include "system.h"

#define UNKNOWN_CMD "+ERR=-1\r\n\r\n"

enum parser_state
{
ATCI_START_STATE = 0,
ATCI_PREFIX_STATE,
ATCI_ATTENTION_STATE
};

static void _atci_process_character(char character);
static void _atci_process_line(void);
static void _atci_process_command(void);
static void finish_next_data(atci_data_status_t status);

static struct
Expand All @@ -16,6 +25,7 @@ static struct
size_t rx_length;
bool rx_error;
bool aborted;
enum parser_state state;

char tmp[256];

Expand Down Expand Up @@ -251,7 +261,7 @@ void atci_help_action(atci_param_t *param)
console_write("\r\n", 2);
}

static void _atci_process_line(void)
static void _atci_process_command(void)
{
log_debug("ATCI: %s", _atci.rx_buffer);

Expand All @@ -268,7 +278,7 @@ static void _atci_process_line(void)

_atci.rx_buffer[_atci.rx_length] = 0;

char *line = _atci.rx_buffer + 2;
char *name = _atci.rx_buffer + 2;

size_t length = _atci.rx_length - 2;

Expand All @@ -287,7 +297,7 @@ static void _atci_process_line(void)
continue;
}

if (strncmp(line, command->command, command_len) != 0)
if (strncmp(name, command->command, command_len) != 0)
{
continue;
}
Expand All @@ -300,9 +310,9 @@ static void _atci_process_line(void)
return;
}
}
else if (line[command_len] == '=')
else if (name[command_len] == '=')
{
if ((line[command_len + 1]) == '?' && (command_len + 2 == length))
if ((name[command_len + 1]) == '?' && (command_len + 2 == length))
{
if (command->help != NULL)
{
Expand All @@ -314,44 +324,38 @@ static void _atci_process_line(void)
if (command->set != NULL)
{
atci_param_t param = {
.txt = line + command_len + 1,
.txt = name + command_len + 1,
.length = length - command_len - 1,
.offset = 0};

command->set(&param);
return;
}
}
else if (line[command_len] == '?' && command_len + 1 == length)
else if (name[command_len] == '?' && command_len + 1 == length)
{
if (command->read != NULL)
{
command->read();
return;
}
}
else if (line[command_len] == ' ' && command_len + 1 < length)
else if (name[command_len] == ' ' && command_len + 1 < length)
{
if (command->action != NULL)
{
atci_param_t param = {
.txt = line + command_len + 1,
.txt = name + command_len + 1,
.length = length - command_len - 1,
.offset = 0};

command->action(&param);
return;
}
}
// else
// {
// atci_printf("Unknown: %s", command->command);
// return;
// }
// break;
}

console_write("+ERR=-1\r\n\r\n", 11);
console_write(UNKNOWN_CMD, sizeof(UNKNOWN_CMD) - 1);
}

static void finish_next_data(atci_data_status_t status)
Expand All @@ -373,82 +377,122 @@ static void finish_next_data(atci_data_status_t status)
_atci.rx_length = 0;
}

static void _atci_process_character(char character)
static void _atci_process_data(char character)
{
int c;
static bool even = true;
// log_debug("c %c %d %d", character, character, _atci.read_next_data.length);

if (_atci.read_next_data.length != 0)
switch(_atci.read_next_data.encoding)
{
switch(_atci.read_next_data.encoding)
{
case ATCI_ENCODING_BIN:
_atci.rx_buffer[_atci.rx_length++] = character;
break;

case ATCI_ENCODING_HEX:
c = hex2bin(character);
if (c < 0) {
_atci.rx_error = true;
break;
}
if (even)
{
_atci.rx_buffer[_atci.rx_length] = c << 4;
even = false;
}
else
{
_atci.rx_buffer[_atci.rx_length++] |= c;
even = true;
}
break;
case ATCI_ENCODING_BIN:
_atci.rx_buffer[_atci.rx_length++] = character;
break;

default:
halt("Unsupported payload encoding");
case ATCI_ENCODING_HEX:
c = hex2bin(character);
if (c < 0) {
_atci.rx_error = true;
break;
}
}
if (even)
{
_atci.rx_buffer[_atci.rx_length] = c << 4;
even = false;
}
else
{
_atci.rx_buffer[_atci.rx_length++] |= c;
even = true;
}
break;

if (_atci.read_next_data.length == _atci.rx_length || _atci.rx_error)
{
even = true;
finish_next_data(_atci.rx_error ? ATCI_DATA_ENCODING_ERROR : ATCI_DATA_OK);
_atci.rx_error = false;
}
return;
default:
halt("Unsupported payload encoding");
break;
}

if (character == '\r')
if (_atci.read_next_data.length == _atci.rx_length || _atci.rx_error)
{
if (_atci.rx_error)
{
console_write("+ERR=-1\r\n\r\n", 11);
}
else if (_atci.rx_length > 0)
{
_atci.rx_buffer[_atci.rx_length] = 0;
_atci_process_line();
}

_atci.rx_length = 0;
even = true;
finish_next_data(_atci.rx_error ? ATCI_DATA_ENCODING_ERROR : ATCI_DATA_OK);
_atci.rx_error = false;
}
else if (character == '\n')
}

static void reset(void)
{
_atci.rx_length = 0;
_atci.state = ATCI_START_STATE;
}

static int append_to_buffer(char c)
{
if (_atci.rx_length >= sizeof(_atci.rx_buffer) - 1)
{
return -1;
}

_atci.rx_buffer[_atci.rx_length++] = c;
return 0;
}

static void _atci_process_character(char character)
{
if (_atci.read_next_data.length != 0)
{
_atci_process_data(character);
return;
}
else if (character == '\x1b')

if (character == '\n')
{
_atci.rx_length = 0;
_atci.rx_error = false;
// Ignore LF characters, AT commands are terminated with CR
return;
}
else if (_atci.rx_length == sizeof(_atci.rx_buffer) - 1)
else if (character == '\x1b')
{
_atci.rx_error = true;
// If we get an ESC character, reset the buffer
reset();
return;
}
else if (!_atci.rx_error)
{
_atci.rx_buffer[_atci.rx_length++] = character;

switch (_atci.state) {
case ATCI_START_STATE:
if (character == 'A')
{
append_to_buffer(character);
_atci.state = ATCI_PREFIX_STATE;
}
break;

case ATCI_PREFIX_STATE:
if (character == 'T')
{
append_to_buffer(character);
_atci.state = ATCI_ATTENTION_STATE;
}
else
{
reset();
}
break;

case ATCI_ATTENTION_STATE:
if (character == '\r')
{
_atci.rx_buffer[_atci.rx_length] = 0;
_atci_process_command();
reset();
}
else if (append_to_buffer(character) < 0)
{
console_write(UNKNOWN_CMD, sizeof(UNKNOWN_CMD) - 1);
reset();
}
break;

default:
halt("Bug: Invalid state in ATCI parser");
break;
}
}

0 comments on commit ad78c0b

Please sign in to comment.