/*******************************************************************************
    OpenAirInterface
    Copyright(c) 1999 - 2014 Eurecom

    OpenAirInterface is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.


    OpenAirInterface 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 OpenAirInterface.The full GNU General Public License is
   included in this distribution in the file called "COPYING". If not,
   see <http://www.gnu.org/licenses/>.

  Contact Information
  OpenAirInterface Admin: openair_admin@eurecom.fr
  OpenAirInterface Tech : openair_tech@eurecom.fr
  OpenAirInterface Dev  : openair4g-devel@lists.eurecom.fr

  Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/
/*****************************************************************************

Source    at_command.c

Version   0.1

Date    2012/03/07

Product   NAS stack

Subsystem Application Programming Interface

Author    Frederic Maurel, Baris Demiray

Description Defines the ATtention (AT) command set supported by the NAS
    sublayer protocol

*****************************************************************************/

#include "at_command.h"
#include "commonDef.h"
#include "nas_log.h"

#include "at_error.h"

#include <ctype.h>  // isspace, isdigit
#include <stdlib.h> // strtol, malloc, free
#include <string.h> // strncpy, strstr, strchr, strcmp, strncasecmp
// strlen, memmove, strdup, strtok
#include <stdarg.h> // va_start, va_arg, va_end

/****************************************************************************/
/****************  E X T E R N A L    D E F I N I T I O N S  ****************/
/****************************************************************************/

extern int at_response_format_v1;
extern int at_error_code_suppression_q1;
extern at_error_format_t at_error_format;

/****************************************************************************/
/*******************  L O C A L    D E F I N I T I O N S  *******************/
/****************************************************************************/

/* Handlers used to parse AT commands */
static int parse_e0(const char*, int, at_command_t*);
static int parse_e1(const char*, int, at_command_t*);
static int parse_q0(const char*, int, at_command_t*);
static int parse_q1(const char*, int, at_command_t*);
static int parse_v0(const char*, int, at_command_t*);
static int parse_v1(const char*, int, at_command_t*);
static int parse_cgsn(const char*, int, at_command_t*);
static int parse_cgsn_test(const char*, int, at_command_t*);
static int parse_cgmi(const char*, int, at_command_t*);
static int parse_cgmi_test(const char*, int, at_command_t*);
static int parse_cgmm(const char*, int, at_command_t*);
static int parse_cgmm_test(const char*, int, at_command_t*);
static int parse_cgmr(const char*, int, at_command_t*);
static int parse_cgmr_test(const char*, int, at_command_t*);
static int parse_cimi(const char*, int, at_command_t*);
static int parse_cimi_test(const char*, int, at_command_t*);
static int parse_cfun_set(const char*, int, at_command_t*);
static int parse_cfun_get(const char*, int, at_command_t*);
static int parse_cfun_test(const char*, int, at_command_t*);
static int parse_cpin_set(const char*, int, at_command_t*);
static int parse_cpin_get(const char*, int, at_command_t*);
static int parse_cpin_test(const char*, int, at_command_t*);
static int parse_csq(const char*, int, at_command_t*);
static int parse_csq_test(const char*, int, at_command_t*);
static int parse_cesq(const char*, int, at_command_t*);
static int parse_cesq_test(const char*, int, at_command_t*);
static int parse_clac(const char*, int, at_command_t*);
static int parse_clac_test(const char*, int, at_command_t*);
static int parse_cmee_set(const char*, int, at_command_t*);
static int parse_cmee_get(const char*, int, at_command_t*);
static int parse_cmee_test(const char*, int, at_command_t*);
static int parse_cnum(const char*, int, at_command_t*);
static int parse_cnum_test(const char*, int, at_command_t*);
static int parse_clck_set(const char*, int, at_command_t*);
static int parse_clck_test(const char*, int, at_command_t*);
static int parse_cops_set(const char*, int, at_command_t*);
static int parse_cops_get(const char*, int, at_command_t*);
static int parse_cops_test(const char*, int, at_command_t*);
static int parse_creg_set(const char*, int, at_command_t*);
static int parse_creg_get(const char*, int, at_command_t*);
static int parse_creg_test(const char*, int, at_command_t*);
static int parse_cgatt_set(const char*, int, at_command_t*);
static int parse_cgatt_get(const char*, int, at_command_t*);
static int parse_cgatt_test(const char*, int, at_command_t*);
static int parse_cgreg_set(const char*, int, at_command_t*);
static int parse_cgreg_get(const char*, int, at_command_t*);
static int parse_cgreg_test(const char*, int, at_command_t*);
static int parse_cereg_set(const char*, int, at_command_t*);
static int parse_cereg_get(const char*, int, at_command_t*);
static int parse_cereg_test(const char*, int, at_command_t*);
static int parse_cgdcont_set(const char*, int, at_command_t*);
static int parse_cgdcont_get(const char*, int, at_command_t*);
static int parse_cgdcont_test(const char*, int, at_command_t*);
static int parse_cgact_set(const char*, int, at_command_t*);
static int parse_cgact_get(const char*, int, at_command_t*);
static int parse_cgact_test(const char*, int, at_command_t*);
static int parse_cgpaddr_set(const char*, int, at_command_t*);
static int parse_cgpaddr_test(const char*, int, at_command_t*);

/* List of AT command handlers */
typedef struct {
  char *command;
  int (*handler)(const char *, int, at_command_t*);
  at_command_type_t type;
} at_command_handler_t;

