/* (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdio.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>

#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>

#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/logging.h>

#define comment_start() fprintf(stderr, "\n===== %s\n", __func__);
#define comment(fmt, args...) fprintf(stderr, "\n--- " fmt "\n\n", ## args);
#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__);

#define fill_invalid(x) _fill_invalid(&x, sizeof(x))
static void _fill_invalid(void *dest, size_t size)
{
	uint8_t *pos = dest;
	size_t remain = size;
	int wrote = 0;
	do {
		remain -= wrote;
		pos += wrote;
		wrote = snprintf((void*)pos, remain, "-invalid-data");
	} while (wrote < remain);
}

/* Perform a function call and verbosely assert that its return value is as expected.
 * The return code is then available in g_rc. */
#define ASSERT_RC(call, expect_rc) \
	do { \
    if ((expect_rc) == -ENOKEY) \
		  fprintf(stderr, #call " --> -ENOKEY\n"); \
    else if ((expect_rc) == -ENOTSUP) \
		  fprintf(stderr, #call " --> -ENOTSUP\n"); \
    else \
		  fprintf(stderr, #call " --> " #expect_rc "\n"); \
		g_rc = call; \
		if (g_rc != (expect_rc)) \
			fprintf(stderr, " MISMATCH: got rc = %d, expected: " \
                                        #expect_rc " = %d\n", g_rc, expect_rc); \
		OSMO_ASSERT(g_rc == (expect_rc)); \
		fprintf(stderr, "\n"); \
	} while (0)

/* Do db_subscr_get_by_xxxx and verbosely assert that its return value is as expected.
 * Print the subscriber struct to stderr to be validated by db_test.err.
 * The result is then available in g_subscr. */
#define ASSERT_SEL(by, val, expect_rc) \
	do { \
		int rc; \
		fill_invalid(g_subscr); \
    if ((expect_rc) == -ENOKEY) \
		  fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOKEY \n"); \
    else if ((expect_rc) == -ENOTSUP) \
		  fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOTSUP \n"); \
    else \
		  fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \
                                #expect_rc "\n"); \
		rc = db_subscr_get_by_##by(dbc, val, &g_subscr); \
		if (rc != (expect_rc)) \
			fprintf(stderr, " MISMATCH: got rc = %d, expected: " \
                                        #expect_rc " = %d\n", rc, expect_rc); \
		OSMO_ASSERT(rc == (expect_rc)); \
		if (!rc) \
			dump_subscr(&g_subscr); \
		fprintf(stderr, "\n"); \
	} while (0)

/* Do db_get_auth_data() and verbosely assert that its return value is as expected.
 * Print the subscriber struct to stderr to be validated by db_test.err.
 * The results are then available in g_aud2g and g_aud3g. */
#define ASSERT_SEL_AUD(imsi, expect_rc, expect_id) \
	do { \
		fill_invalid(g_aud2g); \
		fill_invalid(g_aud3g); \
		g_id = 0; \
		ASSERT_RC(db_get_auth_data(dbc, imsi, &g_aud2g, &g_aud3g, &g_id), expect_rc); \
		if (!g_rc) { \
			dump_aud("2G", &g_aud2g); \
			dump_aud("3G", &g_aud3g); \
		}\
		if (g_id != expect_id) {\
			fprintf(stderr, "MISMATCH: got subscriber id %"PRId64 \
				", expected %"PRId64"\n", g_id, (int64_t)(expect_id)); \
			OSMO_ASSERT(g_id == expect_id); \
		} \
		fprintf(stderr, "\n"); \
	} while (0)

#define N_VECTORS 3

#define ASSERT_DB_GET_AUC(imsi, expect_rc) \
	do { \
		struct osmo_auth_vector vec[N_VECTORS]; \
		ASSERT_RC(db_get_auc(dbc, imsi, 3, vec, N_VECTORS, NULL, NULL, false), expect_rc); \
	} while (0)

/* Not linking the real auc_compute_vectors(), just returning num_vec.
 * This gets called by db_get_auc(), but we're only interested in its rc. */
int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec,
			struct osmo_sub_auth_data *aud2g,
			struct osmo_sub_auth_data *aud3g,
			const uint8_t *rand_auts, const uint8_t *auts)
{ return num_vec; }

static struct db_context *dbc = NULL;
static void *ctx = NULL;
static struct hlr_subscriber g_subscr;
static struct osmo_sub_auth_data g_aud2g;
static struct osmo_sub_auth_data g_aud3g;
static int g_rc;
static int64_t g_id;

#define Pfv(name, fmt, val) \
	fprintf(stderr, "  ." #name " = " fmt ",\n", val)
#define Pfo(name, fmt, obj) \
	Pfv(name, fmt, obj->name)

/* Print a subscriber struct to stderr to be validated by db_test.err. */
void dump_subscr(struct hlr_subscriber *subscr)
{
#define Ps(name) \
	if (*subscr->name) \
		Pfo(name, "'%s'", subscr)
#define Pgt(name) \
	Pfv(name, "%s", osmo_ipa_name_to_str(&subscr->name))
#define Pd(name) \
	Pfv(name, "%"PRId64, (int64_t)subscr->name)
#define Pd_nonzero(name) \
	if (subscr->name) \
		Pd(name)
#define Pb(if_val, name) \
	if (subscr->name == (if_val)) \
		Pfv(name, "%s", subscr->name ? "true" : "false")

	fprintf(stderr, "struct hlr_subscriber {\n");
	Pd(id);
	Ps(imsi);
	Ps(msisdn);
	Ps(imei);
	Ps(vlr_number);
	Ps(sgsn_number);
	Ps(sgsn_address);
	Pd_nonzero(periodic_lu_timer);
	Pd_nonzero(periodic_rau_tau_timer);
	Pb(false, nam_cs);
	Pb(false, nam_ps);
	if (subscr->lmsi)
		Pfo(lmsi, "0x%x", subscr);
	Pb(true, ms_purged_cs);
	Pb(true, ms_purged_ps);
	fprintf(stderr, "}\n");
#undef Ps
#undef Pd
#undef Pd_nonzero
#undef Pb
}

void dump_aud(const char *label, struct osmo_sub_auth_data *aud)
{
	if (aud->type == OSMO_AUTH_TYPE_NONE) {
		fprintf(stderr, "%s: none\n", label);
		return;
	}

	fprintf(stderr, "%s: struct osmo_sub_auth_data {\n", label);
#define Pf(name, fmt) \
	Pfo(name, fmt, aud)
#define Phex(name) \
	Pfv(name, "'%s'", osmo_hexdump_nospc(aud->name, sizeof(aud->name)))

	Pfv(type, "%s", osmo_sub_auth_type_name(aud->type));
	Pfv(algo, "%s", osmo_auth_alg_name(aud->algo));
	switch (aud->type) {
	case OSMO_AUTH_TYPE_GSM:
		Phex(u.gsm.ki);
		break;
	case OSMO_AUTH_TYPE_UMTS:
		Phex(u.umts.opc);
		Pf(u.umts.opc_is_op, "%u");
		Phex(u.umts.k);
		Phex(u.umts.amf);
		if (aud->u.umts.sqn) {
			Pf(u.umts.sqn, "%"PRIu64);
			Pf(u.umts.sqn, "0x%"PRIx64);
		}
		if (aud->u.umts.ind_bitlen)
			Pf(u.umts.ind_bitlen, "%u");
		break;
	default:
		OSMO_ASSERT(false);
	}

	fprintf(stderr, "}\n");

#undef Pf
#undef Phex
}

void db_raw_sql(struct db_context *dbc, const char *sql)
{
	sqlite3_stmt *stmt;

	fprintf(stderr, "raw SQL: %s\n", sql);
	ASSERT_RC(sqlite3_prepare_v2(dbc->db, sql, -1, &stmt, NULL), SQLITE_OK);
	ASSERT_RC(sqlite3_step(stmt), SQLITE_DONE);
	db_remove_reset(stmt);
	sqlite3_finalize(stmt);
}

static const char *imsi0 = "123456789000000";
static const char *imsi1 = "123456789000001";
static const char *imsi2 = "123456789000002";
static const char *short_imsi = "123456";
static const char *unknown_imsi = "999999999";

static int db_subscr_lu_str(struct db_context *dbc, int64_t subscr_id,
			    const char *vlr_or_sgsn_number, bool is_ps)
{
	struct osmo_ipa_name vlr_nr;
	osmo_ipa_name_set_str(&vlr_nr, vlr_or_sgsn_number);
	return db_subscr_lu(dbc, subscr_id, &vlr_nr, is_ps, NULL);
}

static void test_subscr_create_update_sel_delete()
{
	int64_t id0, id1, id2, id_short;
	comment_start();

	comment("Create with valid / invalid IMSI");

	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	id0 = g_subscr.id;
	ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi1, 0);
	id1 = g_subscr.id;
	ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi2, 0);
	id2 = g_subscr.id;
	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EEXIST);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EEXIST);
	ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EEXIST);
	ASSERT_SEL(imsi, imsi1, 0);
	ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EEXIST);
	ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EEXIST);
	ASSERT_SEL(imsi, imsi2, 0);

	ASSERT_RC(db_subscr_create(dbc, "123456789 000003", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
	ASSERT_SEL(imsi, "123456789000003", -ENOENT);

	ASSERT_RC(db_subscr_create(dbc, "123456789000002123456", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS),
		  -EINVAL);
	ASSERT_SEL(imsi, "123456789000002123456", -ENOENT);

	ASSERT_RC(db_subscr_create(dbc, "foobar123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
	ASSERT_SEL(imsi, "foobar123", -ENOENT);

	ASSERT_RC(db_subscr_create(dbc, "123", DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), -EINVAL);
	ASSERT_SEL(imsi, "123", -ENOENT);

	ASSERT_RC(db_subscr_create(dbc, short_imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, short_imsi, 0);
	id_short = g_subscr.id;

	comment("Check if subscriber exists (by IMSI)");

	ASSERT_RC(db_subscr_exists_by_imsi(dbc, imsi0), 0);
	ASSERT_RC(db_subscr_exists_by_imsi(dbc, unknown_imsi), -ENOENT);

	comment("Set valid / invalid MSISDN");

	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321"), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "54321", 0);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "54321012345678912345678"), -EINVAL);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "54321", 0);
	ASSERT_SEL(msisdn, "54321012345678912345678", -ENOENT);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "543 21"), -EINVAL);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "543 21", -ENOENT);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "foobar123"), -EINVAL);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "foobar123", -ENOENT);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "5"), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "5", 0);
	ASSERT_SEL(msisdn, "54321", -ENOENT);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "543210123456789"), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "543210123456789", 0);
	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0,
					  "5432101234567891"), -EINVAL);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_SEL(msisdn, "5432101234567891", -ENOENT);

	comment("Check if subscriber exists (by MSISDN)");

	ASSERT_RC(db_subscr_exists_by_msisdn(dbc, "543210123456789"), 0);
	ASSERT_RC(db_subscr_exists_by_msisdn(dbc, "5432101234567891"), -ENOENT);

	comment("Set MSISDN on non-existent / invalid IMSI");

	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99"), -ENOENT);
	ASSERT_SEL(msisdn, "99", -ENOENT);

	ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99"), -ENOENT);
	ASSERT_SEL(msisdn, "99", -ENOENT);

	comment("Set valid / invalid IMEI");

	ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0);
	ASSERT_SEL(imei, "12345678901234", 0);

	ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "123456789012345"), -EINVAL); /* too long */
	ASSERT_SEL(imei, "12345678901234", 0);
	ASSERT_SEL(imei, "123456789012345", -ENOENT);

	comment("Set the same IMEI again");
	ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0);
	ASSERT_SEL(imei, "12345678901234", 0);

	comment("Remove IMEI");
	ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, NULL), 0);
	ASSERT_SEL(imei, "12345678901234", -ENOENT);

	comment("Set / unset nam_cs and nam_ps");

	/*                                nam_val, is_ps */
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	comment("Set / unset nam_cs and nam_ps *again*");
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	comment("Set nam_cs and nam_ps on non-existent / invalid IMSI");

	ASSERT_RC(db_subscr_nam(dbc, unknown_imsi, false, true), -ENOENT);
	ASSERT_RC(db_subscr_nam(dbc, unknown_imsi, false, false), -ENOENT);
	ASSERT_SEL(imsi, unknown_imsi, -ENOENT);

	ASSERT_RC(db_subscr_nam(dbc, "foobar", false, true), -ENOENT);
	ASSERT_RC(db_subscr_nam(dbc, "foobar", false, false), -ENOENT);

	comment("Record LU for PS and CS (SGSN and VLR names)");

	ASSERT_RC(db_subscr_lu_str(dbc, id0, "5952", true), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "712", false), 0);
	ASSERT_SEL(id, id0, 0);

	comment("Record LU for PS and CS (SGSN and VLR names) *again*");

	ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
	ASSERT_SEL(id, id0, 0);

	comment("Unset LU info for PS and CS (SGSN and VLR names)");
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "", true), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "", false), 0);
	ASSERT_SEL(id, id0, 0);

	ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, true), 0);
	ASSERT_SEL(id, id0, 0);
	ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, false), 0);
	ASSERT_SEL(id, id0, 0);

	comment("Record LU for non-existent ID");
	ASSERT_RC(db_subscr_lu_str(dbc, 99999, "5952", true), -ENOENT);
	ASSERT_RC(db_subscr_lu_str(dbc, 99999, "712", false), -ENOENT);
	ASSERT_SEL(id, 99999, -ENOENT);

	comment("Purge and un-purge PS and CS");

	/*                               purge_val, is_ps */
	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	comment("Purge PS and CS *again*");

	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	comment("Purge on non-existent / invalid IMSI");

	ASSERT_RC(db_subscr_purge(dbc, unknown_imsi, true, true), -ENOENT);
	ASSERT_SEL(imsi, unknown_imsi, -ENOENT);
	ASSERT_RC(db_subscr_purge(dbc, unknown_imsi, true, false), -ENOENT);
	ASSERT_SEL(imsi, unknown_imsi, -ENOENT);

	comment("Delete non-existent / invalid IDs");

	ASSERT_RC(db_subscr_delete_by_id(dbc, 999), -ENOENT);
	ASSERT_RC(db_subscr_delete_by_id(dbc, -10), -ENOENT);

	comment("Delete subscribers");

	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id0), 0);
	ASSERT_SEL(imsi, imsi0, -ENOENT);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id0), -ENOENT);

	ASSERT_SEL(imsi, imsi1, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id1), 0);
	ASSERT_SEL(imsi, imsi1, -ENOENT);

	ASSERT_SEL(imsi, imsi2, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id2), 0);
	ASSERT_SEL(imsi, imsi2, -ENOENT);

	ASSERT_SEL(imsi, short_imsi, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id_short), 0);
	ASSERT_SEL(imsi, short_imsi, -ENOENT);

	comment("Create and delete subscribers with non-default nam_cs and nam_ps");

	ASSERT_RC(db_subscr_create(dbc, imsi0, 0x00), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	id0 = g_subscr.id;
	ASSERT_RC(db_subscr_create(dbc, imsi1, DB_SUBSCR_FLAG_NAM_CS), 0);
	ASSERT_SEL(imsi, imsi1, 0);
	id1 = g_subscr.id;
	ASSERT_RC(db_subscr_create(dbc, imsi2, DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi2, 0);
	id2 = g_subscr.id;

	ASSERT_RC(db_subscr_delete_by_id(dbc, id0), 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id1), 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id2), 0);

	comment_end();
}

