aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/hlr/db.h8
-rw-r--r--sql/hlr.sql11
-rw-r--r--src/db.c107
-rw-r--r--src/db_hlr.c122
-rw-r--r--src/gsupclient/gsup_peer_id.c3
-rw-r--r--src/hlr.c17
-rw-r--r--tests/db/db_test.c53
-rw-r--r--tests/db/db_test.err101
-rw-r--r--tests/db_upgrade/db_upgrade_test.ok11
9 files changed, 428 insertions, 5 deletions
diff --git a/include/osmocom/hlr/db.h b/include/osmocom/hlr/db.h
index 9309b8f..d283192 100644
--- a/include/osmocom/hlr/db.h
+++ b/include/osmocom/hlr/db.h
@@ -4,6 +4,7 @@
#include <sqlite3.h>
#include <osmocom/gsupclient/gsup_peer_id.h>
+#include <osmocom/gsm/gsup.h>
struct hlr;
@@ -33,6 +34,9 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN_PS,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
+ DB_STMT_IND_SELECT,
+ DB_STMT_IND_ADD,
+ DB_STMT_IND_DEL,
_NUM_DB_STMT
};
@@ -163,6 +167,10 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
+int db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
+ unsigned int *ind);
+int db_ind_del(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr);
+
/*! Call sqlite3_column_text() and copy result to a char[].
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
* \param[in] stmt An sqlite3_stmt*.
diff --git a/sql/hlr.sql b/sql/hlr.sql
index 98e586d..6b05b6d 100644
--- a/sql/hlr.sql
+++ b/sql/hlr.sql
@@ -79,8 +79,17 @@ CREATE TABLE auc_3g (
ind_bitlen INTEGER NOT NULL DEFAULT 5
);
+CREATE TABLE ind (
+ cn_domain INTEGER NOT NULL,
+ -- 3G auth IND bucket to be used for this VLR, where IND = (idx << 1) + cn_domain -1
+ ind INTEGER PRIMARY KEY,
+ -- VLR identification, usually the GSUP source_name
+ vlr TEXT NOT NULL,
+ UNIQUE (cn_domain, vlr)
+);
+
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
-- Set HLR database schema version number
-- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
-PRAGMA user_version = 5;
+PRAGMA user_version = 6;
diff --git a/src/db.c b/src/db.c
index 3cbd9c9..b7d749a 100644
--- a/src/db.c
+++ b/src/db.c
@@ -30,7 +30,7 @@
#include "db_bootstrap.h"
/* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
-#define CURRENT_SCHEMA_VERSION 5
+#define CURRENT_SCHEMA_VERSION 6
#define SEL_COLUMNS \
"id," \
@@ -87,6 +87,86 @@ static const char *stmt_sql[] = {
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
+ [DB_STMT_IND_SELECT] = "SELECT ind FROM ind WHERE cn_domain = $cn_domain AND vlr = $vlr",
+ [DB_STMT_IND_DEL] = "DELETE FROM ind WHERE cn_domain = $cn_domain AND vlr = $vlr",
+ [DB_STMT_IND_ADD] =
+ /* This SQL statement is quite the works, so let me elaborate.
+ * This is about auc_3g IND pool choice for a given attached VLR (MSC or SGSN).
+ * - We want to insert an unused IND into the table, where a CS IND should be odd-numbered and a PS IND
+ * should be even (see OS#4319). In short, an IND collision between MSC and SGSN of the same site is a
+ * grave sink of SQN numbers and HLR CPU cycles, so it is worth it to avoid that with 100% certainty.
+ * - We want to start from zero/one (for PS/CS) and,
+ * - When there is a gap due to deletion, we always want to first fill up the gaps before picking unused
+ * INDs from the end of the range.
+ * - We also want to treat $cn_domain as an integer, to be ready for future added cn_domain enum values.
+ * That implies having one single table for all cn_domains,
+ * - The other benefit of having a single table for both cn_domains is that we can beyond all doubt
+ * prevent any IND assigned twice.
+ * - If too many sites show up for the IND_bitlen of a subscriber, the auc_3g code actually takes the
+ * modulo to fit in the IND_bitlen space, so here all we do is grow IND values into "all infinity",
+ * causing effective round-robin of any arbitrary IND_bitlen space. That is why we fill gaps first.
+ *
+ * $cn_domain is: PS=1 CS=2, so $cn_domain - 1 gives PS=0 CS=1
+ * Given any arbitrary nr, this always hits the right even/odd per CN domain:
+ * nr - (nr % 2) + ($cn_domain-1)
+ * However, CN domains are always spaced two apart, so we often want (nr + 2).
+ * With above always-hit-the-right-bucket, that gives
+ * (nr+2) - ((nr+2) % 2) + ($cn_domain-1)
+ * This modulo dance is aggressively applied to gracefully recover even when a user has manually
+ * modified the IND table to actually pair an even/odd IND to the wrong cn_domain.
+ *
+ * The deeper SELECT between THEN and ELSE picks the lowest unused IND for a given $cn_domain.
+ * However, that only works when there already is any one entry in the table.
+ * That's why we need the entire CASE WHEN .. THEN .. ELSE .. END stuff.
+ *
+ * That CASE's ELSE..END part returns the absolute first value for a $cn_domain for an empty table.
+ *
+ * The outermost SELECT puts the values ($cn_domain, $ind, $vlr) together.
+ *
+ * So, again, this time from outside to inside:
+ * INSERT...
+ * SELECT ($cn_domain, <IND>, $vlr)
+ *
+ * where <IND> is done like:
+ * CASE WHEN <table-already-has-such-$cn_domain>
+ * THEN
+ * <FIND-UNUSED-IND>
+ * ELSE
+ * <use-first-ind-for-this-$cn_domain>
+ *
+ * where in turn <FIND-UNUSED-IND> is [CC-BY-SA-4.0]
+ * kindly taken from the answer of https://stackoverflow.com/users/55159/quassnoi (MySQL section)
+ * to the question https://stackoverflow.com/questions/1312101/how-do-i-find-a-gap-in-running-counter-with-sql
+ * and modified to use the even/odd skipping according to $cn_domain instead of simple increment.
+ * <FIND-UNUSED-IND> works such that it selects an IND number for which IND + 2 yields no entry,
+ * modification here: the entry must also match the given $cn_domain.
+ *
+ * The C invoking this still first tries to just find an entry for a given $vlr, so when this statement
+ * is invoked, we actually definitely want to insert an entry and expect no constraint conflicts.
+ *
+ * Parameters are $cn_domain (integer) and $vlr (text). The $cn_domain should be either 1 (PS)
+ * or 2 (CS), any other value should default to 1 (because according to GSUP specs PS is the default).
+ */
+ "INSERT INTO ind (cn_domain, ind, vlr)"
+ "SELECT $cn_domain,"
+ " CASE WHEN EXISTS(SELECT NULL FROM ind WHERE cn_domain = $cn_domain LIMIT 1)"
+ " THEN"
+ " ("
+ " SELECT ((ind + 2) - ((ind + 2)%2) + ($cn_domain-1))"
+ " FROM ind as mo"
+ " WHERE NOT EXISTS ("
+ " SELECT NULL"
+ " FROM ind as mi"
+ " WHERE cn_domain = $cn_domain"
+ " AND mi.ind = ((mo.ind + 2) - ((mo.ind + 2)%2) + $cn_domain-1)"
+ " )"
+ " ORDER BY ind"
+ " LIMIT 1"
+ " )"
+ " ELSE ($cn_domain-1)"
+ " END ind"
+ " , $vlr"
+ ,
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -481,6 +561,30 @@ static int db_upgrade_v5(struct db_context *dbc)
return rc;
}
+static int db_upgrade_v6(struct db_context *dbc)
+{
+ int rc;
+ const char *statements[] = {
+ "CREATE TABLE ind (\n"
+ " cn_domain INTEGER NOT NULL,\n"
+ " -- 3G auth IND bucket to be used for this VLR, where IND = (idx << 1) + cn_domain -1\n"
+ " ind INTEGER PRIMARY KEY,\n"
+ " -- VLR identification, usually the GSUP source_name\n"
+ " vlr TEXT NOT NULL,\n"
+ " UNIQUE (cn_domain, vlr)\n"
+ ")"
+ ,
+ "PRAGMA user_version = 6",
+ };
+
+ rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements));
+ if (rc != SQLITE_DONE) {
+ LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 6\n");
+ return rc;
+ }
+ return rc;
+}
+
typedef int (*db_upgrade_func_t)(struct db_context *dbc);
static db_upgrade_func_t db_upgrade_path[] = {
db_upgrade_v1,
@@ -488,6 +592,7 @@ static db_upgrade_func_t db_upgrade_path[] = {
db_upgrade_v3,
db_upgrade_v4,
db_upgrade_v5,
+ db_upgrade_v6,
};
static int db_get_user_version(struct db_context *dbc)
diff --git a/src/db_hlr.c b/src/db_hlr.c
index 030a6a7..78d8e8f 100644
--- a/src/db_hlr.c
+++ b/src/db_hlr.c
@@ -884,3 +884,125 @@ out:
return ret;
}
+
+static int _db_ind_run(struct db_context *dbc, sqlite3_stmt *stmt, int cn_domain, const char *vlr, bool reset)
+{
+ int rc;
+
+ /* These are the current actual manifestations expected by DB_STMT_IND_SELECT. */
+ OSMO_ASSERT(cn_domain == 1 || cn_domain == 2);
+
+ if (!db_bind_int(stmt, "$cn_domain", cn_domain))
+ return -EIO;
+
+ if (!db_bind_text(stmt, "$vlr", vlr))
+ return -EIO;
+
+ /* execute the statement */
+ rc = sqlite3_step(stmt);
+ if (reset)
+ db_remove_reset(stmt);
+ return rc;
+}
+
+static int _db_ind_add(struct db_context *dbc, int cn_domain, const char *vlr)
+{
+ sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_ADD];
+ if (_db_ind_run(dbc, stmt, cn_domain, vlr, true) != SQLITE_DONE) {
+ LOGP(DDB, LOGL_ERROR, "Cannot create IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr, -1));
+ return -EIO;
+ }
+ return 0;
+}
+
+static int _db_ind_del(struct db_context *dbc, int cn_domain, const char *vlr)
+{
+ sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_DEL];
+ _db_ind_run(dbc, stmt, cn_domain, vlr, true);
+ /* We don't really care about the result. If it didn't exist, then that was the goal anyway. */
+ return 0;
+}
+
+static int _db_ind_get(struct db_context *dbc, int cn_domain, const char *vlr, unsigned int *ind)
+{
+ int ret = 0;
+ sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_SELECT];
+ int rc = _db_ind_run(dbc, stmt, cn_domain, vlr, false);
+ if (rc == SQLITE_DONE) {
+ /* Does not exist yet */
+ ret = -ENOENT;
+ goto out;
+ } else if (rc != SQLITE_ROW) {
+ LOGP(DDB, LOGL_ERROR, "Error executing SQL: %d\n", rc);
+ ret = -EIO;
+ goto out;
+ }
+
+ OSMO_ASSERT(ind);
+ *ind = sqlite3_column_int64(stmt, 0);
+out:
+ db_remove_reset(stmt);
+ return ret;
+}
+
+int _db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
+ unsigned int *ind, bool del)
+{
+ const char *vlr_name = NULL;
+ int rc;
+ int cn_domain_int;
+
+ switch (vlr->type) {
+ case OSMO_GSUP_PEER_ID_IPA_NAME:
+ if (vlr->ipa_name.len < 2 || vlr->ipa_name.val[vlr->ipa_name.len - 1] != '\0') {
+ LOGP(DDB, LOGL_ERROR, "Expecting VLR ipa_name to be zero terminated; found %s\n",
+ osmo_ipa_name_to_str(&vlr->ipa_name));
+ return -ENOTSUP;
+ }
+ vlr_name = (const char*)vlr->ipa_name.val;
+ break;
+ default:
+ LOGP(DDB, LOGL_ERROR, "Unsupported osmo_gsup_peer_id type: %s\n",
+ osmo_gsup_peer_id_type_name(vlr->type));
+ return -ENOTSUP;
+ }
+
+ switch (cn_domain) {
+ default:
+ /* According to GSUP specs, PS is the default. */
+ case OSMO_GSUP_CN_DOMAIN_PS:
+ cn_domain_int = 1;
+ break;
+ case OSMO_GSUP_CN_DOMAIN_CS:
+ cn_domain_int = 2;
+ break;
+ }
+
+ if (del)
+ return _db_ind_del(dbc, cn_domain_int, vlr_name);
+
+ rc = _db_ind_get(dbc, cn_domain_int, vlr_name, ind);
+ if (!rc)
+ return 0;
+
+ /* Does not exist yet, create. */
+ rc = _db_ind_add(dbc, cn_domain_int, vlr_name);
+ if (rc) {
+ LOGP(DDB, LOGL_ERROR, "Error creating IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr_name, -1));
+ return rc;
+ }
+
+ /* To be sure, query again from scratch. */
+ return _db_ind_get(dbc, cn_domain_int, vlr_name, ind);
+}
+
+int db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
+ unsigned int *ind)
+{
+ return _db_ind(dbc, cn_domain, vlr, ind, false);
+}
+
+int db_ind_del(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr)
+{
+ return _db_ind(dbc, cn_domain, vlr, NULL, true);
+}
diff --git a/src/gsupclient/gsup_peer_id.c b/src/gsupclient/gsup_peer_id.c
index 9ac3af9..0a7bd73 100644
--- a/src/gsupclient/gsup_peer_id.c
+++ b/src/gsupclient/gsup_peer_id.c
@@ -132,8 +132,11 @@ int osmo_gsup_peer_id_set_str(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_
va_list ap;
int rc;
+ *gsup_peer_id = (struct osmo_gsup_peer_id){};
+
switch (type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
+ gsup_peer_id->type = OSMO_GSUP_PEER_ID_IPA_NAME;
va_start(ap, str_fmt);
rc = osmo_ipa_name_set_str_va(&gsup_peer_id->ipa_name, str_fmt, ap);
va_end(ap);
diff --git a/src/hlr.c b/src/hlr.c
index 802ad4e..3d40240 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -280,12 +280,13 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
***********************************************************************/
/* process an incoming SAI request */
-static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
+static int rx_send_auth_info(struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_out = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
};
bool separation_bit = false;
+ unsigned int auc_3g_ind;
int rc;
subscr_create_on_demand(req->gsup.imsi);
@@ -293,6 +294,18 @@ static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS)
separation_bit = true;
+ rc = db_ind(g_hlr->dbc, req->gsup.cn_domain, &req->source_name, &auc_3g_ind);
+ if (rc) {
+ /* Super unlikely to fail: just getting and possibly adding an ID.
+ * If the DB per se fails, then below db_get_auc() should also fail.
+ * Still leave the benefit of the doubt at servicing instead of refusing. */
+ LOG_GSUP_REQ(req, LOGL_ERROR,
+ "Unable to determine 3G auth IND for source %s (rc=%d),"
+ " generating tuples with IND = 0\n",
+ osmo_gsup_peer_id_to_str(&req->source_name), rc);
+ auc_3g_ind = 0;
+ }
+
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
@@ -515,7 +528,7 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
- rx_send_auth_info(conn->auc_3g_ind, req);
+ rx_send_auth_info(req);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
rx_upd_loc_req(conn, req);
diff --git a/tests/db/db_test.c b/tests/db/db_test.c
index 4a0f3e8..203a8bc 100644
--- a/tests/db/db_test.c
+++ b/tests/db/db_test.c
@@ -918,6 +918,58 @@ static void test_subscr_sqn()
comment_end();
}
+static void test_ind()
+{
+ comment_start();
+
+#define ASSERT_IND(CN_DOMAIN, VLR, IND) do { \
+ unsigned int ind; \
+ struct osmo_gsup_peer_id vlr; \
+ OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
+ ASSERT_RC(db_ind(dbc, CN_DOMAIN, &vlr, &ind), 0); \
+ fprintf(stderr, #CN_DOMAIN " %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(CN_DOMAIN, VLR) do { \
+ struct osmo_gsup_peer_id vlr; \
+ OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
+ ASSERT_RC(db_ind_del(dbc, CN_DOMAIN, &vlr), 0); \
+ fprintf(stderr, #CN_DOMAIN " %s ind deleted\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len)); \
+ } while (0)
+#define CS OSMO_GSUP_CN_DOMAIN_CS
+#define PS OSMO_GSUP_CN_DOMAIN_PS
+
+ ASSERT_IND(CS, "msc-23", 1);
+ ASSERT_IND(PS, "sgsn-11", 0);
+ ASSERT_IND(CS, "msc-42", 3);
+ ASSERT_IND(PS, "sgsn-22", 2);
+ ASSERT_IND(CS, "msc-0x17", 5);
+ ASSERT_IND(PS, "sgsn-0xaa", 4);
+ ASSERT_IND(CS, "msc-42", 3);
+ ASSERT_IND(PS, "sgsn-22", 2);
+ ASSERT_IND(CS, "msc-0x17", 5);
+ ASSERT_IND(PS, "sgsn-0xaa", 4);
+ ASSERT_IND(CS, "msc-0x2a", 7);
+ ASSERT_IND(PS, "sgsn-0xbb", 6);
+ ASSERT_IND(CS, "msc-42", 3);
+ ASSERT_IND(PS, "sgsn-22", 2);
+ ASSERT_IND(CS, "msc-23", 1);
+ ASSERT_IND(PS, "sgsn-11", 0);
+
+ ASSERT_IND(CS, "same", 9);
+ ASSERT_IND(PS, "same", 8);
+ ASSERT_IND(CS, "same", 9);
+ ASSERT_IND(PS, "same", 8);
+
+ IND_DEL(CS, "msc-0x17"); /* dropped IND == 5 */
+ ASSERT_IND(PS, "unrelated-PS", 8);
+ ASSERT_IND(CS, "msc-0x2a", 7); /* known CS remains where it is */
+ ASSERT_IND(CS, "any-unknown-CS", 5); /* takes spot of IND == 5 */
+
+ comment_end();
+}
+
static struct {
bool verbose;
} cmdline_opts = {
@@ -998,6 +1050,7 @@ int main(int argc, char **argv)
test_subscr_aud();
test_subscr_aud_invalid_len();
test_subscr_sqn();
+ test_ind();
printf("Done\n");
db_close(dbc);
diff --git a/tests/db/db_test.err b/tests/db/db_test.err
index 871e722..2ca54fe 100644
--- a/tests/db/db_test.err
+++ b/tests/db/db_test.err
@@ -1613,3 +1613,104 @@ db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
===== test_subscr_sqn: SUCCESS
+
+===== test_ind
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-23\0" ind = 1
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-11\0" ind = 0
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-42\0" ind = 3
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-22\0" ind = 2
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-0x17\0" ind = 5
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-0xaa\0" ind = 4
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-42\0" ind = 3
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-22\0" ind = 2
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-0x17\0" ind = 5
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-0xaa\0" ind = 4
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-0x2a\0" ind = 7
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-0xbb\0" ind = 6
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-42\0" ind = 3
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-22\0" ind = 2
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-23\0" ind = 1
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "sgsn-11\0" ind = 0
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "same\0" ind = 9
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "same\0" ind = 8
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "same\0" ind = 9
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "same\0" ind = 8
+
+db_ind_del(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr) --> 0
+
+CS "msc-0x17\0" ind deleted
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
+
+PS "unrelated-PS\0" ind = 10
+
+ ERROR: expected 8
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "msc-0x2a\0" ind = 7
+
+db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
+
+CS "any-unknown-CS\0" ind = 5
+
+===== test_ind: SUCCESS
+
diff --git a/tests/db_upgrade/db_upgrade_test.ok b/tests/db_upgrade/db_upgrade_test.ok
index 2bc6a39..fc57068 100644
--- a/tests/db_upgrade/db_upgrade_test.ok
+++ b/tests/db_upgrade/db_upgrade_test.ok
@@ -85,6 +85,7 @@ DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 2
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 3
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 4
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 5
+DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 6
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
Resulting db:
@@ -117,6 +118,14 @@ algo_id_3g|ind_bitlen|k|op|opc|sqn|subscriber_id
5|5|44444444444444444444444444444444|44444444444444444444444444444444||0|5
5|5|55555555555555555555555555555555||55555555555555555555555555555555|0|6
+Table: ind
+name|type|notnull|dflt_value|pk
+cn_domain|INTEGER|1||0
+ind|INTEGER|0||1
+vlr|TEXT|1||0
+
+Table ind contents:
+
Table: subscriber
name|type|notnull|dflt_value|pk
ggsn_number|VARCHAR(15)|0||0
@@ -171,5 +180,5 @@ osmo-hlr --database $db --db-check --config-file $srcdir/osmo-hlr.cfg
rc = 0
DMAIN hlr starting
DDB using database: <PATH>test.db
-DDB Database <PATH>test.db' has HLR DB schema version 5
+DDB Database <PATH>test.db' has HLR DB schema version 6
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.