static const at_command_handler_t _at_command_handler[] = {
  /* TE-TA interface commands         */
  { "E0",   parse_e0,   AT_COMMAND_ACT },
  { "E1",   parse_e1,   AT_COMMAND_ACT },
  { "Q0",   parse_q0,   AT_COMMAND_ACT },
  { "Q1",   parse_q1,   AT_COMMAND_ACT },
  { "V0",   parse_v0,   AT_COMMAND_ACT },
  { "V1",   parse_v1,   AT_COMMAND_ACT },
  { "E0Q0V0",   parse_v0,   AT_COMMAND_ACT },
  { "E0Q0V1",   parse_v1,   AT_COMMAND_ACT },
  { "E1Q0V0",   parse_v0,   AT_COMMAND_ACT },
  { "E1Q0V1",   parse_v1,   AT_COMMAND_ACT },
  /* Request Product Serial Number identification (IMEI)  */
  { "+CGSN",    parse_cgsn,   AT_COMMAND_ACT },
  { "+CGSN=?",  parse_cgsn_test,  AT_COMMAND_TST },
  /* Request manufacturer identification      */
  { "+CGMI",    parse_cgmi,     AT_COMMAND_ACT },
  { "+CGMI=?",  parse_cgmi_test,  AT_COMMAND_TST },
  /* Request model identification       */
  { "+CGMM",    parse_cgmm,     AT_COMMAND_ACT },
  { "+CGMM=?",  parse_cgmm_test,  AT_COMMAND_TST },
  /* Request revision identification        */
  { "+CGMR",    parse_cgmr,     AT_COMMAND_ACT },
  { "+CGMR=?",  parse_cgmr_test,  AT_COMMAND_TST },
  /* Request International Mobile Subscriber Identity   */
  { "+CIMI",    parse_cimi,     AT_COMMAND_ACT },
  { "+CIMI=?",  parse_cimi_test,  AT_COMMAND_TST },
  /* Set phone functionality          */
  { "+CFUN=",   parse_cfun_set,   AT_COMMAND_SET },
  { "+CFUN?",   parse_cfun_get,   AT_COMMAND_GET },
  { "+CFUN=?",  parse_cfun_test,  AT_COMMAND_TST },
  /* Enter PIN            */
  { "+CPIN=",   parse_cpin_set,   AT_COMMAND_SET },
  { "+CPIN?",   parse_cpin_get,   AT_COMMAND_GET },
  { "+CPIN=?",  parse_cpin_test,  AT_COMMAND_TST },
  /* Signal quality           */
  { "+CSQ",     parse_csq,    AT_COMMAND_ACT },
  { "+CSQ=?",   parse_csq_test,   AT_COMMAND_TST },
  /* Extended signal quality          */
  { "+CESQ",    parse_cesq,     AT_COMMAND_ACT },
  { "+CESQ=?",  parse_cesq_test,  AT_COMMAND_TST },
  /* List all available AT commands       */
  { "+CLAC",    parse_clac,     AT_COMMAND_ACT },
  { "+CLAC=?",  parse_clac_test,  AT_COMMAND_TST },
  /*  Report mobile termination error       */
  { "+CMEE=",   parse_cmee_set,   AT_COMMAND_SET },
  { "+CMEE?",   parse_cmee_get,   AT_COMMAND_GET },
  { "+CMEE=?",  parse_cmee_test,  AT_COMMAND_TST },
  /* Subscriber number          */
  { "+CNUM",    parse_cnum,     AT_COMMAND_ACT },
  { "+CNUM=?",  parse_cnum_test,  AT_COMMAND_TST },
  /* Facility lock            */
  { "+CLCK=",   parse_clck_set,   AT_COMMAND_SET },
  { "+CLCK=?",  parse_clck_test,  AT_COMMAND_TST },
  /* PLMN selection             */
  { "+COPS=",   parse_cops_set,   AT_COMMAND_SET },
  { "+COPS?",   parse_cops_get,   AT_COMMAND_GET },
  { "+COPS=?",  parse_cops_test,  AT_COMMAND_TST },
  /* Network registration         */
  { "+CREG=",   parse_creg_set,   AT_COMMAND_SET },
  { "+CREG?",   parse_creg_get,   AT_COMMAND_GET },
  { "+CREG=?",  parse_creg_test,  AT_COMMAND_TST },
  /* GPRS service attach/detach       */
  { "+CGATT=",  parse_cgatt_set,  AT_COMMAND_SET },
  { "+CGATT?",  parse_cgatt_get,  AT_COMMAND_GET },
  { "+CGATT=?",   parse_cgatt_test,   AT_COMMAND_TST },
  /* GPRS network registration status       */
  { "+CGREG=",  parse_cgreg_set,  AT_COMMAND_SET },
  { "+CGREG?",  parse_cgreg_get,  AT_COMMAND_GET },
  { "+CGREG=?",   parse_cgreg_test,   AT_COMMAND_TST },
  /* EPS network registration status        */
  { "+CEREG=",  parse_cereg_set,  AT_COMMAND_SET },
  { "+CEREG?",  parse_cereg_get,  AT_COMMAND_GET },
  { "+CEREG=?",   parse_cereg_test,   AT_COMMAND_TST },
  /* Define PDP Context         */
  { "+CGDCONT=",  parse_cgdcont_set,  AT_COMMAND_SET },
  { "+CGDCONT?",  parse_cgdcont_get,  AT_COMMAND_GET },
  { "+CGDCONT=?",   parse_cgdcont_test,   AT_COMMAND_TST },
  /* PDP context activate or deactivate     */
  { "+CGACT=",  parse_cgact_set,  AT_COMMAND_SET },
  { "+CGACT?",  parse_cgact_get,  AT_COMMAND_GET },
  { "+CGACT=?",   parse_cgact_test,   AT_COMMAND_TST },
  /* Show PDP address(es)         */
  { "+CGPADDR=",  parse_cgpaddr_set,  AT_COMMAND_SET },
  { "+CGPADDR=?",   parse_cgpaddr_test,   AT_COMMAND_TST },

  { "null", NULL }
};

static int ParseString(char * at_str, at_command_t* at_command);
static int ParseCommand(const unsigned char *str, const char *format, ...);
static size_t GrabString(const unsigned char *str, char **output);
static unsigned int NumberOfParameters(const unsigned char* str);
static unsigned int IsNumeric(const char* str, size_t len);

/****************************************************************************/
/******************  E X P O R T E D    F U N C T I O N S  ******************/
/****************************************************************************/

/****************************************************************************
 **                                                                        **
 ** Name:  at_command_decode()                                       **
 **                                                                        **
 ** Description: Parses AT command line and accordingly fills data struc-  **
 **    ture. The main functions of the AT command parser are:    **
 **        - check the AT command syntax                         **
 **        - fill parameter values into the data structure     **
 **                                                                        **
 ** Inputs:  buffer:  Pointer to the buffer containing AT com-   **
 **       mand line                                  **
 **      length:  Number of bytes that should be processed   **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   at_command:  AT command data structure to be filled     **
 **      Return:  The number of AT commands successfully     **
 **       decoded; RETURNerror if an error occurred  **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
int at_command_decode(const char* buffer, int length, at_command_t* at_command)
{
  LOG_FUNC_IN;

  int rc = RETURNok;

  if (length < 3) {
    /* Minimum size to carry an AT command is 3 */
    LOG_TRACE(ERROR, "USR-API   - Input is short of size to carry an AT command!");
    return RETURNerror;
  } else if (strncasecmp(buffer, "AT", 2) != 0) {
    /* There must be AT initials in input... */
    LOG_TRACE(ERROR, "USR-API   - There is no proper initials (AT) in input!");
    return RETURNerror;
  } else if (buffer[length - 1] != '\r') {
    /* And there must be CR at the end */
    LOG_TRACE(ERROR, "USR-API   - There is no Carriage Return at the end!");
    return RETURNerror;
  } else if (strcasecmp(buffer, "AT\r") == 0) {
    /* AT command alone; just return OK */
    at_command->id = 0;
    at_command->mask = AT_COMMAND_NO_PARAM;
    return (1);
  }

  /* Parse the AT command line */
  int i;
  char* buf = strdup(buffer+2);
  char* cmd = strtok(buf, ";");

  for (i=0; cmd && (rc != RETURNerror); i++) {
    rc = ParseString(cmd, &at_command[i]);
    cmd = strtok(NULL, ";");
  }

  free(buf);

  if (rc != RETURNerror) {
    LOG_FUNC_RETURN (i);
  }

  LOG_FUNC_RETURN (rc);
}

