Skip to content

File syn_at_parser.c

File List > proto > syn_at_parser.c

Go to the documentation of this file

#include "syn_at_parser.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

SYN_Status syn_at_parser_init(SYN_AtParser *parser, char *buf, size_t buf_size)
{
    if (parser == NULL || buf == NULL || buf_size == 0) {
        return SYN_INVALID_PARAM;
    }

    parser->line_buf = buf;
    parser->line_buf_size = buf_size;
    syn_at_parser_reset(parser);

    return SYN_OK;
}

void syn_at_parser_reset(SYN_AtParser *parser)
{
    if (parser == NULL) {
        return;
    }

    parser->line_len = 0;
    parser->last_resp = SYN_AT_RESP_NONE;
    parser->cme_error_code = -1;
    parser->prompt_detected = false;
    if (parser->line_buf != NULL && parser->line_buf_size > 0) {
        parser->line_buf[0] = '\0';
    }
}

static bool starts_with(const char *str, const char *prefix)
{
    if (str == NULL || prefix == NULL)
        return false;
    return strncmp(str, prefix, strlen(prefix)) == 0;
}

SYN_AtRespType syn_at_parser_feed_char(SYN_AtParser *parser, char c)
{
    if (parser == NULL || parser->line_buf == NULL) {
        return SYN_AT_RESP_NONE;
    }

    /* Ignore carriage return characters (\r) in CRLF sequences */
    if (c == '\r') {
        return SYN_AT_RESP_NONE;
    }

    /* Prompt detection: '>' as first non-whitespace character */
    if (c == '>' && parser->line_len == 0) {
        parser->prompt_detected = true;
        parser->last_resp = SYN_AT_RESP_PROMPT;
        return SYN_AT_RESP_PROMPT;
    }

    if (c == '\n') {
        if (parser->line_len == 0) {
            /* Ignore empty lines (leading \n sequences) */
            return SYN_AT_RESP_NONE;
        }

        parser->line_buf[parser->line_len] = '\0';
        const char *line = parser->line_buf;

        /* Classify response line */
        if (strcmp(line, "OK") == 0) {
            parser->last_resp = SYN_AT_RESP_OK;
        } else if (strcmp(line, "ERROR") == 0) {
            parser->last_resp = SYN_AT_RESP_ERROR;
        } else if (starts_with(line, "+CME ERROR:") || starts_with(line, "+CMS ERROR:")) {
            parser->last_resp = SYN_AT_RESP_CME_ERROR;
            const char *p = strchr(line, ':');
            if (p != NULL) {
                parser->cme_error_code = atoi(p + 1);
            } else {
                parser->cme_error_code = -1;
            }
        } else if (starts_with(line, "+RECEIVE,") || starts_with(line, "+IPD,") ||
                   starts_with(line, "CLOSED") || starts_with(line, "SHUT OK") ||
                   starts_with(line, "RING") || starts_with(line, "+CMTI:")) {
            parser->last_resp = SYN_AT_RESP_URC;
        } else {
            parser->last_resp = SYN_AT_RESP_LINE;
        }

        parser->line_len = 0; /* Reset buffer for next line */
        return parser->last_resp;
    }

    /* Accumulate character */
    if (parser->line_len < parser->line_buf_size - 1) {
        parser->line_buf[parser->line_len++] = c;
    }

    return SYN_AT_RESP_NONE;
}

SYN_AtRespType syn_at_parser_feed_stream(SYN_AtParser *parser, SYN_Stream *stream)
{
    if (parser == NULL || stream == NULL) {
        return SYN_AT_RESP_NONE;
    }

    uint8_t byte;
    while (syn_stream_read(stream, &byte, 1) == 1) {
        SYN_AtRespType resp = syn_at_parser_feed_char(parser, (char)byte);
        if (resp != SYN_AT_RESP_NONE) {
            return resp;
        }
    }

    return SYN_AT_RESP_NONE;
}

const char *syn_at_parser_get_line(const SYN_AtParser *parser)
{
    if (parser == NULL || parser->line_buf == NULL) {
        return "";
    }
    return parser->line_buf;
}

int syn_at_parser_get_cme_error(const SYN_AtParser *parser)
{
    if (parser == NULL) {
        return -1;
    }
    return parser->cme_error_code;
}

static const char *find_param_start(const char *line, size_t param_idx)
{
    if (line == NULL) {
        return NULL;
    }

    /* Skip header prefix if present (e.g. "+CSQ: ") */
    const char *p = strchr(line, ':');
    if (p != NULL) {
        p++;
    } else {
        p = line;
    }

    while (*p == ' ') {
        p++;
    }

    size_t curr_idx = 0;
    while (curr_idx < param_idx) {
        p = strchr(p, ',');
        if (p == NULL) {
            return NULL;
        }
        p++;
        while (*p == ' ') {
            p++;
        }
        curr_idx++;
    }

    return p;
}

bool syn_at_parser_get_param_int(const char *line, size_t param_idx, int *out_val)
{
    if (out_val == NULL) {
        return false;
    }

    const char *p = find_param_start(line, param_idx);
    if (p == NULL || *p == '\0') {
        return false;
    }

    *out_val = atoi(p);
    return true;
}

bool syn_at_parser_get_param_str(const char *line, size_t param_idx, char *out_buf, size_t max_len)
{
    if (out_buf == NULL || max_len == 0) {
        return false;
    }

    const char *p = find_param_start(line, param_idx);
    if (p == NULL) {
        return false;
    }

    /* Skip leading quote */
    if (*p == '"') {
        p++;
    }

    size_t out_idx = 0;
    while (*p != '\0' && *p != ',' && *p != '"' && *p != '\r' && *p != '\n' &&
           out_idx < max_len - 1) {
        out_buf[out_idx++] = *p++;
    }
    out_buf[out_idx] = '\0';

    return true;
}