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