/****************************************************************************
 **                                                                        **
 ** Name:  at_command_get_list()                                     **
 **                                                                        **
 ** Description: Returns the list of supported AT commands.                **
 **                                                                        **
 ** Inputs:  buffer:  Pointer to the buffer containing the       **
 **       string representaton of the first AT com-  **
 **       mand that is supported by the NAS sublayer **
 **      n_max:   Maximum number of AT commands the buffer   **
 **       may contain                                **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **    Return:  The number of AT commands actually suppor- **
 **       ted by the NAS sublayer                    **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
int at_command_get_list(const char** buffer, int n_max)
{
  LOG_FUNC_IN;

  int handler_index = 0;

  while ( (strcmp(_at_command_handler[handler_index].command, "null") != 0) &&
          (handler_index < n_max) ) {
    *buffer++ = _at_command_handler[handler_index++].command;
  }

  LOG_FUNC_RETURN (handler_index);
}

/****************************************************************************/
/*********************  L O C A L    F U N C T I O N S  *********************/
/****************************************************************************/

/****************************************************************************
 **                                                                        **
 ** Name:  ParseString()                                             **
 **                                                                        **
 ** Description: Calls the appropriate parsing handler after having        **
 **    successfully parsed the AT command prefix.                **
 **                                                                        **
 ** Inputs:  at_str:  Pointer to the buffer containing AT com-   **
 **       mand line                                  **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   at_command:  AT command structure to be filled          **
 **      Return:  The AT command identifier when the buffer  **
 **       has been successfully processed;           **
 **       RETURNerror otherwise.                     **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static int ParseString(char * at_str, at_command_t* at_command)
{
  char* terminal_character_index = NULL;
  int num_of_chars_to_compare = -1;
  int handler_index;
  int handler_found = FALSE;
  int check_for_carriage_return_index = 0;

  /*
   * Find the number of characters we should be comparing against
   * the command list
   */
  if ((terminal_character_index = strstr(at_str, "=?")) != NULL) {
    /* `test' command */
    num_of_chars_to_compare = terminal_character_index - at_str + 2;
    check_for_carriage_return_index = 1;
    at_command->type = AT_COMMAND_TST;
  } else if ((terminal_character_index = strchr(at_str, '?')) != NULL) {
    /* `read' command */
    num_of_chars_to_compare = terminal_character_index - at_str + 1;
    check_for_carriage_return_index = 1;
    at_command->type = AT_COMMAND_GET;
  } else if ((terminal_character_index = strchr(at_str, '=')) != NULL) {
    /* `set' command */
    num_of_chars_to_compare = terminal_character_index - at_str + 1;
    at_command->type = AT_COMMAND_SET;
  } else if ((terminal_character_index = strchr(at_str, '\r')) != NULL) {
    /* Basic/action command */
    num_of_chars_to_compare = terminal_character_index - at_str;
    check_for_carriage_return_index = 1;
    at_command->type = AT_COMMAND_ACT;
  } else {
    /* One action command among many commands */
    num_of_chars_to_compare = strchr(at_str, '\0') - at_str;
    at_command->type = AT_COMMAND_ACT;
  }

  /*
   * The AT command strings in `Commands' list (that we compare the incoming
   * command against) don't have CR at the end. So we have to ensure that CR
   * comes just after characters like = and ?
   */
  if (check_for_carriage_return_index == 1) {
    if ( (at_str[num_of_chars_to_compare] != '\0') &&
         (at_str[num_of_chars_to_compare] != '\r') ) {
      LOG_TRACE(ERROR, "USR-API   - Carriage return is not at where it should be!");
      return RETURNerror;
    }
  }

  /* Check if this command has a relevant command handler */
  for (handler_index=0;
       strcmp(_at_command_handler[handler_index].command, "null") != 0;
       handler_index++) {
    /* First match the command types... */
    if (at_command->type != _at_command_handler[handler_index].type)
      continue;

    /* Match the command length... */
    if (strlen(_at_command_handler[handler_index].command) !=
        num_of_chars_to_compare) continue;

    /* Now do string comparison... */
    if (strncasecmp(at_str, _at_command_handler[handler_index].command,
                    num_of_chars_to_compare) != 0) continue;

    handler_found = TRUE;
    break;
  }

  /* Call the relevant handler or return with error */
  if (!handler_found) {
    LOG_TRACE(ERROR, "USR-API   - Unsupported or invalid command!");
    return RETURNerror;
  }

  return _at_command_handler[handler_index].handler(at_str,
         num_of_chars_to_compare,
         at_command);
}

