1 /*
2 * SPDX-License-Identifier: Apache-2.0
3 *
4 * Copyright (C) 2025, Joakim Andersson
5 */
6
7 /**
8 * @addtogroup t_modem_driver
9 * @{
10 * @defgroup t_modem_cmd_handler test_modem_cmd_handler
11 * @}
12 */
13
14
15 #include <zephyr/ztest.h>
16 #include <zephyr/fff.h>
17 #include <modem_cmd_handler.h>
18
19 DEFINE_FFF_GLOBALS;
20
21 static struct modem_cmd_handler_data cmd_handler_data;
22 static struct modem_cmd_handler cmd_handler;
23 static struct modem_iface mock_modem_iface;
24 static struct k_sem sem_response;
25 #define MDM_RECV_BUF_SIZE 512
26 NET_BUF_POOL_DEFINE(_mdm_recv_pool, 10, MDM_RECV_BUF_SIZE, 0, NULL);
27
28 uint8_t cmd_match_buf[MDM_RECV_BUF_SIZE + 1];
29
MODEM_CMD_DEFINE(mock_on_cmd_ok)30 MODEM_CMD_DEFINE(mock_on_cmd_ok)
31 {
32 modem_cmd_handler_set_error(data, 0);
33 k_sem_give(&sem_response);
34 return 0;
35 }
36
MODEM_CMD_DEFINE(mock_on_cmd_error)37 MODEM_CMD_DEFINE(mock_on_cmd_error)
38 {
39 modem_cmd_handler_set_error(data, -EIO);
40 k_sem_give(&sem_response);
41 return 0;
42 }
43
44 static const struct modem_cmd mock_response_cmds[] = {
45 MODEM_CMD("OK", mock_on_cmd_ok, 0U, ""), /* 3GPP */
46 MODEM_CMD("ERROR", mock_on_cmd_error, 0U, ""), /* 3GPP */
47 };
48
49 static const struct modem_cmd_handler_config cmd_handler_config = {
50 .match_buf = &cmd_match_buf[0],
51 .match_buf_len = sizeof(cmd_match_buf),
52 .buf_pool = &_mdm_recv_pool,
53 .alloc_timeout = K_NO_WAIT,
54 .eol = "\r",
55 .user_data = NULL,
56 .response_cmds = mock_response_cmds,
57 .response_cmds_len = ARRAY_SIZE(mock_response_cmds),
58 .unsol_cmds = NULL,
59 .unsol_cmds_len = 0,
60 };
61
62 static const char *response_delayed;
63 size_t mock_write_expected_data_count;
64 static const char *mock_write_expected_data[10];
65
66 FAKE_VALUE_FUNC(int, mock_read, struct modem_iface *, uint8_t *, size_t, size_t *);
67 FAKE_VALUE_FUNC(int, mock_write, struct modem_iface *, const uint8_t *, size_t);
68 FAKE_VALUE_FUNC(int, mock_on_response, struct modem_cmd_handler_data *, uint16_t,
69 uint8_t **, uint16_t);
70
mock_modem_iface_init(struct modem_iface * iface)71 static void mock_modem_iface_init(struct modem_iface *iface)
72 {
73 iface->read = mock_read;
74 iface->write = mock_write;
75 }
76
mock_modem_iface_receive_data(struct modem_iface * iface,uint8_t * data,size_t len,size_t * bytes_read)77 static int mock_modem_iface_receive_data(struct modem_iface *iface,
78 uint8_t *data,
79 size_t len, size_t *bytes_read)
80 {
81 if (!response_delayed) {
82 *bytes_read = 0;
83 return mock_read_fake.return_val;
84 }
85
86 size_t response_delayed_len = strlen(response_delayed);
87
88 zassert_true(len > response_delayed_len, "Insufficient data length for response");
89 memcpy(data, response_delayed, response_delayed_len);
90 *bytes_read = response_delayed_len;
91 response_delayed = NULL;
92
93 return mock_read_fake.return_val;
94 }
95
_send_response_delayed_work(struct k_work * work)96 static void _send_response_delayed_work(struct k_work *work)
97 {
98 mock_read_fake.custom_fake = mock_modem_iface_receive_data;
99 modem_cmd_handler_process(&cmd_handler, &mock_modem_iface);
100 }
101
102 K_WORK_DELAYABLE_DEFINE(response_delayable, _send_response_delayed_work);
103
recv_data_delayed(const char * str,k_timeout_t delay)104 void recv_data_delayed(const char *str, k_timeout_t delay)
105 {
106 response_delayed = str;
107 k_work_schedule(&response_delayable, delay);
108 }
109
modem_modem_iface_send_data(struct modem_iface * iface,const uint8_t * data,size_t len)110 static int modem_modem_iface_send_data(struct modem_iface *iface,
111 const uint8_t *data,
112 size_t len)
113 {
114 zassert_true(strncmp((const char *)data,
115 mock_write_expected_data[mock_write_fake.call_count - 1], len) == 0,
116 "Sent command does not match expected");
117 return mock_write_fake.return_val;
118 }
119
send_data_verify(const char * expected_cmd)120 void send_data_verify(const char *expected_cmd)
121 {
122 mock_write_expected_data[mock_write_expected_data_count++] = expected_cmd;
123 mock_write_fake.custom_fake = modem_modem_iface_send_data;
124 }
125
ZTEST(suite_modem_cmd_send,test_recv_ok)126 ZTEST(suite_modem_cmd_send, test_recv_ok)
127 {
128 int ret = modem_cmd_handler_init(&cmd_handler,
129 &cmd_handler_data,
130 &cmd_handler_config);
131 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
132
133 send_data_verify("AT+INIT");
134 send_data_verify("\r");
135
136 recv_data_delayed("OK\r", K_MSEC(100));
137
138 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
139 NULL, 0U, "AT+INIT", &sem_response,
140 K_SECONDS(1));
141 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
142 }
143
ZTEST(suite_modem_cmd_send,test_recv_error)144 ZTEST(suite_modem_cmd_send, test_recv_error)
145 {
146 int ret = modem_cmd_handler_init(&cmd_handler,
147 &cmd_handler_data,
148 &cmd_handler_config);
149 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
150
151 send_data_verify("AT+INIT");
152 send_data_verify("\r");
153
154 recv_data_delayed("ERROR\r", K_MSEC(100));
155
156 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
157 NULL, 0U, "AT+INIT", &sem_response,
158 K_SECONDS(1));
159 zassert_equal(ret, -EIO, "modem_cmd_send should return -EIO on error");
160 }
161
ZTEST(suite_modem_cmd_send,test_recv_timeout)162 ZTEST(suite_modem_cmd_send, test_recv_timeout)
163 {
164 int ret = modem_cmd_handler_init(&cmd_handler,
165 &cmd_handler_data,
166 &cmd_handler_config);
167 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
168
169 send_data_verify("AT+INIT");
170 send_data_verify("\r");
171
172 /* No response is sent to trigger timeout */
173
174 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
175 NULL, 0U, "AT+INIT", &sem_response,
176 K_SECONDS(1));
177 zassert_equal(ret, -ETIMEDOUT, "modem_cmd_send should return -ETIMEDOUT on timeout");
178 }
179
MODEM_CMD_DEFINE(on_cmd_response)180 MODEM_CMD_DEFINE(on_cmd_response)
181 {
182 zassert_equal(argc, 1);
183 zassert_equal(strcmp(argv[0], "123"), 0);
184 return 0;
185 }
186
ZTEST(suite_modem_cmd_send,test_recv_response)187 ZTEST(suite_modem_cmd_send, test_recv_response)
188 {
189 int ret = modem_cmd_handler_init(&cmd_handler,
190 &cmd_handler_data,
191 &cmd_handler_config);
192 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
193
194 send_data_verify("AT+CMD");
195 send_data_verify("\r");
196
197 recv_data_delayed("+CMD: 123\r"
198 "OK\r",
199 K_MSEC(100));
200
201 mock_on_response_fake.custom_fake = on_cmd_response;
202 struct modem_cmd cmd[] = {
203 MODEM_CMD("+CMD: ", mock_on_response, 1, ""),
204 };
205
206 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
207 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
208 K_SECONDS(1));
209 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
210 zassert_equal(mock_on_response_fake.call_count, 1);
211 }
212
MODEM_CMD_DEFINE(on_cmd_response_parse_args)213 MODEM_CMD_DEFINE(on_cmd_response_parse_args)
214 {
215 zassert_equal(argc, 4);
216 zassert_equal(strcmp(argv[0], "1"), 0);
217 zassert_equal(strcmp(argv[1], "\"two\""), 0);
218 zassert_equal(strcmp(argv[2], "\"three\""), 0);
219 zassert_equal(strcmp(argv[3], "4"), 0);
220 return 0;
221 }
222
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args)223 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args)
224 {
225 int ret = modem_cmd_handler_init(&cmd_handler,
226 &cmd_handler_data,
227 &cmd_handler_config);
228 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
229
230 send_data_verify("AT+CMD");
231 send_data_verify("\r");
232
233 recv_data_delayed("+CMD: 1,\"two\",\"three\",4\r"
234 "OK\r",
235 K_MSEC(100));
236
237 mock_on_response_fake.custom_fake = on_cmd_response_parse_args;
238 struct modem_cmd cmd[] = {
239 MODEM_CMD("+CMD: ", mock_on_response, 4, ","),
240 };
241
242 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
243 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
244 K_SECONDS(1));
245 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
246 zassert_equal(mock_on_response_fake.call_count, 1);
247 }
248
MODEM_CMD_DEFINE(on_cmd_response_parse_args_quoted_delim)249 MODEM_CMD_DEFINE(on_cmd_response_parse_args_quoted_delim)
250 {
251 zassert_equal(argc, 4);
252 zassert_equal(strcmp(argv[0], "1"), 0);
253 zassert_equal(strcmp(argv[1], "\"two\""), 0);
254 zassert_equal(strcmp(argv[2], "\"thr,ee\""), 0);
255 zassert_equal(strcmp(argv[3], "4"), 0);
256 return 0;
257 }
258
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args_quoted_delim)259 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args_quoted_delim)
260 {
261 int ret = modem_cmd_handler_init(&cmd_handler,
262 &cmd_handler_data,
263 &cmd_handler_config);
264 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
265
266 send_data_verify("AT+CMD");
267 send_data_verify("\r");
268
269 recv_data_delayed("+CMD: 1,\"two\",\"thr,ee\",4\r"
270 "OK\r",
271 K_MSEC(100));
272
273 mock_on_response_fake.custom_fake = on_cmd_response_parse_args_quoted_delim;
274 struct modem_cmd cmd[] = {
275 MODEM_CMD("+CMD: ", mock_on_response, 4, ","),
276 };
277
278 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
279 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
280 K_SECONDS(1));
281 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
282 zassert_equal(mock_on_response_fake.call_count, 1);
283 }
284
MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg)285 MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg)
286 {
287 zassert_equal(argc, 4);
288 zassert_equal(strcmp(argv[0], "1"), 0);
289 zassert_equal(strcmp(argv[1], "\"two\""), 0);
290 zassert_equal(strcmp(argv[2], ""), 0);
291 zassert_equal(strcmp(argv[3], "4"), 0);
292 return 0;
293 }
294
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args_empty_arg)295 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args_empty_arg)
296 {
297 int ret = modem_cmd_handler_init(&cmd_handler,
298 &cmd_handler_data,
299 &cmd_handler_config);
300 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
301
302 send_data_verify("AT+CMD");
303 send_data_verify("\r");
304
305 recv_data_delayed("+CMD: 1,\"two\",,4\r"
306 "OK\r",
307 K_MSEC(100));
308
309 mock_on_response_fake.custom_fake = on_cmd_response_parse_args_empty_arg;
310 struct modem_cmd cmd[] = {
311 MODEM_CMD("+CMD: ", mock_on_response, 4, ","),
312 };
313
314 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
315 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
316 K_SECONDS(1));
317 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
318 zassert_equal(mock_on_response_fake.call_count, 1);
319 }
320
MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg_end)321 MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg_end)
322 {
323 zassert_equal(argc, 4);
324 zassert_equal(strcmp(argv[0], "1"), 0);
325 zassert_equal(strcmp(argv[1], "\"two\""), 0);
326 zassert_equal(strcmp(argv[2], "\"three\""), 0);
327 zassert_equal(strcmp(argv[3], ""), 0);
328 return 0;
329 }
330
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args_empty_arg_end)331 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args_empty_arg_end)
332 {
333 int ret = modem_cmd_handler_init(&cmd_handler,
334 &cmd_handler_data,
335 &cmd_handler_config);
336 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
337
338 send_data_verify("AT+CMD");
339 send_data_verify("\r");
340
341 recv_data_delayed("+CMD: 1,\"two\",\"three\",\r"
342 "OK\r",
343 K_MSEC(100));
344
345 mock_on_response_fake.custom_fake = on_cmd_response_parse_args_empty_arg_end;
346 struct modem_cmd cmd[] = {
347 MODEM_CMD("+CMD: ", mock_on_response, 4, ","),
348 };
349
350 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
351 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
352 K_SECONDS(1));
353 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
354 zassert_equal(mock_on_response_fake.call_count, 1);
355 }
356
MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg_begin)357 MODEM_CMD_DEFINE(on_cmd_response_parse_args_empty_arg_begin)
358 {
359 zassert_equal(argc, 4);
360 zassert_equal(strcmp(argv[0], ""), 0);
361 zassert_equal(strcmp(argv[1], "\"two\""), 0);
362 zassert_equal(strcmp(argv[2], "\"three\""), 0);
363 zassert_equal(strcmp(argv[3], "4"), 0);
364 return 0;
365 }
366
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args_empty_arg_begin)367 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args_empty_arg_begin)
368 {
369 int ret = modem_cmd_handler_init(&cmd_handler,
370 &cmd_handler_data,
371 &cmd_handler_config);
372 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
373
374 send_data_verify("AT+CMD");
375 send_data_verify("\r");
376
377 recv_data_delayed("+CMD: ,\"two\",\"three\",4\r"
378 "OK\r",
379 K_MSEC(100));
380
381 mock_on_response_fake.custom_fake = on_cmd_response_parse_args_empty_arg_begin;
382 struct modem_cmd cmd[] = {
383 MODEM_CMD("+CMD: ", mock_on_response, 4, ","),
384 };
385
386 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
387 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
388 K_SECONDS(1));
389 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
390 zassert_equal(mock_on_response_fake.call_count, 1);
391 }
392
MODEM_CMD_DEFINE(on_cmd_response_parse_args_multi_delim)393 MODEM_CMD_DEFINE(on_cmd_response_parse_args_multi_delim)
394 {
395 zassert_equal(argc, 4);
396 zassert_equal(strcmp(argv[0], "1"), 0);
397 zassert_equal(strcmp(argv[1], "\"two\""), 0);
398 zassert_equal(strcmp(argv[2], "\"three\""), 0);
399 zassert_equal(strcmp(argv[3], "4"), 0);
400 return 0;
401 }
402
ZTEST(suite_modem_cmd_send,test_recv_response_parse_args_multi_delim)403 ZTEST(suite_modem_cmd_send, test_recv_response_parse_args_multi_delim)
404 {
405 int ret = modem_cmd_handler_init(&cmd_handler,
406 &cmd_handler_data,
407 &cmd_handler_config);
408 zassert_equal(ret, 0, "modem_cmd_handler_init should return 0 on success");
409
410 send_data_verify("AT+CMD");
411 send_data_verify("\r");
412
413 recv_data_delayed("+CMD: 1:\"two\";\"three\",4\r"
414 "OK\r",
415 K_MSEC(100));
416
417 mock_on_response_fake.custom_fake = on_cmd_response_parse_args_multi_delim;
418 struct modem_cmd cmd[] = {
419 MODEM_CMD("+CMD: ", mock_on_response, 4, ",;:"),
420 };
421
422 ret = modem_cmd_send(&mock_modem_iface, &cmd_handler,
423 cmd, ARRAY_SIZE(cmd), "AT+CMD", &sem_response,
424 K_SECONDS(1));
425 zassert_equal(ret, 0, "modem_cmd_send should return 0 on success");
426 zassert_equal(mock_on_response_fake.call_count, 1);
427 }
428
test_setup(void * fixture)429 static void test_setup(void *fixture)
430 {
431 mock_write_expected_data_count = 0;
432
433 RESET_FAKE(mock_read);
434 RESET_FAKE(mock_write);
435 RESET_FAKE(mock_on_response);
436
437 k_sem_init(&sem_response, 0, 1);
438
439 mock_modem_iface_init(&mock_modem_iface);
440 }
441
test_teardown(void * fixture)442 static void test_teardown(void *fixture)
443 {
444 zassert_equal(mock_write_fake.call_count, mock_write_expected_data_count,
445 "Not all expected commands were sent");
446 }
447
448 ZTEST_SUITE(suite_modem_cmd_send, NULL, NULL, test_setup, test_teardown, NULL);
449