1 /*
2  * Copyright (c) 2022 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL);
9 
10 #include <zephyr/kernel.h>
11 #include <string.h>
12 
13 #include <zephyr/modem/chat.h>
14 
15 #define MODEM_CHAT_MATCHES_INDEX_RESPONSE (0)
16 #define MODEM_CHAT_MATCHES_INDEX_ABORT	  (1)
17 #define MODEM_CHAT_MATCHES_INDEX_UNSOL	  (2)
18 
19 #define MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT (0)
20 
21 #if defined(CONFIG_LOG) && (CONFIG_MODEM_MODULES_LOG_LEVEL == LOG_LEVEL_DBG)
22 
23 static char log_buffer[CONFIG_MODEM_CHAT_LOG_BUFFER];
24 
modem_chat_log_received_command(struct modem_chat * chat)25 static void modem_chat_log_received_command(struct modem_chat *chat)
26 {
27 	uint16_t log_buffer_pos = 0;
28 	uint16_t argv_len;
29 
30 	for (uint16_t i = 0; i < chat->argc; i++) {
31 		argv_len = (uint16_t)strlen(chat->argv[i]);
32 
33 		/* Validate argument fits in log buffer including termination */
34 		if (sizeof(log_buffer) < (log_buffer_pos + argv_len + 1)) {
35 			LOG_WRN("log buffer overrun");
36 			break;
37 		}
38 
39 		/* Copy argument and append space */
40 		memcpy(&log_buffer[log_buffer_pos], chat->argv[i], argv_len);
41 		log_buffer_pos += argv_len;
42 		log_buffer[log_buffer_pos] = ' ';
43 		log_buffer_pos++;
44 	}
45 
46 	/* Terminate line after last argument, overwriting trailing space */
47 	log_buffer_pos = log_buffer_pos == 0 ? log_buffer_pos : log_buffer_pos - 1;
48 	log_buffer[log_buffer_pos] = '\0';
49 
50 	LOG_DBG("%s", log_buffer);
51 }
52 
53 #else
54 
modem_chat_log_received_command(struct modem_chat * chat)55 static void modem_chat_log_received_command(struct modem_chat *chat)
56 {
57 }
58 
59 #endif
60 
modem_chat_script_stop(struct modem_chat * chat,enum modem_chat_script_result result)61 static void modem_chat_script_stop(struct modem_chat *chat, enum modem_chat_script_result result)
62 {
63 	/* Handle result */
64 	if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) {
65 		LOG_DBG("%s: complete", chat->script->name);
66 	} else if (result == MODEM_CHAT_SCRIPT_RESULT_ABORT) {
67 		LOG_WRN("%s: aborted", chat->script->name);
68 	} else {
69 		LOG_WRN("%s: timed out", chat->script->name);
70 	}
71 
72 	/* Call back with result */
73 	if (chat->script->callback != NULL) {
74 		chat->script->callback(chat, result, chat->user_data);
75 	}
76 
77 	/* Clear reference to script */
78 	chat->script = NULL;
79 
80 	/* Clear response and abort commands */
81 	chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
82 	chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
83 	chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
84 	chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
85 
86 	/* Cancel timeout work */
87 	k_work_cancel_delayable(&chat->script_timeout_work);
88 
89 	/* Clear script running state */
90 	atomic_clear_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
91 
92 	/* Store result of script for script stoppted indication */
93 	chat->script_result = result;
94 
95 	/* Indicate script stopped */
96 	k_sem_give(&chat->script_stopped_sem);
97 }
98 
modem_chat_script_send(struct modem_chat * chat)99 static void modem_chat_script_send(struct modem_chat *chat)
100 {
101 	/* Initialize script send work */
102 	chat->script_send_request_pos = 0;
103 	chat->script_send_delimiter_pos = 0;
104 
105 	/* Schedule script send work */
106 	k_work_schedule(&chat->script_send_work, K_NO_WAIT);
107 }
108 
modem_chat_script_next(struct modem_chat * chat,bool initial)109 static void modem_chat_script_next(struct modem_chat *chat, bool initial)
110 {
111 	const struct modem_chat_script_chat *script_chat;
112 
113 	/* Advance iterator if not initial */
114 	if (initial == true) {
115 		/* Reset iterator */
116 		chat->script_chat_it = 0;
117 	} else {
118 		/* Advance iterator */
119 		chat->script_chat_it++;
120 	}
121 
122 	/* Check if end of script reached */
123 	if (chat->script_chat_it == chat->script->script_chats_size) {
124 		modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS);
125 
126 		return;
127 	}
128 
129 	LOG_DBG("%s: step: %u", chat->script->name, chat->script_chat_it);
130 
131 	script_chat = &chat->script->script_chats[chat->script_chat_it];
132 
133 	/* Set response command handlers */
134 	chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches;
135 	chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches_size;
136 
137 	/* Check if work must be sent */
138 	if (script_chat->request_size > 0) {
139 		LOG_DBG("sending: %.*s", script_chat->request_size, script_chat->request);
140 		modem_chat_script_send(chat);
141 	}
142 }
143 
modem_chat_script_start(struct modem_chat * chat,const struct modem_chat_script * script)144 static void modem_chat_script_start(struct modem_chat *chat, const struct modem_chat_script *script)
145 {
146 	/* Save script */
147 	chat->script = script;
148 
149 	/* Set abort matches */
150 	chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches;
151 	chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches_size;
152 
153 	LOG_DBG("running script: %s", chat->script->name);
154 
155 	/* Set first script command */
156 	modem_chat_script_next(chat, true);
157 
158 	/* Start timeout work if script started */
159 	if (chat->script != NULL) {
160 		k_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout));
161 	}
162 }
163 
modem_chat_script_run_handler(struct k_work * item)164 static void modem_chat_script_run_handler(struct k_work *item)
165 {
166 	struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_run_work);
167 
168 	/* Start script */
169 	modem_chat_script_start(chat, chat->pending_script);
170 }
171 
modem_chat_script_timeout_handler(struct k_work * item)172 static void modem_chat_script_timeout_handler(struct k_work *item)
173 {
174 	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
175 	struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_timeout_work);
176 
177 	/* Abort script */
178 	modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_TIMEOUT);
179 }
180 
modem_chat_script_abort_handler(struct k_work * item)181 static void modem_chat_script_abort_handler(struct k_work *item)
182 {
183 	struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_abort_work);
184 
185 	/* Validate script is currently running */
186 	if (chat->script == NULL) {
187 		return;
188 	}
189 
190 	/* Abort script */
191 	modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
192 }
193 
modem_chat_script_send_request(struct modem_chat * chat)194 static bool modem_chat_script_send_request(struct modem_chat *chat)
195 {
196 	const struct modem_chat_script_chat *script_chat =
197 		&chat->script->script_chats[chat->script_chat_it];
198 
199 	uint8_t *script_chat_request_start;
200 	uint16_t script_chat_request_remaining;
201 	int ret;
202 
203 	/* Validate data to send */
204 	if (script_chat->request_size == chat->script_send_request_pos) {
205 		return true;
206 	}
207 
208 	script_chat_request_start = (uint8_t *)&script_chat->request[chat->script_send_request_pos];
209 	script_chat_request_remaining = script_chat->request_size - chat->script_send_request_pos;
210 
211 	/* Send data through pipe */
212 	ret = modem_pipe_transmit(chat->pipe, script_chat_request_start,
213 				  script_chat_request_remaining);
214 
215 	/* Validate transmit successful */
216 	if (ret < 1) {
217 		return false;
218 	}
219 
220 	/* Update script send position */
221 	chat->script_send_request_pos += (uint16_t)ret;
222 
223 	/* Check if data remains */
224 	if (chat->script_send_request_pos < script_chat->request_size) {
225 		return false;
226 	}
227 
228 	return true;
229 }
230 
modem_chat_script_send_delimiter(struct modem_chat * chat)231 static bool modem_chat_script_send_delimiter(struct modem_chat *chat)
232 {
233 	uint8_t *script_chat_delimiter_start;
234 	uint8_t script_chat_delimiter_remaining;
235 	int ret;
236 
237 	/* Validate data to send */
238 	if (chat->delimiter_size == chat->script_send_delimiter_pos) {
239 		return true;
240 	}
241 
242 	script_chat_delimiter_start = (uint8_t *)&chat->delimiter[chat->script_send_delimiter_pos];
243 	script_chat_delimiter_remaining = chat->delimiter_size - chat->script_send_delimiter_pos;
244 
245 	/* Send data through pipe */
246 	ret = modem_pipe_transmit(chat->pipe, script_chat_delimiter_start,
247 				  script_chat_delimiter_remaining);
248 
249 	/* Validate transmit successful */
250 	if (ret < 1) {
251 		return false;
252 	}
253 
254 	/* Update script send position */
255 	chat->script_send_delimiter_pos += (uint8_t)ret;
256 
257 	/* Check if data remains */
258 	if (chat->script_send_delimiter_pos < chat->delimiter_size) {
259 		return false;
260 	}
261 
262 	return true;
263 }
264 
modem_chat_script_chat_is_no_response(struct modem_chat * chat)265 static bool modem_chat_script_chat_is_no_response(struct modem_chat *chat)
266 {
267 	const struct modem_chat_script_chat *script_chat =
268 		&chat->script->script_chats[chat->script_chat_it];
269 
270 	return (script_chat->response_matches_size == 0) ? true : false;
271 }
272 
modem_chat_script_chat_get_send_timeout(struct modem_chat * chat)273 static uint16_t modem_chat_script_chat_get_send_timeout(struct modem_chat *chat)
274 {
275 	const struct modem_chat_script_chat *script_chat =
276 		&chat->script->script_chats[chat->script_chat_it];
277 
278 	return script_chat->timeout;
279 }
280 
modem_chat_script_send_handler(struct k_work * item)281 static void modem_chat_script_send_handler(struct k_work *item)
282 {
283 	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
284 	struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_send_work);
285 	uint16_t timeout;
286 
287 	/* Validate script running */
288 	if (chat->script == NULL) {
289 		return;
290 	}
291 
292 	/* Send request */
293 	if (modem_chat_script_send_request(chat) == false) {
294 		k_work_schedule(&chat->script_send_work, chat->process_timeout);
295 		return;
296 	}
297 
298 	/* Send delimiter */
299 	if (modem_chat_script_send_delimiter(chat) == false) {
300 		k_work_schedule(&chat->script_send_work, chat->process_timeout);
301 		return;
302 	}
303 
304 	/* Check if script command is no response */
305 	if (modem_chat_script_chat_is_no_response(chat)) {
306 		timeout = modem_chat_script_chat_get_send_timeout(chat);
307 
308 		if (timeout == 0) {
309 			modem_chat_script_next(chat, false);
310 		} else {
311 			k_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout));
312 		}
313 	}
314 }
315 
modem_chat_script_send_timeout_handler(struct k_work * item)316 static void modem_chat_script_send_timeout_handler(struct k_work *item)
317 {
318 	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
319 	struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_send_timeout_work);
320 
321 	/* Validate script is currently running */
322 	if (chat->script == NULL) {
323 		return;
324 	}
325 
326 	modem_chat_script_next(chat, false);
327 }
328 
modem_chat_parse_reset(struct modem_chat * chat)329 static void modem_chat_parse_reset(struct modem_chat *chat)
330 {
331 	/* Reset parameters used for parsing */
332 	chat->receive_buf_len = 0;
333 	chat->delimiter_match_len = 0;
334 	chat->argc = 0;
335 	chat->parse_match = NULL;
336 }
337 
338 /* Exact match is stored at end of receive buffer */
modem_chat_parse_save_match(struct modem_chat * chat)339 static void modem_chat_parse_save_match(struct modem_chat *chat)
340 {
341 	uint8_t *argv;
342 
343 	/* Store length of match including NULL to avoid overwriting it if buffer overruns */
344 	chat->parse_match_len = chat->receive_buf_len + 1;
345 
346 	/* Copy match to end of receive buffer */
347 	argv = &chat->receive_buf[chat->receive_buf_size - chat->parse_match_len];
348 
349 	/* Copy match to end of receive buffer (excluding NULL) */
350 	memcpy(argv, &chat->receive_buf[0], chat->parse_match_len - 1);
351 
352 	/* Save match */
353 	chat->argv[chat->argc] = argv;
354 
355 	/* Terminate match */
356 	chat->receive_buf[chat->receive_buf_size - 1] = '\0';
357 
358 	/* Increment argument count */
359 	chat->argc++;
360 }
361 
modem_chat_match_matches_received(struct modem_chat * chat,const struct modem_chat_match * match)362 static bool modem_chat_match_matches_received(struct modem_chat *chat,
363 					      const struct modem_chat_match *match)
364 {
365 	for (uint16_t i = 0; i < match->match_size; i++) {
366 		if ((match->match[i] == chat->receive_buf[i]) ||
367 		    (match->wildcards == true && match->match[i] == '?')) {
368 			continue;
369 		}
370 
371 		return false;
372 	}
373 
374 	return true;
375 }
376 
modem_chat_parse_find_match(struct modem_chat * chat)377 static bool modem_chat_parse_find_match(struct modem_chat *chat)
378 {
379 	/* Find in all matches types */
380 	for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
381 		/* Find in all matches of matches type */
382 		for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
383 			/* Validate match size matches received data length */
384 			if (chat->matches[i][u].match_size != chat->receive_buf_len) {
385 				continue;
386 			}
387 
388 			/* Validate match */
389 			if (modem_chat_match_matches_received(chat, &chat->matches[i][u]) ==
390 			    false) {
391 				continue;
392 			}
393 
394 			/* Complete match found */
395 			chat->parse_match = &chat->matches[i][u];
396 			chat->parse_match_type = i;
397 			return true;
398 		}
399 	}
400 
401 	return false;
402 }
403 
modem_chat_parse_is_separator(struct modem_chat * chat)404 static bool modem_chat_parse_is_separator(struct modem_chat *chat)
405 {
406 	for (uint16_t i = 0; i < chat->parse_match->separators_size; i++) {
407 		if ((chat->parse_match->separators[i]) ==
408 		    (chat->receive_buf[chat->receive_buf_len - 1])) {
409 			return true;
410 		}
411 	}
412 
413 	return false;
414 }
415 
modem_chat_parse_end_del_start(struct modem_chat * chat)416 static bool modem_chat_parse_end_del_start(struct modem_chat *chat)
417 {
418 	for (uint8_t i = 0; i < chat->delimiter_size; i++) {
419 		if (chat->receive_buf[chat->receive_buf_len - 1] == chat->delimiter[i]) {
420 			return true;
421 		}
422 	}
423 
424 	return false;
425 }
426 
modem_chat_parse_end_del_complete(struct modem_chat * chat)427 static bool modem_chat_parse_end_del_complete(struct modem_chat *chat)
428 {
429 	/* Validate length of end delimiter */
430 	if (chat->receive_buf_len < chat->delimiter_size) {
431 		return false;
432 	}
433 
434 	/* Compare end delimiter with receive buffer content */
435 	return (memcmp(&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size],
436 		       chat->delimiter, chat->delimiter_size) == 0)
437 		       ? true
438 		       : false;
439 }
440 
modem_chat_on_command_received_unsol(struct modem_chat * chat)441 static void modem_chat_on_command_received_unsol(struct modem_chat *chat)
442 {
443 	/* Callback */
444 	if (chat->parse_match->callback != NULL) {
445 		chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
446 	}
447 }
448 
modem_chat_on_command_received_abort(struct modem_chat * chat)449 static void modem_chat_on_command_received_abort(struct modem_chat *chat)
450 {
451 	/* Callback */
452 	if (chat->parse_match->callback != NULL) {
453 		chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
454 	}
455 
456 	/* Abort script */
457 	modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
458 }
459 
modem_chat_on_command_received_resp(struct modem_chat * chat)460 static void modem_chat_on_command_received_resp(struct modem_chat *chat)
461 {
462 	/* Callback */
463 	if (chat->parse_match->callback != NULL) {
464 		chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
465 	}
466 
467 	/* Validate response command is not partial */
468 	if (chat->parse_match->partial) {
469 		return;
470 	}
471 
472 	/* Advance script */
473 	modem_chat_script_next(chat, false);
474 }
475 
modem_chat_parse_find_catch_all_match(struct modem_chat * chat)476 static bool modem_chat_parse_find_catch_all_match(struct modem_chat *chat)
477 {
478 	/* Find in all matches types */
479 	for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
480 		/* Find in all matches of matches type */
481 		for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
482 			/* Validate match config is matching previous bytes */
483 			if (chat->matches[i][u].match_size == 0) {
484 				chat->parse_match = &chat->matches[i][u];
485 				chat->parse_match_type = i;
486 				return true;
487 			}
488 		}
489 	}
490 
491 	return false;
492 }
493 
modem_chat_on_command_received(struct modem_chat * chat)494 static void modem_chat_on_command_received(struct modem_chat *chat)
495 {
496 	modem_chat_log_received_command(chat);
497 
498 	switch (chat->parse_match_type) {
499 	case MODEM_CHAT_MATCHES_INDEX_UNSOL:
500 		modem_chat_on_command_received_unsol(chat);
501 		break;
502 
503 	case MODEM_CHAT_MATCHES_INDEX_ABORT:
504 		modem_chat_on_command_received_abort(chat);
505 		break;
506 
507 	case MODEM_CHAT_MATCHES_INDEX_RESPONSE:
508 		modem_chat_on_command_received_resp(chat);
509 		break;
510 	}
511 }
512 
modem_chat_on_unknown_command_received(struct modem_chat * chat)513 static void modem_chat_on_unknown_command_received(struct modem_chat *chat)
514 {
515 	/* Terminate received command */
516 	chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
517 
518 	/* Try to find catch all match */
519 	if (modem_chat_parse_find_catch_all_match(chat) == false) {
520 		LOG_DBG("%s", chat->receive_buf);
521 		return;
522 	}
523 
524 	/* Parse command */
525 	chat->argv[0] = "";
526 	chat->argv[1] = chat->receive_buf;
527 	chat->argc = 2;
528 
529 	modem_chat_on_command_received(chat);
530 }
531 
modem_chat_process_byte(struct modem_chat * chat,uint8_t byte)532 static void modem_chat_process_byte(struct modem_chat *chat, uint8_t byte)
533 {
534 	/* Validate receive buffer not overrun */
535 	if (chat->receive_buf_size == chat->receive_buf_len) {
536 		LOG_WRN("receive buffer overrun");
537 		modem_chat_parse_reset(chat);
538 		return;
539 	}
540 
541 	/* Validate argv buffer not overrun */
542 	if (chat->argc == chat->argv_size) {
543 		LOG_WRN("argv buffer overrun");
544 		modem_chat_parse_reset(chat);
545 		return;
546 	}
547 
548 	/* Copy byte to receive buffer */
549 	chat->receive_buf[chat->receive_buf_len] = byte;
550 	chat->receive_buf_len++;
551 
552 	/* Validate end delimiter not complete */
553 	if (modem_chat_parse_end_del_complete(chat) == true) {
554 		/* Filter out empty lines */
555 		if (chat->receive_buf_len == chat->delimiter_size) {
556 			/* Reset parser */
557 			modem_chat_parse_reset(chat);
558 			return;
559 		}
560 
561 		/* Check if match exists */
562 		if (chat->parse_match == NULL) {
563 			/* Handle unknown command */
564 			modem_chat_on_unknown_command_received(chat);
565 
566 			/* Reset parser */
567 			modem_chat_parse_reset(chat);
568 			return;
569 		}
570 
571 		/* Check if trailing argument exists */
572 		if (chat->parse_arg_len > 0) {
573 			chat->argv[chat->argc] =
574 				&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size -
575 						   chat->parse_arg_len];
576 			chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
577 			chat->argc++;
578 		}
579 
580 		/* Handle received command */
581 		modem_chat_on_command_received(chat);
582 
583 		/* Reset parser */
584 		modem_chat_parse_reset(chat);
585 		return;
586 	}
587 
588 	/* Validate end delimiter not started */
589 	if (modem_chat_parse_end_del_start(chat) == true) {
590 		return;
591 	}
592 
593 	/* Find matching command if missing */
594 	if (chat->parse_match == NULL) {
595 		/* Find matching command */
596 		if (modem_chat_parse_find_match(chat) == false) {
597 			return;
598 		}
599 
600 		/* Save match */
601 		modem_chat_parse_save_match(chat);
602 
603 		/* Prepare argument parser */
604 		chat->parse_arg_len = 0;
605 		return;
606 	}
607 
608 	/* Check if separator reached */
609 	if (modem_chat_parse_is_separator(chat) == true) {
610 		/* Check if argument is empty */
611 		if (chat->parse_arg_len == 0) {
612 			/* Save empty argument */
613 			chat->argv[chat->argc] = "";
614 		} else {
615 			/* Save pointer to start of argument */
616 			chat->argv[chat->argc] =
617 				&chat->receive_buf[chat->receive_buf_len - chat->parse_arg_len - 1];
618 
619 			/* Replace separator with string terminator */
620 			chat->receive_buf[chat->receive_buf_len - 1] = '\0';
621 		}
622 
623 		/* Increment argument count */
624 		chat->argc++;
625 
626 		/* Reset parse argument length */
627 		chat->parse_arg_len = 0;
628 		return;
629 	}
630 
631 	/* Increment argument length */
632 	chat->parse_arg_len++;
633 }
634 
modem_chat_discard_byte(struct modem_chat * chat,uint8_t byte)635 static bool modem_chat_discard_byte(struct modem_chat *chat, uint8_t byte)
636 {
637 	for (uint8_t i = 0; i < chat->filter_size; i++) {
638 		if (byte == chat->filter[i]) {
639 			return true;
640 		}
641 	}
642 
643 	return false;
644 }
645 
646 /* Process chunk of received bytes */
modem_chat_process_bytes(struct modem_chat * chat)647 static void modem_chat_process_bytes(struct modem_chat *chat)
648 {
649 	for (uint16_t i = 0; i < chat->work_buf_len; i++) {
650 		if (modem_chat_discard_byte(chat, chat->work_buf[i])) {
651 			continue;
652 		}
653 
654 		modem_chat_process_byte(chat, chat->work_buf[i]);
655 	}
656 }
657 
modem_chat_process_handler(struct k_work * item)658 static void modem_chat_process_handler(struct k_work *item)
659 {
660 	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
661 	struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, process_work);
662 	int ret;
663 
664 	/* Fill work buffer */
665 	ret = modem_pipe_receive(chat->pipe, chat->work_buf, sizeof(chat->work_buf));
666 	if (ret < 1) {
667 		return;
668 	}
669 
670 	/* Save received data length */
671 	chat->work_buf_len = (size_t)ret;
672 
673 	/* Process data */
674 	modem_chat_process_bytes(chat);
675 	k_work_schedule(&chat->process_work, K_NO_WAIT);
676 }
677 
modem_chat_pipe_callback(struct modem_pipe * pipe,enum modem_pipe_event event,void * user_data)678 static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
679 				     void *user_data)
680 {
681 	struct modem_chat *chat = (struct modem_chat *)user_data;
682 
683 	if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
684 		k_work_schedule(&chat->process_work, chat->process_timeout);
685 	}
686 }
687 
modem_chat_init(struct modem_chat * chat,const struct modem_chat_config * config)688 int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config)
689 {
690 	__ASSERT_NO_MSG(chat != NULL);
691 	__ASSERT_NO_MSG(config != NULL);
692 	__ASSERT_NO_MSG(config->receive_buf != NULL);
693 	__ASSERT_NO_MSG(config->receive_buf_size > 0);
694 	__ASSERT_NO_MSG(config->argv != NULL);
695 	__ASSERT_NO_MSG(config->argv_size > 0);
696 	__ASSERT_NO_MSG(config->delimiter != NULL);
697 	__ASSERT_NO_MSG(config->delimiter_size > 0);
698 	__ASSERT_NO_MSG(!((config->filter == NULL) && (config->filter > 0)));
699 	__ASSERT_NO_MSG(!((config->unsol_matches == NULL) && (config->unsol_matches_size > 0)));
700 
701 	memset(chat, 0x00, sizeof(*chat));
702 	chat->pipe = NULL;
703 	chat->user_data = config->user_data;
704 	chat->receive_buf = config->receive_buf;
705 	chat->receive_buf_size = config->receive_buf_size;
706 	chat->argv = config->argv;
707 	chat->argv_size = config->argv_size;
708 	chat->delimiter = config->delimiter;
709 	chat->delimiter_size = config->delimiter_size;
710 	chat->filter = config->filter;
711 	chat->filter_size = config->filter_size;
712 	chat->matches[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches;
713 	chat->matches_size[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches_size;
714 	chat->process_timeout = config->process_timeout;
715 	atomic_set(&chat->script_state, 0);
716 	k_sem_init(&chat->script_stopped_sem, 0, 1);
717 	k_work_init_delayable(&chat->process_work, modem_chat_process_handler);
718 	k_work_init(&chat->script_run_work, modem_chat_script_run_handler);
719 	k_work_init_delayable(&chat->script_timeout_work, modem_chat_script_timeout_handler);
720 	k_work_init(&chat->script_abort_work, modem_chat_script_abort_handler);
721 	k_work_init_delayable(&chat->script_send_work, modem_chat_script_send_handler);
722 	k_work_init_delayable(&chat->script_send_timeout_work,
723 			      modem_chat_script_send_timeout_handler);
724 
725 	return 0;
726 }
727 
modem_chat_attach(struct modem_chat * chat,struct modem_pipe * pipe)728 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe)
729 {
730 	chat->pipe = pipe;
731 	modem_chat_parse_reset(chat);
732 	modem_pipe_attach(chat->pipe, modem_chat_pipe_callback, chat);
733 	return 0;
734 }
735 
modem_chat_run_script_async(struct modem_chat * chat,const struct modem_chat_script * script)736 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script)
737 {
738 	bool script_is_running;
739 
740 	if (chat->pipe == NULL) {
741 		return -EPERM;
742 	}
743 
744 	/* Validate script */
745 	if ((script->script_chats == NULL) || (script->script_chats_size == 0) ||
746 	    ((script->abort_matches != NULL) && (script->abort_matches_size == 0))) {
747 		return -EINVAL;
748 	}
749 
750 	/* Validate script commands */
751 	for (uint16_t i = 0; i < script->script_chats_size; i++) {
752 		if ((script->script_chats[i].request_size == 0) &&
753 		    (script->script_chats[i].response_matches_size == 0)) {
754 			return -EINVAL;
755 		}
756 	}
757 
758 	script_is_running =
759 		atomic_test_and_set_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
760 
761 	if (script_is_running == true) {
762 		return -EBUSY;
763 	}
764 
765 	chat->pending_script = script;
766 	k_work_submit(&chat->script_run_work);
767 	return 0;
768 }
769 
modem_chat_run_script(struct modem_chat * chat,const struct modem_chat_script * script)770 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script)
771 {
772 	int ret;
773 
774 	k_sem_reset(&chat->script_stopped_sem);
775 
776 	ret = modem_chat_run_script_async(chat, script);
777 	if (ret < 0) {
778 		return ret;
779 	}
780 
781 	ret = k_sem_take(&chat->script_stopped_sem, K_FOREVER);
782 	if (ret < 0) {
783 		return ret;
784 	}
785 
786 	return chat->script_result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS ? 0 : -EAGAIN;
787 }
788 
modem_chat_script_abort(struct modem_chat * chat)789 void modem_chat_script_abort(struct modem_chat *chat)
790 {
791 	k_work_submit(&chat->script_abort_work);
792 }
793 
modem_chat_release(struct modem_chat * chat)794 void modem_chat_release(struct modem_chat *chat)
795 {
796 	struct k_work_sync sync;
797 
798 	if (chat->pipe) {
799 		modem_pipe_release(chat->pipe);
800 	}
801 
802 	k_work_cancel_sync(&chat->script_run_work, &sync);
803 	k_work_cancel_sync(&chat->script_abort_work, &sync);
804 	k_work_cancel_delayable_sync(&chat->process_work, &sync);
805 	k_work_cancel_delayable_sync(&chat->script_send_work, &sync);
806 
807 	chat->pipe = NULL;
808 	chat->receive_buf_len = 0;
809 	chat->work_buf_len = 0;
810 	chat->argc = 0;
811 	chat->script = NULL;
812 	chat->script_chat_it = 0;
813 	atomic_set(&chat->script_state, 0);
814 	chat->script_result = MODEM_CHAT_SCRIPT_RESULT_ABORT;
815 	k_sem_reset(&chat->script_stopped_sem);
816 	chat->script_send_request_pos = 0;
817 	chat->script_send_delimiter_pos = 0;
818 	chat->parse_match = NULL;
819 	chat->parse_match_len = 0;
820 	chat->parse_arg_len = 0;
821 }
822