/****************************************************************************
 **                                                                        **
 ** Name:  ParseCommand()                                            **
 **                                                                        **
 ** Description: Parses the input string on the basis of the format string **
 **    provided.                                                 **
 **                                                                        **
 ** Inputs:  str:   The string to be parsed                    **
 **    format:  The format of the input string             **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  RETURNok, RETURNerror                      **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static int ParseCommand(const unsigned char *str, const char *format, ...)
{
  const unsigned char *fmt = (unsigned char*) format;
  const char *inp = (char*) str;
  char *endptr;
  char *out_us;
  char *buffer;
  size_t length;
  size_t storage_size;
  int *out_i;
  long int *out_l;
  va_list ap;
  int error = RETURNok;

  va_start(ap, format);

  while (*fmt) {
    switch (*fmt++) {
    case '@':
      if (*fmt == 0) {
        /* Invalid format string */
        LOG_TRACE(ERROR, "USR-API   - Invalid format string!");
        error = RETURNerror;
        goto end;
      }

      switch (*fmt++) {
      case 'i':
        out_i = va_arg(ap, int *);
        *out_i = strtol(inp, &endptr, 10);

        if (endptr == inp) {
          error = RETURNerror;
          goto end;
        }

        inp = endptr;
        break;

      case 'I':
        out_i = va_arg(ap, int *);
        *out_i = strtol(inp, &endptr, 10);

        if (endptr == inp) {
          /* Number empty */
          /* optional parameter ??? */
          LOG_TRACE(WARNING, "USR-API   - Number empty!");
          *out_i = 0;
        } else {
          inp = endptr;
        }

        break;

      case 'l':
        out_l = va_arg(ap, long int *);
        *out_l = strtol(inp, &endptr, 10);

        if (endptr == inp) {
          error = RETURNerror;
          goto end;
        }

        inp = endptr;
        break;

      case 'r':
        out_us = va_arg(ap, char *);
        storage_size = va_arg(ap, size_t);
        length = GrabString((unsigned char*) inp, &buffer);

        if (strlen(buffer) > storage_size) {
          free(buffer);
          error = RETURNerror;
          goto end;
        }

        strcpy(out_us, buffer);
        free(buffer);
        inp += length;
        break;

      case '@':
        if (*inp++ != '@') {
          error = RETURNerror;
          goto end;
        }

        break;

      case '0':
        /* Just skip the rest */
        goto end;

      default:
        /* Invalid format string */
        error = RETURNerror;
        goto end;
      }

      break;

    case ' ':
      while (isspace(*inp))
        inp++;

      break;

    default:
      if (*inp++ != *(fmt - 1)) {
        error = RETURNerror;
        goto end;
      }

      break;
    }
  }

  /* Ignore trailing spaces */
  while (isspace(*inp))
    inp++;

  if (*inp != 0) {
    error = RETURNerror;
    goto end;
  }

end:
  va_end(ap);

  return error;
}

/****************************************************************************
 **                                                                        **
 ** Name:  GrabString()                                              **
 **                                                                        **
 ** Description: Grabs single string, removing possible quotes.            **
 **                                                                        **
 ** Inputs:  str:   The string to be parsed                    **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   output:  Allocated output string buffer             **
 **      Return:  Length of parsed string                    **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static size_t GrabString(const unsigned char *str, char **output)
{
  size_t size = 4, position = 0;
  int inside_quotes = 0;

  /* Allocate initial buffer in case string is empty */
  *output = malloc(size);

  if (*output == NULL) {
    return 0;
  }

  while (inside_quotes || (*str != ',' && *str != ')' &&
                           *str != 0x0d && *str != 0x0a && *str != 0x00)) {
    /* Check for quotes */
    if (*str == '"') {
      inside_quotes = !inside_quotes;
    }

    /* We also allocate space for trailing zero */
    if (position + 2 > size) {
      size += 10;
      *output = realloc(*output, size);

      if (*output == NULL) {
        return 0;
      }
    }

    /* Copy to output */
    (*output)[position] = *str;
    position++;
    str++;
  }

  (*output)[position] = 0;

  /* Strip quotes */
  if ((*output)[0] == '"' && (*output)[position - 1]) {
    memmove(*output, (*output) + 1, position - 2);
    (*output)[position - 2] = 0;
  }

  return position;
}

/****************************************************************************
 **                                                                        **
 ** Name:  NumberOfParameters()                                      **
 **                                                                        **
 ** Description: Returns the number of parameters found in the given AT    **
 **    command line.                                             **
 **                                                                        **
 ** Inputs:  str:   The string to be parsed                    **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  The number of parameter delimiter (comma)  **
 **       found in the input string buffer           **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static unsigned int NumberOfParameters(const unsigned char* str)
{
  if (*str == '\r')
    return 0;

  int count = 1;

  while (*str) {
    if (*str++ == ',') {
      ++count;
    }
  }

  return count;
}

/****************************************************************************
 **                                                                        **
 ** Name:  IsNumeric()                                               **
 **                                                                        **
 ** Description: Checks whether the input string buffer has all digits     **
 **                                                                        **
 ** Inputs:  str:   The string to be parsed                    **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  TRUE, FALSE                                **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static unsigned int IsNumeric(const char* str, size_t len)
{
  while (len-- > 0) {
    if (!isdigit(str[len])) {
      return FALSE;
    }
  }

  return TRUE;
}

/****************************************************************************/
/******************** Handlers used to parse AT commands ********************/
/****************************************************************************/

