|  | /* | 
|  | * Copyright (c) 2022 Trackunit Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #undef _POSIX_C_SOURCE | 
|  | #define _POSIX_C_SOURCE 200809L | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL); | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <zephyr/modem/chat.h> | 
|  |  | 
|  | #include "modem_workqueue.h" | 
|  |  | 
|  | const struct modem_chat_match modem_chat_any_match = MODEM_CHAT_MATCH("", "", NULL); | 
|  | const struct modem_chat_match modem_chat_empty_matches[0]; | 
|  | const struct modem_chat_script_chat modem_chat_empty_script_chats[0]; | 
|  |  | 
|  | #define MODEM_CHAT_MATCHES_INDEX_RESPONSE (0) | 
|  | #define MODEM_CHAT_MATCHES_INDEX_ABORT	  (1) | 
|  | #define MODEM_CHAT_MATCHES_INDEX_UNSOL	  (2) | 
|  |  | 
|  | #define MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT (0) | 
|  |  | 
|  | #if defined(CONFIG_LOG) && (CONFIG_MODEM_MODULES_LOG_LEVEL == LOG_LEVEL_DBG) | 
|  |  | 
|  | static char log_buffer[CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE]; | 
|  |  | 
|  | static void modem_chat_log_received_command(struct modem_chat *chat) | 
|  | { | 
|  | uint16_t log_buffer_pos = 0; | 
|  | uint16_t argv_len; | 
|  |  | 
|  | for (uint16_t i = 0; i < chat->argc; i++) { | 
|  | argv_len = (uint16_t)strlen(chat->argv[i]); | 
|  |  | 
|  | /* Validate argument fits in log buffer including termination */ | 
|  | if (sizeof(log_buffer) < (log_buffer_pos + argv_len + 1)) { | 
|  | LOG_WRN("log buffer overrun"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Copy argument and append space */ | 
|  | memcpy(&log_buffer[log_buffer_pos], chat->argv[i], argv_len); | 
|  | log_buffer_pos += argv_len; | 
|  | log_buffer[log_buffer_pos] = ' '; | 
|  | log_buffer_pos++; | 
|  | } | 
|  |  | 
|  | /* Terminate line after last argument, overwriting trailing space */ | 
|  | log_buffer_pos = log_buffer_pos == 0 ? log_buffer_pos : log_buffer_pos - 1; | 
|  | log_buffer[log_buffer_pos] = '\0'; | 
|  |  | 
|  | LOG_DBG("%s", log_buffer); | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | static void modem_chat_log_received_command(struct modem_chat *chat) | 
|  | { | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | static void modem_chat_script_stop(struct modem_chat *chat, enum modem_chat_script_result result) | 
|  | { | 
|  | if ((chat == NULL) || (chat->script == NULL)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Handle result */ | 
|  | if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) { | 
|  | LOG_DBG("%s: complete", chat->script->name); | 
|  | } else if (result == MODEM_CHAT_SCRIPT_RESULT_ABORT) { | 
|  | LOG_WRN("%s: aborted", chat->script->name); | 
|  | } else { | 
|  | LOG_WRN("%s: timed out", chat->script->name); | 
|  | } | 
|  |  | 
|  | /* Call back with result */ | 
|  | if (chat->script->callback != NULL) { | 
|  | chat->script->callback(chat, result, chat->user_data); | 
|  | } | 
|  |  | 
|  | /* Clear parse_match in case it is stored in the script being stopped */ | 
|  | if ((chat->parse_match != NULL) && | 
|  | ((chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_ABORT) || | 
|  | (chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_RESPONSE))) { | 
|  | chat->parse_match = NULL; | 
|  | chat->parse_match_len = 0; | 
|  | } | 
|  |  | 
|  | /* Clear reference to script */ | 
|  | chat->script = NULL; | 
|  |  | 
|  | /* Clear response and abort commands */ | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0; | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0; | 
|  |  | 
|  | /* Cancel work */ | 
|  | k_work_cancel_delayable(&chat->script_timeout_work); | 
|  | k_work_cancel(&chat->script_send_work); | 
|  | k_work_cancel_delayable(&chat->script_send_timeout_work); | 
|  |  | 
|  | /* Clear script running state */ | 
|  | atomic_clear_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT); | 
|  |  | 
|  | /* Store result of script for script stoppted indication */ | 
|  | chat->script_result = result; | 
|  |  | 
|  | /* Indicate script stopped */ | 
|  | k_sem_give(&chat->script_stopped_sem); | 
|  | } | 
|  |  | 
|  | static void modem_chat_set_script_send_state(struct modem_chat *chat, | 
|  | enum modem_chat_script_send_state state) | 
|  | { | 
|  | chat->script_send_pos = 0; | 
|  | chat->script_send_state = state; | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_send(struct modem_chat *chat) | 
|  | { | 
|  | modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST); | 
|  | modem_work_submit(&chat->script_send_work); | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_set_response_matches(struct modem_chat *chat) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat = | 
|  | &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches_size; | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_clear_response_matches(struct modem_chat *chat) | 
|  | { | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_script_chat_has_request(struct modem_chat *chat) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat = | 
|  | &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | return script_chat->request_size > 0; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_script_chat_has_matches(struct modem_chat *chat) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat = | 
|  | &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | return script_chat->response_matches_size > 0; | 
|  | } | 
|  |  | 
|  | static uint16_t modem_chat_script_chat_get_send_timeout(struct modem_chat *chat) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat = | 
|  | &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | return script_chat->timeout; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_script_chat_has_send_timeout(struct modem_chat *chat) | 
|  | { | 
|  | return modem_chat_script_chat_get_send_timeout(chat) > 0; | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_chat_schedule_send_timeout(struct modem_chat *chat) | 
|  | { | 
|  | uint16_t timeout = modem_chat_script_chat_get_send_timeout(chat); | 
|  |  | 
|  | modem_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout)); | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_next(struct modem_chat *chat, bool initial) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat; | 
|  |  | 
|  | /* Advance iterator if not initial */ | 
|  | if (initial == true) { | 
|  | /* Reset iterator */ | 
|  | chat->script_chat_it = 0; | 
|  | } else { | 
|  | /* Advance iterator */ | 
|  | chat->script_chat_it++; | 
|  | } | 
|  |  | 
|  | /* Check if end of script reached */ | 
|  | if (chat->script_chat_it == chat->script->script_chats_size) { | 
|  | modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | LOG_DBG("%s: step: %u", chat->script->name, chat->script_chat_it); | 
|  |  | 
|  | script_chat = &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | /* Continue script */ | 
|  | if (modem_chat_script_chat_has_request(chat)) { | 
|  | LOG_DBG("sending: %.*s", script_chat->request_size, script_chat->request); | 
|  | modem_chat_script_clear_response_matches(chat); | 
|  | modem_chat_script_send(chat); | 
|  | } else if (modem_chat_script_chat_has_matches(chat)) { | 
|  | modem_chat_script_set_response_matches(chat); | 
|  | } else { | 
|  | modem_chat_script_chat_schedule_send_timeout(chat); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_start(struct modem_chat *chat, const struct modem_chat_script *script) | 
|  | { | 
|  | /* Save script */ | 
|  | chat->script = script; | 
|  |  | 
|  | /* Set abort matches */ | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches_size; | 
|  |  | 
|  | LOG_DBG("running script: %s", chat->script->name); | 
|  |  | 
|  | /* Set first script command */ | 
|  | modem_chat_script_next(chat, true); | 
|  |  | 
|  | /* Start timeout work if script started */ | 
|  | if (chat->script != NULL) { | 
|  | modem_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_run_handler(struct k_work *item) | 
|  | { | 
|  | struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_run_work); | 
|  |  | 
|  | /* Start script */ | 
|  | modem_chat_script_start(chat, chat->pending_script); | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_timeout_handler(struct k_work *item) | 
|  | { | 
|  | struct k_work_delayable *dwork = k_work_delayable_from_work(item); | 
|  | struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_timeout_work); | 
|  |  | 
|  | /* Abort script */ | 
|  | modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_TIMEOUT); | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_abort_handler(struct k_work *item) | 
|  | { | 
|  | struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_abort_work); | 
|  |  | 
|  | /* Validate script is currently running */ | 
|  | if (chat->script == NULL) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Abort script */ | 
|  | modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT); | 
|  | } | 
|  |  | 
|  | /* Returns true when request part has been sent */ | 
|  | static bool modem_chat_send_script_request_part(struct modem_chat *chat) | 
|  | { | 
|  | const struct modem_chat_script_chat *script_chat = | 
|  | &chat->script->script_chats[chat->script_chat_it]; | 
|  |  | 
|  | uint8_t *request_part; | 
|  | uint16_t request_size; | 
|  | uint16_t request_part_size; | 
|  | int ret; | 
|  |  | 
|  | switch (chat->script_send_state) { | 
|  | case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST: | 
|  | request_part = (uint8_t *)(&script_chat->request[chat->script_send_pos]); | 
|  | request_size = script_chat->request_size; | 
|  | break; | 
|  |  | 
|  | case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER: | 
|  | request_part = (uint8_t *)(&chat->delimiter[chat->script_send_pos]); | 
|  | request_size = chat->delimiter_size; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return false; | 
|  | } | 
|  |  | 
|  | request_part_size = request_size - chat->script_send_pos; | 
|  | ret = modem_pipe_transmit(chat->pipe, request_part, request_part_size); | 
|  | if (ret < 1) { | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Failed to %s %u bytes. (%d)", "transmit", request_part_size, ret); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | chat->script_send_pos += (uint16_t)ret; | 
|  |  | 
|  | /* Return true if all data was sent */ | 
|  | return request_size <= chat->script_send_pos; | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_send_handler(struct k_work *item) | 
|  | { | 
|  | struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_send_work); | 
|  |  | 
|  | if (chat->script == NULL) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | switch (chat->script_send_state) { | 
|  | case MODEM_CHAT_SCRIPT_SEND_STATE_IDLE: | 
|  | return; | 
|  |  | 
|  | case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST: | 
|  | if (!modem_chat_send_script_request_part(chat)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER); | 
|  | __fallthrough; | 
|  |  | 
|  | case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER: | 
|  | if (!modem_chat_send_script_request_part(chat)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_IDLE); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (modem_chat_script_chat_has_matches(chat)) { | 
|  | modem_chat_script_set_response_matches(chat); | 
|  | } else if (modem_chat_script_chat_has_send_timeout(chat)) { | 
|  | modem_chat_script_chat_schedule_send_timeout(chat); | 
|  | } else { | 
|  | modem_chat_script_next(chat, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void modem_chat_script_send_timeout_handler(struct k_work *item) | 
|  | { | 
|  | struct k_work_delayable *dwork = k_work_delayable_from_work(item); | 
|  | struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_send_timeout_work); | 
|  |  | 
|  | /* Validate script is currently running */ | 
|  | if (chat->script == NULL) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | modem_chat_script_next(chat, false); | 
|  | } | 
|  |  | 
|  | #if CONFIG_MODEM_STATS | 
|  | static uint32_t get_receive_buf_length(struct modem_chat *chat) | 
|  | { | 
|  | return chat->receive_buf_len; | 
|  | } | 
|  |  | 
|  | static void advertise_receive_buf_stats(struct modem_chat *chat) | 
|  | { | 
|  | uint32_t length; | 
|  |  | 
|  | length = get_receive_buf_length(chat); | 
|  | modem_stats_buffer_advertise_length(&chat->receive_buf_stats, length); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void modem_chat_parse_reset(struct modem_chat *chat) | 
|  | { | 
|  | #if CONFIG_MODEM_STATS | 
|  | advertise_receive_buf_stats(chat); | 
|  | #endif | 
|  |  | 
|  | /* Reset parameters used for parsing */ | 
|  | chat->receive_buf_len = 0; | 
|  | chat->delimiter_match_len = 0; | 
|  | chat->argc = 0; | 
|  | chat->parse_match = NULL; | 
|  | } | 
|  |  | 
|  | /* Exact match is stored at end of receive buffer */ | 
|  | static void modem_chat_parse_save_match(struct modem_chat *chat) | 
|  | { | 
|  | uint8_t *argv; | 
|  |  | 
|  | /* Store length of match including NULL to avoid overwriting it if buffer overruns */ | 
|  | chat->parse_match_len = chat->receive_buf_len + 1; | 
|  |  | 
|  | /* Copy match to end of receive buffer */ | 
|  | argv = &chat->receive_buf[chat->receive_buf_size - chat->parse_match_len]; | 
|  |  | 
|  | /* Copy match to end of receive buffer (excluding NULL) */ | 
|  | memcpy(argv, &chat->receive_buf[0], chat->parse_match_len - 1); | 
|  |  | 
|  | /* Save match */ | 
|  | chat->argv[chat->argc] = argv; | 
|  |  | 
|  | /* Terminate match */ | 
|  | chat->receive_buf[chat->receive_buf_size - 1] = '\0'; | 
|  |  | 
|  | /* Increment argument count */ | 
|  | chat->argc++; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_match_matches_received(struct modem_chat *chat, | 
|  | const struct modem_chat_match *match) | 
|  | { | 
|  | for (uint16_t i = 0; i < match->match_size; i++) { | 
|  | if ((match->match[i] == chat->receive_buf[i]) || | 
|  | (match->wildcards == true && match->match[i] == '?')) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_parse_find_match(struct modem_chat *chat) | 
|  | { | 
|  | /* Find in all matches types */ | 
|  | for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) { | 
|  | /* Find in all matches of matches type */ | 
|  | for (uint16_t u = 0; u < chat->matches_size[i]; u++) { | 
|  | /* Validate match size matches received data length */ | 
|  | if (chat->matches[i][u].match_size != chat->receive_buf_len) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Validate match */ | 
|  | if (modem_chat_match_matches_received(chat, &chat->matches[i][u]) == | 
|  | false) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Complete match found */ | 
|  | chat->parse_match = &chat->matches[i][u]; | 
|  | chat->parse_match_type = i; | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_parse_is_separator(struct modem_chat *chat) | 
|  | { | 
|  | for (uint16_t i = 0; i < chat->parse_match->separators_size; i++) { | 
|  | if ((chat->parse_match->separators[i]) == | 
|  | (chat->receive_buf[chat->receive_buf_len - 1])) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_parse_end_del_start(struct modem_chat *chat) | 
|  | { | 
|  | for (uint8_t i = 0; i < chat->delimiter_size; i++) { | 
|  | if (chat->receive_buf[chat->receive_buf_len - 1] == chat->delimiter[i]) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_parse_end_del_complete(struct modem_chat *chat) | 
|  | { | 
|  | /* Validate length of end delimiter */ | 
|  | if (chat->receive_buf_len < chat->delimiter_size) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Compare end delimiter with receive buffer content */ | 
|  | return (memcmp(&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size], | 
|  | chat->delimiter, chat->delimiter_size) == 0) | 
|  | ? true | 
|  | : false; | 
|  | } | 
|  |  | 
|  | static void modem_chat_on_command_received_unsol(struct modem_chat *chat) | 
|  | { | 
|  | /* Callback */ | 
|  | if (chat->parse_match->callback != NULL) { | 
|  | chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void modem_chat_on_command_received_abort(struct modem_chat *chat) | 
|  | { | 
|  | /* Callback */ | 
|  | if (chat->parse_match->callback != NULL) { | 
|  | chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data); | 
|  | } | 
|  |  | 
|  | /* Abort script */ | 
|  | modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT); | 
|  | } | 
|  |  | 
|  | static void modem_chat_on_command_received_resp(struct modem_chat *chat) | 
|  | { | 
|  | /* Callback */ | 
|  | if (chat->parse_match->callback != NULL) { | 
|  | chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data); | 
|  | } | 
|  |  | 
|  | /* Validate response command is not partial */ | 
|  | if (chat->parse_match->partial) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Advance script */ | 
|  | modem_chat_script_next(chat, false); | 
|  | } | 
|  |  | 
|  | static bool modem_chat_parse_find_catch_all_match(struct modem_chat *chat) | 
|  | { | 
|  | /* Find in all matches types */ | 
|  | for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) { | 
|  | /* Find in all matches of matches type */ | 
|  | for (uint16_t u = 0; u < chat->matches_size[i]; u++) { | 
|  | /* Validate match config is matching previous bytes */ | 
|  | if (chat->matches[i][u].match_size == 0) { | 
|  | chat->parse_match = &chat->matches[i][u]; | 
|  | chat->parse_match_type = i; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void modem_chat_on_command_received(struct modem_chat *chat) | 
|  | { | 
|  | modem_chat_log_received_command(chat); | 
|  |  | 
|  | switch (chat->parse_match_type) { | 
|  | case MODEM_CHAT_MATCHES_INDEX_UNSOL: | 
|  | modem_chat_on_command_received_unsol(chat); | 
|  | break; | 
|  |  | 
|  | case MODEM_CHAT_MATCHES_INDEX_ABORT: | 
|  | modem_chat_on_command_received_abort(chat); | 
|  | break; | 
|  |  | 
|  | case MODEM_CHAT_MATCHES_INDEX_RESPONSE: | 
|  | modem_chat_on_command_received_resp(chat); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void modem_chat_on_unknown_command_received(struct modem_chat *chat) | 
|  | { | 
|  | /* Terminate received command */ | 
|  | chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0'; | 
|  |  | 
|  | /* Try to find catch all match */ | 
|  | if (modem_chat_parse_find_catch_all_match(chat) == false) { | 
|  | LOG_DBG("%s", chat->receive_buf); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Parse command */ | 
|  | chat->argv[0] = ""; | 
|  | chat->argv[1] = chat->receive_buf; | 
|  | chat->argc = 2; | 
|  |  | 
|  | modem_chat_on_command_received(chat); | 
|  | } | 
|  |  | 
|  | static void modem_chat_process_byte(struct modem_chat *chat, uint8_t byte) | 
|  | { | 
|  | /* Validate receive buffer not overrun */ | 
|  | if (chat->receive_buf_size == chat->receive_buf_len) { | 
|  | LOG_WRN("receive buffer overrun"); | 
|  | modem_chat_parse_reset(chat); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Validate argv buffer not overrun */ | 
|  | if (chat->argc == chat->argv_size) { | 
|  | LOG_WRN("argv buffer overrun"); | 
|  | modem_chat_parse_reset(chat); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Copy byte to receive buffer */ | 
|  | chat->receive_buf[chat->receive_buf_len] = byte; | 
|  | chat->receive_buf_len++; | 
|  |  | 
|  | /* Validate end delimiter not complete */ | 
|  | if (modem_chat_parse_end_del_complete(chat) == true) { | 
|  | /* Filter out empty lines */ | 
|  | if (chat->receive_buf_len == chat->delimiter_size) { | 
|  | /* Reset parser */ | 
|  | modem_chat_parse_reset(chat); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Check if match exists */ | 
|  | if (chat->parse_match == NULL) { | 
|  | /* Handle unknown command */ | 
|  | modem_chat_on_unknown_command_received(chat); | 
|  |  | 
|  | /* Reset parser */ | 
|  | modem_chat_parse_reset(chat); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Check if trailing argument exists */ | 
|  | if (chat->parse_arg_len > 0) { | 
|  | chat->argv[chat->argc] = | 
|  | &chat->receive_buf[chat->receive_buf_len - chat->delimiter_size - | 
|  | chat->parse_arg_len]; | 
|  | chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0'; | 
|  | chat->argc++; | 
|  | } | 
|  |  | 
|  | /* Handle received command */ | 
|  | modem_chat_on_command_received(chat); | 
|  |  | 
|  | /* Reset parser */ | 
|  | modem_chat_parse_reset(chat); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Validate end delimiter not started */ | 
|  | if (modem_chat_parse_end_del_start(chat) == true) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Find matching command if missing */ | 
|  | if (chat->parse_match == NULL) { | 
|  | /* Find matching command */ | 
|  | if (modem_chat_parse_find_match(chat) == false) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Save match */ | 
|  | modem_chat_parse_save_match(chat); | 
|  |  | 
|  | /* Prepare argument parser */ | 
|  | chat->parse_arg_len = 0; | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Check if separator reached */ | 
|  | if (modem_chat_parse_is_separator(chat) == true) { | 
|  | /* Check if argument is empty */ | 
|  | if (chat->parse_arg_len == 0) { | 
|  | /* Save empty argument */ | 
|  | chat->argv[chat->argc] = ""; | 
|  | } else { | 
|  | /* Save pointer to start of argument */ | 
|  | chat->argv[chat->argc] = | 
|  | &chat->receive_buf[chat->receive_buf_len - chat->parse_arg_len - 1]; | 
|  |  | 
|  | /* Replace separator with string terminator */ | 
|  | chat->receive_buf[chat->receive_buf_len - 1] = '\0'; | 
|  | } | 
|  |  | 
|  | /* Increment argument count */ | 
|  | chat->argc++; | 
|  |  | 
|  | /* Reset parse argument length */ | 
|  | chat->parse_arg_len = 0; | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Increment argument length */ | 
|  | chat->parse_arg_len++; | 
|  | } | 
|  |  | 
|  | static bool modem_chat_discard_byte(struct modem_chat *chat, uint8_t byte) | 
|  | { | 
|  | for (uint8_t i = 0; i < chat->filter_size; i++) { | 
|  | if (byte == chat->filter[i]) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Process chunk of received bytes */ | 
|  | static void modem_chat_process_bytes(struct modem_chat *chat) | 
|  | { | 
|  | for (uint16_t i = 0; i < chat->work_buf_len; i++) { | 
|  | if (modem_chat_discard_byte(chat, chat->work_buf[i])) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | modem_chat_process_byte(chat, chat->work_buf[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if CONFIG_MODEM_STATS | 
|  | static uint32_t get_work_buf_length(struct modem_chat *chat) | 
|  | { | 
|  | return chat->work_buf_len; | 
|  | } | 
|  |  | 
|  | static void advertise_work_buf_stats(struct modem_chat *chat) | 
|  | { | 
|  | uint32_t length; | 
|  |  | 
|  | length = get_work_buf_length(chat); | 
|  | modem_stats_buffer_advertise_length(&chat->work_buf_stats, length); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void modem_chat_process_handler(struct k_work *item) | 
|  | { | 
|  | struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, receive_work); | 
|  | int ret; | 
|  |  | 
|  | /* Fill work buffer */ | 
|  | ret = modem_pipe_receive(chat->pipe, chat->work_buf, sizeof(chat->work_buf)); | 
|  | if (ret < 1) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Save received data length */ | 
|  | chat->work_buf_len = (size_t)ret; | 
|  |  | 
|  | #if CONFIG_MODEM_STATS | 
|  | advertise_work_buf_stats(chat); | 
|  | #endif | 
|  |  | 
|  | /* Process data */ | 
|  | modem_chat_process_bytes(chat); | 
|  | modem_work_submit(&chat->receive_work); | 
|  | } | 
|  |  | 
|  | static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, | 
|  | void *user_data) | 
|  | { | 
|  | struct modem_chat *chat = (struct modem_chat *)user_data; | 
|  |  | 
|  | switch (event) { | 
|  | case MODEM_PIPE_EVENT_RECEIVE_READY: | 
|  | modem_work_submit(&chat->receive_work); | 
|  | break; | 
|  |  | 
|  | case MODEM_PIPE_EVENT_TRANSMIT_IDLE: | 
|  | modem_work_submit(&chat->script_send_work); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool modem_chat_validate_array(const void *array, size_t size) | 
|  | { | 
|  | return ((array == NULL) && (size == 0)) || | 
|  | ((array != NULL) && (size > 0)); | 
|  | } | 
|  |  | 
|  | #if CONFIG_MODEM_STATS | 
|  | static uint32_t get_receive_buf_size(struct modem_chat *chat) | 
|  | { | 
|  | return chat->receive_buf_size; | 
|  | } | 
|  |  | 
|  | static uint32_t get_work_buf_size(struct modem_chat *chat) | 
|  | { | 
|  | return sizeof(chat->work_buf); | 
|  | } | 
|  |  | 
|  | static void init_buf_stats(struct modem_chat *chat) | 
|  | { | 
|  | uint32_t size; | 
|  |  | 
|  | size = get_receive_buf_size(chat); | 
|  | modem_stats_buffer_init(&chat->receive_buf_stats, "chat_rx", size); | 
|  | size = get_work_buf_size(chat); | 
|  | modem_stats_buffer_init(&chat->work_buf_stats, "chat_work", size); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config) | 
|  | { | 
|  | __ASSERT_NO_MSG(chat != NULL); | 
|  | __ASSERT_NO_MSG(config != NULL); | 
|  | __ASSERT_NO_MSG(config->receive_buf != NULL); | 
|  | __ASSERT_NO_MSG(config->receive_buf_size > 0); | 
|  | __ASSERT_NO_MSG(config->argv != NULL); | 
|  | __ASSERT_NO_MSG(config->argv_size > 0); | 
|  | __ASSERT_NO_MSG(config->delimiter != NULL); | 
|  | __ASSERT_NO_MSG(config->delimiter_size > 0); | 
|  | __ASSERT_NO_MSG(!((config->filter == NULL) && (config->filter_size > 0))); | 
|  | __ASSERT_NO_MSG(!((config->unsol_matches == NULL) && (config->unsol_matches_size > 0))); | 
|  |  | 
|  | memset(chat, 0x00, sizeof(*chat)); | 
|  | chat->pipe = NULL; | 
|  | chat->user_data = config->user_data; | 
|  | chat->receive_buf = config->receive_buf; | 
|  | chat->receive_buf_size = config->receive_buf_size; | 
|  | chat->argv = config->argv; | 
|  | chat->argv_size = config->argv_size; | 
|  | chat->delimiter = config->delimiter; | 
|  | chat->delimiter_size = config->delimiter_size; | 
|  | chat->filter = config->filter; | 
|  | chat->filter_size = config->filter_size; | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches_size; | 
|  | atomic_set(&chat->script_state, 0); | 
|  | k_sem_init(&chat->script_stopped_sem, 0, 1); | 
|  | k_work_init(&chat->receive_work, modem_chat_process_handler); | 
|  | k_work_init(&chat->script_run_work, modem_chat_script_run_handler); | 
|  | k_work_init_delayable(&chat->script_timeout_work, modem_chat_script_timeout_handler); | 
|  | k_work_init(&chat->script_abort_work, modem_chat_script_abort_handler); | 
|  | k_work_init(&chat->script_send_work, modem_chat_script_send_handler); | 
|  | k_work_init_delayable(&chat->script_send_timeout_work, | 
|  | modem_chat_script_send_timeout_handler); | 
|  |  | 
|  | #if CONFIG_MODEM_STATS | 
|  | init_buf_stats(chat); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe) | 
|  | { | 
|  | chat->pipe = pipe; | 
|  | modem_chat_parse_reset(chat); | 
|  | modem_pipe_attach(chat->pipe, modem_chat_pipe_callback, chat); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | bool modem_chat_is_running(struct modem_chat *chat) | 
|  | { | 
|  | return atomic_test_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT); | 
|  | } | 
|  |  | 
|  | int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script) | 
|  | { | 
|  | bool script_is_running; | 
|  |  | 
|  | if (chat->pipe == NULL) { | 
|  | return -EPERM; | 
|  | } | 
|  |  | 
|  | /* Validate script */ | 
|  | if (script->script_chats == NULL || | 
|  | (script->script_chats_size == 0 | 
|  | && script->script_chats != modem_chat_empty_script_chats) || | 
|  | (script->abort_matches_size == 0 | 
|  | && script->abort_matches != NULL | 
|  | && script->abort_matches != modem_chat_empty_matches)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Validate script commands */ | 
|  | for (uint16_t i = 0; i < script->script_chats_size; i++) { | 
|  | if ((script->script_chats[i].request_size == 0) && | 
|  | (script->script_chats[i].response_matches_size == 0) && | 
|  | (script->script_chats[i].timeout == 0)) { | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | script_is_running = | 
|  | atomic_test_and_set_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT); | 
|  |  | 
|  | if (script_is_running == true) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | k_sem_reset(&chat->script_stopped_sem); | 
|  |  | 
|  | chat->pending_script = script; | 
|  | modem_work_submit(&chat->script_run_work); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = modem_chat_run_script_async(chat, script); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = k_sem_take(&chat->script_stopped_sem, K_FOREVER); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return (chat->script_result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) ? 0 : -EAGAIN; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_abort(struct modem_chat *chat) | 
|  | { | 
|  | modem_work_submit(&chat->script_abort_work); | 
|  | } | 
|  |  | 
|  | void modem_chat_release(struct modem_chat *chat) | 
|  | { | 
|  | struct k_work_sync sync; | 
|  |  | 
|  | if (chat->pipe) { | 
|  | modem_pipe_release(chat->pipe); | 
|  | } | 
|  |  | 
|  | k_work_cancel_sync(&chat->script_run_work, &sync); | 
|  | k_work_cancel_sync(&chat->script_abort_work, &sync); | 
|  | k_work_cancel_sync(&chat->receive_work, &sync); | 
|  | k_work_cancel_sync(&chat->script_send_work, &sync); | 
|  |  | 
|  | chat->pipe = NULL; | 
|  | chat->receive_buf_len = 0; | 
|  | chat->work_buf_len = 0; | 
|  | chat->argc = 0; | 
|  | chat->script = NULL; | 
|  | chat->script_chat_it = 0; | 
|  | atomic_set(&chat->script_state, 0); | 
|  | chat->script_result = MODEM_CHAT_SCRIPT_RESULT_ABORT; | 
|  | k_sem_reset(&chat->script_stopped_sem); | 
|  | chat->script_send_state = MODEM_CHAT_SCRIPT_SEND_STATE_IDLE; | 
|  | chat->script_send_pos = 0; | 
|  | chat->parse_match = NULL; | 
|  | chat->parse_match_len = 0; | 
|  | chat->parse_arg_len = 0; | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0; | 
|  | chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL; | 
|  | chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0; | 
|  | } | 
|  |  | 
|  | void modem_chat_match_init(struct modem_chat_match *chat_match) | 
|  | { | 
|  | memset(chat_match, 0, sizeof(struct modem_chat_match)); | 
|  | } | 
|  |  | 
|  | int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match) | 
|  | { | 
|  | size_t size; | 
|  |  | 
|  | size = strnlen(match, UINT8_MAX + 1); | 
|  |  | 
|  | if (size == (UINT8_MAX + 1)) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | chat_match->match = match; | 
|  | chat_match->match_size = (uint8_t)size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators) | 
|  | { | 
|  | size_t size; | 
|  |  | 
|  | size = strnlen(separators, UINT8_MAX + 1); | 
|  |  | 
|  | if (size == (UINT8_MAX + 1)) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | chat_match->separators = separators; | 
|  | chat_match->separators_size = (uint8_t)size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void modem_chat_match_set_callback(struct modem_chat_match *match, | 
|  | modem_chat_match_callback callback) | 
|  | { | 
|  | match->callback = callback; | 
|  | } | 
|  |  | 
|  | void modem_chat_match_set_partial(struct modem_chat_match *match, bool partial) | 
|  | { | 
|  | match->partial = partial; | 
|  | } | 
|  |  | 
|  | void modem_chat_match_enable_wildcards(struct modem_chat_match *match, bool enable) | 
|  | { | 
|  | match->wildcards = enable; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat) | 
|  | { | 
|  | memset(script_chat, 0, sizeof(struct modem_chat_script_chat)); | 
|  | } | 
|  |  | 
|  | int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat, | 
|  | const char *request) | 
|  | { | 
|  | size_t size; | 
|  |  | 
|  | size = strnlen(request, UINT16_MAX + 1); | 
|  |  | 
|  | if (size == (UINT16_MAX + 1)) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | script_chat->request = request; | 
|  | script_chat->request_size = (uint16_t)size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat, | 
|  | const struct modem_chat_match *response_matches, | 
|  | uint16_t response_matches_size) | 
|  | { | 
|  | if (!modem_chat_validate_array(response_matches, response_matches_size)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | script_chat->response_matches = response_matches; | 
|  | script_chat->response_matches_size = response_matches_size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat, | 
|  | uint16_t timeout) | 
|  | { | 
|  | script_chat->timeout = timeout; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_init(struct modem_chat_script *script) | 
|  | { | 
|  | memset(script, 0, sizeof(struct modem_chat_script)); | 
|  | script->name = ""; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_set_name(struct modem_chat_script *script, const char *name) | 
|  | { | 
|  | script->name = name; | 
|  | } | 
|  |  | 
|  | int modem_chat_script_set_script_chats(struct modem_chat_script *script, | 
|  | const struct modem_chat_script_chat *script_chats, | 
|  | uint16_t script_chats_size) | 
|  | { | 
|  | if (!modem_chat_validate_array(script_chats, script_chats_size)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | script->script_chats = script_chats; | 
|  | script->script_chats_size = script_chats_size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int modem_chat_script_set_abort_matches(struct modem_chat_script *script, | 
|  | const struct modem_chat_match *abort_matches, | 
|  | uint16_t abort_matches_size) | 
|  | { | 
|  | if (!modem_chat_validate_array(abort_matches, abort_matches_size)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | script->abort_matches = abort_matches; | 
|  | script->abort_matches_size = abort_matches_size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_set_callback(struct modem_chat_script *script, | 
|  | modem_chat_script_callback callback) | 
|  | { | 
|  | script->callback = callback; | 
|  | } | 
|  |  | 
|  | void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s) | 
|  | { | 
|  | script->timeout = timeout_s; | 
|  | } |