blob: 24c86e533988058f0c9bb36ab2dbbdaf998f8f6e [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 },
Vadim Yanitskiye1e72472019-04-09 16:55:44 +0700250 {
251 .name = "Empty TP-UD",
252 .sms = {
253 .msg_ref = 0x38,
254 .src = SMS_ADDR("3678983772"),
255 .dst = SMS_ADDR("3678983378"),
256 .validity_minutes = 450,
257 .is_report = true,
258 .reply_path_req = 0x01,
259 .status_rep_req = 0x01,
260 .protocol_id = 0x55,
261 .data_coding_scheme = 0x08,
262 .ud_hdr_ind = 0x00,
263 .user_data_len = 0x00,
264 /* No TP-User-Data */
265 },
266 .ud = NULL,
267 },
Vadim Yanitskiye1e72472019-04-09 16:55:44 +0700268};
269
270static void prepare_sms_test_set(void)
271{
272 int i;
273
274 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
275 struct sms_test *test = &sms_test_set[i];
276 const struct sms_tp_ud *ud = test->ud;
277
278 /* ID auto-increment */
279 test->sms.id = i + 1;
280
281 if (ud == NULL)
282 continue;
283
284 test->sms.data_coding_scheme = ud->dcs;
285 test->sms.user_data_len = ud->length;
286
287 if (ud->filler_byte) {
288 memset(test->sms.user_data, ud->filler_byte,
289 sizeof(test->sms.user_data));
290 } else {
291 memcpy(test->sms.user_data, ud->data, sizeof(ud->data));
292 if (ud->dec_text[0] != '\0')
293 strcpy(test->sms.text, ud->dec_text);
294 }
295 }
296}
297
298static void test_db_sms_store(void)
299{
300 int rc, i;
301
302 LOGP(DDB, LOGL_INFO, "Testing db_sms_store()...\n");
303
304 /* Store test SMS messages */
305 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
306 struct sms_test *test = &sms_test_set[i];
307
308 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
309
310 rc = db_sms_store(&test->sms);
311 if (!test->exp_db_sms_store_fail && rc == 0)
312 LOGPC(DDB, LOGL_INFO, "success, as expected\n");
313 else if (test->exp_db_sms_store_fail && rc != 0)
314 LOGPC(DDB, LOGL_INFO, "failure, as expected\n");
315 else
316 LOGPC(DDB, LOGL_ERROR, "unexpected rc=%d\n", rc);
317 }
318}
319
320static int verify_sms(const struct sms_test *test, const struct gsm_sms *sms)
321{
322 int rc;
323
324 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
325
326#define MATCH_SMS_ADDR(ADDR) \
327 if (strcmp(sms->ADDR.addr, test->sms.ADDR.addr) \
328 || sms->ADDR.npi != test->sms.ADDR.npi \
329 || sms->ADDR.ton != test->sms.ADDR.ton) { \
330 LOGPC(DDB, LOGL_ERROR, #ADDR " address mismatch\n"); \
331 return -EINVAL; \
332 }
333
334 MATCH_SMS_ADDR(src);
335 MATCH_SMS_ADDR(dst);
336
337#define MATCH_SMS_PARAM(PARAM, FMT) \
338 if (sms->PARAM != test->sms.PARAM) { \
339 LOGPC(DDB, LOGL_ERROR, \
340 #PARAM " mismatch: E%" FMT " vs A%" FMT "\n", \
341 test->sms.PARAM, sms->PARAM); \
342 return -EINVAL; \
343 }
344
345 MATCH_SMS_PARAM(id, "llu");
346 MATCH_SMS_PARAM(validity_minutes, "lu");
347 MATCH_SMS_PARAM(is_report, "i");
348 MATCH_SMS_PARAM(reply_path_req, PRIu8);
349 MATCH_SMS_PARAM(status_rep_req, PRIu8);
350 MATCH_SMS_PARAM(ud_hdr_ind, PRIu8);
351 MATCH_SMS_PARAM(protocol_id, PRIu8);
352 MATCH_SMS_PARAM(data_coding_scheme, PRIu8);
353 MATCH_SMS_PARAM(msg_ref, PRIu8);
354 MATCH_SMS_PARAM(user_data_len, PRIu8);
355
356 /* Compare TP-User-Data */
357 rc = memcmp(sms->user_data, test->sms.user_data,
358 sizeof(sms->user_data));
359 if (rc) {
Pau Espin Pedrol17aa4642019-06-04 11:18:37 +0200360 LOGPC(DDB, LOGL_ERROR, "TP-User-Data mismatch\n");
Vadim Yanitskiye1e72472019-04-09 16:55:44 +0700361 return -EINVAL;
362 }
363
364 /* Compare decoded text */
365 rc = strncmp(sms->text, test->sms.text, sizeof(sms->text));
366 if (rc) {
Pau Espin Pedrol17aa4642019-06-04 11:18:37 +0200367 LOGPC(DDB, LOGL_ERROR, "TP-User-Data (text) mismatch\n");
Vadim Yanitskiye1e72472019-04-09 16:55:44 +0700368 return -EINVAL;
369 }
370
371 LOGPC(DDB, LOGL_NOTICE, "match\n");
372 return 0;
373}
374
375static void test_db_sms_get(void)
376{
377 struct gsm_sms *sms;
378 int i;
379
380 LOGP(DDB, LOGL_INFO, "Testing db_sms_get()...\n");
381
382 /* Retrieve stored SMS messages */
383 for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) {
384 const struct sms_test *test = &sms_test_set[i];
385
386 LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name);
387
388 sms = db_sms_get(NULL, test->sms.id);
389 if (!test->exp_db_sms_get_fail && sms != NULL)
390 LOGPC(DDB, LOGL_INFO, "success, as expected\n");
391 else if (test->exp_db_sms_get_fail && sms == NULL)
392 LOGPC(DDB, LOGL_INFO, "failure, as expected\n");
393 else
394 LOGPC(DDB, LOGL_ERROR, "unexpected result\n");
395
396 if (sms) {
397 verify_sms(test, sms);
398 talloc_free(sms);
399 }
400 }
401}
402
403static void test_db_sms_delivery(void)
404{
405 struct gsm_sms *sms1, *sms2;
406 struct gsm_sms *sms;
407 int rc;
408
409 LOGP(DDB, LOGL_INFO, "Testing db_sms_get_next_unsent() "
410 "and db_sms_mark_delivered()...\n");
411
412 /* Retrieve both #1 and #2 */
413 sms1 = db_sms_get_next_unsent(NULL, 1, 0);
414 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#1): %s\n",
415 sms1 ? "found" : "not found");
416 if (sms1 != NULL)
417 verify_sms(&sms_test_set[0], sms1);
418
419 sms2 = db_sms_get_next_unsent(NULL, 2, 0);
420 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#2): %s\n",
421 sms2 ? "found" : "not found");
422 if (sms2 != NULL)
423 verify_sms(&sms_test_set[1], sms2);
424
425 /* Mark both #1 and #2 and delivered, release memory */
426 if (sms1) {
427 LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms1->id);
428 rc = db_sms_mark_delivered(sms1);
429 LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc);
430 talloc_free(sms1);
431 }
432
433 if (sms2) {
434 LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms2->id);
435 rc = db_sms_mark_delivered(sms2);
436 LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc);
437 talloc_free(sms2);
438 }
439
440 /* Expect #3 as the next undelivered */
441 sms = db_sms_get_next_unsent(NULL, 1, 0);
442 LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(starting from #1): %s\n",
443 sms ? "found" : "not found");
444 if (sms) {
445 verify_sms(&sms_test_set[2], sms);
446 talloc_free(sms);
447 }
448}
449
450static void test_db_sms_delete(void)
451{
452 int rc;
453
454 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_sent_message_by_id()...\n");
455
456 /* Delete #1, which is marked as sent */
457 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#1, sent): ");
458 rc = db_sms_delete_sent_message_by_id(1);
459 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
460 /* Don't expect to retrieve this message anymore */
461 sms_test_set[0].exp_db_sms_get_fail = true;
462
463 /* Try to delete #3, which is not marked as sent */
464 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#3, not sent): ");
465 rc = db_sms_delete_sent_message_by_id(3);
466 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
467 /* Do expect to retrieve this message anyway */
468 sms_test_set[2].exp_db_sms_get_fail = false;
469
470 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_by_msisdn()...\n");
471
472 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_by_msisdn('72631'): ");
473 rc = db_sms_delete_by_msisdn("72631");
474 LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc);
475
476 /* Don't expect both #8 and #9 anymore */
477 sms_test_set[7].exp_db_sms_get_fail = true;
478 sms_test_set[8].exp_db_sms_get_fail = true;
479
480 LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_oldest_expired_message()...\n");
481
482 LOGP(DDB, LOGL_NOTICE, "db_sms_delete_oldest_expired_message()\n");
483 db_sms_delete_oldest_expired_message();
484
485 /* Don't expect #10 anymore */
486 sms_test_set[9].exp_db_sms_get_fail = true;
487
488 /* We need to make sure that we removed exactly what we expected to remove */
489 LOGP(DDB, LOGL_INFO, "Expectations updated, retrieving all messages again\n");
490 test_db_sms_get();
491}
492
493static struct log_info_cat db_sms_test_categories[] = {
494 [DDB] = {
495 .name = "DDB",
496 .description = "Database Layer",
497 .enabled = 1, .loglevel = LOGL_DEBUG,
498 },
499};
500
501static struct log_info info = {
502 .cat = db_sms_test_categories,
503 .num_cat = ARRAY_SIZE(db_sms_test_categories),
504};
505
506int main(int argc, char **argv)
507{
508 void *logging_ctx;
509 int rc;
510
511 /* Track the use of talloc NULL memory contexts */
512 talloc_enable_null_tracking();
513
514 talloc_ctx = talloc_named_const(NULL, 0, "db_sms_test");
515 logging_ctx = talloc_named_const(talloc_ctx, 0, "logging");
516 osmo_init_logging2(logging_ctx, &info);
517
518 OSMO_ASSERT(osmo_stderr_target);
519 log_set_use_color(osmo_stderr_target, 0);
520 log_set_print_timestamp(osmo_stderr_target, 0);
521 log_set_print_filename(osmo_stderr_target, 0);
522 log_set_print_category(osmo_stderr_target, 1);
523 log_set_print_level(osmo_stderr_target, 1);
524
525#if 0
526 /* Having the database stored in a regular file may be useful
527 * for debugging, but this comes at the price of performance. */
528 FILE *dbf = fopen("db_sms_test.db", "wb");
529 OSMO_ASSERT(dbf != NULL);
530 fclose(dbf);
531#endif
532
533 /* Init a volatile database in RAM */
534 LOGP(DDB, LOGL_DEBUG, "Init a new database\n");
535
536 /* HACK: db_init() prints libdbi version using LOGL_NOTICE, so
537 * the test output is not deterministic. Let's suppress this
538 * message by increasing the log level to LOGL_ERROR. */
539 log_parse_category_mask(osmo_stderr_target, "DDB,7");
540 rc = db_init(":memory:");
541 OSMO_ASSERT(rc == 0);
542
543 /* HACK: relax log level back to LOGL_DEBUG (see note above) */
544 log_parse_category_mask(osmo_stderr_target, "DDB,1");
545
546 /* Prepare some tables */
547 rc = db_prepare();
548 OSMO_ASSERT(rc == 0);
549 LOGP(DDB, LOGL_DEBUG, "Init complete\n");
550
551 /* Prepare the test set */
552 prepare_sms_test_set();
553
554 test_db_sms_store();
555 test_db_sms_get();
556
557 test_db_sms_delivery();
558 test_db_sms_delete();
559
560 /* Close the database */
561 db_fini();
562
563 /* Deinit logging */
564 log_fini();
565
566 /* Check for memory leaks */
567 rc = talloc_total_blocks(talloc_ctx);
568 OSMO_ASSERT(rc == 2); /* db_sms_test + logging */
569 talloc_free(talloc_ctx);
570
571 talloc_report_full(NULL, stderr);
572 talloc_disable_null_tracking();
573
574 return 0;
575}