static int parse_e0(const char* string, int position, at_command_t* at_command)
{
  /* Command echo E0 */
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_e1(const char* string, int position, at_command_t* at_command)
{
  /* Command echo E1 */
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_q0(const char* string, int position, at_command_t* at_command)
{
  /* Result code suppression Q0 */
  at_error_code_suppression_q1 = FALSE;
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_q1(const char* string, int position, at_command_t* at_command)
{
  /* Result code suppression Q1 */
  at_error_code_suppression_q1 = TRUE;
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_v0(const char* string, int position, at_command_t* at_command)
{
  /* Response format V0 (numeric result code + limited header) */
  at_error_format = AT_ERROR_NUMERIC;
  at_response_format_v1 = FALSE;
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_v1(const char* string, int position, at_command_t* at_command)
{
  /* Response format V1 (verbose result code + full header) */
  at_error_format = AT_ERROR_VERBOSE;
  at_response_format_v1 = TRUE;
  at_command->id = 0;
  at_command->mask = AT_COMMAND_NO_PARAM;
  return at_command->id;
}

static int parse_cgsn(const char* string, int position, at_command_t* at_command)
{
  /* CGSN action command - No parameter */
  at_command->id = AT_CGSN;
  at_command->mask = AT_COMMAND_CGSN_MASK;
  return at_command->id;
}

static int parse_cgsn_test(const char* string, int position, at_command_t* at_command)
{
  /* CGSN test command - No parameter */
  at_command->id = AT_CGSN;
  return at_command->id;
}

static int parse_cgmi(const char* string, int position, at_command_t* at_command)
{
  /* CGMI action command - No parameter */
  at_command->id = AT_CGMI;
  at_command->mask = AT_COMMAND_CGMI_MASK;
  return at_command->id;
}

static int parse_cgmi_test(const char* string, int position, at_command_t* at_command)
{
  /* CGMI test command - No parameter */
  at_command->id = AT_CGMI;
  return at_command->id;
}

static int parse_cgmm(const char* string, int position, at_command_t* at_command)
{
  /* CGMM action command - No parameter */
  at_command->id = AT_CGMM;
  at_command->mask = AT_COMMAND_CGMM_MASK;
  return at_command->id;
}

static int parse_cgmm_test(const char* string, int position, at_command_t* at_command)
{
  /* CGMM test command - No parameter */
  at_command->id = AT_CGMM;
  return at_command->id;
}

static int parse_cgmr(const char* string, int position, at_command_t* at_command)
{
  /* CGMR action command - No parameter */
  at_command->id = AT_CGMR;
  at_command->mask = AT_COMMAND_CGMR_MASK;
  return at_command->id;
}

static int parse_cgmr_test(const char* string, int position, at_command_t* at_command)
{
  /* CGMR test command - No parameter */
  at_command->id = AT_CGMR;
  return at_command->id;
}

static int parse_cimi(const char* string, int position, at_command_t* at_command)
{
  /* CIMI action command - No parameter */
  at_command->id = AT_CIMI;
  at_command->mask = AT_COMMAND_CIMI_MASK;
  return at_command->id;
}

static int parse_cimi_test(const char* string, int position, at_command_t* at_command)
{
  /* CIMI test command - No parameter */
  at_command->id = AT_CIMI;
  return at_command->id;
}

static int parse_cfun_set(const char* string, int position, at_command_t* at_command)
{
  /* CFUN parameter command - Parameters [<fun>[,<rst>]] */
  at_command->id = AT_CFUN;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CFUN= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <fun> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cfun.fun) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CFUN=<fun> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CFUN=<fun> command succeed (fun:%d)", at_command->command.cfun.fun);

    at_command->mask = AT_CFUN_FUN_MASK;
    break;

  case 2: // <fun>,<rst> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@i",
                     &at_command->command.cfun.fun,
                     &at_command->command.cfun.rst) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CFUN=<fun>,<rst> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CFUN=<fun>,<rst> command succeed (fun:%d, rst:%d)", at_command->command.cfun.fun, at_command->command.cfun.rst);

    at_command->mask = AT_CFUN_FUN_MASK | AT_CFUN_RST_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cfun_get(const char* string, int position, at_command_t* at_command)
{
  /* CFUN action command - No parameter */
  at_command->id = AT_CFUN;
  return at_command->id;
}

static int parse_cfun_test(const char* string, int position, at_command_t* at_command)
{
  /* CFUN test command - No parameter */
  at_command->id = AT_CFUN;
  return at_command->id;
}

static int parse_cpin_set(const char* string, int position, at_command_t* at_command)
{
  /* CPIN parameter command - Parameters <pin>[,<newpin>] */
  at_command->id = AT_CPIN;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 1: // Only <pin> parameter is present
    if (ParseCommand(parameter_start_index, "@r",
                     at_command->command.cpin.pin, AT_CPIN_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CPIN=<pin> command failed");
      return RETURNerror;
    }

    if (!IsNumeric(at_command->command.cpin.pin, AT_CPIN_SIZE)) {
      LOG_TRACE(ERROR, "USR-API   - Incoming parameter is not a valid PIN code!");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CPIN=<pin> command succeed (pin:%s)", at_command->command.cpin.pin);

    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 2: // <pin>,<newpin> parameters are present
    if (ParseCommand(parameter_start_index, "@r,@r",
                     at_command->command.cpin.pin, AT_CPIN_SIZE,
                     at_command->command.cpin.newpin, AT_CPIN_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CPIN=<pin>,<newpin> command failed");
      return RETURNerror;
    }

    if (!IsNumeric(at_command->command.cpin.pin, AT_CPIN_SIZE) ||
        !IsNumeric(at_command->command.cpin.newpin, AT_CPIN_SIZE)) {
      LOG_TRACE(ERROR, "USR-API   - Incoming parameter is not a valid PIN code!");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CPIN=<pin>,<newpin> command succeed (pin:%s, newpin:%s)", at_command->command.cpin.pin, at_command->command.cpin.newpin);

    at_command->mask = AT_CPIN_NEWPIN_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cpin_get(const char* string, int position, at_command_t* at_command)
{
  /* CPIN action command - No parameter */
  at_command->id = AT_CPIN;
  return at_command->id;
}

static int parse_cpin_test(const char* string, int position, at_command_t* at_command)
{
  /* CPIN test command - No parameter */
  at_command->id = AT_CPIN;
  return at_command->id;
}

static int parse_csq(const char* string, int position, at_command_t* at_command)
{
  /* CSQ action command - No parameter */
  at_command->id = AT_CSQ;
  at_command->mask = AT_COMMAND_CSQ_MASK;
  return at_command->id;
}

static int parse_csq_test(const char* string, int position, at_command_t* at_command)
{
  /* CSQ test command - No parameter */
  at_command->id = AT_CSQ;
  return at_command->id;
}

static int parse_cesq(const char* string, int position, at_command_t* at_command)
{
  /* CESQ action command - No parameter */
  at_command->id = AT_CESQ;
  at_command->mask = AT_COMMAND_CESQ_MASK;
  return at_command->id;
}

static int parse_cesq_test(const char* string, int position, at_command_t* at_command)
{
  /* CESQ test command - No parameter */
  at_command->id = AT_CESQ;
  return at_command->id;
}

static int parse_clac(const char* string, int position, at_command_t* at_command)
{
  /* CLAC action command - No parameter */
  at_command->id = AT_CLAC;
  at_command->mask = AT_COMMAND_CLAC_MASK;
  return at_command->id;
}

static int parse_clac_test(const char* string, int position, at_command_t* at_command)
{
  /* CLAC test command - No parameter */
  at_command->id = AT_CLAC;
  return at_command->id;
}

static int parse_cmee_set(const char* string, int position, at_command_t* at_command)
{
  /* CMEE parameter command - Parameter [<n>] */
  at_command->id = AT_CMEE;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CMEE= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <n> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cmee.n) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CMEE=<n> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CMEE=<n> command succeed (n:%d)", at_command->command.cmee.n);

    at_command->mask = AT_CMEE_N_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cmee_get(const char* string, int position, at_command_t* at_command)
{
  /* CMEE read command - No parameter */
  at_command->id = AT_CMEE;
  return at_command->id;
}

static int parse_cmee_test(const char* string, int position, at_command_t* at_command)
{
  /* CMEE test command - No parameter */
  at_command->id = AT_CMEE;
  return at_command->id;
}

static int parse_cnum(const char* string, int position, at_command_t* at_command)
{
  /* CNUM action command - No parameter */
  at_command->id = AT_CNUM;
  at_command->mask = AT_COMMAND_CNUM_MASK;
  return at_command->id;
}

static int parse_cnum_test(const char* string, int position, at_command_t* at_command)
{
  /* CNUM test command - No parameter */
  at_command->id = AT_CNUM;
  return at_command->id;
}

static int parse_clck_set(const char* string, int position, at_command_t* at_command)
{
  /* CLCK action command - Parameters <fac>,<mode>[,<passwd>] */
  at_command->id = AT_CLCK;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  switch (number_of_parameters) {
  case 2: // <fac>,<mode> parameters are present
    if (ParseCommand(parameter_start_index, "@r,@i",
                     &at_command->command.clck.fac, AT_CLCK_FAC_SIZE,
                     &at_command->command.clck.mode) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CLCK=<fac>,<mode> "
                "failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CLCK=<fac>,<mode> "
              "succeed (fac:%s, mode:%d)",
              at_command->command.clck.fac,
              at_command->command.clck.mode);
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 3: // <fac>,<mode>,<passwd> parameters are present
    if (ParseCommand(parameter_start_index, "@r,@i,@r",
                     &at_command->command.clck.fac, AT_CLCK_FAC_SIZE,
                     &at_command->command.clck.mode,
                     &at_command->command.clck.passwd,
                     AT_CLCK_PASSWD_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CLCK=<fac>,<mode>"
                "<passwd> failed");
      return RETURNerror;
    }

    if (!IsNumeric(at_command->command.clck.passwd,AT_CLCK_PASSWD_SIZE)) {
      LOG_TRACE(ERROR, "USR-API   - Incoming parameter is not a valid password!");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CLCK=<fac>,<mode>"
              "<passwd> succeed (fac:%s, mode:%d, passwd:%s)",
              at_command->command.clck.fac,
              at_command->command.clck.mode,
              at_command->command.clck.passwd);
    at_command->mask = AT_CLCK_PASSWD_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return -1;
  }

  return at_command->id;
}

static int parse_clck_test(const char* string, int position, at_command_t* at_command)
{
  /* CLCK test command - No parameter */
  at_command->id = AT_CLCK;
  return at_command->id;
}

static int parse_cops_set(const char* string, int position, at_command_t* at_command)
{
  /* COPS parameter command - Parameters [<mode>[,<format>[,<oper>[,<AcT>]]]] */
  at_command->id = AT_COPS;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  char oper_value[AT_COPS_NUM_SIZE];

  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <mode> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cops.mode) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode> command succeed (mode:%d)", at_command->command.cops.mode);

    at_command->mask = AT_COPS_MODE_MASK;
    break;

  case 2: // <mode>,<format> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@i",
                     &at_command->command.cops.mode,
                     &at_command->command.cops.format) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format> command succeed (mode:%d, format:%d)", at_command->command.cops.mode, at_command->command.cops.format);

    at_command->mask = AT_COPS_MODE_MASK | AT_COPS_FORMAT_MASK;
    break;

  case 3: // <mode>,<format>,<oper> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@i,@r",
                     &at_command->command.cops.mode,
                     &at_command->command.cops.format,
                     oper_value, NET_FORMAT_MAX_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper> command succeed (mode:%d, format:%d)", at_command->command.cops.mode, at_command->command.cops.format);

    /* Now, process <oper> parameter according to the specified <format> parameter */
    switch (at_command->command.cops.format) {
    case AT_COPS_LONG: /* long format alphanumeric <oper> */
      strncpy((char*) at_command->command.cops.plmn.id.alpha_long,
              oper_value, AT_COPS_LONG_SIZE);
      LOG_TRACE(INFO, "USR-API   - <oper> parameter's value is '%s'", at_command->command.cops.plmn.id.alpha_long);
      break;

    case AT_COPS_SHORT: /* short format alphanumeric <oper> */
      strncpy((char*) at_command->command.cops.plmn.id.alpha_short, oper_value, AT_COPS_SHORT_SIZE);
      LOG_TRACE(INFO, "USR-API   - <oper> parameter's value is '%s'", at_command->command.cops.plmn.id.alpha_short);
      break;

    case AT_COPS_NUM: /* numeric <oper> */
      strncpy((char*) at_command->command.cops.plmn.id.num,
              oper_value, AT_COPS_NUM_SIZE);
      LOG_TRACE(INFO, "USR-API   - <oper> parameter's value is '%s'", at_command->command.cops.plmn.id.num);
      break;

    default:
      LOG_TRACE(ERROR, "USR-API   - Invalid format value (%d)",
                at_command->command.cops.format);
      return RETURNerror;
    }

    at_command->mask = AT_COPS_MODE_MASK | AT_COPS_FORMAT_MASK | AT_COPS_OPER_MASK;
    break;

  case 4: // <mode>,<format>,<oper>,<AcT> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@i,@0",
                     &at_command->command.cops.mode,
                     &at_command->command.cops.format) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format> command failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper> command succeed (mode:%d, format:%d)", at_command->command.cops.mode, at_command->command.cops.format);

    /* Now, process <oper> parameter according to the specified <format> parameter */
    switch (at_command->command.cops.format) {
    case AT_COPS_LONG: /* long format alphanumeric <oper> */
      if (ParseCommand(parameter_start_index, "@i,@i,@r,@i",
                       &at_command->command.cops.mode,
                       &at_command->command.cops.format,
                       oper_value, AT_COPS_LONG_SIZE,
                       &at_command->command.cops.AcT) != RETURNok) {
        LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command failed");
        return RETURNerror;
      }

      strncpy((char*) at_command->command.cops.plmn.id.alpha_long,
              oper_value, AT_COPS_LONG_SIZE);
      LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command succeed (oper:%s, Act:%d)", at_command->command.cops.plmn.id.alpha_long, at_command->command.cops.AcT);
      break;

    case AT_COPS_SHORT: /* short format alphanumeric <oper> */
      if (ParseCommand(parameter_start_index, "@i,@i,@r,@i",
                       &at_command->command.cops.mode,
                       &at_command->command.cops.format,
                       oper_value, AT_COPS_SHORT_SIZE,
                       &at_command->command.cops.AcT) != RETURNok) {
        LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command failed");
        return RETURNerror;
      }

      strncpy((char*) at_command->command.cops.plmn.id.alpha_short,
              oper_value, AT_COPS_SHORT_SIZE);
      LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command succeed (oper:%s, Act:%d)",
                at_command->command.cops.plmn.id.alpha_short,
                at_command->command.cops.AcT);
      break;

    case AT_COPS_NUM: /* numeric <oper> */
      if (ParseCommand(parameter_start_index, "@i,@i,@r,@i",
                       &at_command->command.cops.mode,
                       &at_command->command.cops.format,
                       oper_value, AT_COPS_NUM_SIZE,
                       &at_command->command.cops.AcT) != RETURNok) {
        LOG_TRACE(ERROR, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command failed");
        return RETURNerror;
      }

      strncpy((char*) at_command->command.cops.plmn.id.num,
              oper_value, AT_COPS_NUM_SIZE);
      LOG_TRACE(INFO, "USR-API   - Parsing of AT+COPS=<mode>,<format>,<oper>,<AcT> command succeed (oper:%s, Act:%d)",
                at_command->command.cops.plmn.id.num,
                at_command->command.cops.AcT);
      break;

    default:
      LOG_TRACE(ERROR, "USR-API   - Invalid format value (%d)",
                at_command->command.cops.format);
      return RETURNerror;
    }

    at_command->mask = AT_COPS_MODE_MASK | AT_COPS_FORMAT_MASK | AT_COPS_OPER_MASK | AT_COPS_ACT_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cops_get(const char* string, int position, at_command_t* at_command)
{
  /* COPS test command - No parameter */
  at_command->id = AT_COPS;
  return at_command->id;
}

static int parse_cops_test(const char* string, int position, at_command_t* at_command)
{
  /* COPS test command - No parameter */
  at_command->id = AT_COPS;
  return at_command->id;
}

static int parse_creg_set(const char* string, int position, at_command_t* at_command)
{
  /* CREG parameter command - Parameter [<n>] */
  at_command->id = AT_CREG;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CREG= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <n> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.creg.n) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CREG=<n> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CREG=<n> command succeed (n:%d)", at_command->command.creg.n);

    at_command->mask = AT_CREG_N_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_creg_get(const char* string, int position, at_command_t* at_command)
{
  /* CREG read command - No parameter */
  at_command->id = AT_CREG;
  return at_command->id;
}

static int parse_creg_test(const char* string, int position, at_command_t* at_command)
{
  /* CREG test command - No parameter */
  at_command->id = AT_CREG;
  return at_command->id;
}

static int parse_cgatt_set(const char* string, int position, at_command_t* at_command)
{
  /* CGATT parameter command - Parameter [<state>] */
  at_command->id = AT_CGATT;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGATT= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <state> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cgatt.state) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGATT=<state> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGATT=<state> command succeed (state:%d)", at_command->command.cgatt.state);

    at_command->mask = AT_CGATT_STATE_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cgatt_get(const char* string, int position, at_command_t* at_command)
{
  /* CGATT read command - No parameter */
  at_command->id = AT_CGATT;
  return at_command->id;
}

static int parse_cgatt_test(const char* string, int position, at_command_t* at_command)
{
  /* CGATT test command - No parameter */
  at_command->id = AT_CGATT;
  return at_command->id;
}

static int parse_cgreg_set(const char* string, int position, at_command_t* at_command)
{
  /* CGREG parameter command - Parameter [<n>] */
  at_command->id = AT_CGREG;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGREG= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <n> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cgreg.n) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGREG=<n> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGREG=<n> command succeed (n:%d)", at_command->command.cgreg.n);

    at_command->mask = AT_CGREG_N_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cgreg_get(const char* string, int position, at_command_t* at_command)
{
  /* CGREG read command - No parameter */
  at_command->id = AT_CGREG;
  return at_command->id;
}

static int parse_cgreg_test(const char* string, int position, at_command_t* at_command)
{
  /* CGREG test command - No parameter */
  at_command->id = AT_CGREG;
  return at_command->id;
}

static int parse_cereg_set(const char* string, int position, at_command_t* at_command)
{
  /* CEREG parameter command - Parameter [<n>] */
  at_command->id = AT_CEREG;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CEREG= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <n> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cereg.n) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CEREG=<n> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CEREG=<n> command succeed (n:%d)", at_command->command.cereg.n);

    at_command->mask = AT_CEREG_N_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cereg_get(const char* string, int position, at_command_t* at_command)
{
  /* CEREG read command - No parameter */
  at_command->id = AT_CEREG;
  return at_command->id;
}

static int parse_cereg_test(const char* string, int position, at_command_t* at_command)
{
  /* CEREG test command - No parameter */
  at_command->id = AT_CEREG;
  return at_command->id;
}

static int parse_cgdcont_set(const char* string, int position, at_command_t* at_command)
{
  /* CGDCONT parameter command - Parameters [<cid>[,<PDP_type>[,<APN>[,<PDP_addr>[,<d_comp>[,<h_comp>[,<IPv4AddrAlloc>[,<emergency indication>[,<P-CSCF_discovery>[,<IM_CN_Signalling_Flag_Ind>]]]]]]]]]] */
  at_command->id = AT_CGDCONT;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  /* Parse the parameters */
  switch (number_of_parameters) {
  case 1: // Only <cid> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cgdcont.cid) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid> command succeed (cid:%d)", at_command->command.cgdcont.cid);

    at_command->mask = AT_CGDCONT_CID_MASK;
    break;

  case 2: // <cid>,<PDP_type> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type> succeed (cid:%d, pdp_type:%s)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK;
    break;

  case 3: // <cid>,<PDP_type>,<APN> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN> succeed (cid:%d, pdp_type:%s, apn:%s)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK;
    break;

  case 4: // <cid>,<PDP_type>,<APN>,<PDP_addr> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK;
    break;

  case 5: // <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK;
    break;

  case 6: // <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp,
                     &at_command->command.cgdcont.h_comp) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d, h_comp:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp,
              at_command->command.cgdcont.h_comp);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK | AT_CGDCONT_H_COMP_MASK;
    break;

  case 7: // <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i,@i,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp,
                     &at_command->command.cgdcont.h_comp,
                     &at_command->command.cgdcont.IPv4AddrAlloc) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO,
              "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d, h_comp:%d, ipv4addralloc:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp,
              at_command->command.cgdcont.h_comp,
              at_command->command.cgdcont.IPv4AddrAlloc);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK | AT_CGDCONT_H_COMP_MASK | AT_CGDCONT_IPV4ADDRALLOC_MASK;
    break;

  case 8: // <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i,@i,@i,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp,
                     &at_command->command.cgdcont.h_comp,
                     &at_command->command.cgdcont.IPv4AddrAlloc,
                     &at_command->command.cgdcont.emergency_indication) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO,
              "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d, h_comp:%d, ipv4addralloc:%d, emergency_indication:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp,
              at_command->command.cgdcont.h_comp,
              at_command->command.cgdcont.IPv4AddrAlloc,
              at_command->command.cgdcont.emergency_indication);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK | AT_CGDCONT_H_COMP_MASK | AT_CGDCONT_IPV4ADDRALLOC_MASK |
                       AT_CGDCONT_EMERGECY_INDICATION_MASK;
    break;

  case 9: /* <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,
     * <IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery>
     * parameters are present */
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i,@i,@i,@i,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp,
                     &at_command->command.cgdcont.h_comp,
                     &at_command->command.cgdcont.IPv4AddrAlloc,
                     &at_command->command.cgdcont.emergency_indication,
                     &at_command->command.cgdcont.P_CSCF_discovery) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO,
              "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d, h_comp:%d, ipv4addralloc:%d, emergency_indication:%d, p_cscf_discovery:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp,
              at_command->command.cgdcont.h_comp,
              at_command->command.cgdcont.IPv4AddrAlloc,
              at_command->command.cgdcont.emergency_indication,
              at_command->command.cgdcont.P_CSCF_discovery);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK | AT_CGDCONT_H_COMP_MASK | AT_CGDCONT_IPV4ADDRALLOC_MASK |
                       AT_CGDCONT_EMERGECY_INDICATION_MASK | AT_CGDCONT_P_CSCF_DISCOVERY_MASK;
    break;

  case 10: /* <cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,
     * <IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery>,
     * <IM_CN_Signalling_Flag_Ind> parameters are present */
    if (ParseCommand(parameter_start_index, "@i,@r,@r,@r,@i,@i,@i,@i,@i,@i",
                     &at_command->command.cgdcont.cid,
                     &at_command->command.cgdcont.PDP_type, AT_CGDCONT_PDP_SIZE,
                     &at_command->command.cgdcont.APN, AT_CGDCONT_APN_SIZE,
                     &at_command->command.cgdcont.PDP_addr, AT_CGDCONT_ADDR_SIZE,
                     &at_command->command.cgdcont.d_comp,
                     &at_command->command.cgdcont.h_comp,
                     &at_command->command.cgdcont.IPv4AddrAlloc,
                     &at_command->command.cgdcont.emergency_indication,
                     &at_command->command.cgdcont.P_CSCF_discovery,
                     &at_command->command.cgdcont.IM_CN_Signalling_Flag_Ind) != RETURNok) {
      LOG_TRACE(ERROR,
                "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery>, <IM_CN_Signalling_Flag_Ind> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO,
              "USR-API   - Parsing of AT+CGDCONT=<cid>,<PDP_type>,<APN>,<PDP_addr>,<d_comp>,<h_comp>,<IPv4AddrAlloc>,<emergency indication>,<P-CSCF_discovery>, <IM_CN_Signalling_Flag_Ind> succeed (cid:%d, pdp_type:%s, apn:%s, pdp_addr:%s, d_comp:%d, h_comp:%d, ipv4addralloc:%d, emergency_indication:%d, p_cscf_discovery:%d, im_cn_signalling_flag:%d)",
              at_command->command.cgdcont.cid,
              at_command->command.cgdcont.PDP_type,
              at_command->command.cgdcont.APN,
              at_command->command.cgdcont.PDP_addr,
              at_command->command.cgdcont.d_comp,
              at_command->command.cgdcont.h_comp,
              at_command->command.cgdcont.IPv4AddrAlloc,
              at_command->command.cgdcont.emergency_indication,
              at_command->command.cgdcont.P_CSCF_discovery,
              at_command->command.cgdcont.IM_CN_Signalling_Flag_Ind);

    at_command->mask = AT_CGDCONT_CID_MASK | AT_CGDCONT_PDP_TYPE_MASK | AT_CGDCONT_APN_MASK | AT_CGDCONT_PDP_ADDR_MASK | AT_CGDCONT_D_COMP_MASK | AT_CGDCONT_H_COMP_MASK | AT_CGDCONT_IPV4ADDRALLOC_MASK |
                       AT_CGDCONT_EMERGECY_INDICATION_MASK | AT_CGDCONT_P_CSCF_DISCOVERY_MASK | AT_CGDCONT_IM_CN_SIGNALLING_FLAG_IND_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return RETURNerror;
  }

  return at_command->id;
}

