blob: 93aed2b23794713b443e748eec79ba2dc263899b [file] [log] [blame]
Vadim Yanitskiye1e72472019-04-09 16:55:44 +07001/*
2 * Test the storage API of the internal SMS Centre.
3 *
4 * (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
5 *
6 * All Rights Reserved
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23#include <inttypes.h>
24#include <stdbool.h>
25#include <string.h>
26#include <stdio.h>
27
28#include <osmocom/core/application.h>
29#include <osmocom/core/utils.h>
30
31#include <osmocom/gsm/protocol/gsm_03_40.h>
32
33#include <osmocom/msc/gsm_data.h>
34#include <osmocom/msc/debug.h>
35#include <osmocom/msc/vlr.h>
36#include <osmocom/msc/db.h>
37
38/* Talloc context of this unit test */
39static void *talloc_ctx = NULL;
40
41static const struct sms_tp_ud {
42 /* Data Coding Scheme */
43 uint8_t dcs;
44 /* TP User-Data-Length (depends on DCS) */
45 uint8_t length;
46 /* Static TP User-Data filler (0 means disabled) */
47 uint8_t filler_byte;
48 /* TP User-Data */
49 uint8_t data[GSM340_UDL_OCT_MAX];
50 /* Decoded text (for 7-bit default alphabet only) */
51 char dec_text[GSM340_UDL_SPT_MAX + 1];
52} sms_tp_ud_set[] = {
53 {
54 .dcs = 0x00, /* Default GSM 7-bit alphabet */
55 .length = 9, /* in septets */
56 .dec_text = "Mahlzeit!",
57 .data = {
58 0xcd, 0x30, 0x9a, 0xad, 0x2f, 0xa7, 0xe9, 0x21,
59 },
60 },
61 {
62 .dcs = 0x08, /* UCS-2 (16-bit) / UTF-16 */
63 .length = 120, /* in octets */
64 .data = {
65 0x04, 0x23, 0x04, 0x32, 0x04, 0x30, 0x04, 0x36,
66 0x04, 0x30, 0x04, 0x35, 0x04, 0x3c, 0x04, 0x4b,
67 0x04, 0x39, 0x00, 0x20, 0x04, 0x3a, 0x04, 0x3b,
68 0x04, 0x38, 0x04, 0x35, 0x04, 0x3d, 0x04, 0x42,
69 0x00, 0x21, 0x00, 0x20, 0x04, 0x1d, 0x04, 0x30,
70 0x04, 0x41, 0x04, 0x42, 0x04, 0x40, 0x04, 0x3e,
71 0x04, 0x39, 0x04, 0x3a, 0x04, 0x38, 0x00, 0x20,
72 0x00, 0x49, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65,
73 0x00, 0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74,
74 0x00, 0x20, 0x04, 0x38, 0x00, 0x20, 0x00, 0x4d,
75 0x00, 0x4d, 0x00, 0x53, 0x00, 0x20, 0x04, 0x31,
76 0x04, 0x43, 0x04, 0x34, 0x04, 0x43, 0x04, 0x42,
77 0x00, 0x20, 0x04, 0x34, 0x04, 0x3e, 0x04, 0x41,
78 0x04, 0x42, 0x04, 0x30, 0x04, 0x32, 0x04, 0x3b,
79 0x04, 0x35, 0x04, 0x3d, 0x04, 0x4b, 0x00, 0x2e,
80 },
81 },
82 {
83 .dcs = 0x04, /* 8-bit data */
84 .length = 12, /* in octets */
85 .data = {
86 /* User-Data-Header */
87 0x1e, /* Buffer-overflow! (should be 0x05) */
88 /* Concatenated SM, 8-bit reference number */
89 0x00, 0x03, 0x5a, 0x05, 0x01,
90
91 /* Dummy payload... */
92 0x05, 0x04, 0x0b, 0x84, 0x0b, 0x84,
93 },
94 },
95 {
96 .dcs = 0x00, /* Default GSM 7-bit alphabet */
97 .length = 160, /* maximum, in septets */
98 .filler_byte = 0x41,
99 },
100 {
101 .dcs = 0x04, /* 8-bit data */
102 .length = 140, /* maximum, in octets */
103 .filler_byte = 0x42,
104 },
105 {
106 .dcs = 0x00, /* Default GSM 7-bit alphabet */
107 .length = 200, /* invalid, buffer overflow */
108 .filler_byte = 0x41,
109 },
110 {
111 .dcs = 0x04, /* 8-bit data */
112 .length = 0xff, /* invalid, buffer overflow */
113 .filler_byte = 0x42,
114 },
115};
116
117#define SMS_ADDR(addr) \
118 { 0x00, 0x00, addr }
119
120static struct sms_test {
121 /* Human-readable name of particular test message */
122 const char *name;
123 /* Whether we expect db_sms_store() to fail */
124 bool exp_db_sms_store_fail;
125 /* Whether we expect db_sms_get() to fail */
126 bool exp_db_sms_get_fail;
127 /* SM TP-User-Data from sms_tp_ud_set[] */
128 const struct sms_tp_ud *ud;
129 /* The message itself */
130 struct gsm_sms sms;
131} sms_test_set[] = {
132 {
133 .name = "Regular MO SMS",
134 .sms = {
135 .msg_ref = 0xde,
136 .src = SMS_ADDR("123456"),
137 .dst = SMS_ADDR("654321"),
138 .validity_minutes = 10,
139 .protocol_id = 0x00,
140 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
141 },
142 .ud = &sms_tp_ud_set[0],
143 },
144 {
145 .name = "Regular MT SMS",
146 .sms = {
147 .msg_ref = 0xbe,
148 .src = SMS_ADDR("654321"),
149 .dst = SMS_ADDR("123456"),
150 .validity_minutes = 180,
151 .protocol_id = 0x00,
152 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
153 },
154 .ud = &sms_tp_ud_set[1],
155 },
156 {
157 .name = "Complete TP-UD (160 septets, 7-bit encoding)",
158 .sms = {
159 .msg_ref = 0xee,
160 .src = SMS_ADDR("266753837248772"),
161 .dst = SMS_ADDR("266753837248378"),
162 .validity_minutes = 360,
163 .protocol_id = 0x00,
164 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
165 },
166 .ud = &sms_tp_ud_set[3],
167 },
168 {
169 .name = "Complete TP-UD (140 octets, 8-bit encoding)",
170 .sms = {
171 .msg_ref = 0xee,
172 .src = SMS_ADDR("266753838248772"),
173 .dst = SMS_ADDR("266753838248378"),
174 .validity_minutes = 360,
175 .protocol_id = 0xaa,
176 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
177 },
178 .ud = &sms_tp_ud_set[4],
179 },
180 {
181 .name = "TP-UD buffer overflow (UDH-Length > UD-Length)",
182 .sms = {
183 .msg_ref = 0x88,
184 .src = SMS_ADDR("834568373569772"),
185 .dst = SMS_ADDR("834568373569378"),
186 .validity_minutes = 200,
187 .protocol_id = 0xbb,
188 .ud_hdr_ind = 0x01,
189 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
190 },
191 .ud = &sms_tp_ud_set[2],
192 },
193 {
194 .name = "Truncated TP-UD (200 septets, 7-bit encoding)",
195 .sms = {
196 .msg_ref = 0xee,
197 .src = { 0x01, 0x00, "8786228337248772" },
198 .dst = { 0x00, 0x01, "8786228337248378" },
199 .validity_minutes = 360,
200 .protocol_id = 0xcc,
201 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
202 },
203 .ud = &sms_tp_ud_set[5],
204 },
205 {
206 .name = "Truncated TP-UD (255 octets, 8-bit encoding)",
207 .sms = {
208 .msg_ref = 0xee,
209 .src = { 0x01, 0x01, "8786228338248772" },
210 .dst = { 0xaa, 0xff, "8786228338248378" },
211 .validity_minutes = 360,
212 .protocol_id = 0xbb,
213 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
214 },
215 .ud = &sms_tp_ud_set[6],
216 },
217 {
218 .name = "Same MSISDN #1",
219 .sms = {
220 .msg_ref = 0x11,
221 .src = SMS_ADDR("72631"),
222 .dst = SMS_ADDR("72632"),
223 .validity_minutes = 10,
224 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
225 },
226 .ud = &sms_tp_ud_set[0],
227 },
228 {
229 .name = "Same MSISDN #2",
230 .sms = {
231 .msg_ref = 0x12,
232 .src = SMS_ADDR("72632"),
233 .dst = SMS_ADDR("72631"),
234 .validity_minutes = 10,
235 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
236 },
237 .ud = &sms_tp_ud_set[0],
238 },
239 {
240 .name = "Expired SMS",
241 .sms = {
242 .msg_ref = 0xde,
243 .src = SMS_ADDR("3974733772"),
244 .dst = SMS_ADDR("3974733378"),
245 .validity_minutes = 0,
246 /* SM TP-User-Data is taken from sms_tp_ud_set[] */
247 },
248 .ud = &sms_tp_ud_set[0],
249 },
250#if 0
251 /* FIXME: there is a bug that causes ASAN / Valgrind to complain */
252 {
253 .name = "Empty TP-UD",
254 .sms = {
255 .msg_ref = 0x38,
256 .src = SMS_ADDR("3678983772"),
257 .dst = SMS_ADDR("3678983378"),
258 .validity_minutes = 450,
259 .is_report = true,
260 .reply_path_req = 0x01,
261 .status_rep_req = 0x01,
262 .protocol_id = 0x55,
263 .data_coding_scheme = 0x08,
264 .ud_hdr_ind = 0x00,
265 .user_data_len = 0x00,
266 /* No TP-User-Data */
267 },
268 .ud = NULL,
269 },
270#endif
271};
272
273static void prepare_sms_test_set(void)
274{
275 int i;
276
277 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
278 struct sms_test *test = &sms_test_set[i];
279 const struct sms_tp_ud *ud = test->ud;
280
281 /* ID auto-increment */
282 test->sms.id = i + 1;
283
284 if (ud == NULL)
285 continue;
286
287 test->sms.data_coding_scheme = ud->dcs;
288 test->sms.user_data_len = ud->length;
289
290 if (ud->filler_byte) {
291 memset(test->sms.user_data, ud->filler_byte,
292 sizeof(test->sms.user_data));
293 } else {
294 memcpy(test->sms.user_data, ud->data, sizeof(ud->data));
295 if (ud->dec_text[0] != '\0')
296 strcpy(test->sms.text, ud->dec_text);
297 }
298 }
299}
300
301static void test_db_sms_store(void)
302{
303 int rc, i;
304
305 LOGP(DDB, LOGL_INFO, "Testing db_sms_store()...\n");
306
307 /* Store test SMS messages */
308 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
309 struct sms_test *test = &sms_test_set[i];
310
311 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
312
313 rc = db_sms_store(&test->sms);
314 if (!test->exp_db_sms_store_fail && rc == 0)
315 LOGPC(DDB, LOGL_INFO, "success, as expected\n");
316 else if (test->exp_db_sms_store_fail && rc != 0)
317 LOGPC(DDB, LOGL_INFO, "failure, as expected\n");
318 else
319 LOGPC(DDB, LOGL_ERROR, "unexpected rc=%d\n", rc);
320 }
321}
322
323static int verify_sms(const struct sms_test *test, const struct gsm_sms *sms)
324{
325 int rc;
326
327 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
328
329#define MATCH_SMS_ADDR(ADDR) \
330 if (strcmp(sms->ADDR.addr, test->sms.ADDR.addr) \
331 || sms->ADDR.npi != test->sms.ADDR.npi \
332 || sms->ADDR.ton != test->sms.ADDR.ton) { \
333 LOGPC(DDB, LOGL_ERROR, #ADDR " address mismatch\n"); \
334 return -EINVAL; \
335 }
336
337 MATCH_SMS_ADDR(src);
338 MATCH_SMS_ADDR(dst);
339
340#define MATCH_SMS_PARAM(PARAM, FMT) \
341 if (sms->PARAM != test->sms.PARAM) { \
342 LOGPC(DDB, LOGL_ERROR, \
343 #PARAM " mismatch: E%" FMT " vs A%" FMT "\n", \
344 test->sms.PARAM, sms->PARAM); \
345 return -EINVAL; \
346 }
347
348 MATCH_SMS_PARAM(id, "llu");
349 MATCH_SMS_PARAM(validity_minutes, "lu");
350 MATCH_SMS_PARAM(is_report, "i");
351 MATCH_SMS_PARAM(reply_path_req, PRIu8);
352 MATCH_SMS_PARAM(status_rep_req, PRIu8);
353 MATCH_SMS_PARAM(ud_hdr_ind, PRIu8);
354 MATCH_SMS_PARAM(protocol_id, PRIu8);
355 MATCH_SMS_PARAM(data_coding_scheme, PRIu8);
356 MATCH_SMS_PARAM(msg_ref, PRIu8);
357 MATCH_SMS_PARAM(user_data_len, PRIu8);
358
359 /* Compare TP-User-Data */
360 rc = memcmp(sms->user_data, test->sms.user_data,
361 sizeof(sms->user_data));
362 if (rc) {
363 LOGPC(DDB, LOGL_ERROR, "TP-User-Data mismatch (diff=%d/%zu)\n",
364 rc, sizeof(sms->user_data));
365 return -EINVAL;
366 }
367
368 /* Compare decoded text */
369 rc = strncmp(sms->text, test->sms.text, sizeof(sms->text));
370 if (rc) {
371 LOGPC(DDB, LOGL_ERROR, "TP-User-Data (text) mismatch (diff=%d/%zu)\n",
372 rc, sizeof(sms->text));
373 return -EINVAL;
374 }
375
376 LOGPC(DDB, LOGL_NOTICE, "match\n");
377 return 0;
378}
379
380static void test_db_sms_get(void)
381{
382 struct gsm_sms *sms;
383 int i;
384
385 LOGP(DDB, LOGL_INFO, "Testing db_sms_get()...\n");
386
387 /* Retrieve stored SMS messages */
388 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
389 const struct sms_test *test = &sms_test_set[i];
390
391 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
392
393 sms = db_sms_get(NULL, test->sms.id);
394 if (!test->exp_db_sms_get_fail && sms != NULL)
395 LOGPC(DDB, LOGL_INFO, "success, as expected\n");
396 else if (test->exp_db_sms_get_fail && sms == NULL)
397 LOGPC(DDB, LOGL_INFO, "failure, as expected\n");
398 else
399 LOGPC(DDB, LOGL_ERROR, "unexpected result\n");
400
401 if (sms) {
402 verify_sms(test, sms);
403 talloc_free(sms);
404 }
405 }
406}
407
408static void test_db_sms_delivery(void)
409{
410 struct gsm_sms *sms1, *sms2;
411 struct gsm_sms *sms;
412 int rc;
413
414 LOGP(DDB, LOGL_INFO, "Testing db_sms_get_next_unsent() "
415 "and db_sms_mark_delivered()...\n");
416
417 /* Retrieve both #1 and #2 */
418 sms1 = db_sms_get_next_unsent(NULL, 1, 0);
419 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#1): %s\n",
420 sms1 ? "found" : "not found");
421 if (sms1 != NULL)
422 verify_sms(&sms_test_set[0], sms1);
423
424 sms2 = db_sms_get_next_unsent(NULL, 2, 0);
425 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#2): %s\n",
426 sms2 ? "found" : "not found");
427 if (sms2 != NULL)
428 verify_sms(&sms_test_set[1], sms2);
429
430 /* Mark both #1 and #2 and delivered, release memory */
431 if (sms1) {
432 LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms1->id);
433 rc = db_sms_mark_delivered(sms1);
434 LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc);
435 talloc_free(sms1);
436 }
437
438 if (sms2) {
439 LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms2->id);
440 rc = db_sms_mark_delivered(sms2);
441 LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc);
442 talloc_free(sms2);
443 }
444
445 /* Expect #3 as the next undelivered */
446 sms = db_sms_get_next_unsent(NULL, 1, 0);
447 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(starting from #1): %s\n",
448 sms ? "found" : "not found");
449 if (sms) {
450 verify_sms(&sms_test_set[2], sms);
451 talloc_free(sms);
452 }
453}
454
455static void test_db_sms_delete(void)
456{
457 int rc;
458
459 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_sent_message_by_id()...\n");
460
461 /* Delete #1, which is marked as sent */
462 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#1, sent): ");
463 rc = db_sms_delete_sent_message_by_id(1);
464 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
465 /* Don't expect to retrieve this message anymore */
466 sms_test_set[0].exp_db_sms_get_fail = true;
467
468 /* Try to delete #3, which is not marked as sent */
469 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#3, not sent): ");
470 rc = db_sms_delete_sent_message_by_id(3);
471 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
472 /* Do expect to retrieve this message anyway */
473 sms_test_set[2].exp_db_sms_get_fail = false;
474
475 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_by_msisdn()...\n");
476
477 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_by_msisdn('72631'): ");
478 rc = db_sms_delete_by_msisdn("72631");
479 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
480
481 /* Don't expect both #8 and #9 anymore */
482 sms_test_set[7].exp_db_sms_get_fail = true;
483 sms_test_set[8].exp_db_sms_get_fail = true;
484
485 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_oldest_expired_message()...\n");
486
487 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_oldest_expired_message()\n");
488 db_sms_delete_oldest_expired_message();
489
490 /* Don't expect #10 anymore */
491 sms_test_set[9].exp_db_sms_get_fail = true;
492
493 /* We need to make sure that we removed exactly what we expected to remove */
494 LOGP(DDB, LOGL_INFO, "Expectations updated, retrieving all messages again\n");
495 test_db_sms_get();
496}
497
498static struct log_info_cat db_sms_test_categories[] = {
499 [DDB] = {
500 .name = "DDB",
501 .description = "Database Layer",
502 .enabled = 1, .loglevel = LOGL_DEBUG,
503 },
504};
505
506static struct log_info info = {
507 .cat = db_sms_test_categories,
508 .num_cat = ARRAY_SIZE(db_sms_test_categories),
509};
510
511int main(int argc, char **argv)
512{
513 void *logging_ctx;
514 int rc;
515
516 /* Track the use of talloc NULL memory contexts */
517 talloc_enable_null_tracking();
518
519 talloc_ctx = talloc_named_const(NULL, 0, "db_sms_test");
520 logging_ctx = talloc_named_const(talloc_ctx, 0, "logging");
521 osmo_init_logging2(logging_ctx, &info);
522
523 OSMO_ASSERT(osmo_stderr_target);
524 log_set_use_color(osmo_stderr_target, 0);
525 log_set_print_timestamp(osmo_stderr_target, 0);
526 log_set_print_filename(osmo_stderr_target, 0);
527 log_set_print_category(osmo_stderr_target, 1);
528 log_set_print_level(osmo_stderr_target, 1);
529
530#if 0
531 /* Having the database stored in a regular file may be useful
532 * for debugging, but this comes at the price of performance. */
533 FILE *dbf = fopen("db_sms_test.db", "wb");
534 OSMO_ASSERT(dbf != NULL);
535 fclose(dbf);
536#endif
537
538 /* Init a volatile database in RAM */
539 LOGP(DDB, LOGL_DEBUG, "Init a new database\n");
540
541 /* HACK: db_init() prints libdbi version using LOGL_NOTICE, so
542 * the test output is not deterministic. Let's suppress this
543 * message by increasing the log level to LOGL_ERROR. */
544 log_parse_category_mask(osmo_stderr_target, "DDB,7");
545 rc = db_init(":memory:");
546 OSMO_ASSERT(rc == 0);
547
548 /* HACK: relax log level back to LOGL_DEBUG (see note above) */
549 log_parse_category_mask(osmo_stderr_target, "DDB,1");
550
551 /* Prepare some tables */
552 rc = db_prepare();
553 OSMO_ASSERT(rc == 0);
554 LOGP(DDB, LOGL_DEBUG, "Init complete\n");
555
556 /* Prepare the test set */
557 prepare_sms_test_set();
558
559 test_db_sms_store();
560 test_db_sms_get();
561
562 test_db_sms_delivery();
563 test_db_sms_delete();
564
565 /* Close the database */
566 db_fini();
567
568 /* Deinit logging */
569 log_fini();
570
571 /* Check for memory leaks */
572 rc = talloc_total_blocks(talloc_ctx);
573 OSMO_ASSERT(rc == 2); /* db_sms_test + logging */
574 talloc_free(talloc_ctx);
575
576 talloc_report_full(NULL, stderr);
577 talloc_disable_null_tracking();
578
579 return 0;
580}