static const struct sub_auth_data_str *mk_aud_2g(enum osmo_auth_algo algo,
						 const char *ki)
{
	static struct sub_auth_data_str aud;
	aud = (struct sub_auth_data_str){
		.type = OSMO_AUTH_TYPE_GSM,
		.algo = algo,
		.u.gsm.ki = ki,
	};
	return &aud;
}

static const struct sub_auth_data_str *mk_aud_3g(enum osmo_auth_algo algo,
						 const char *opc, bool opc_is_op,
						 const char *k, unsigned int ind_bitlen)
{
	static struct sub_auth_data_str aud;
	aud = (struct sub_auth_data_str){
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = algo,
		.u.umts.k = k,
		.u.umts.opc = opc,
		.u.umts.opc_is_op = opc_is_op ? 1 : 0,
		.u.umts.ind_bitlen = ind_bitlen,
	};
	return &aud;
}

static void test_subscr_aud()
{
	int64_t id;

	comment_start();

	comment("Get auth data for non-existent subscriber");
	ASSERT_SEL_AUD(unknown_imsi, -ENOENT, 0);
	ASSERT_DB_GET_AUC(imsi0, -ENOENT);

	comment("Create subscriber");

	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	id = g_subscr.id;
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);


	comment("Set auth data, 2G only");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);
	ASSERT_DB_GET_AUC(imsi0, N_VECTORS);

	/* same again */
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_COMP128v2, "BeadedBeeAced1EbbedDefacedFacade")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "DeafBeddedBabeAcceededFadedDecaf")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Remove 2G auth data");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)),
		0);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);

	/* Removing nothing results in -ENOENT */
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)),
		-ENOENT);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")),
		0);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);


	comment("Set auth data, 3G only");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", true,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);
	ASSERT_DB_GET_AUC(imsi0, N_VECTORS);

	/* same again */
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", true,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "Deaf0ff1ceD0d0DabbedD1ced1ceF00d", true,
			  "F1bbed0afD0eF0bD0ffed0ddF1fe0b0e", 0)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", false,
			  "DeafBeddedBabeAcceededFadedDecaf",
			  OSMO_MILENAGE_IND_BITLEN_MAX)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "CededEffacedAceFacedBadFadedBeef", false,
			  "BeefedCafeFaceAcedAddedDecadeFee", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Remove 3G auth data");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)),
		0);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);

	/* Removing nothing results in -ENOENT */
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)),
		-ENOENT);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "CededEffacedAceFacedBadFadedBeef", false,
			  "BeefedCafeFaceAcedAddedDecadeFee", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);
	ASSERT_DB_GET_AUC(imsi0, N_VECTORS);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_NONE,
			  "asdfasdfasd", false,
			  "asdfasdfasdf", 99999)),
		0);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);


	comment("Set auth data, 2G and 3G");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "CededEffacedAceFacedBadFadedBeef")),
		0);
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", false,
			  "DeafBeddedBabeAcceededFadedDecaf", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);
	ASSERT_DB_GET_AUC(imsi0, N_VECTORS);


	comment("Set invalid auth data");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(99999, "f000000000000f00000000000f000000")),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_XOR, "f000000000000f00000000000f000000f00000000")),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_XOR, "f00")),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_2g(OSMO_AUTH_ALG_MILENAGE, "0123456789abcdef0123456789abcdef")),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "0f000000000000f00000000000f000000", false,
			  "f000000000000f00000000000f000000", 5)),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "f000000000000f00000000000f000000", false,
			  "000000000000f00000000000f000000", 5)),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "f000000000000f00000000000f000000", false,
			  "f000000000000f00000000000f000000",
			  OSMO_MILENAGE_IND_BITLEN_MAX + 1)),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "X000000000000f00000000000f000000", false,
			  "f000000000000f00000000000f000000", 5)),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "f000000000000f00000000000f000000", false,
			  "f000000000000 f00000000000 f000000", 5)),
		-EINVAL);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Delete subscriber");

	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
	ASSERT_SEL(imsi, imsi0, -ENOENT);

	comment("Re-add subscriber and verify auth data didn't come back");

	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	/* For this test to work, we want to get the same subscriber ID back,
	 * and make sure there are no auth data leftovers for this ID. */
	OSMO_ASSERT(id == g_subscr.id);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);
	ASSERT_DB_GET_AUC(imsi0, -ENOKEY);

	ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
	ASSERT_SEL(imsi, imsi0, -ENOENT);
	ASSERT_DB_GET_AUC(imsi0, -ENOENT);

	comment_end();
}