static int parse_cgdcont_get(const char* string, int position, at_command_t* at_command)
{
  /* CGDCONT read command - No parameter */
  at_command->id = AT_CGDCONT;
  return at_command->id;
}

static int parse_cgdcont_test(const char* string, int position, at_command_t* at_command)
{
  /* CGDCONT test command - No parameter */
  at_command->id = AT_CGDCONT;
  return at_command->id;
}

static int parse_cgact_set(const char* string, int position, at_command_t* at_command)
{
  /* CGACT parameter command - Parameters <state>[,<cid>] */
  at_command->id = AT_CGACT;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  switch (number_of_parameters) {
  case 1: // Only <state> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cgact.state) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGACT=<state> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGACT=<state> succeed (state:%d)", at_command->command.cgact.state);
    at_command->mask = AT_CGACT_STATE_MASK;
    break;

  case 2: // <state>,<cid> parameters are present
    if (ParseCommand(parameter_start_index, "@i,@i",
                     &at_command->command.cgact.state,
                     &at_command->command.cgact.cid) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGACT=<state>,<cid> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGACT=<state>,<cid> succeed (state:%d, cid:%d)",
              at_command->command.cgact.state,
              at_command->command.cgact.cid);

    at_command->mask = AT_CGACT_STATE_MASK | AT_CGACT_CID_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return -1;
  }

  return at_command->id;
}

