Hello Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/4273

to look at the new patch set (#2).

implement subscriber vty interface, tests

Implement VTY commands for subscriber manipulation:
- create / delete subscriber
- modify MSISDN
- add/edit/remove 2G and 3G authentication data
- show by IMSI, MSISDN or DB ID.
(enable/disable CS/PS and purge/unpurge to follow later.)

Implement VTY unit tests for the new commands using new
osmo_verify_transcript_vty.py from osmo-python-tests.

Depends: libosmocore I1e94f5b0717b947d2a7a7d36bacdf04a75cb3522
         osmo-python-tests Id47331009910e651372b9c9c76e12f2e8964cc2c
Change-Id: I42b3b70a0439a8f2e4964d7cc31e593c1f0d7537
---
M src/Makefile.am
M src/hlr_vty.c
A src/hlr_vty_subscr.c
A src/hlr_vty_subscr.h
M tests/Makefile.am
A tests/test_subscriber.vty
6 files changed, 859 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-hlr refs/changes/73/4273/2

diff --git a/src/Makefile.am b/src/Makefile.am
index b410ff3..fc7c653 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -23,6 +23,7 @@
        rand.h \
        ctrl.h \
        hlr_vty.h \
+       hlr_vty_subscr.h \
        $(NULL)
 
 bin_PROGRAMS = \
@@ -46,6 +47,7 @@
        logging.c \
        rand_urandom.c \
        hlr_vty.c \
+       hlr_vty_subscr.c \
        $(NULL)
 
 osmo_hlr_LDADD = \
diff --git a/src/hlr_vty.c b/src/hlr_vty.c
index 946117e..a5eb26f 100644
--- a/src/hlr_vty.c
+++ b/src/hlr_vty.c
@@ -26,6 +26,7 @@
 #include <osmocom/vty/logging.h>
 
 #include "hlr_vty.h"
+#include "hlr_vty_subscr.h"
 
 static struct hlr *g_hlr = NULL;
 
@@ -135,4 +136,6 @@
        install_default(GSUP_NODE);
 
        install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
+
+       hlr_vty_subscriber_init(hlr);
 }
diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c
new file mode 100644
index 0000000..018872a
--- /dev/null
+++ b/src/hlr_vty_subscr.c
@@ -0,0 +1,484 @@
+/* OsmoHLR subscriber management VTY implementation */
+/* (C) 2017 by sysmocom s.f.m.c. GmbH <i...@sysmocom.de>
+ * All Rights Reserved
+ *
+ * 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 Affero 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 <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/core/utils.h>
+
+#include "hlr.h"
+#include "db.h"
+
+struct vty;
+
+#define osmo_hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
+
+static struct hlr *g_hlr = NULL;
+
+static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber 
*subscr)
+{
+       int rc;
+       struct osmo_sub_auth_data aud2g;
+       struct osmo_sub_auth_data aud3g;
+
+       vty_out(vty, "    ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
+
+       vty_out(vty, "    IMSI: %s%s", subscr->imsi ? subscr->imsi : "none", 
VTY_NEWLINE);
+       vty_out(vty, "    MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : 
"none", VTY_NEWLINE);
+       if (*subscr->vlr_number)
+               vty_out(vty, "    VLR number: %s%s", subscr->vlr_number, 
VTY_NEWLINE);
+       if (*subscr->sgsn_number)
+               vty_out(vty, "    SGSN number: %s%s", subscr->sgsn_number, 
VTY_NEWLINE);
+       if (*subscr->sgsn_address)
+               vty_out(vty, "    SGSN address: %s%s", subscr->sgsn_address, 
VTY_NEWLINE);
+       if (subscr->periodic_lu_timer)
+               vty_out(vty, "    Periodic LU timer: %u%s", 
subscr->periodic_lu_timer, VTY_NEWLINE);
+       if (subscr->periodic_rau_tau_timer)
+               vty_out(vty, "    Periodic RAU/TAU timer: %u%s", 
subscr->periodic_rau_tau_timer, VTY_NEWLINE);
+       if (subscr->lmsi)
+               vty_out(vty, "    LMSI: %x%s", subscr->lmsi, VTY_NEWLINE);
+       if (!subscr->nam_cs)
+               vty_out(vty, "    CS disabled%s", VTY_NEWLINE);
+       if (subscr->ms_purged_cs)
+               vty_out(vty, "    CS purged%s", VTY_NEWLINE);
+       if (!subscr->nam_ps)
+               vty_out(vty, "    PS disabled%s", VTY_NEWLINE);
+       if (subscr->ms_purged_ps)
+               vty_out(vty, "    PS purged%s", VTY_NEWLINE);
+
+       if (!*subscr->imsi)
+               return;
+
+       OSMO_ASSERT(g_hlr);
+       rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL);
+
+       if (rc) {
+               if (rc == -ENOENT) {
+                       aud2g.algo = OSMO_AUTH_ALG_NONE;
+                       aud3g.algo = OSMO_AUTH_ALG_NONE;
+               } else {
+                       vty_out(vty, "%% Error retrieving data from database 
(%d)%s", rc, VTY_NEWLINE);
+                       return;
+               }
+       }
+
+       if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != 
OSMO_AUTH_TYPE_GSM) {
+               vty_out(vty, "%% Error: 2G auth data is not of type 'GSM'%s", 
VTY_NEWLINE);
+               aud2g = (struct osmo_sub_auth_data){};
+       }
+
+       if (aud3g.type != OSMO_AUTH_TYPE_NONE && aud3g.type != 
OSMO_AUTH_TYPE_UMTS) {
+               vty_out(vty, "%% Error: 3G auth data is not of type 'UMTS'%s", 
VTY_NEWLINE);
+               aud3g = (struct osmo_sub_auth_data){};
+       }
+
+       if (aud2g.algo != OSMO_AUTH_ALG_NONE && aud2g.type != 
OSMO_AUTH_TYPE_NONE) {
+               vty_out(vty, "    2G auth: %s%s",
+                       osmo_auth_alg_name(aud2g.algo), VTY_NEWLINE);
+               vty_out(vty, "             KI=%s%s",
+                       osmo_hexdump_buf(aud2g.u.gsm.ki), VTY_NEWLINE);
+       }
+
+       if (aud3g.algo != OSMO_AUTH_ALG_NONE && aud3g.type != 
OSMO_AUTH_TYPE_NONE) {
+               vty_out(vty, "    3G auth: %s%s", 
osmo_auth_alg_name(aud3g.algo), VTY_NEWLINE);
+               vty_out(vty, "             K=%s%s", 
osmo_hexdump_buf(aud3g.u.umts.k), VTY_NEWLINE);
+               vty_out(vty, "             %s=%s%s", aud3g.u.umts.opc_is_op? 
"OP" : "OPC",
+                       osmo_hexdump_buf(aud3g.u.umts.opc), VTY_NEWLINE);
+               vty_out(vty, "             IND-bitlen=%u", 
aud3g.u.umts.ind_bitlen);
+               if (aud3g.u.umts.sqn)
+                       vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn);
+               vty_out(vty, VTY_NEWLINE);
+       }
+}
+
+static int get_subscr_by_argv(struct vty *vty, const char *type, const char 
*id, struct hlr_subscriber *subscr)
+{
+       int rc = -1;
+       if (strcmp(type, "imsi") == 0)
+               rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
+       else if (strcmp(type, "msisdn") == 0)
+               rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
+       else if (strcmp(type, "id") == 0)
+               rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
+       if (rc)
+               vty_out(vty, "%% No subscriber for %s = '%s'%s",
+                       type, id, VTY_NEWLINE);
+       return rc;
+}
+
+#define SUBSCR_CMD "subscriber "
+#define SUBSCR_CMD_HELP "Subscriber management commands\n"
+
+#define SUBSCR_ID "(imsi|msisdn|id) IDENT "
+#define SUBSCR_ID_HELP \
+       "Identify subscriber by IMSI\n" \
+       "Identify subscriber by MSISDN (phone number)\n" \
+       "Identify subscriber by database ID\n" \
+       "IMSI/MSISDN/ID of the subscriber\n"
+
+#define SUBSCR                 SUBSCR_CMD SUBSCR_ID
+#define SUBSCR_HELP    SUBSCR_CMD_HELP SUBSCR_ID_HELP
+
+#define SUBSCR_UPDATE          SUBSCR "update "
+#define SUBSCR_UPDATE_HELP     SUBSCR_HELP "Set or update subscriber data\n"
+
+DEFUN(subscriber_show,
+      subscriber_show_cmd,
+      SUBSCR "show",
+      SUBSCR_HELP "Show subscriber information\n")
+{
+       struct hlr_subscriber subscr;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       subscr_dump_full_vty(vty, &subscr);
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_create,
+      subscriber_create_cmd,
+      SUBSCR_CMD "imsi IDENT create",
+      SUBSCR_CMD_HELP
+      "Create subscriber by IMSI\n"
+      "IMSI/MSISDN/ID of the subscriber\n")
+{
+       int rc;
+       struct hlr_subscriber subscr;
+       const char *imsi = argv[0];
+       
+       if (!osmo_imsi_str_valid(imsi)) {
+               vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       rc = db_subscr_create(g_hlr->dbc, imsi);
+
+       if (rc) {
+               if (rc == -EEXIST)
+                       vty_out(vty, "%% Subscriber already exists for IMSI = 
%s%s",
+                               imsi, VTY_NEWLINE);
+               else
+                       vty_out(vty, "%% Error (rc=%d): cannot create 
subscriber for IMSI = %s%s",
+                               rc, imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
+       vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
+
+       subscr_dump_full_vty(vty, &subscr);
+
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_delete,
+      subscriber_delete_cmd,
+      SUBSCR "delete",
+      SUBSCR_HELP "Delete subscriber from database\n")
+{
+       struct hlr_subscriber subscr;
+       int rc;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+
+       /* Find out the IMSI regardless of which way the caller decided to
+        * identify the subscriber by. */
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
+       if (rc) {
+               vty_out(vty, "%% Error: Failed to remove subscriber for IMSI 
'%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, 
VTY_NEWLINE);
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_msisdn,
+      subscriber_msisdn_cmd,
+      SUBSCR_UPDATE "msisdn MSISDN",
+      SUBSCR_UPDATE_HELP
+      "Set MSISDN (phone number) of the subscriber\n"
+      "New MSISDN (phone number)\n")
+{
+       struct hlr_subscriber subscr;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+       const char *msisdn = argv[2];
+
+       if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
+               vty_out(vty, "%% MSISDN is too long, max. %zu characters are 
allowed%s",
+                       sizeof(subscr.msisdn)-1, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       if (!osmo_msisdn_str_valid(msisdn)) {
+               vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
+               vty_out(vty, "%% Error: cannot update MSISDN for subscriber 
IMSI='%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
+               subscr.imsi, msisdn, VTY_NEWLINE);
+       return CMD_SUCCESS;
+}
+
+static bool is_hexkey_valid(struct vty *vty, const char *label,
+                           const char *hex_str, int minlen, int maxlen)
+{
+       if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
+               return true;
+       vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, 
VTY_NEWLINE);
+       return false;
+}
+
+#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
+#define AUTH_ALG_TYPES_2G_HELP \
+       "Use COMP128v1 algorithm\n" \
+       "Use COMP128v2 algorithm\n" \
+       "Use COMP128v3 algorithm\n" \
+       "Use XOR algorithm\n"
+
+#define AUTH_ALG_TYPES_3G "milenage"
+#define AUTH_ALG_TYPES_3G_HELP \
+       "Use Milenage algorithm\n"
+
+#define A38_XOR_MIN_KEY_LEN    12
+#define A38_XOR_MAX_KEY_LEN    16
+#define A38_COMP128_KEY_LEN    16
+
+#define MILENAGE_KEY_LEN 16
+
+static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
+                           int *minlen, int *maxlen)
+{
+       if (!strcasecmp(alg_str, "none")) {
+               *algo = OSMO_AUTH_ALG_NONE;
+               *minlen = *maxlen = 0;
+       } else if (!strcasecmp(alg_str, "comp128v1")) {
+               *algo = OSMO_AUTH_ALG_COMP128v1;
+               *minlen = *maxlen = A38_COMP128_KEY_LEN;
+       } else if (!strcasecmp(alg_str, "comp128v2")) {
+               *algo = OSMO_AUTH_ALG_COMP128v2;
+               *minlen = *maxlen = A38_COMP128_KEY_LEN;
+       } else if (!strcasecmp(alg_str, "comp128v3")) {
+               *algo = OSMO_AUTH_ALG_COMP128v3;
+               *minlen = *maxlen = A38_COMP128_KEY_LEN;
+       } else if (!strcasecmp(alg_str, "xor")) {
+               *algo = OSMO_AUTH_ALG_XOR;
+               *minlen = A38_XOR_MIN_KEY_LEN;
+               *maxlen = A38_XOR_MAX_KEY_LEN;
+       } else if (!strcasecmp(alg_str, "milenage")) {
+               *algo = OSMO_AUTH_ALG_MILENAGE;
+               *minlen = *maxlen = MILENAGE_KEY_LEN;
+       } else
+               return false;
+       return true;
+}
+
+DEFUN(subscriber_no_aud2g,
+      subscriber_no_aud2g_cmd,
+      SUBSCR_UPDATE "aud2g none",
+      SUBSCR_UPDATE_HELP
+      "Set 2G authentication data\n"
+      "Delete 2G authentication data\n")
+{
+       struct hlr_subscriber subscr;
+       int rc;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+       struct sub_auth_data_str aud = {
+               .type = OSMO_AUTH_TYPE_GSM,
+               .algo = OSMO_AUTH_ALG_NONE,
+       };
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
+
+       if (rc) {
+               vty_out(vty, "%% Error: cannot disable 2G auth data for 
IMSI='%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_aud2g,
+      subscriber_aud2g_cmd,
+      SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
+      SUBSCR_UPDATE_HELP
+      "Set 2G authentication data\n"
+      AUTH_ALG_TYPES_2G_HELP
+      "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
+{
+       struct hlr_subscriber subscr;
+       int rc;
+       int minlen = 0;
+       int maxlen = 0;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+       const char *alg_type = argv[2];
+       const char *ki = argv[3];
+       struct sub_auth_data_str aud2g = {
+               .type = OSMO_AUTH_TYPE_GSM,
+               .u.gsm.ki = ki,
+       };
+
+       if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
+               vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, 
VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
+               return CMD_WARNING;
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
+
+       if (rc) {
+               vty_out(vty, "%% Error: cannot set 2G auth data for 
IMSI='%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_no_aud3g,
+      subscriber_no_aud3g_cmd,
+      SUBSCR_UPDATE "aud3g none",
+      SUBSCR_UPDATE_HELP
+      "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
+      "Delete 3G authentication data\n")
+{
+       struct hlr_subscriber subscr;
+       int rc;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+       struct sub_auth_data_str aud = {
+               .type = OSMO_AUTH_TYPE_UMTS,
+               .algo = OSMO_AUTH_ALG_NONE,
+       };
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
+
+       if (rc) {
+               vty_out(vty, "%% Error: cannot disable 3G auth data for 
IMSI='%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+       return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_aud3g,
+      subscriber_aud3g_cmd,
+      SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
+      " k K"
+      " (op|opc) OP_C"
+      " [ind-bitlen] [<0-28>]",
+      SUBSCR_UPDATE_HELP
+      "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
+      AUTH_ALG_TYPES_3G_HELP
+      "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
+      "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
+      "Set IND bit length\n" "IND bit length value (default: 5)\n")
+{
+       struct hlr_subscriber subscr;
+       int minlen = 0;
+       int maxlen = 0;
+       int rc;
+       const char *id_type = argv[0];
+       const char *id = argv[1];
+       const char *alg_type = AUTH_ALG_TYPES_3G;
+       const char *k = argv[2];
+       bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
+       const char *op_opc = argv[4];
+       int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
+       struct sub_auth_data_str aud3g = {
+               .type = OSMO_AUTH_TYPE_UMTS,
+               .u.umts = {
+                       .k = k,
+                       .opc_is_op = opc_is_op,
+                       .opc = op_opc,
+                       .ind_bitlen = ind_bitlen,
+               },
+       };
+       
+       if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
+               vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, 
VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+
+       if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
+               return CMD_WARNING;
+
+       if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
+                            MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
+               return CMD_WARNING;
+
+       if (get_subscr_by_argv(vty, id_type, id, &subscr))
+               return CMD_WARNING;
+
+       rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
+
+       if (rc) {
+               vty_out(vty, "%% Error: cannot set 3G auth data for 
IMSI='%s'%s",
+                       subscr.imsi, VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+       return CMD_SUCCESS;
+}
+
+void hlr_vty_subscriber_init(struct hlr *hlr)
+{
+       g_hlr = hlr;
+
+       install_element_ve(&subscriber_show_cmd);
+       install_element(ENABLE_NODE, &subscriber_create_cmd);
+       install_element(ENABLE_NODE, &subscriber_delete_cmd);
+       install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
+       install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
+       install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
+       install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
+       install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
+}
diff --git a/src/hlr_vty_subscr.h b/src/hlr_vty_subscr.h
new file mode 100644
index 0000000..841db5a
--- /dev/null
+++ b/src/hlr_vty_subscr.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void hlr_vty_subscriber_init(struct hlr *hlr);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0b625f5..8f1826d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -26,6 +26,7 @@
        testsuite.at \
        $(srcdir)/package.m4 \
        $(TESTSUITE) \
+       test_subscriber.vty \
        ctrl_test_runner.py \
        $(NULL)
 
@@ -36,10 +37,26 @@
        $(NULL)
 
 if ENABLE_EXT_TESTS
-python-tests: $(BUILT_SOURCES)
+python-tests:
+# don't run vty and ctrl tests concurrently so that the ports don't conflict
+       $(MAKE) vty-test
        $(PYTHON) $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
+
+VTY_TEST_DB = hlr_vty_test.db
+
+# To update the VTY script from current application behavior,
+# pass -u to vty_script_runner.py by doing:
+#   make vty-test U=-u
+vty-test:
+       -rm -f $(VTY_TEST_DB)
+       sqlite3 $(VTY_TEST_DB) < $(top_srcdir)/sql/hlr.sql
+       osmo_verify_transcript_vty.py -v \
+               -n OsmoHLR -p 4258 \
+               -r "$(top_builddir)/src/osmo-hlr -c 
$(top_srcdir)/doc/examples/osmo-hlr.cfg -l hlr_vty_test.db" \
+               $(U) $(srcdir)/*.vty
+       -rm -f $(VTY_TEST_DB)
 else
-python-tests: $(BUILT_SOURCES)
+python-tests:
        echo "Not running python-based tests (determined at configure-time)"
 endif
 
diff --git a/tests/test_subscriber.vty b/tests/test_subscriber.vty
new file mode 100644
index 0000000..2e0bdce
--- /dev/null
+++ b/tests/test_subscriber.vty
@@ -0,0 +1,348 @@
+OsmoHLR> enable
+
+OsmoHLR# list
+...
+  subscriber (imsi|msisdn|id) IDENT show
+  subscriber imsi IDENT create
+  subscriber (imsi|msisdn|id) IDENT delete
+  subscriber (imsi|msisdn|id) IDENT update msisdn MSISDN
+  subscriber (imsi|msisdn|id) IDENT update aud2g none
+  subscriber (imsi|msisdn|id) IDENT update aud2g 
(comp128v1|comp128v2|comp128v3|xor) ki KI
+  subscriber (imsi|msisdn|id) IDENT update aud3g none
+  subscriber (imsi|msisdn|id) IDENT update aud3g milenage k K (op|opc) OP_C 
[ind-bitlen] [<0-28>]
+
+OsmoHLR# subscriber?
+  subscriber  Subscriber management commands
+
+OsmoHLR# subscriber ?
+  imsi    Identify subscriber by IMSI
+  msisdn  Identify subscriber by MSISDN (phone number)
+  id      Identify subscriber by database ID
+
+OsmoHLR# subscriber imsi ?
+  IDENT  IMSI/MSISDN/ID of the subscriber
+OsmoHLR# subscriber msisdn ?
+  IDENT  IMSI/MSISDN/ID of the subscriber
+OsmoHLR# subscriber id ?
+  IDENT  IMSI/MSISDN/ID of the subscriber
+
+OsmoHLR# subscriber imsi 123456789023000 show
+% No subscriber for imsi = '123456789023000'
+OsmoHLR# subscriber id 1 show
+% No subscriber for id = '1'
+OsmoHLR# subscriber msisdn 12345 show
+% No subscriber for msisdn = '12345'
+
+OsmoHLR# subscriber imsi 1234567890230001 create
+% Not a valid IMSI: 1234567890230001
+OsmoHLR# subscriber imsi 12345678902300x create
+% Not a valid IMSI: 12345678902300x
+OsmoHLR# subscriber imsi 12345 create
+% Not a valid IMSI: 12345
+
+OsmoHLR# subscriber imsi 123456789023000 create
+% Created subscriber 123456789023000
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: none
+
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: none
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: none
+OsmoHLR# subscriber msisdn 12345 show
+% No subscriber for msisdn = '12345'
+
+OsmoHLR# subscriber imsi 123456789023000 update msisdn 12345
+% Updated subscriber IMSI='123456789023000' to MSISDN='12345'
+
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 12345
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 12345
+OsmoHLR# subscriber msisdn 12345 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 12345
+
+OsmoHLR# subscriber msisdn 12345 update msisdn 423
+% Updated subscriber IMSI='123456789023000' to MSISDN='423'
+OsmoHLR# subscriber msisdn 12345 show
+% No subscriber for msisdn = '12345'
+
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+OsmoHLR# subscriber msisdn 423 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+
+OsmoHLR# subscriber imsi 123456789023000 update ?
+  msisdn  Set MSISDN (phone number) of the subscriber
+  aud2g   Set 2G authentication data
+  aud3g   Set UMTS authentication data (3G, and 2G with UMTS AKA)
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g ?
+  none       Delete 2G authentication data
+  comp128v1  Use COMP128v1 algorithm
+  comp128v2  Use COMP128v2 algorithm
+  comp128v3  Use COMP128v3 algorithm
+  xor        Use XOR algorithm
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ?
+  ki  Set Ki Encryption Key
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki ?
+  KI  Ki as 32 hexadecimal characters
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki val ?
+  <cr>  
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g xor ki 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: XOR
+             KI=deaf0ff1ced0d0dabbedd1ced1cef00d
+
+OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki 
BeefedCafeFaceAcedAddedDecadeFee
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v1
+             KI=beefedcafefaceacedaddeddecadefee
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v1
+             KI=beefedcafefaceacedaddeddecadefee
+OsmoHLR# subscriber msisdn 423 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v1
+             KI=beefedcafefaceacedaddeddecadefee
+
+OsmoHLR# subscriber id 1 update aud2g comp128v2 ki 
CededEffacedAceFacedBadFadedBeef
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v2
+             KI=cededeffacedacefacedbadfadedbeef
+OsmoHLR# subscriber msisdn 423 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v2
+             KI=cededeffacedacefacedbadfadedbeef
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v2
+             KI=cededeffacedacefacedbadfadedbeef
+
+OsmoHLR# subscriber msisdn 423 update aud2g comp128v3 ki 
C01ffedC1cadaeAc1d1f1edAcac1aB0a
+OsmoHLR# subscriber msisdn 423 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+
+OsmoHLR# subscriber id 1 update aud2g nonsense ki 
BeefedCafeFaceAcedAddedDecadeFee
+% Unknown command.
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+
+OsmoHLR# subscriber id 1 update aud2g milenage ki 
BeefedCafeFaceAcedAddedDecadeFee
+% Unknown command.
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+
+OsmoHLR# subscriber id 1 update aud2g xor ki CoiffedCicadaeAcidifiedAcaciaBoa
+% Invalid value for KI: 'CoiffedCicadaeAcidifiedAcaciaBoa'
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+
+OsmoHLR# subscriber id 1 update aud2g xor ki C01ffedC1cadaeAc1d1f1edAcac1aB0aX
+% Invalid value for KI: 'C01ffedC1cadaeAc1d1f1edAcac1aB0aX'
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v3
+             KI=c01ffedc1cadaeac1d1f1edacac1ab0a
+
+OsmoHLR# subscriber id 1 update aud2g none
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g ?
+  none      Delete 3G authentication data
+  milenage  Use Milenage algorithm
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage ?
+  k  Set Encryption Key K
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k ?
+  K  K as 32 hexadecimal characters
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d ?
+  op   Set OP key
+  opc  Set OPC key
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc ?
+  OP_C  OP or OPC as 32 hexadecimal characters
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef ?
+  [ind-bitlen]  Set IND bit length
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef 
ind-bitlen ?
+  [<0-28>]  IND bit length value (default: 5)
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=5
+
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d op DeafBeddedBabeAcceededFadedDecaf
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OP=deafbeddedbabeacceededfadeddecaf
+             IND-bitlen=5
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g none
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef 
ind-bitlen 23
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=23
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
CoiffedCicadaeAcidifiedAcaciaBoa opc CededEffacedAceFacedBadFadedBeef
+% Invalid value for K: 'CoiffedCicadaeAcidifiedAcaciaBoa'
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=23
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CoiffedCicadaeAcidifiedAcaciaBoa
+% Invalid value for OPC: 'CoiffedCicadaeAcidifiedAcaciaBoa'
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=23
+
+OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k 
Deaf0ff1ceD0d0DabbedD1ced1ceF00d op CoiffedCicadaeAcidifiedAcaciaBoa
+% Invalid value for OP: 'CoiffedCicadaeAcidifiedAcaciaBoa'
+OsmoHLR# subscriber imsi 123456789023000 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=23
+
+OsmoHLR# subscriber id 1 update aud2g comp128v2 ki 
CededEffacedAceFacedBadFadedBeef
+OsmoHLR# subscriber id 1 show
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: 423
+    2G auth: COMP128v2
+             KI=cededeffacedacefacedbadfadedbeef
+    3G auth: MILENAGE
+             K=deaf0ff1ced0d0dabbedd1ced1cef00d
+             OPC=cededeffacedacefacedbadfadedbeef
+             IND-bitlen=23
+
+OsmoHLR# subscriber imsi 123456789023000 delete
+% Deleted subscriber for IMSI '123456789023000'
+
+OsmoHLR# subscriber imsi 123456789023000 show
+% No subscriber for imsi = '123456789023000'
+OsmoHLR# subscriber id 1 show
+% No subscriber for id = '1'
+OsmoHLR# subscriber msisdn 423 show
+% No subscriber for msisdn = '423'
+
+OsmoHLR# subscriber imsi 123456789023000 create
+% Created subscriber 123456789023000
+    ID: 1
+    IMSI: 123456789023000
+    MSISDN: none
+
+OsmoHLR# subscriber imsi 123456789023000 delete
+% Deleted subscriber for IMSI '123456789023000'

-- 
To view, visit https://gerrit.osmocom.org/4273
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: I42b3b70a0439a8f2e4964d7cc31e593c1f0d7537
Gerrit-PatchSet: 2
Gerrit-Project: osmo-hlr
Gerrit-Branch: master
Gerrit-Owner: Neels Hofmeyr <nhofm...@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder

Reply via email to