diff --git a/.clang-format b/.clang-format index e5f69355cbe0..e2f9a3bdfa86 100644 --- a/.clang-format +++ b/.clang-format @@ -78,9 +78,11 @@ IncludeCategories: - Regex: '.*' Priority: 3 IndentCaseLabels: false +IndentGotoLabels: false IndentWidth: 8 InsertBraces: true SpaceBeforeParens: ControlStatementsExceptControlMacros +SpaceBeforeInheritanceColon: False SortIncludes: Never UseTab: Always WhitespaceSensitiveMacros: diff --git a/CODEOWNERS b/CODEOWNERS index 9c50326777f6..da0945672228 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -105,6 +105,7 @@ Kconfig* @tejlmand /lib/bin/ @rlubos @lemrey /lib/adp536x/ @nrfconnect/ncs-cia /lib/at_cmd_parser/ @rlubos +/lib/at_parser/ @MirkoCovizzi /lib/at_cmd_custom/ @eivindj-nordic /lib/at_host/ @rlubos /lib/at_monitor/ @lemrey @rlubos @@ -287,6 +288,7 @@ Kconfig* @tejlmand /tests/drivers/lpuart/ @nordic-krch /tests/drivers/nrfx_integration_test/ @anangl /tests/lib/at_cmd_parser/ @rlubos +/tests/lib/at_parser/ @MirkoCovizzi /tests/lib/at_cmd_custom/ @eivindj-nordic /tests/lib/date_time/ @trantanen @tokangas /tests/lib/edge_impulse/ @pdunaj @MarekPieta diff --git a/include/modem/at_parser.h b/include/modem/at_parser.h new file mode 100644 index 000000000000..bc18297273b8 --- /dev/null +++ b/include/modem/at_parser.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef AT_PARSER_H__ +#define AT_PARSER_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum at_token_type { + AT_TOKEN_TYPE_INVALID = 0, + AT_TOKEN_TYPE_CMD_TEST, + AT_TOKEN_TYPE_CMD_READ, + AT_TOKEN_TYPE_CMD_SET, + AT_TOKEN_TYPE_NOTIF, + AT_TOKEN_TYPE_INT, + AT_TOKEN_TYPE_QUOTED_STRING, + AT_TOKEN_TYPE_ARRAY, + AT_TOKEN_TYPE_EMPTY, + AT_TOKEN_TYPE_STRING, + AT_TOKEN_TYPE_RESP +}; + +enum at_token_variant { + AT_TOKEN_VARIANT_NO_COMMA = 0, + AT_TOKEN_VARIANT_COMMA +}; + +struct at_token { + const char *start; + uint16_t len; + uint8_t type :4; + uint8_t variant :1; +} __attribute__ ((__packed__)); + +struct at_parser { + const char *ptr; + size_t count; + struct { + size_t cmd_count; + size_t notif_count; + size_t subparam_count; + size_t string_count; + size_t end_count; + } counters; + struct at_token prev_token; + bool is_next_empty; + bool initialized; +}; + +/** + * @brief Initialize an AT parser. + * + * This function initializes an AT parser with the given AT command string. + * + * @param parser Pointer to the @ref at_parser structure to be initialized. + * @param at Pointer to a null-terminated string containing the AT command string to be + * parsed. + * + * @retval 0 If the operation was successful. + * @retval -EINVAL @p parser or @p at are NULL. + */ +int at_parser_init(struct at_parser *parser, const char *at); + +/** + * @brief Parse the next token from the AT command string. + * + * This function parses the next token from the AT command string using the parser + * state. + * + * @param parser Pointer to the @ref at_parser structure that holds the state of the parser. + * @param token Pointer to an @ref at_token structure where the parsed token will be stored. + * + * @retval 0 If the operation was successful. + * @retval -EINVAL @p parser or @p token are NULL or the parser state is invalid, + * @retval -EPERM @p parser has not been initialized, + * @retval -EIO There is nothing left to parse in the AT command string, + * @retval -EBADMSG The token parsed is invalid. + */ +int at_parser_tok(struct at_parser *parser, struct at_token *token); + +/** + * @brief Seek to the specified token index in the AT command string. + * + * This function parses the next token from the AT command string using the parser + * state. + * + * @param parser Pointer to the @ref at_parser structure that holds the state of the parser. + * @param index Zero-based index of the token to seek to within the AT command string. + * @param token Pointer to an @ref at_token structure where the parsed token will be stored. + * + * @retval 0 If the operation was successful. + * @retval -EINVAL @p parser or @p token are NULL or the parser state is invalid, + * @retval -EPERM @p parser has not been initialized, + * @retval -EIO There is nothing left to parse in the AT command string, + * @retval -EBADMSG The token parsed is invalid. + */ +int at_parser_seek(struct at_parser *parser, size_t index, struct at_token *token); +int at_token_line_parse(struct at_token *tokens, size_t num_tokens, const char *at, + const char **next_at); +int at_token_uint16_get(const struct at_token *token, uint16_t *value); +int at_token_int16_get(const struct at_token *token, int16_t *value); +int at_token_uint32_get(const struct at_token *token, uint32_t *value); +int at_token_int32_get(const struct at_token *token, int32_t *value); +int at_token_int64_get(const struct at_token *token, int64_t *value); +int at_token_string_get(const struct at_token *token, char *value, size_t *len); +size_t at_token_valid_count_get(const struct at_token *tokens, size_t num_tokens); +enum at_token_type at_cmd_type_get(const char *at); + +#ifdef __cplusplus +} +#endif + +#endif /* AT_PARSER_H__ */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e749660522e5..766c78f2ffb5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory_ifdef(CONFIG_AT_CMD_CUSTOM at_cmd_custom) add_subdirectory_ifdef(CONFIG_AT_MONITOR at_monitor) add_subdirectory_ifdef(CONFIG_AT_HOST_LIBRARY at_host) add_subdirectory_ifdef(CONFIG_AT_CMD_PARSER at_cmd_parser) +add_subdirectory_ifdef(CONFIG_AT_PARSER at_parser) add_subdirectory_ifdef(CONFIG_LTE_LINK_CONTROL lte_link_control) add_subdirectory_ifdef(CONFIG_MODEM_BATTERY modem_battery) add_subdirectory_ifdef(CONFIG_MODEM_INFO modem_info) diff --git a/lib/Kconfig b/lib/Kconfig index 8786c8c114cc..45a9aa4edf1f 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -18,6 +18,7 @@ rsource "at_cmd_custom/Kconfig" rsource "at_host/Kconfig" rsource "dk_buttons_and_leds/Kconfig" rsource "at_cmd_parser/Kconfig" +rsource "at_parser/Kconfig" rsource "modem_battery/Kconfig" rsource "modem_info/Kconfig" rsource "pdn/Kconfig" diff --git a/lib/at_parser/CMakeLists.txt b/lib/at_parser/CMakeLists.txt new file mode 100644 index 000000000000..0c519da83477 --- /dev/null +++ b/lib/at_parser/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_sources( + at_parser.c + at_match.c +) + +zephyr_include_directories(include) diff --git a/lib/at_parser/Kconfig b/lib/at_parser/Kconfig new file mode 100644 index 000000000000..b4419b8971b3 --- /dev/null +++ b/lib/at_parser/Kconfig @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config AT_PARSER + bool "AT parser library" + depends on NEWLIB_LIBC || EXTERNAL_LIBC || PICOLIBC diff --git a/lib/at_parser/at_match.c b/lib/at_parser/at_match.c new file mode 100644 index 000000000000..24eac71a1ee0 --- /dev/null +++ b/lib/at_parser/at_match.c @@ -0,0 +1,1825 @@ +/* Generated by re2c 3.0 on Tue Apr 2 09:29:20 2024 */ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Generated by re2c 3.0 + * + * re2c at_match.re -o at_match.c -W --no-debug-info + */ + +#include + +struct at_token at_match(const char *at, const char **remainder) +{ + const char *cursor = at; + const char *marker = NULL; + + { + unsigned char yych; + unsigned int yyaccept = 0; + yych = *cursor; + switch (yych) { + case '\r': + goto yy3; + case ' ': + goto yy4; + case '"': + goto yy5; + case '#': + case '%': + goto yy6; + case '(': + goto yy7; + case '+': + goto yy8; + case ',': + goto yy9; + case '-': + goto yy10; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy11; + case 'A': + goto yy13; + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy15; + default: + goto yy1; + } +yy1: + ++cursor; +yy2: { + return (struct at_token){.type = AT_TOKEN_TYPE_INVALID}; +} +yy3: + yych = *++cursor; + switch (yych) { + case '\n': + goto yy17; + default: + goto yy2; + } +yy4: + yyaccept = 0; + yych = *(marker = ++cursor); + switch (yych) { + case '"': + goto yy19; + case '(': + goto yy22; + case '+': + case '-': + goto yy23; + case ',': + goto yy9; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy24; + default: + goto yy2; + } +yy5: + yyaccept = 0; + yych = *(marker = ++cursor); + if (yych <= 0x00) { + goto yy2; + } + goto yy20; +yy6: + yyaccept = 0; + yych = *(marker = ++cursor); + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy27; + default: + goto yy2; + } +yy7: + yyaccept = 0; + yych = *(marker = ++cursor); + switch (yych) { + case '"': + goto yy29; + case '(': + goto yy30; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy31; + default: + goto yy2; + } +yy8: + yyaccept = 0; + yych = *(marker = ++cursor); + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy32; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy27; + default: + goto yy2; + } +yy9: + ++cursor; + { + int off_len = 1; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_EMPTY, + .variant = AT_TOKEN_VARIANT_COMMA}; + } +yy10: + yych = *++cursor; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy24; + default: + goto yy2; + } +yy11: + yych = *++cursor; + switch (yych) { + case ' ': + case '-': + case '.': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy15; + case ',': + goto yy33; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy11; + default: + goto yy12; + } +yy12: { + char first = *at; + char last = *(cursor - 1); + int off_start = first == ' ' ? 1 : 0; + int off_len = (last == ',' ? 1 : 0) + off_start; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at + off_start, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_INT, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA + : AT_TOKEN_VARIANT_NO_COMMA}; +} +yy13: + yych = *++cursor; + switch (yych) { + case 'T': + goto yy34; + default: + goto yy16; + } +yy14: { + char first = *at; + char last = *(cursor - 1); + int off_start = first == '\r' ? 2 : (first == ' ' ? 1 : 0); + int off_len = (last == ' ' ? 1 : 0) + off_start; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at + off_start, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_STRING}; +} +yy15: + yych = *++cursor; +yy16: + switch (yych) { + case ' ': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy15; + default: + goto yy14; + } +yy17: + yyaccept = 1; + yych = *(marker = ++cursor); + switch (yych) { + case '#': + case '%': + goto yy36; + case '+': + goto yy37; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy15; + case 'E': + goto yy38; + case 'O': + goto yy39; + default: + goto yy18; + } +yy18: { + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at, .len = cursor - at, .type = AT_TOKEN_TYPE_RESP}; +} +yy19: + yych = *++cursor; +yy20: + switch (yych) { + case 0x00: + goto yy21; + case '"': + goto yy25; + default: + goto yy19; + } +yy21: + cursor = marker; + switch (yyaccept) { + case 0: + goto yy2; + case 1: + goto yy18; + case 2: + goto yy12; + case 3: + goto yy35; + case 4: + goto yy14; + default: + goto yy88; + } +yy22: + yych = *++cursor; + switch (yych) { + case '"': + goto yy29; + case '(': + goto yy30; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy31; + default: + goto yy21; + } +yy23: + yych = *++cursor; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy24; + default: + goto yy21; + } +yy24: + yych = *++cursor; + switch (yych) { + case ',': + goto yy33; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy24; + default: + goto yy12; + } +yy25: + yych = *++cursor; + switch (yych) { + case ',': + goto yy40; + default: + goto yy26; + } +yy26: { + char first = *at; + char last = *(cursor - 1); + /* `+1` accounts for leading quote. */ + int off_start = (first == ' ' ? 1 : 0) + 1; + /* `+1` accounts for trailing quote. */ + int off_len = (last == ',' ? 1 : 0) + off_start + 1; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at + off_start, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_QUOTED_STRING, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA + : AT_TOKEN_VARIANT_NO_COMMA}; +} +yy27: + yych = *++cursor; +yy28: + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy27; + case ':': + goto yy41; + default: + goto yy21; + } +yy29: + yych = *++cursor; + switch (yych) { + case 0x00: + goto yy21; + case '"': + goto yy43; + default: + goto yy29; + } +yy30: + yych = *++cursor; + switch (yych) { + case '"': + goto yy44; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy45; + default: + goto yy21; + } +yy31: + yych = *++cursor; + switch (yych) { + case ')': + goto yy46; + case ',': + goto yy22; + case '-': + goto yy48; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy31; + default: + goto yy21; + } +yy32: + yyaccept = 2; + yych = *(marker = ++cursor); + switch (yych) { + case ',': + goto yy33; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy32; + case ':': + goto yy41; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy27; + default: + goto yy12; + } +yy33: + ++cursor; + goto yy12; +yy34: + yyaccept = 3; + yych = *(marker = ++cursor); + switch (yych) { + case ' ': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy15; + case '#': + case '%': + case '+': + goto yy49; + case '=': + goto yy50; + default: + goto yy35; + } +yy35: { + char last = *(cursor - 1); + int off_len = last == '=' ? 1 : 0; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){ + .start = at, .len = cursor - at - off_len, .type = AT_TOKEN_TYPE_CMD_SET}; +} +yy36: + yych = *++cursor; + switch (yych) { + case ':': + goto yy21; + default: + goto yy28; + } +yy37: + yych = *++cursor; + switch (yych) { + case ':': + goto yy21; + case 'C': + goto yy51; + default: + goto yy28; + } +yy38: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy52; + default: + goto yy16; + } +yy39: + yych = *++cursor; + switch (yych) { + case 'K': + goto yy53; + default: + goto yy16; + } +yy40: + ++cursor; + goto yy26; +yy41: + yych = *++cursor; + switch (yych) { + case ' ': + goto yy54; + default: + goto yy42; + } +yy42: { + char first = *at; + char last = *(cursor - 1); + int off_start = first == '\r' ? 2 : 0; + /* `+1` accounts for colon. */ + int off_len = (last == ' ' ? 1 : 0) + off_start + 1; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, .type = AT_TOKEN_TYPE_NOTIF}; +} +yy43: + yych = *++cursor; + switch (yych) { + case ')': + goto yy46; + case ',': + goto yy22; + case '-': + goto yy48; + default: + goto yy21; + } +yy44: + yych = *++cursor; + switch (yych) { + case 0x00: + goto yy21; + case '"': + goto yy55; + default: + goto yy44; + } +yy45: + yych = *++cursor; + switch (yych) { + case ')': + goto yy56; + case ',': + goto yy30; + case '-': + goto yy57; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy45; + default: + goto yy21; + } +yy46: + yych = *++cursor; + switch (yych) { + case ',': + goto yy58; + default: + goto yy47; + } +yy47: { + char first = *at; + char last = *(cursor - 1); + /* `+1` accounts for leading parenthesis. */ + int off_start = (first == ' ' ? 1 : 0) + 1; + /* `+1` accounts for trailing parenthesis. */ + int off_len = (last == ',' ? 1 : 0) + off_start + 1; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at + off_start, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_ARRAY, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA + : AT_TOKEN_VARIANT_NO_COMMA}; +} +yy48: + yych = *++cursor; + switch (yych) { + case '"': + goto yy59; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy60; + default: + goto yy21; + } +yy49: + yych = *++cursor; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy61; + default: + goto yy21; + } +yy50: + ++cursor; + goto yy35; +yy51: + yych = *++cursor; + switch (yych) { + case 'M': + goto yy62; + default: + goto yy28; + } +yy52: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy63; + default: + goto yy16; + } +yy53: + yyaccept = 4; + yych = *(marker = ++cursor); + switch (yych) { + case '\r': + goto yy64; + default: + goto yy16; + } +yy54: + ++cursor; + goto yy42; +yy55: + yych = *++cursor; + switch (yych) { + case ')': + goto yy56; + case ',': + goto yy30; + case '-': + goto yy57; + default: + goto yy21; + } +yy56: + yych = *++cursor; + switch (yych) { + case ')': + goto yy46; + case ',': + goto yy22; + default: + goto yy21; + } +yy57: + yych = *++cursor; + switch (yych) { + case '"': + goto yy65; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy66; + default: + goto yy21; + } +yy58: + ++cursor; + goto yy47; +yy59: + yych = *++cursor; + switch (yych) { + case 0x00: + goto yy21; + case '"': + goto yy56; + default: + goto yy59; + } +yy60: + yych = *++cursor; + switch (yych) { + case ')': + goto yy46; + case ',': + goto yy22; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy60; + default: + goto yy21; + } +yy61: + yyaccept = 3; + yych = *(marker = ++cursor); + switch (yych) { + case '\r': + goto yy67; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy61; + case '=': + goto yy68; + case '?': + goto yy69; + default: + goto yy35; + } +yy62: + yych = *++cursor; + switch (yych) { + case 'E': + case 'S': + goto yy70; + default: + goto yy28; + } +yy63: + yych = *++cursor; + switch (yych) { + case 'O': + goto yy71; + default: + goto yy16; + } +yy64: + yych = *++cursor; + switch (yych) { + case '\n': + goto yy72; + default: + goto yy21; + } +yy65: + yych = *++cursor; + switch (yych) { + case 0x00: + goto yy21; + case '"': + goto yy73; + default: + goto yy65; + } +yy66: + yych = *++cursor; + switch (yych) { + case ')': + goto yy56; + case ',': + goto yy30; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy66; + default: + goto yy21; + } +yy67: + yych = *++cursor; + switch (yych) { + case '\n': + goto yy74; + default: + goto yy21; + } +yy68: + yych = *++cursor; + switch (yych) { + case '?': + goto yy75; + default: + goto yy35; + } +yy69: + ++cursor; + { + int off_len = 1; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_CMD_READ}; + } +yy70: + yych = *++cursor; + switch (yych) { + case ' ': + goto yy76; + default: + goto yy28; + } +yy71: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy53; + default: + goto yy16; + } +yy72: + ++cursor; + goto yy18; +yy73: + yych = *++cursor; + switch (yych) { + case ')': + goto yy56; + case ',': + goto yy30; + default: + goto yy21; + } +yy74: + yych = *++cursor; + switch (yych) { + case 'A': + goto yy77; + default: + goto yy21; + } +yy75: + ++cursor; + { + int off_len = 2; + + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at, + .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_CMD_TEST}; + } +yy76: + yych = *++cursor; + switch (yych) { + case 'E': + goto yy78; + default: + goto yy21; + } +yy77: + yych = *++cursor; + switch (yych) { + case 'T': + goto yy79; + default: + goto yy21; + } +yy78: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy80; + default: + goto yy21; + } +yy79: + yych = *++cursor; + switch (yych) { + case '#': + case '%': + case '+': + goto yy81; + default: + goto yy21; + } +yy80: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy82; + default: + goto yy21; + } +yy81: + yych = *++cursor; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy83; + default: + goto yy21; + } +yy82: + yych = *++cursor; + switch (yych) { + case 'O': + goto yy84; + default: + goto yy21; + } +yy83: + yych = *++cursor; + switch (yych) { + case '\r': + goto yy85; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + goto yy83; + default: + goto yy21; + } +yy84: + yych = *++cursor; + switch (yych) { + case 'R': + goto yy86; + default: + goto yy21; + } +yy85: + yych = *++cursor; + switch (yych) { + case '\n': + goto yy87; + default: + goto yy21; + } +yy86: + yych = *++cursor; + switch (yych) { + case ':': + goto yy89; + default: + goto yy21; + } +yy87: + yyaccept = 5; + yych = *(marker = ++cursor); + switch (yych) { + case 'A': + goto yy77; + default: + goto yy88; + } +yy88: { + if (remainder) { + *remainder = cursor; + } + return (struct at_token){.start = at, .len = cursor - at, .type = AT_TOKEN_TYPE_STRING}; +} +yy89: + yych = *++cursor; + switch (yych) { + case ' ': + goto yy90; + default: + goto yy21; + } +yy90: + yych = *++cursor; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy91; + default: + goto yy21; + } +yy91: + yych = *++cursor; + switch (yych) { + case '\r': + goto yy64; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto yy91; + default: + goto yy21; + } + } +} diff --git a/lib/at_parser/at_match.h b/lib/at_parser/at_match.h new file mode 100644 index 000000000000..6e5eea468df0 --- /dev/null +++ b/lib/at_parser/at_match.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef AT_MATCH_H__ +#define AT_MATCH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct at_token at_match(const char *at, const char **remainder); + +#ifdef __cplusplus +} +#endif + +#endif /* AT_MATCH_H__ */ diff --git a/lib/at_parser/at_match.re b/lib/at_parser/at_match.re new file mode 100644 index 000000000000..0f357fdc2994 --- /dev/null +++ b/lib/at_parser/at_match.re @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Generated by re2c 3.0 + * + * re2c at_match.re -o at_match.c -W --no-debug-info + */ + +#include + +struct at_token at_match(const char *at, const char **remainder) +{ + const char *cursor = at; + const char *marker = NULL; + + /*!re2c + re2c:define:YYCTYPE = "unsigned char"; + re2c:define:YYCURSOR = cursor; + re2c:define:YYMARKER = marker; + re2c:yyfill:enable = 0; + + CMD = [#%+][A-Za-z0-9]+; + INT = [0-9]+; + CRLF = "\r\n"; + SPACE = " "; + COMMA = ","; + QUOTE = "\""; + STR = [A-Za-z0-9][A-Za-z_\-.0-9 ]*; + QUOTED_STR = QUOTE [^"\x00]* QUOTE; + RANGE = (INT|QUOTED_STR) "-" (INT|QUOTED_STR); + ARRAY_ELEM = (INT|RANGE|QUOTED_STR); + ARRAY = "(" ARRAY_ELEM (COMMA ARRAY_ELEM)* ")"; + + cmd_test = "AT" CMD "=?"; + cmd_read = "AT" CMD "?"; + cmd_set = "AT" CMD? "="?; + notif = CRLF? CMD ":" SPACE?; + int = SPACE? [+\-]? INT COMMA?; + quoted_str = SPACE? QUOTED_STR COMMA?; + array = SPACE? "(" (ARRAY_ELEM|ARRAY) (COMMA (ARRAY_ELEM|ARRAY))* ")" COMMA?; + empty = SPACE? COMMA; + str = CRLF? STR; + clac = ("AT" CMD CRLF)("AT" CMD CRLF)+; + resp = CRLF (("OK"|"ERROR"|"+CME ERROR: " INT|"+CMS ERROR: " INT) CRLF)?; + + * { return (struct at_token){ .type = AT_TOKEN_TYPE_INVALID }; } + + cmd_test + { + int off_len = 2; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_CMD_TEST + }; + } + + cmd_read + { + int off_len = 1; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_CMD_READ + }; + } + + cmd_set + { + char last = *(cursor - 1); + int off_len = last == '=' ? 1 : 0; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_CMD_SET + }; + } + + notif + { + char first = *at; + char last = *(cursor - 1); + int off_start = first == '\r' ? 2 : 0; + /* `+1` accounts for colon. */ + int off_len = (last == ' ' ? 1 : 0) + off_start + 1; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_NOTIF + }; + } + + int + { + char first = *at; + char last = *(cursor - 1); + int off_start = first == ' ' ? 1 : 0; + int off_len = (last == ',' ? 1 : 0) + off_start; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_INT, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA : + AT_TOKEN_VARIANT_NO_COMMA + }; + } + + quoted_str + { + char first = *at; + char last = *(cursor - 1); + /* `+1` accounts for leading quote. */ + int off_start = (first == ' ' ? 1 : 0) + 1; + /* `+1` accounts for trailing quote. */ + int off_len = (last == ',' ? 1 : 0) + off_start + 1; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_QUOTED_STRING, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA : + AT_TOKEN_VARIANT_NO_COMMA + }; + } + + array + { + char first = *at; + char last = *(cursor - 1); + /* `+1` accounts for leading parenthesis. */ + int off_start = (first == ' ' ? 1 : 0) + 1; + /* `+1` accounts for trailing parenthesis. */ + int off_len = (last == ',' ? 1 : 0) + off_start + 1; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_ARRAY, + .variant = last == ',' ? AT_TOKEN_VARIANT_COMMA : + AT_TOKEN_VARIANT_NO_COMMA + }; + } + + empty + { + int off_len = 1; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_EMPTY, .variant = AT_TOKEN_VARIANT_COMMA + }; + } + + str + { + char first = *at; + char last = *(cursor - 1); + int off_start = first == '\r' ? 2 : (first == ' ' ? 1 : 0); + int off_len = (last == ' ' ? 1 : 0) + off_start; + + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at + off_start, .len = cursor - at - off_len, + .type = AT_TOKEN_TYPE_STRING + }; + } + + clac + { + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at, .type = AT_TOKEN_TYPE_STRING + }; + } + + resp + { + if (remainder) *remainder = cursor; + return (struct at_token){ + .start = at, .len = cursor - at, .type = AT_TOKEN_TYPE_RESP + }; + } + */ +} diff --git a/lib/at_parser/at_parser.c b/lib/at_parser/at_parser.c new file mode 100644 index 000000000000..26ed6979b7bb --- /dev/null +++ b/lib/at_parser/at_parser.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include "at_match.h" + +enum num_type { + I16, + U16, + I32, + U32, + I64, +}; + +static bool is_subparam(const struct at_token *token) +{ + switch (token->type) { + case AT_TOKEN_TYPE_INT: + case AT_TOKEN_TYPE_QUOTED_STRING: + case AT_TOKEN_TYPE_ARRAY: + case AT_TOKEN_TYPE_EMPTY: + return true; + default: + return false; + } +} + +static void update_at_parser_counters(struct at_parser *parser, const struct at_token *token) +{ + /* Beginning of new notification line. + * Example: "+NOTIF: 1\r\n+NOTIF2: ..." + * In the example above, the parser has parsed "+NOTIF2" last. This is the beginning of a + * new line, so reset the counters. */ + if (parser->counters.notif_count == 2) { + memset(&parser->counters, 0, sizeof(parser->counters)); + parser->counters.notif_count = 1; + } + + switch (token->type) { + case AT_TOKEN_TYPE_CMD_TEST: + case AT_TOKEN_TYPE_CMD_READ: + case AT_TOKEN_TYPE_CMD_SET: + parser->counters.cmd_count++; + break; + case AT_TOKEN_TYPE_NOTIF: + parser->counters.notif_count++; + break; + case AT_TOKEN_TYPE_STRING: + parser->counters.string_count++; + break; + case AT_TOKEN_TYPE_RESP: + parser->counters.end_count++; + break; + default: + parser->counters.subparam_count++; + } +} + +static bool is_valid_at_token(const struct at_token *token) +{ + if (!token->start) { + return false; + } + + size_t len = strlen(token->start); + + switch (token->type) { + case AT_TOKEN_TYPE_CMD_TEST: + case AT_TOKEN_TYPE_CMD_READ: + case AT_TOKEN_TYPE_CMD_SET: + case AT_TOKEN_TYPE_NOTIF: + case AT_TOKEN_TYPE_INT: + case AT_TOKEN_TYPE_ARRAY: + case AT_TOKEN_TYPE_STRING: + case AT_TOKEN_TYPE_RESP: + return len != 0 && token->len != 0 && token->len <= len; + case AT_TOKEN_TYPE_QUOTED_STRING: + return len != 0 && token->len <= len; + case AT_TOKEN_TYPE_EMPTY: + return token->len == 0; + default: + return false; + } +} + +static bool is_valid_at_token_line(struct at_parser *parser) +{ + /* At most one command per line. */ + if (parser->counters.cmd_count > 1) { + return false; + } + + /* There must be at least one of either command or notification if there is more than + * one subparameter. + */ + if (parser->counters.subparam_count >= 1 && parser->counters.cmd_count < 1 && + parser->counters.notif_count < 1) { + return false; + } + + /* If there is more than one notification, then there must be at least one subparameter + * (belonging to the first notification line). + */ + if (parser->counters.notif_count > 1 && parser->counters.subparam_count < 1) { + return false; + } + + return true; +} + +int at_parser_init(struct at_parser *parser, const char *at) +{ + if (!parser || !at) { + return -EINVAL; + } + + size_t len = strlen(at); + + /* Check bounds. Set maximum limit to a reasonable amount of 65535 characters (64KB). */ + if (len == 0 || len > UINT16_MAX) { + return -ERANGE; + } + + memset(parser, 0, sizeof(struct at_parser)); + + parser->ptr = at; + parser->initialized = true; + + return 0; +} + +int at_parser_tok(struct at_parser *parser, struct at_token *token) +{ + const char *remainder = NULL; + struct at_token tok = {0}; + + if (!parser || !token) { + return -EINVAL; + } + + if (!parser->initialized) { + return -EPERM; + } + + if (!parser->ptr) { + return -EINVAL; + } + + /* The lexer cannot match empty strings, so intercept the special case where the empty + * subparameter is the one after the previous token. This case is detected in the previous + * call to this function. + */ + if (parser->is_next_empty) { + tok.start = parser->ptr; + tok.len = 0; + tok.type = AT_TOKEN_TYPE_EMPTY; + tok.variant = AT_TOKEN_VARIANT_NO_COMMA; + + /* Set the remainder to the current cursor because an empty token has length + * zero. + */ + remainder = parser->ptr; + + goto matched; + } + + /* Nothing left to tokenize. */ + if (strlen(parser->ptr) == 0) { + return -EIO; + } + + tok = at_match(parser->ptr, &remainder); + if (tok.type == AT_TOKEN_TYPE_INVALID) { + return -EBADMSG; + } + +matched: + + /* The lexer cannot differentiate between an integer and a non-quoted string containing + * purely numbers because they are syntactically equivalent. + * If the first token of the line is detected as an integer and does not have a trailing + * comma, then it is only valid as a non-quoted string because a subparameter as the first + * token of the line makes the line invalid. + */ + if (tok.type == AT_TOKEN_TYPE_INT && tok.variant == AT_TOKEN_VARIANT_NO_COMMA && + parser->count == 0) { + tok.type = AT_TOKEN_TYPE_STRING; + } + + /* Update the parser's internal counters and ensure that the line is consistent. */ + update_at_parser_counters(parser, &tok); + if (!is_valid_at_token_line(parser)) { + return -EBADMSG; + } + + /* Reset this flag at each call. */ + parser->is_next_empty = false; + if (is_subparam(&tok)) { + size_t rem_len = strlen(remainder); + bool lookahead_crlf = rem_len >= 2 && remainder[0] == '\r' && remainder[1] == '\n'; + + /* if the token is a subparameter but does not have a trailing comma then it is the + * last subparameter of the line. + * If there is more to parse, then the token must be followed by CRLF, otherwise the + * string is malformed. + */ + if (tok.variant != AT_TOKEN_VARIANT_COMMA) { + if (rem_len > 0 && !lookahead_crlf) { + return -EBADMSG; + } + } else { + /* The next token is of type empty if and only if the current token has a + * trailing comma and the remaining string either is empty or starts with + * CRLF. + */ + if (rem_len == 0 || lookahead_crlf) { + parser->is_next_empty = true; + } + } + } + + parser->count++; + parser->ptr = remainder; + parser->prev_token = tok; + + memcpy(token, &tok, sizeof(struct at_token)); + + return 0; +} + +int at_parser_seek(struct at_parser *parser, size_t index, struct at_token *token) +{ + int ret; + struct at_token tok = {0}; + + if (!parser || !token) { + return -EINVAL; + } + + /* Prevent from going backwards. */ + if (index + 1 <= parser->count) { + return -ERANGE; + } + + do { + ret = at_parser_tok(parser, &tok); + } while ((index + 1 > parser->count) && (ret == 0)); + + if (ret == 0) { + memcpy(token, &tok, sizeof(struct at_token)); + } + + return ret; +} + +int at_token_line_parse(struct at_token *tokens, size_t num_tokens, const char *at, + const char **next_at) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + + if (!tokens || !at) { + return -EINVAL; + } + + ret = at_parser_init(&parser, at); + if (ret < 0) { + return ret; + } + + memset(tokens, 0, num_tokens * sizeof(struct at_token)); + + while (parser.count < num_tokens) { + ret = at_parser_tok(&parser, &token); + /* When reading a line, it is acceptable to parse until the null terminator even + * if there is no AT response. + */ + if (ret == -EIO) { + return 0; + } + if (ret < 0) { + return ret; + } + + /* Beginning of the next line. Inform the user that there is a new line to parse. */ + if (token.type == AT_TOKEN_TYPE_NOTIF && parser.counters.notif_count == 2) { + return -EAGAIN; + } + + if (token.type == AT_TOKEN_TYPE_RESP) { + /* Null-terminate the remainder. There is nothing left to parse. */ + if (next_at) { + *next_at = "\0"; + } + + return 0; + } + + if (next_at) { + *next_at = parser.ptr; + } + + memcpy(&tokens[parser.count - 1], &token, sizeof(struct at_token)); + } + + return 0; +} + +static int at_token_num_get(const struct at_token *token, void *value, enum num_type t, int64_t max, + int64_t min) +{ + if (!value || !token) { + return -EINVAL; + } + + if (token->type != AT_TOKEN_TYPE_INT) { + return -EINVAL; + } + + if (!is_valid_at_token(token)) { + return -EINVAL; + } + + char *next = NULL; + int64_t val = strtoll(token->start, &next, 10); + + /* `strtoll` returns value out of range. */ + if ((val == INT64_MIN || val == INT64_MAX) && errno == ERANGE) { + return -ERANGE; + } + + /* Value out of range according to the expected type. */ + if ((val > max) || (val < min)) { + return -ERANGE; + } + + switch (t) { + case I16: + *(int16_t *)(value) = (int16_t)val; + break; + case U16: + *(uint16_t *)(value) = (uint16_t)val; + break; + case I32: + *(int32_t *)(value) = (int32_t)val; + break; + case U32: + *(uint32_t *)(value) = (uint32_t)val; + break; + case I64: + *(int64_t *)(value) = (int64_t)val; + break; + default: + /* Unreachable. */ + } + + return 0; +} + +int at_token_int16_get(const struct at_token *token, int16_t *value) +{ + return at_token_num_get(token, (void *)value, I16, INT16_MAX, INT16_MIN); +} + +int at_token_uint16_get(const struct at_token *token, uint16_t *value) +{ + return at_token_num_get(token, (void *)value, U16, UINT16_MAX, 0); +} + +int at_token_int32_get(const struct at_token *token, int32_t *value) +{ + return at_token_num_get(token, (void *)value, I32, INT32_MAX, INT32_MIN); +} + +int at_token_uint32_get(const struct at_token *token, uint32_t *value) +{ + return at_token_num_get(token, (void *)value, U32, UINT32_MAX, 0); +} + +int at_token_int64_get(const struct at_token *token, int64_t *value) +{ + return at_token_num_get(token, (void *)value, I64, INT64_MAX, INT64_MIN); +} + +int at_token_string_get(const struct at_token *token, char *str, size_t *len) +{ + if (!token || !str || !len) { + return -EINVAL; + } + + switch (token->type) { + /* Acceptable types. */ + case AT_TOKEN_TYPE_CMD_TEST: + case AT_TOKEN_TYPE_CMD_SET: + case AT_TOKEN_TYPE_CMD_READ: + case AT_TOKEN_TYPE_NOTIF: + case AT_TOKEN_TYPE_QUOTED_STRING: + case AT_TOKEN_TYPE_STRING: + case AT_TOKEN_TYPE_ARRAY: + case AT_TOKEN_TYPE_RESP: + break; + default: + return -EINVAL; + } + + if (!is_valid_at_token(token)) { + return -EINVAL; + } + + /* Check if there is enough memory for null terminator. */ + if (*len < token->len + 1) { + return -ENOMEM; + } + + memcpy(str, token->start, token->len); + + /* Null-terminate the output string. */ + str[token->len] = '\0'; + + /* Update the length to reflect the copied string length. */ + *len = token->len; + + return 0; +} + +size_t at_token_valid_count_get(const struct at_token *tokens, size_t num_tokens) +{ + size_t count = 0; + + if (!tokens) { + return -EINVAL; + } + + for (size_t i = 0; i < num_tokens; i++) { + if (is_valid_at_token(&tokens[i])) { + count++; + } + } + + return count; +} + +enum at_token_type at_cmd_type_get(const char *at) +{ + struct at_token tok; + + if (!at) { + return AT_TOKEN_TYPE_INVALID; + } + + tok = at_match(at, NULL); + switch (tok.type) { + case AT_TOKEN_TYPE_CMD_TEST: + case AT_TOKEN_TYPE_CMD_READ: + case AT_TOKEN_TYPE_CMD_SET: + return tok.type; + default: + return AT_TOKEN_TYPE_INVALID; + } +} diff --git a/tests/lib/at_parser/CMakeLists.txt b/tests/lib/at_parser/CMakeLists.txt new file mode 100644 index 000000000000..0e74a6e4a862 --- /dev/null +++ b/tests/lib/at_parser/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(at_parser) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/at_parser/boards/native_posix.conf b/tests/lib/at_parser/boards/native_posix.conf new file mode 100644 index 000000000000..1b94794f8afe --- /dev/null +++ b/tests/lib/at_parser/boards/native_posix.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NEWLIB_LIBC=n diff --git a/tests/lib/at_parser/prj.conf b/tests/lib/at_parser/prj.conf new file mode 100644 index 000000000000..2f5ed1d16d78 --- /dev/null +++ b/tests/lib/at_parser/prj.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_ZTEST=y + +CONFIG_AT_PARSER=y diff --git a/tests/lib/at_parser/src/main.c b/tests/lib/at_parser/src/main.c new file mode 100644 index 000000000000..925589f2335c --- /dev/null +++ b/tests/lib/at_parser/src/main.c @@ -0,0 +1,3872 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include + +/* Imported from `at_cmd_parser` test. */ +#define SINGLELINE_PARAM_COUNT 5 +#define PDULINE_PARAM_COUNT 4 +#define SINGLEPARAMLINE_PARAM_COUNT 1 +#define EMPTYPARAMLINE_PARAM_COUNT 6 +#define CERTIFICATE_PARAM_COUNT 5 + +/* Imported from `at_cmd_parser` test. */ +static const char * const singleline[] = { + "+CEREG: 2,\"76C1\",\"0102DA04\", 7\r\n+CME ERROR: 10\r\n", + "+CEREG: 2,\"76C1\",\"0102DA04\", 7\r\nOK\r\n", + "+CEREG: 2,\"76C1\",\"0102DA04\", 7\r\n" +}; + +/* Imported from `at_cmd_parser` test. */ +static const char * const multiline[] = { + "+CGEQOSRDP: 0,0,,\r\n" + "+CGEQOSRDP: 1,2,,\r\n" + "+CGEQOSRDP: 2,4,,,1,65280000\r\n", + "+CGEQOSRDP: 0,0,,\r\n" + "+CGEQOSRDP: 1,2,,\r\n" + "+CGEQOSRDP: 2,4,,,1,65280000\r\nOK\r\n" + "+CGEQOSRDP: 0,0,,\r\n" + "+CGEQOSRDP: 1,2,,\r\n" + "+CGEQOSRDP: 2,4,,,1,65280000\r\nERROR\r\n" +}; + +/* Imported from `at_cmd_parser` test. */ +static const char * const pduline[] = { + "+CMT: \"12345678\", 24\r\n" + "06917429000171040A91747966543100009160402143708006C8329BFD0601\r\n+CME ERROR: 123\r\n", + "+CMT: \"12345678\", 24\r\n" + "06917429000171040A91747966543100009160402143708006C8329BFD0601\r\nOK\r\n", + "\r\n+CMT: \"12345678\", 24\r\n" + "06917429000171040A91747966543100009160402143708006C8329BFD0601\r\n\r\nOK\r\n", + "+CMT: \"12345678\", 24\r\n" + "06917429000171040A91747966543100009160402143708006C8329BFD0601\r\n", + "\r\n+CMT: \"12345678\", 24\r\n" + "06917429000171040A91747966543100009160402143708006C8329BFD0601\r\n" +}; + +/* Imported from `at_cmd_parser` test. */ +static const char * const singleparamline[] = { + "mfw_nrf9160_0.7.0-23.prealpha\r\n+CMS ERROR: 123\r\n", + "mfw_nrf9160_0.7.0-23.prealpha\r\nOK\r\n", + "mfw_nrf9160_0.7.0-23.prealpha\r\n" +}; + +/* Imported from `at_cmd_parser` test. */ +static const char * const emptyparamline[] = { + "+CPSMS: 1,,,\"10101111\",\"01101100\"\r\n", + "+CPSMS: 1,,,\"10101111\",\"01101100\"\r\nOK\r\n", + "+CPSMS: 1,,,\"10101111\",\"01101100\"\r\n+CME ERROR: 123\r\n" +}; + +/* Imported from `at_cmd_parser` test. */ +static const char * const certificate = + "%CMNG: 12345678, 0, \"978C...02C4\"," + "\"-----BEGIN CERTIFICATE-----" + "MIIBc464..." + "...bW9aAa4" + "-----END CERTIFICATE-----\"\r\nERROR\r\n"; + +static void test_params_before(void *fixture) +{ + ARG_UNUSED(fixture); +} + +static void test_params_after(void *fixture) +{ + ARG_UNUSED(fixture); +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_token_line_parse_quoted_string) +{ + int ret; + const char *remainder = NULL; + struct at_token tokens[32] = { 0 }; + char tmpbuf[32]; + uint32_t tmpbuf_len; + int32_t tmpint; + + const char *str1 = "+TEST:1,\"Hello World!\"\r\n"; + const char *str2 = "%TEST: 1, \"Hello World!\"\r\n"; + const char *str3 = "#TEST:1,\"Hello World!\"\r\n" + "+TEST: 2, \"FOOBAR\"\r\n"; + + /* String without spaces between parameters (str1)*/ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, &remainder); + zassert_ok(ret); + zassert_equal(*remainder, '\0'); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_QUOTED_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("+TEST"), tmpbuf_len); + zassert_mem_equal("+TEST", tmpbuf, tmpbuf_len); + + ret = at_token_int32_get(&tokens[1], &tmpint); + zassert_ok(ret); + zassert_equal(tmpint, 1); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[2], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("Hello World!"), tmpbuf_len); + zassert_mem_equal("Hello World!", tmpbuf, tmpbuf_len); + + /* String with spaces between parameters (str2)*/ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, &remainder); + zassert_ok(ret); + zassert_equal(*remainder, '\0'); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_QUOTED_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("%TEST"), tmpbuf_len); + zassert_mem_equal("%TEST", tmpbuf, tmpbuf_len); + + ret = at_token_int32_get(&tokens[1], &tmpint); + zassert_ok(ret); + zassert_equal(tmpint, 1); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[2], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("Hello World!"), tmpbuf_len); + zassert_mem_equal("Hello World!", tmpbuf, tmpbuf_len); + + /* String with multiple notifications (str3) */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, &remainder); + zassert_equal(ret, -EAGAIN); + zassert_not_equal(*remainder, '\0'); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_QUOTED_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("#TEST"), tmpbuf_len); + zassert_mem_equal("#TEST", tmpbuf, tmpbuf_len); + + ret = at_token_int32_get(&tokens[1], &tmpint); + zassert_ok(ret); + zassert_equal(tmpint, 1); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[2], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("Hello World!"), tmpbuf_len); + zassert_mem_equal("Hello World!", tmpbuf, tmpbuf_len); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), remainder, &remainder); + zassert_ok(ret); + zassert_equal(*remainder, '\0'); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_QUOTED_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("+TEST"), tmpbuf_len); + zassert_mem_equal("+TEST", tmpbuf, tmpbuf_len); + + ret = at_token_int32_get(&tokens[1], &tmpint); + zassert_ok(ret); + zassert_equal(tmpint, 2); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[2], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("FOOBAR"), tmpbuf_len); + zassert_mem_equal("FOOBAR", tmpbuf, tmpbuf_len); +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_token_line_parse_empty) +{ + int ret; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+TEST: 1,\r\n"; + const char *str2 = "+TEST: ,1\r\n"; + const char *str3 = "+TEST: 1,,\"Hello World!\""; + const char *str4 = "+TEST: 1,,,\r\n"; + const char *str5 = "+TEST: ,,,1\r\n"; + + /* Empty parameter at the end of the parameter list */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_EMPTY); + + /* Empty parameter at the beginning of the parameter list */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 3); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_EMPTY); + + /* Empty parameter between two other parameter types */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_EMPTY); + + /* 3 empty parameter at the end of the parameter list */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str4, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 5); + for (int i = 2; i < 4; i++) { + zassert_equal(tokens[i].type, AT_TOKEN_TYPE_EMPTY); + } + + /* 3 empty parameter at the beginning of the parameter list */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str5, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 5); + for (int i = 1; i < 3; i++) { + zassert_equal(tokens[i].type, AT_TOKEN_TYPE_EMPTY); + } +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_token_line_parse_testcases) +{ + int ret; + const char *remainder; + struct at_token tokens[32] = { 0 }; + + /* Try to parse the singleline string */ + for (size_t i = 0; i < ARRAY_SIZE(singleline); i++) { + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), singleline[i], NULL); + zassert_ok(ret); + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, SINGLELINE_PARAM_COUNT); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_QUOTED_STRING); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_QUOTED_STRING); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INT); + + /* Try to parse the pduline string */ + for (size_t i = 0; i < ARRAY_SIZE(pduline); i++) { + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), pduline[i], NULL); + zassert_ok(ret); + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, PDULINE_PARAM_COUNT); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_QUOTED_STRING); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_STRING); + + /* Try to parse the singleparamline string */ + for (size_t i = 0; i < ARRAY_SIZE(singleparamline); i++) { + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), singleparamline[i], NULL); + zassert_ok(ret); + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, SINGLEPARAMLINE_PARAM_COUNT); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_STRING); + + /* Try to parse the string containing empty/optional parameters */ + for (size_t i = 0; i < ARRAY_SIZE(emptyparamline); i++) { + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), emptyparamline[i], NULL); + zassert_ok(ret); + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, EMPTYPARAMLINE_PARAM_COUNT); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_QUOTED_STRING); + zassert_equal(tokens[5].type, AT_TOKEN_TYPE_QUOTED_STRING); + + /* Try to parse the string containing multiple notifications */ + for (size_t i = 0; i < ARRAY_SIZE(multiline); i++) { + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), multiline[i], &remainder); + zassert_equal(ret, -EAGAIN); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 5); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_EMPTY); + + /* 2nd iteration */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), remainder, &remainder); + zassert_equal(ret, -EAGAIN); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 5); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_EMPTY); + + /* 3rd iteration */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), remainder, &remainder); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 7); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[5].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[6].type, AT_TOKEN_TYPE_INT); + } + + /* Try to parse the string containing certificate data */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), certificate, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, CERTIFICATE_PARAM_COUNT); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_QUOTED_STRING); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_QUOTED_STRING); +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_cmd_type_get_cmd_set) +{ + int ret; + struct at_token tokens[32] = { 0 }; + enum at_token_type type; + char tmpbuf[64]; + uint32_t tmpbuf_len; + int16_t tmpshrt; + uint16_t tmpushrt; + + /* CGMI */ + static const char at_cmd_cgmi[] = "AT+CGMI"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_cgmi, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + type = at_cmd_type_get(at_cmd_cgmi); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT+CGMI", tmpbuf, tmpbuf_len); + + /* CCLK */ + static const char at_cmd_cclk[] = "AT+CCLK=\"18/12/06,22:10:00+08\""; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_cclk, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 2); + + type = at_cmd_type_get(at_cmd_cclk); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); + + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_CMD_SET); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_QUOTED_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT+CCLK", tmpbuf, tmpbuf_len); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[1], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("18/12/06,22:10:00+08", tmpbuf, tmpbuf_len); + + /* XSYSTEMMODE */ + static const char at_cmd_xsystemmode[] = "AT%XSYSTEMMODE=1,2,3,4"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_xsystemmode, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 5); + + type = at_cmd_type_get(at_cmd_xsystemmode); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); + + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_CMD_SET); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &tmpshrt); + zassert_ok(ret); + zassert_equal(tmpshrt, 1); + + ret = at_token_int16_get(&tokens[2], &tmpshrt); + zassert_ok(ret); + zassert_equal(tmpshrt, 2); + + ret = at_token_int16_get(&tokens[3], &tmpshrt); + zassert_ok(ret); + zassert_equal(tmpshrt, 3); + + ret = at_token_int16_get(&tokens[4], &tmpshrt); + zassert_ok(ret); + zassert_equal(tmpshrt, 4); + + ret = at_token_uint16_get(&tokens[1], &tmpushrt); + zassert_ok(ret); + zassert_equal(tmpushrt, 1); + + ret = at_token_uint16_get(&tokens[2], &tmpushrt); + zassert_ok(ret); + zassert_equal(tmpushrt, 2); + + ret = at_token_uint16_get(&tokens[3], &tmpushrt); + zassert_ok(ret); + zassert_equal(tmpushrt, 3); + + ret = at_token_uint16_get(&tokens[4], &tmpushrt); + zassert_ok(ret); + zassert_equal(tmpushrt, 4); + + /* AT */ + static const char lone_at_cmd[] = "AT"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), lone_at_cmd, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + type = at_cmd_type_get(lone_at_cmd); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT", tmpbuf, tmpbuf_len); + + /* CLAC */ + static const char at_cmd_clac[] = "AT+CLAC\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_clac, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + type = at_cmd_type_get(at_cmd_clac); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT+CLAC", tmpbuf, tmpbuf_len); + + /* CLAC RSP */ + static const char at_clac_rsp[] = "AT+CLAC\r\nAT+COPS\r\nAT%COPS\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_clac_rsp, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal(at_clac_rsp, tmpbuf, tmpbuf_len); +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_cmd_type_get_cmd_read) +{ + int ret; + char tmpbuf[64]; + uint32_t tmpbuf_len; + struct at_token tokens[32] = { 0 }; + enum at_token_type type; + + static const char at_cmd_cfun_read[] = "AT+CFUN?"; + + type = at_cmd_type_get(at_cmd_cfun_read); + zassert_equal(type, AT_TOKEN_TYPE_CMD_READ); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_cfun_read, NULL); + zassert_ok(ret, "%d", ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_CMD_READ); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT+CFUN", tmpbuf, tmpbuf_len); +} + +/* Ported from `at_cmd_parser` test. */ +ZTEST(at_parser, test_at_cmd_type_get_cmd_test) +{ + int ret; + char tmpbuf[64]; + uint32_t tmpbuf_len; + struct at_token tokens[32] = { 0 }; + enum at_token_type type; + + static const char at_cmd_cfun_test[] = "AT+CFUN=?"; + + type = at_cmd_type_get(at_cmd_cfun_test); + zassert_equal(type, AT_TOKEN_TYPE_CMD_TEST); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), at_cmd_cfun_test, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 1); + + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_CMD_TEST); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_mem_equal("AT+CFUN", tmpbuf, tmpbuf_len); +} + +ZTEST(at_parser, test_at_parser_init_einval) +{ + int ret; + struct at_parser parser = { 0 }; + + ret = at_parser_init(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_init(&parser, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_init(NULL, "AT+CFUN?"); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_parser_init_erange) +{ + int ret; + struct at_parser parser = {0}; + + /* 1) String too short. */ + ret = at_parser_init(&parser, ""); + zassert_equal(ret, -ERANGE); + + /* 2) String too large. */ + static char str[UINT16_MAX + 8] = {0}; + + for (int i = 0; i < UINT16_MAX + 1; i++) { + str[i] = 'A'; + } + + zassert_true(strlen(str) > UINT16_MAX); + + ret = at_parser_init(&parser, str); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_parser_init) +{ + int ret; + struct at_parser parser = { 0 }; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); +} + +ZTEST(at_parser, test_at_parser_tok_einval) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + ret = at_parser_tok(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_tok(&parser, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_tok(NULL, &tok); + zassert_equal(ret, -EINVAL); + + const char *str1 = "+TEST: 1,2\r\nOK\r\n"; + + /* Initialize and then modify the parser's `ptr` cursor. */ + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + parser.ptr = NULL; + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_parser_tok_eperm) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + /* The parser has not been initialized. */ + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EPERM); +} + +ZTEST(at_parser, test_at_parser_tok_eio) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_RESP); + + /* There is nothing left to parse. */ + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EIO); +} + +ZTEST(at_parser, test_at_parser_tok_invalid_token_ebadmsg) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + /* Invalid token. `#31` is not a valid token. */ + const char *str1 = "+NOTIF: 1,#31,1"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + /* Invalid token. ` !+NOTIF: ` is not a valid token. */ + const char *str2 = " !+NOTIF: 1,2,3"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. `+!NOTIF: ` is not a valid token. */ + const char *str3 = "+!NOTIF: 1,2,3"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. `%!NOTIF: ` is not a valid token. */ + const char *str4 = "%!NOTIF: 1,2,3"; + + ret = at_parser_init(&parser, str4); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. Cannot have null terminator inside quoted string. */ + const char *str5 = "+NOTIF: \"\0ABCD\""; + + ret = at_parser_init(&parser, str5); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. Cannot have null terminator inside quoted string. */ + const char *str6 = "+NOTIF: \"A\0BCD\""; + + ret = at_parser_init(&parser, str6); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. Cannot have non-quoted string inside parenthesis. */ + const char *str7 = "+NOTIF: (ABCD,1)"; + + ret = at_parser_init(&parser, str7); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); + + /* Invalid token. A non-quoted string cannot start with `-`. */ + const char *str8 = "-ABCD"; + + ret = at_parser_init(&parser, str8); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &tok); + zassert_equal(ret, -EBADMSG); +} + +ZTEST(at_parser, test_at_parser_tok_invalid_constraints_ebadmsg) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + /* 1) At most one command per line. */ + const char *str1 = "AT+TEST=AT+TEST?\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + /* 2) There must be at least one of either command or notification if there is more than + * one subparameter (except non-quoted string). + */ + const char *str2 = "1,\"test\",2\r\n"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* 3) If there is more than one notification, then there must be at least one subparameter + * (belonging to the first notification line). + */ + const char *str3 = "+NOTIF: #TEST: \r\nOK\r\n"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret, "%d", ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + /* 4) For each notification line, there must be at least one subparameter. + */ + const char *str4= "+NOTIF: 1\r\n#TEST: +TEST:\r\nOK\r\n"; + + ret = at_parser_init(&parser, str4); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#TEST"), buffer_len); + zassert_mem_equal("#TEST", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#TEST"), buffer_len); + zassert_mem_equal("#TEST", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_parser_tok_last_subparameter_ebadmsg) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + + /* 1) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by an integer rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str1 = "+NOTIF: 1,2,\"TEST\"9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + /* 2) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str2 = "+NOTIF: 1,2,\"TEST\"\r9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + /* 3) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR which is also the last character of the + * string. Thus, the string is malformed. + */ + const char *str3 = "+NOTIF: 1,2,\"TEST\"\r"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + /* 4) The last subparamter of the line is empty, however it is followed only by a CR which + * is also the last character of the string. Thus, the string is malformed. + */ + const char *str4 = "+NOTIF: 1,2,\r"; + + ret = at_parser_init(&parser, str4); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_parser_tok(&parser, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_INT); +} + +ZTEST(at_parser, test_at_parser_tok_long_string) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + int32_t val; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + const char *str = "+NOTIF: ,178,(1,2-3,\"a\"-\"b\"),+21,\"TEST\",-9,,\"TEST2, \",\r\n" + "#ABBA: 1,2\r\n" + "%TEST: 1\r\n" + "OK\r\n"; + + ret = at_parser_init(&parser, str); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 178); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 21); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("TEST"), buffer_len); + zassert_mem_equal("TEST", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, -9); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("TEST2, "), buffer_len); + zassert_mem_equal("TEST2, ", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#ABBA"), buffer_len); + zassert_mem_equal("#ABBA", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 2); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("%TEST"), buffer_len); + zassert_mem_equal("%TEST", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_len); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_parser_tok_long_string_spaces) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + int32_t val; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + const char *str = "+NOTIF: , 178, (1,2-3,\"a\"-\"b\"), +21, \"TEST\", -9, ,\"TEST2, \",\r\n" + "#ABBA:1, 2\r\n" + "%TEST: 1\r\n" + "OK\r\n"; + + ret = at_parser_init(&parser, str); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 178); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 21); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("TEST"), buffer_len); + zassert_mem_equal("TEST", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, -9); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("TEST2, "), buffer_len); + zassert_mem_equal("TEST2, ", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_EMPTY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#ABBA"), buffer_len); + zassert_mem_equal("#ABBA", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 2); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("%TEST"), buffer_len); + zassert_mem_equal("%TEST", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_len); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_parser_tok_resp_errors) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + int32_t val; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + /* ERROR */ + const char *str1 = "+NOTIF: 1\r\nERROR\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\nERROR\r\n"), buffer_len); + zassert_mem_equal("\r\nERROR\r\n", buffer, buffer_len); + + /* +CME ERROR */ + const char *str2 = "+NOTIF: 1\r\n+CME ERROR: 123\r\n"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\n+CME ERROR: 123\r\n"), buffer_len); + zassert_mem_equal("\r\n+CME ERROR: 123\r\n", buffer, buffer_len); + + /* +CMS ERROR */ + const char *str3 = "+NOTIF: 1\r\n+CMS ERROR: 123\r\n"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\n+CMS ERROR: 123\r\n"), buffer_len); + zassert_mem_equal("\r\n+CMS ERROR: 123\r\n", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_parser_seek_einval) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + const char *at = "+TEST: 1,2\r\nOK\r\n"; + size_t index = 1; + + ret = at_parser_seek(NULL, index, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_seek(&parser, index, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_seek(NULL, index, &tok); + zassert_equal(ret, -EINVAL); + + /* Initialize and then modify the parser's `ptr` cursor. */ + ret = at_parser_init(&parser, at); + zassert_ok(ret); + + parser.ptr = NULL; + + ret = at_parser_seek(&parser, index, &tok); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_parser_seek_eperm) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + /* The parser has not been initialized. */ + ret = at_parser_seek(&parser, 0, &tok); + zassert_equal(ret, -EPERM); +} + +ZTEST(at_parser, test_at_parser_seek_eio) +{ + int ret; + struct at_parser parser = { 0 }; + struct at_token tok = { 0 }; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 4, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_RESP); + + ret = at_parser_seek(&parser, 5, &tok); + zassert_equal(ret, -EIO); +} + +ZTEST(at_parser, test_at_parser_seek_invalid_token_ebadmsg) +{ + int ret; + struct at_parser parser = {0}; + struct at_token tok = {0}; + + /* Invalid token. */ + const char *str1 = "+NOTIF: 1,#31,1"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 3, &tok); + zassert_equal(ret, -EBADMSG); +} + +ZTEST(at_parser, test_at_parser_seek_invalid_constraints_ebadmsg) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + /* 1) At most one command per line. */ + const char *str1 = "AT+TEST=AT+TEST?\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 0, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_parser_seek(&parser, 1, &token); + zassert_equal(ret, -EBADMSG); + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + /* 2) There must be at least one of either command or notification if there is more than + * one subparameter (except non-quoted string). + */ + const char *str2 = "1,\"test\",2\r\n"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 0, &token); + zassert_equal(ret, -EBADMSG); + + /* 3) If there is more than one notification, then there must be at least one subparameter + * (belonging to the first notification line). + */ + const char *str3 = "+NOTIF: #TEST: \r\nOK\r\n"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 0, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + ret = at_parser_seek(&parser, 1, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret, "%d", ret); + zassert_equal(strlen("+NOTIF"), buffer_len); + zassert_mem_equal("+NOTIF", buffer, buffer_len); + + /* 4) For each notification line, there must be at least one subparameter. + */ + const char *str4 = "+NOTIF: 1\r\n#TEST: +TEST:\r\nOK\r\n"; + + ret = at_parser_init(&parser, str4); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 2, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#TEST"), buffer_len); + zassert_mem_equal("#TEST", buffer, buffer_len); + + ret = at_parser_seek(&parser, 3, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("#TEST"), buffer_len); + zassert_mem_equal("#TEST", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_parser_seek_last_subparameter_ebadmsg) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + + /* 1) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by an integer rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str1 = "+NOTIF: ,178,21,\"TEST\"9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* Seeking directly towards the malformed token. */ + ret = at_parser_seek(&parser, 4, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* 2) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str2 = "+NOTIF: ,178,21,\"TEST\"\r9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_parser_init(&parser, str2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* Seeking directly towards the malformed token. */ + ret = at_parser_seek(&parser, 4, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* 3) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR which is also the last character of the + * string. Thus, the string is malformed. + */ + const char *str3 = "+NOTIF: 1,2,\"TEST\"\r"; + + ret = at_parser_init(&parser, str3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* Seeking directly towards the malformed token. */ + ret = at_parser_seek(&parser, 3, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* 4) The last subparamter of the line is empty, however it is followed only by a CR which + * is also the last character of the string. Thus, the string is malformed. + */ + const char *str4 = "+NOTIF: 1,2,\r"; + + ret = at_parser_init(&parser, str4); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + /* Seeking directly towards the malformed token. */ + ret = at_parser_seek(&parser, 3, &token); + zassert_equal(ret, -EBADMSG); + + /* Unchanged. */ + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); +} + +ZTEST(at_parser, test_at_parser_seek_erange) +{ + int ret; + struct at_parser parser = {0}; + struct at_token tok = {0}; + uint32_t val; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 2, &tok); + zassert_ok(ret); + zassert_equal(tok.type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tok, &val); + zassert_ok(ret); + zassert_equal(val, 2); + + /* Let's try going backwards. */ + ret = at_parser_seek(&parser, 1, &tok); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_parser_seek) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + uint32_t val; + char buffer[32] = {0}; + size_t buffer_len = sizeof(buffer); + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_seek(&parser, 2, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 2); + + ret = at_parser_seek(&parser, 4, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_len = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_len); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_len); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_len); +} + +ZTEST(at_parser, test_at_token_line_parse_einval) +{ + int ret; + struct at_token tokens[32] = { 0 }; + const char *remainder = NULL; + + const char *at = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_token_line_parse(NULL, ARRAY_SIZE(tokens), at, &remainder); + zassert_equal(ret, -EINVAL); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), NULL, &remainder); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_token_line_parse_erange) +{ + int ret; + struct at_token tokens[32] = { 0 }; + const char *remainder = NULL; + + /* 1) String too short. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), "", &remainder); + zassert_equal(ret, -ERANGE); + + /* 2) String too large. */ + static char str[UINT16_MAX + 8] = {0}; + + for (int i = 0; i < UINT16_MAX + 1; i++) { + str[i] = 'A'; + } + + zassert_true(strlen(str) > UINT16_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str, &remainder); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_line_parse_eagain) +{ + int ret; + struct at_token tokens[32] = { 0 }; + const char *remainder = NULL; + const char *multi = "+CGEQOSRDP: 0,0,,\r\n" + "+CGEQOSRDP: 1,2,,\r\n" + "+CGEQOSRDP: 2,4,,,1,65280000\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), multi, &remainder); + zassert_equal(ret, -EAGAIN); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), remainder, &remainder); + zassert_equal(ret, -EAGAIN); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), remainder, &remainder); + zassert_equal(ret, 0); +} + +ZTEST(at_parser, test_at_token_line_parse_ebadmsg) +{ + int ret; + struct at_token tokens[32] = { 0 }; + + /* The notification ID should not contain anything other than [A-Za-z0-9]. */ + const char *str1 = "+NOTIF-: 1,2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_equal(ret, -EBADMSG); +} + +ZTEST(at_parser, test_at_token_line_parse_invalid_constraints_ebadmsg) +{ + int ret; + struct at_token tokens[32] = { 0 }; + + /* At most one command per line. */ + const char *str1 = "AT+TEST=AT+TEST?\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_equal(ret, -EBADMSG); + + /* There must be at least one of either command or notification if there is more than + * one subparameter (except non-quoted string). + */ + const char *str2 = "1,\"test\",2\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_equal(ret, -EBADMSG); + + /* If there is more than one notification, then there must be at least one subparameter + * (belonging to the first notification line). + */ + const char *str3 = "+NOTIF: #TEST: \r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_equal(ret, -EBADMSG); +} + +ZTEST(at_parser, test_at_token_line_parse_last_subparameter_ebadmsg) +{ + int ret; + struct at_token tokens[32] = {0}; + + /* 1) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by an integer rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str1 = "+NOTIF: ,178,21,\"TEST\"9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_equal(ret, -EBADMSG); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INVALID); + + /* 2) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR rather than a CRLF. Thus, the string is + * malformed. + */ + const char *str2 = "+NOTIF: ,178,21,\"TEST\"\r9,(1,7,2,0),,3,\"TEST2, \",\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_equal(ret, -EBADMSG); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INVALID); + + /* 3) "TEST" looks like the last subparameter of the line since it is missing a trailing + * comma, however it is followed by only a CR which is also the last character of the + * string. Thus, the string is malformed. + */ + const char *str3 = "+NOTIF: ,178,21,\"TEST\"\r"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_equal(ret, -EBADMSG); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INVALID); + + /* 4) The last subparamter of the line is empty, however it is followed only by a CR which + * is also the last character of the string. Thus, the string is malformed. + */ + const char *str4 = "+NOTIF: ,178,21,\r"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str4, NULL); + zassert_equal(ret, -EBADMSG); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_INVALID); +} + +ZTEST(at_parser, test_at_token_line_parse_string) +{ + int ret; + const char *remainder = NULL; + struct at_token tokens[32] = { 0 }; + char tmpbuf[32]; + uint32_t tmpbuf_len; + + const char *str1 = "+CGEV: ME PDN ACT 0\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, &remainder); + zassert_ok(ret); + zassert_equal(*remainder, '\0'); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 2); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_STRING); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[0], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("+CGEV"), tmpbuf_len); + zassert_mem_equal("+CGEV", tmpbuf, tmpbuf_len); + + tmpbuf_len = sizeof(tmpbuf); + ret = at_token_string_get(&tokens[1], tmpbuf, &tmpbuf_len); + zassert_ok(ret); + zassert_equal(strlen("ME PDN ACT 0"), tmpbuf_len); + zassert_mem_equal("ME PDN ACT 0", tmpbuf, tmpbuf_len); +} + +ZTEST(at_parser, test_at_token_line_parse_truncated) +{ + int ret; + struct at_token tokens[32] = { 0 }; + /* Only consider the first 2 tokens. */ + size_t count = 2; + + const char *str1 = "+NOTIF: 1,2,3\r\n"; + + ret = at_token_line_parse(tokens, count, str1, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, count); + zassert_equal(ret, count); +} + +ZTEST(at_parser, test_at_token_uint16_get_einval) +{ + int ret; + uint16_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + /* 1) Invalid input pointers. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_token_uint16_get(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_uint16_get(&tokens[1], NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_uint16_get(NULL, &val); + zassert_equal(ret, -EINVAL); + + /* 2) The token is pointing to a null string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = NULL; + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 3) The token is pointing to an empty string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = ""; + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 4) The length of the token is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = 0; + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 5) The length of the token is larger than the length of the string it points to. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = strlen(tokens[1].start) + 1; + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 6) The token has an invalid type. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + /* Override type with anything other than `AT_TOKEN_TYPE_INT`. */ + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + if (i != AT_TOKEN_TYPE_INT) { + tokens[1].type = i; + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + } + } +} + +ZTEST(at_parser, test_at_token_uint16_get_erange) +{ + int ret; + uint16_t val; + struct at_token tokens[32] = { 0 }; + + char str1[64] = {0}; + + snprintf(str1, sizeof(str1), "+NOTIF: %d,-2,3\r\nOK\r\n", UINT16_MAX + 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_uint16_get) +{ + int ret; + uint16_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %d,-2,3\r\nOK\r\n", UINT16_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, UINT16_MAX); +} + +ZTEST(at_parser, test_at_token_int16_get_einval) +{ + int ret; + int16_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + /* 1) Invalid input pointers. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_token_int16_get(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int16_get(&tokens[1], NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int16_get(NULL, &val); + zassert_equal(ret, -EINVAL); + + /* 2) The token is pointing to a null string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = NULL; + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 3) The token is pointing to an empty string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = ""; + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 4) The length of the token is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = 0; + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 5) The length of the token is larger than the length of the string it points to. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = strlen(tokens[1].start) + 1; + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 6) The token has an invalid type. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + /* Override type with anything other than `AT_TOKEN_TYPE_INT`. */ + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + if (i != AT_TOKEN_TYPE_INT) { + tokens[1].type = i; + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + } + } +} + +ZTEST(at_parser, test_at_token_int16_get_erange) +{ + int ret; + int16_t val; + struct at_token tokens[32] = { 0 }; + + char str1[64] = {0}; + + snprintf(str1, sizeof(str1), "+NOTIF: %d,-2,3\r\nOK\r\n", INT16_MAX + 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %d,-2,3\r\nOK\r\n", INT16_MIN - 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_int16_get) +{ + int ret; + int16_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %d,-2,3\r\nOK\r\n", INT16_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT16_MAX); + + char str3[64] = {0}; + + snprintf(str3, sizeof(str3), "+NOTIF: %d,-2,3\r\nOK\r\n", INT16_MIN); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int16_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT16_MIN); +} + +ZTEST(at_parser, test_at_token_uint32_get_einval) +{ + int ret; + uint32_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + /* 1) Invalid input pointers. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_token_uint32_get(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_uint32_get(&tokens[1], NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_uint32_get(NULL, &val); + zassert_equal(ret, -EINVAL); + + /* 2) The token is pointing to a null string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = NULL; + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 3) The token is pointing to an empty string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = ""; + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 4) The length of the token is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = 0; + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 5) The length of the token is larger than the length of the string it points to. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = strlen(tokens[1].start) + 1; + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 6) The token has an invalid type. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + /* Override type with anything other than `AT_TOKEN_TYPE_INT`. */ + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + if (i != AT_TOKEN_TYPE_INT) { + tokens[1].type = i; + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + } + } +} + +ZTEST(at_parser, test_at_token_uint32_get_erange) +{ + int ret; + uint32_t val; + struct at_token tokens[32] = { 0 }; + + char str1[64] = {0}; + + snprintf(str1, sizeof(str1), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)UINT32_MAX + 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_uint32_get) +{ + int ret; + uint32_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)UINT32_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_uint32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, UINT32_MAX); +} + +ZTEST(at_parser, test_at_token_int32_get_einval) +{ + int ret; + int32_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + /* 1) Invalid input pointers. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_token_int32_get(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int32_get(&tokens[1], NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int32_get(NULL, &val); + zassert_equal(ret, -EINVAL); + + /* 2) The token is pointing to a null string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = NULL; + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 3) The token is pointing to an empty string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = ""; + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 4) The length of the token is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = 0; + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 5) The length of the token is larger than the length of the string it points to. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = strlen(tokens[1].start) + 1; + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 6) The token has an invalid type. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + /* Override type with anything other than `AT_TOKEN_TYPE_INT`. */ + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + if (i != AT_TOKEN_TYPE_INT) { + tokens[1].type = i; + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + } + } +} + +ZTEST(at_parser, test_at_token_int32_get_erange) +{ + int ret; + int32_t val; + struct at_token tokens[32] = { 0 }; + + char str1[64] = {0}; + + snprintf(str1, sizeof(str1), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT32_MAX + 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT32_MIN - 1); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_int32_get) +{ + int ret; + int32_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT32_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT32_MAX); + + char str3[64] = {0}; + + snprintf(str3, sizeof(str3), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT32_MIN); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT32_MIN); +} + +ZTEST(at_parser, test_at_token_int64_get_einval) +{ + int ret; + int64_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + /* 1) Invalid input pointers. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_token_int64_get(NULL, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int64_get(&tokens[1], NULL); + zassert_equal(ret, -EINVAL); + + ret = at_token_int64_get(NULL, &val); + zassert_equal(ret, -EINVAL); + + /* 2) The token is pointing to a null string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = NULL; + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 3) The token is pointing to an empty string. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].start = ""; + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 4) The length of the token is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = 0; + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 5) The length of the token is larger than the length of the string it points to. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + tokens[1].len = strlen(tokens[1].start) + 1; + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + + /* 6) The token has an invalid type. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + /* Override type with anything other than `AT_TOKEN_TYPE_INT`. */ + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + if (i != AT_TOKEN_TYPE_INT) { + tokens[1].type = i; + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -EINVAL); + } + } +} + +ZTEST(at_parser, test_at_token_int64_get_erange) +{ + int ret; + int64_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 100000000000000000000,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); + + const char *str2 = "+NOTIF: -100000000000000000000,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_equal(ret, -ERANGE); +} + +ZTEST(at_parser, test_at_token_int64_get) +{ + int ret; + int64_t val; + struct at_token tokens[32] = { 0 }; + + const char *str1 = "+NOTIF: 1,-2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, 1); + + char str2[64] = {0}; + + snprintf(str2, sizeof(str2), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT64_MAX); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str2, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT64_MAX); + + char str3[64] = {0}; + + snprintf(str3, sizeof(str3), "+NOTIF: %lld,-2,3\r\nOK\r\n", (long long int)INT64_MIN); + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str3, NULL); + zassert_ok(ret); + + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + + ret = at_token_int64_get(&tokens[1], &val); + zassert_ok(ret); + zassert_equal(val, INT64_MIN); +} + +ZTEST(at_parser, test_at_token_string_get_enomem) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = { 0 }; + size_t len = sizeof(buffer); + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + len = 1; + + ret = at_token_string_get(&token, buffer, &len); + zassert_equal(ret, -ENOMEM); +} + +ZTEST(at_parser, test_at_token_string_get_einval) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + struct at_token mod_token = {0}; + char buffer[32] = { 0 }; + size_t len = sizeof(buffer); + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_token_string_get(NULL, buffer, &len); + zassert_equal(ret, -EINVAL); + + ret = at_token_string_get(&token, NULL, &len); + zassert_equal(ret, -EINVAL); + + ret = at_token_string_get(&token, buffer, NULL); + zassert_equal(ret, -EINVAL); + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + memcpy(&mod_token, &token, sizeof(struct at_token)); + mod_token.type = AT_TOKEN_TYPE_INT; + + ret = at_token_string_get(&mod_token, buffer, &len); + zassert_equal(ret, -EINVAL); + + memcpy(&mod_token, &token, sizeof(struct at_token)); + mod_token.start = NULL; + + ret = at_token_string_get(&mod_token, buffer, &len); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_token_string_get) +{ + int ret; + struct at_token tokens[32] = {0}; + + const char *str1 = "+CGEV: ME PDN ACT 0"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 2); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_NOTIF); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_STRING); +} + +ZTEST(at_parser, test_at_token_valid_count_get_einval) +{ + int ret; + + ret = at_token_valid_count_get(NULL, 42); + zassert_equal(ret, -EINVAL); +} + +ZTEST(at_parser, test_at_token_valid_count_get_invalid) +{ + int ret; + int original_valid_count; + struct at_token tokens[32] = {0}; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + /* 1) Type is invalid. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + original_valid_count = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(original_valid_count, 4); + + /* Take an invalid value, for example outside the explicit types. + * In this case we choose the maximum possible value for the member `type` of + * `struct at_token`. + */ + int type = 15; + for (int i = AT_TOKEN_TYPE_INVALID; i < AT_TOKEN_TYPE_RESP; i++) { + zassert_not_equal(type, i); + } + + for (int i = 0; i < ARRAY_SIZE(tokens); i++) { + /* Override the type */ + tokens[i].type = type; + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_not_equal(ret, original_valid_count); + zassert_equal(ret, 0); + + /* 2) `start` pointer is invalid. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + original_valid_count = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(original_valid_count, 4); + + /* Override each token to have `start` point to an empty string. This is invalid because + * these tokens are expected to contain a non-empty string. + */ + for (int i = 0; i < ARRAY_SIZE(tokens); i++) { + tokens[i].start = ""; + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_not_equal(ret, original_valid_count); + zassert_equal(ret, 0); + + /* 3) Token length is zero. */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + original_valid_count = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(original_valid_count, 4); + + /* Override each token to have length zero. This is invalid because these tokens are + * expected to have non-zero length. + */ + for (int i = 0; i < ARRAY_SIZE(tokens); i++) { + tokens[i].len = 0; + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_not_equal(ret, original_valid_count); + zassert_equal(ret, 0); + + /* 4) Token length is larger than the length of the remaining string pointed by the + * token. + */ + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + original_valid_count = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(original_valid_count, 4); + + /* Override each token to have length larger than the length of the string they point to. */ + for (int i = 0; i < ARRAY_SIZE(tokens); i++) { + tokens[i].len = UINT16_MAX; + } + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_not_equal(ret, original_valid_count); + zassert_equal(ret, 0); +} + +ZTEST(at_parser, test_at_token_valid_count_get) +{ + int ret; + struct at_token tokens[32] = {0}; + + const char *str1 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 4); +} + +ZTEST(at_parser, test_at_cmd_type_get_invalid) +{ + enum at_token_type type; + + type = at_cmd_type_get(NULL); + zassert_equal(type, AT_TOKEN_TYPE_INVALID); + + type = at_cmd_type_get(""); + zassert_equal(type, AT_TOKEN_TYPE_INVALID); + + const char *str1 = "ABBA"; + + type = at_cmd_type_get(str1); + zassert_equal(type, AT_TOKEN_TYPE_INVALID); + + /* Not a valid command (note: it's a notification). */ + const char *str2 = "+NOTIF: 1,2,3\r\nOK\r\n"; + + type = at_cmd_type_get(str2); + zassert_equal(type, AT_TOKEN_TYPE_INVALID); +} + +ZTEST(at_parser, test_at_cmd_type_get_valid) +{ + enum at_token_type type; + + const char *str1 = "AT+CMD=?"; + + type = at_cmd_type_get(str1); + zassert_equal(type, AT_TOKEN_TYPE_CMD_TEST); + + const char *str2 = "AT+CMD?"; + + type = at_cmd_type_get(str2); + zassert_equal(type, AT_TOKEN_TYPE_CMD_READ); + + const char *str3 = "AT+CMD=1,2,3"; + + type = at_cmd_type_get(str3); + zassert_equal(type, AT_TOKEN_TYPE_CMD_SET); +} + +ZTEST(at_parser, test_at_token_type_empty_cmd_set) +{ + int ret; + struct at_token tokens[32] = {0}; + + const char *str1 = "AT+CMD=1,2,3,,"; + + ret = at_token_line_parse(tokens, ARRAY_SIZE(tokens), str1, NULL); + zassert_ok(ret); + + ret = at_token_valid_count_get(tokens, ARRAY_SIZE(tokens)); + zassert_equal(ret, 6); + zassert_equal(tokens[0].type, AT_TOKEN_TYPE_CMD_SET); + zassert_equal(tokens[1].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[2].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[3].type, AT_TOKEN_TYPE_INT); + zassert_equal(tokens[4].type, AT_TOKEN_TYPE_EMPTY); + zassert_equal(tokens[5].type, AT_TOKEN_TYPE_EMPTY); +} + +ZTEST(at_parser, test_cgmi) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT+CGMI"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGMI"), buffer_size); + zassert_mem_equal("AT+CGMI", buffer, buffer_size); + + const char *resp = "Nordic Semiconductor ASA\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("Nordic Semiconductor ASA"), buffer_size); + zassert_mem_equal("Nordic Semiconductor ASA", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_cgmm) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT+CGMM"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGMM"), buffer_size); + zassert_mem_equal("AT+CGMM", buffer, buffer_size); + + const char *resp = "nRF9160-SICA\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("nRF9160-SICA"), buffer_size); + zassert_mem_equal("nRF9160-SICA", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_cgmr) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT+CGMR"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGMR"), buffer_size); + zassert_mem_equal("AT+CGMR", buffer, buffer_size); + + const char *resp = "mfw_nrf9160_1.1.1\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("mfw_nrf9160_1.1.1"), buffer_size); + zassert_mem_equal("mfw_nrf9160_1.1.1", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_cgsn) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + int32_t val = 0; + + const char *cmd_1 = "AT+CGSN"; + + ret = at_parser_init(&parser, cmd_1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGSN"), buffer_size); + zassert_mem_equal("AT+CGSN", buffer, buffer_size); + + const char *resp_1 = "352656100367872\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp_1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret, "%d", ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("352656100367872"), buffer_size); + zassert_mem_equal("352656100367872", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); + + const char *cmd_2 = "AT+CGSN=1"; + + ret = at_parser_init(&parser, cmd_2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGSN"), buffer_size); + zassert_mem_equal("AT+CGSN", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + const char *resp_2 = "+CGSN: \"352656100367872\"\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp_2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("+CGSN"), buffer_size); + zassert_mem_equal("+CGSN", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("352656100367872"), buffer_size); + zassert_mem_equal("352656100367872", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); + + const char *cmd_3 = "AT+CGSN=?"; + + ret = at_parser_init(&parser, cmd_3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_TEST); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+CGSN"), buffer_size); + zassert_mem_equal("AT+CGSN", buffer, buffer_size); + + const char *resp_3 = "+CGSN: (0-3)\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp_3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("+CGSN"), buffer_size); + zassert_mem_equal("+CGSN", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("0-3"), buffer_size); + zassert_mem_equal("0-3", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_shortswver) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT%SHORTSWVER"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT%SHORTSWVER"), buffer_size); + zassert_mem_equal("AT%SHORTSWVER", buffer, buffer_size); + + const char *resp = "%SHORTSWVER: nrf9160_1.1.2\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("%SHORTSWVER"), buffer_size); + zassert_mem_equal("%SHORTSWVER", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("nrf9160_1.1.2"), buffer_size); + zassert_mem_equal("nrf9160_1.1.2", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_hwversion) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT%HWVERSION"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT%HWVERSION"), buffer_size); + zassert_mem_equal("AT%HWVERSION", buffer, buffer_size); + + const char *resp = "%HWVERSION: nRF9160 SICA B0A\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("%HWVERSION"), buffer_size); + zassert_mem_equal("%HWVERSION", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("nRF9160 SICA B0A"), buffer_size); + zassert_mem_equal("nRF9160 SICA B0A", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_xmodemuuid) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[64] = {0}; + size_t buffer_size = sizeof(buffer); + + const char *cmd = "AT%XMODEMUUID"; + + ret = at_parser_init(&parser, cmd); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT%XMODEMUUID"), buffer_size); + zassert_mem_equal("AT%XMODEMUUID", buffer, buffer_size); + + const char *resp = "%XMODEMUUID: 25c95751-efa4-40d4-8b4a-1dcaab81fac9\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("%XMODEMUUID"), buffer_size); + zassert_mem_equal("%XMODEMUUID", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("25c95751-efa4-40d4-8b4a-1dcaab81fac9"), buffer_size); + zassert_mem_equal("25c95751-efa4-40d4-8b4a-1dcaab81fac9", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_odis) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + + /* 1) Set command. */ + const char *cmd1 = "AT+ODIS=\"HDID01\",\"HDMAN01\",\"HDMOD01\",\"HDSW01\""; + + ret = at_parser_init(&parser, cmd1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+ODIS"), buffer_size); + zassert_mem_equal("AT+ODIS", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDID01"), buffer_size); + zassert_mem_equal("HDID01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDMAN01"), buffer_size); + zassert_mem_equal("HDMAN01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDMOD01"), buffer_size); + zassert_mem_equal("HDMOD01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDSW01"), buffer_size); + zassert_mem_equal("HDSW01", buffer, buffer_size); + + const char *resp1 = "\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); + + /* 2) Read command. */ + const char *cmd2 = "AT+ODIS?"; + + ret = at_parser_init(&parser, cmd2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_READ); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT+ODIS"), buffer_size); + zassert_mem_equal("AT+ODIS", buffer, buffer_size); + + const char *resp2 = "+ODIS: \"HDMAN01\",\"HDMOD01\",\"HDSW01\"\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("+ODIS"), buffer_size); + zassert_mem_equal("+ODIS", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDMAN01"), buffer_size); + zassert_mem_equal("HDMAN01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDMOD01"), buffer_size); + zassert_mem_equal("HDMOD01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("HDSW01"), buffer_size); + zassert_mem_equal("HDSW01", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_cind) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + + /* 3) Test command. */ + const char *resp3 = "+CIND: (\"service\",(0,1)),(\"roam\",(0,1)),(\"message\",(0,1))\r\n" + "OK\r\n"; + + ret = at_parser_init(&parser, resp3); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); +} + +ZTEST(at_parser, test_xbandlock) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[128] = {0}; + size_t buffer_size = sizeof(buffer); + + /* 2) Read command. */ + const char *resp2 = "%XBANDLOCK: \"\",\"000000000000000000000000000000000000000000000000000" + "0000000000000000000000001000000001001\"\r\nOK\r\n"; + + ret = at_parser_init(&parser, resp2); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("%XBANDLOCK"), buffer_size); + zassert_mem_equal("%XBANDLOCK", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen(""), buffer_size, "%d", buffer_size); + zassert_mem_equal("", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_QUOTED_STRING); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret, "%d", ret); + zassert_equal(strlen("000000000000000000000000000000000000000000000000000000000000000000000" + "0000001000000001001"), buffer_size); + zassert_mem_equal("000000000000000000000000000000000000000000000000000000000000000000000000" + "0001000000001001", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\nOK\r\n"), buffer_size); + zassert_mem_equal("\r\nOK\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_xt3412) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + char buffer[32] = {0}; + size_t buffer_size = sizeof(buffer); + int32_t val = 0; + + /* 1) Set command. */ + const char *cmd1 = "AT%XT3412=1,2000,30000"; + + ret = at_parser_init(&parser, cmd1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_CMD_SET); + + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("AT%XT3412"), buffer_size); + zassert_mem_equal("AT%XT3412", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 2000); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 30000); + + const char *resp1 = "%XT3412: 1200000\r\n"; + + ret = at_parser_init(&parser, resp1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("%XT3412"), buffer_size); + zassert_mem_equal("%XT3412", buffer, buffer_size); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_INT); + + ret = at_token_int32_get(&token, &val); + zassert_ok(ret); + zassert_equal(val, 1200000); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); + + buffer_size = sizeof(buffer); + ret = at_token_string_get(&token, buffer, &buffer_size); + zassert_ok(ret); + zassert_equal(strlen("\r\n"), buffer_size); + zassert_mem_equal("\r\n", buffer, buffer_size); +} + +ZTEST(at_parser, test_array) +{ + int ret; + struct at_parser parser = {0}; + struct at_token token = {0}; + + const char *str1 = "+NOTIF: ((\"\"),1)\r\nOK\r\n"; + + ret = at_parser_init(&parser, str1); + zassert_ok(ret); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_NOTIF); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_ARRAY); + + ret = at_parser_tok(&parser, &token); + zassert_ok(ret); + zassert_equal(token.type, AT_TOKEN_TYPE_RESP); +} + +ZTEST_SUITE(at_parser, NULL, NULL, test_params_before, test_params_after, NULL); diff --git a/tests/lib/at_parser/testcase.yaml b/tests/lib/at_parser/testcase.yaml new file mode 100644 index 000000000000..67efe99956c3 --- /dev/null +++ b/tests/lib/at_parser/testcase.yaml @@ -0,0 +1,7 @@ +tests: + at_parser.at_parser: + platform_allow: qemu_cortex_m3 native_posix + integration_platforms: + - qemu_cortex_m3 + - native_posix + tags: at_parser