static int parse_cgact_get(const char* string, int position, at_command_t* at_command)
{
  /* CGACT read command - No parameter */
  at_command->id = AT_CGACT;
  return at_command->id;
}

static int parse_cgact_test(const char* string, int position, at_command_t* at_command)
{
  /* CGACT test command - No parameter */
  at_command->id = AT_CGACT;
  return at_command->id;
}

static int parse_cgpaddr_set(const char* string, int position, at_command_t* at_command)
{
  /* CGPADDR parameter command - Parameters [<cid>] */
  at_command->id = AT_CGPADDR;
  unsigned char* parameter_start_index = (unsigned char*) string + position;

  /* Check the number of parameters */
  unsigned number_of_parameters = NumberOfParameters(parameter_start_index);

  switch (number_of_parameters) {
  case 0: // No any parameter
    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGPADDR= command succeed");
    at_command->mask = AT_COMMAND_NO_PARAM;
    break;

  case 1: // Only <cid> parameter is present
    if (ParseCommand(parameter_start_index, "@i",
                     &at_command->command.cgpaddr.cid) != RETURNok) {
      LOG_TRACE(ERROR, "USR-API   - Parsing of AT+CGPADDR=<state> failed");
      return RETURNerror;
    }

    LOG_TRACE(INFO, "USR-API   - Parsing of AT+CGPADDR=<cid> succeed (cid:%d)", at_command->command.cgpaddr.cid);
    at_command->mask = AT_CGPADDR_CID_MASK;
    break;

  default:
    LOG_TRACE(ERROR, "USR-API   - Invalid number of parameters (%d)", number_of_parameters);
    return -1;
  }

  return at_command->id;
}

static int parse_cgpaddr_test(const char* string, int position, at_command_t* at_command)
{
  /* CGPADDR test command - No parameter */
  at_command->id = AT_CGPADDR;
  return at_command->id;
}