/* Make each key too short in this test. Note that we can't set them longer than the allowed size without changing the
 * table structure. */
static void test_subscr_aud_invalid_len()
{
	int64_t id;

	comment_start();
	comment("Create subscriber");
	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi0, 0);
	id = g_subscr.id;


	/* Invalid Ki length */
	comment("Set auth data, 2G only, with invalid Ki length");
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		  mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")),
		  0);
	/* Use raw SQL to avoid length check in db_subscr_update_aud_by_id(). This changes all rows in the table, which
         * is fine for this test (implicit WHERE 1). */
	db_raw_sql(dbc, "UPDATE auc_2g SET ki = '0123456789abcdef0123456789abcde'");
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);

	comment("Remove 2G auth data");
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		  mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)),
		  0);

	/* Invalid K length */
	comment("Set auth data, 3G only, with invalid K length");
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", true,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	db_raw_sql(dbc, "UPDATE auc_3g SET k = 'C01ffedC1cadaeAc1d1f1edAcac1aB0'");
	ASSERT_SEL_AUD(imsi0, -EIO, id);

	/* Invalid OP length */
	comment("Set auth data, 3G only, with invalid OP length");
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", true,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	db_raw_sql(dbc, "UPDATE auc_3g SET op = 'BeefedCafeFaceAcedAddedDecadeFe'");
	ASSERT_SEL_AUD(imsi0, -EIO, id);

	/* Invalid OPC length */
	comment("Set auth data, 3G only, with invalid OPC length");
	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", false,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	db_raw_sql(dbc, "UPDATE auc_3g SET opc = 'BeefedCafeFaceAcedAddedDecadeFe'");
	ASSERT_SEL_AUD(imsi0, -EIO, id);


	comment("Delete subscriber");
	ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
	comment_end();
}

static void test_subscr_sqn()
{
	int64_t id;

	comment_start();

	comment("Set SQN for unknown subscriber");

	ASSERT_RC(db_update_sqn(dbc, 99, 999), -ENOENT);
	ASSERT_SEL(id, 99, -ENOENT);

	ASSERT_RC(db_update_sqn(dbc, 9999, 99), -ENOENT);
	ASSERT_SEL(id, 9999, -ENOENT);

	comment("Create subscriber");

	ASSERT_RC(db_subscr_create(dbc, imsi0, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS), 0);
	ASSERT_SEL(imsi, imsi0, 0);

	id = g_subscr.id;
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);

	comment("Set SQN, but no 3G auth data present");

	ASSERT_RC(db_update_sqn(dbc, id, 123), -ENOENT);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);

	ASSERT_RC(db_update_sqn(dbc, id, 543), -ENOENT);
	ASSERT_SEL_AUD(imsi0, -ENOKEY, id);

	comment("Set auth 3G data");

	ASSERT_RC(db_subscr_update_aud_by_id(dbc, id,
		mk_aud_3g(OSMO_AUTH_ALG_MILENAGE,
			  "BeefedCafeFaceAcedAddedDecadeFee", true,
			  "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)),
		0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Set SQN");

	ASSERT_RC(db_update_sqn(dbc, id, 23315), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_update_sqn(dbc, id, 23315), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_update_sqn(dbc, id, 423), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Set SQN: thru uint64_t range, using the int64_t SQLite bind");

	ASSERT_RC(db_update_sqn(dbc, id, 0), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_update_sqn(dbc, id, INT64_MAX), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_update_sqn(dbc, id, INT64_MIN), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	ASSERT_RC(db_update_sqn(dbc, id, UINT64_MAX), 0);
	ASSERT_SEL_AUD(imsi0, 0, id);

	comment("Delete subscriber");

	ASSERT_SEL(imsi, imsi0, 0);
	ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0);
	ASSERT_SEL(imsi, imsi0, -ENOENT);

	comment_end();
}

static void test_ind()
{
	comment_start();

#define ASSERT_IND(VLR, IND) do { \
		unsigned int ind; \
		struct osmo_cni_peer_id vlr; \
		OSMO_ASSERT(!osmo_cni_peer_id_set_str(&vlr, OSMO_CNI_PEER_ID_IPA_NAME, VLR)); \
		ASSERT_RC(db_ind(dbc, &vlr, &ind), 0); \
		fprintf(stderr, "%s ind = %u\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len), ind); \
		if (ind != (IND)) \
			fprintf(stderr, "  ERROR: expected " #IND "\n"); \
	} while (0)
#define IND_DEL(VLR) do { \
		struct osmo_cni_peer_id vlr; \
		OSMO_ASSERT(!osmo_cni_peer_id_set_str(&vlr, OSMO_CNI_PEER_ID_IPA_NAME, VLR)); \
		ASSERT_RC(db_ind_del(dbc, &vlr), 0); \
		fprintf(stderr, "%s ind deleted\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len)); \
	} while (0)

	ASSERT_IND("msc-23", 1);
	ASSERT_IND("sgsn-11", 2);
	ASSERT_IND("msc-42", 3);
	ASSERT_IND("sgsn-22", 4);
	ASSERT_IND("msc-0x17", 5);
	ASSERT_IND("sgsn-0xaa", 6);
	ASSERT_IND("msc-42", 3);
	ASSERT_IND("sgsn-22", 4);
	ASSERT_IND("msc-0x17", 5);
	ASSERT_IND("sgsn-0xaa", 6);
	ASSERT_IND("sgsn-0xbb", 7);
	ASSERT_IND("msc-0x2a", 8);
	ASSERT_IND("msc-42", 3);
	ASSERT_IND("sgsn-22", 4);
	ASSERT_IND("msc-23", 1);
	ASSERT_IND("sgsn-11", 2);

	IND_DEL("msc-0x17"); /* dropped IND == 5 */
	ASSERT_IND("msc-0x2a", 8); /* known CS remains where it is */
	ASSERT_IND("any-unknown", 9); /* new VLR takes a new IND from the end */

	comment_end();
}

static struct {
	bool verbose;
} cmdline_opts = {
	.verbose = false,
};

static void print_help(const char *program)
{
	printf("Usage:\n"
	       "  %s [-v] [N [N...]]\n"
	       "Options:\n"
	       "  -h --help      show this text.\n"
	       "  -v --verbose   print source file and line numbers\n",
	       program
	       );
}

static void handle_options(int argc, char **argv)
{
	while (1) {
		int option_index = 0, c;
		static struct option long_options[] = {
			{"help", 0, 0, 'h'},
			{"verbose", 1, 0, 'v'},
			{0, 0, 0, 0}
		};

		c = getopt_long(argc, argv, "hv",
				long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 'h':
			print_help(argv[0]);
			exit(0);
		case 'v':
			cmdline_opts.verbose = true;
			break;
		default:
			/* catch unknown options *as well as* missing arguments. */
			fprintf(stderr, "Error in command line options. Exiting.\n");
			exit(-1);
			break;
		}
	}

	if (optind < argc) {
		fprintf(stderr, "too many args\n");
		exit(-1);
	}
}

int main(int argc, char **argv)
{
	printf("db_test.c\n");

	ctx = talloc_named_const(NULL, 1, "db_test");

	handle_options(argc, argv);

	osmo_init_logging2(ctx, &hlr_log_info);
	log_set_print_filename2(osmo_stderr_target,
				cmdline_opts.verbose ?
					LOG_FILENAME_BASENAME :
					LOG_FILENAME_NONE);
	log_set_print_timestamp(osmo_stderr_target, 0);
	log_set_use_color(osmo_stderr_target, 0);
	log_set_print_category_hex(osmo_stderr_target, 0);
	log_set_print_category(osmo_stderr_target, 1);
	log_parse_category_mask(osmo_stderr_target, "DMAIN,1:DDB,1:DAUC,1");

	/* omit the SQLite version and compilation flags from test output */
	log_set_log_level(osmo_stderr_target, LOGL_ERROR);
	/* Disable SQLite logging so that we're not vulnerable on SQLite error messages changing across
	 * library versions. */
	dbc = db_open(ctx, "db_test.db", false, false);
	log_set_log_level(osmo_stderr_target, 0);
	OSMO_ASSERT(dbc);

	test_subscr_create_update_sel_delete();
	test_subscr_aud();
	test_subscr_aud_invalid_len();
	test_subscr_sqn();
	test_ind();

	printf("Done\n");
	db_close(dbc);
	return 0;
}

/* stubs */
void *lu_op_alloc_conn(void *conn)
{ OSMO_ASSERT(false); return NULL; }
void lu_op_tx_del_subscr_data(void *luop)
{ OSMO_ASSERT(false); }
void lu_op_free(void *luop)
{ OSMO_ASSERT(false); }
