/* (C) 2016 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 <string.h>
#include <inttypes.h>
#include <getopt.h>

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

#include <osmocom/crypt/auth.h>

#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/auc.h>

#define comment_start() fprintf(stderr, "\n===== %s\n", __func__);
#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__);

#define VERBOSE_ASSERT(val, expect_op, fmt) \
	do { \
		fprintf(stderr, #val " == " fmt "\n", (val)); \
		OSMO_ASSERT((val) expect_op); \
	} while (0);

char *vec_str(const struct osmo_auth_vector *vec)
{
	static char buf[1024];
	char *pos = buf;
	char *end = buf + sizeof(buf);

#define append(what) \
	if (pos >= end) \
		return buf; \
	pos += snprintf(pos, sizeof(buf) - (pos - buf), \
                        "  " #what ": %s\n", \
			osmo_hexdump_nospc((void*)&vec->what, sizeof(vec->what)))

	append(rand);
	append(autn);
	append(ck);
	append(ik);
	append(res);
	append(res_len);
	append(kc);
	append(sres);
	append(auth_types);
#undef append

	return buf;
}

#define VEC_IS(vec, expect) do { \
		char *_is = vec_str(vec); \
	        if (strcmp(_is, expect)) { \
			fprintf(stderr, "MISMATCH! expected ==\n%s\n", \
				expect); \
			char *a = _is; \
			char *b = expect; \
			for (; *a && *b; a++, b++) { \
				if (*a != *b) { \
					fprintf(stderr, "mismatch at %d:\n", \
						(int)(a - _is)); \
					while (a > _is && *(a-1) != '\n') { \
						fprintf(stderr, " "); \
						a--; \
					} \
					fprintf(stderr, "v\n%s", a); \
					break; \
				} \
			} \
			OSMO_ASSERT(false); \
		} else \
			fprintf(stderr, "vector matches expectations\n"); \
	} while (0)

uint8_t fake_rand[16] = { 0 };
bool fake_rand_fixed = true;

void next_rand(const char *hexstr, bool fixed)
{
	osmo_hexparse(hexstr, fake_rand, sizeof(fake_rand));
	fake_rand_fixed = fixed;
}

int rand_get(uint8_t *rand, unsigned int len)
{
	int i;
	OSMO_ASSERT(len <= sizeof(fake_rand));
	memcpy(rand, fake_rand, len);
	if (!fake_rand_fixed) {
		for (i = 0; i < len; i++)
			fake_rand[i] += 0x11;
	}
	return len;
}

/* Subscriber with 2G-only (COMP128v1) authentication data */
static void test_gen_vectors_2g_only(void)
{
	struct osmo_sub_auth_data2 aud2g;
	struct osmo_sub_auth_data2 aud3g;
	struct osmo_auth_vector vec;
	int rc;

	comment_start();

	aud2g = (struct osmo_sub_auth_data2){
		.type = OSMO_AUTH_TYPE_GSM,
		.algo = OSMO_AUTH_ALG_COMP128v1,
	};

	osmo_hexparse("EB215756028D60E3275E613320AEC880",
		      aud2g.u.gsm.ki, sizeof(aud2g.u.gsm.ki));

	aud3g = (struct osmo_sub_auth_data2){ 0 };

	next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true);

	vec = (struct osmo_auth_vector){ {0} };
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 00000000000000000000000000000000\n"
	       "  ck: 00000000000000000000000000000000\n"
	       "  ik: 00000000000000000000000000000000\n"
	       "  res: 00000000000000000000000000000000\n"
	       "  res_len: 00\n"
	       "  kc: 241a5b16aeb8e400\n"
	       "  sres: 429d5b27\n"
	       "  auth_types: 01000000\n"
	      );

	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64);

	/* even though vec is not zero-initialized, it should produce the same
	 * result (regardless of the umts sequence nr) */
	aud3g.u.umts.sqn = 123;
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 00000000000000000000000000000000\n"
	       "  ck: 00000000000000000000000000000000\n"
	       "  ik: 00000000000000000000000000000000\n"
	       "  res: 00000000000000000000000000000000\n"
	       "  res_len: 00\n"
	       "  kc: 241a5b16aeb8e400\n"
	       "  sres: 429d5b27\n"
	       "  auth_types: 01000000\n"
	      );

	comment_end();
}

/* Subscriber with separate 2G (COMP128v1) and 3G (MILENAGE) authentication data,
 * reflects the default configuration of sysmoUSIM-SJS1 */
static void test_gen_vectors_2g_plus_3g(void)
{
	struct osmo_sub_auth_data2 aud2g;
	struct osmo_sub_auth_data2 aud3g;
	struct osmo_auth_vector vec;
	int rc;

	comment_start();

	aud2g = (struct osmo_sub_auth_data2){
		.type = OSMO_AUTH_TYPE_GSM,
		.algo = OSMO_AUTH_ALG_COMP128v1,
	};

	osmo_hexparse("EB215756028D60E3275E613320AEC880",
		      aud2g.u.gsm.ki, sizeof(aud2g.u.gsm.ki));

	aud3g = (struct osmo_sub_auth_data2){
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = OSMO_AUTH_ALG_MILENAGE,
		.u.umts.k_len = 16,
		.u.umts.opc_len = 16,
		.u.umts.sqn = 31,
	};

	osmo_hexparse("EB215756028D60E3275E613320AEC880",
		      aud3g.u.umts.k, sizeof(aud3g.u.umts.k));
	osmo_hexparse("FB2A3D1B360F599ABAB99DB8669F8308",
		      aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc));
	next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true);

	vec = (struct osmo_auth_vector){ {0} };
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 8704f5ba55d30000541dde77ea5b1d8c\n"
	       "  ck: f64735036e5871319c679f4742a75ea1\n"
	       "  ik: 27497388b6cb044648f396aa155b95ef\n"
	       "  res: e229c19e791f2e410000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 241a5b16aeb8e400\n"
	       "  sres: 429d5b27\n"
	       "  auth_types: 03000000\n"
	      );

	/* even though vec is not zero-initialized, it should produce the same
	 * result with the same sequence nr */
	aud3g.u.umts.sqn = 31;
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 8704f5ba55d30000541dde77ea5b1d8c\n"
	       "  ck: f64735036e5871319c679f4742a75ea1\n"
	       "  ik: 27497388b6cb044648f396aa155b95ef\n"
	       "  res: e229c19e791f2e410000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 241a5b16aeb8e400\n"
	       "  sres: 429d5b27\n"
	       "  auth_types: 03000000\n"
	      );

	comment_end();
}

void _test_gen_vectors_3g_only__expect_vecs(struct osmo_auth_vector vecs[3])
{
	fprintf(stderr, "[0]: ");
	VEC_IS(&vecs[0],
	       "  rand: 897210a0f7de278f0b8213098e098a3f\n"
	       "  autn: c6b9790dad4b00000cf322869ea6a481\n"
	       "  ck: e9922bd036718ed9e40bd1d02c3b81a5\n"
	       "  ik: f19c20ca863137f8892326d959ec5e01\n"
	       "  res: 9af5a557902d2db80000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 7526fc13c5976685\n"
	       "  sres: 0ad888ef\n"
	       "  auth_types: 03000000\n"
	      );
	fprintf(stderr, "[1]: ");
	VEC_IS(&vecs[1],
	       "  rand: 9a8321b108ef38a01c93241a9f1a9b50\n"
	       "  autn: 79a5113eb0910000be6020540503ffc5\n"
	       "  ck: 3686f05df057d1899c66ae4eb18cf941\n"
	       "  ik: 79f21ed53bcb47787de57d136ff803a5\n"
	       "  res: 43023475cb29292c0000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: aef73dd515e86c15\n"
	       "  sres: 882b1d59\n"
	       "  auth_types: 03000000\n"
	      );
	fprintf(stderr, "[2]: ");
	VEC_IS(&vecs[2],
	       "  rand: ab9432c2190049b12da4352bb02bac61\n"
	       "  autn: 24b018d46c3b00009c7e1b47f3a19b2b\n"
	       "  ck: d86c3191a36fc0602e48202ef2080964\n"
	       "  ik: 648dab72016181406243420649e63dc9\n"
	       "  res: 010cab11cc63a6e40000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: f0eaf8cb19e0758d\n"
	       "  sres: cd6f0df5\n"
	       "  auth_types: 03000000\n"
	      );
}

/* Subscriber with only 3G (MILENAGE) authentication data,
 * reflects the default configuration of sysmoISIM-SJA2. Resulting
 * tuples are suitable for both 2G and 3G authentication */
static void test_gen_vectors_3g_only(void)
{
	struct osmo_sub_auth_data2 aud2g;
	struct osmo_sub_auth_data2 aud3g;
	struct osmo_auth_vector vec;
	struct osmo_auth_vector vecs[3];
	uint8_t auts[14];
	uint8_t rand_auts[16];
	int rc;

	comment_start();

	aud2g = (struct osmo_sub_auth_data2){ 0 };

	aud3g = (struct osmo_sub_auth_data2){
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = OSMO_AUTH_ALG_MILENAGE,
		.u.umts.k_len = 16,
		.u.umts.opc_len = 16,
		.u.umts.sqn = 31,
	};

	osmo_hexparse("EB215756028D60E3275E613320AEC880",
		      aud3g.u.umts.k, sizeof(aud3g.u.umts.k));
	osmo_hexparse("FB2A3D1B360F599ABAB99DB8669F8308",
		      aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc));
	next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true);

	vec = (struct osmo_auth_vector){ {0} };
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 8704f5ba55d30000541dde77ea5b1d8c\n"
	       "  ck: f64735036e5871319c679f4742a75ea1\n"
	       "  ik: 27497388b6cb044648f396aa155b95ef\n"
	       "  res: e229c19e791f2e410000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 059a4f668f6fbe39\n"
	       "  sres: 9b36efdf\n"
	       "  auth_types: 03000000\n"
	      );

	/* Note: 3GPP TS 33.102 6.8.1.2: c3 function to get GSM auth is
	 * KC[0..7] == CK[0..7] ^ CK[8..15] ^ IK[0..7] ^ IK[8..15]
	 * In [16]: hex(  0xf64735036e587131
	 *              ^ 0x9c679f4742a75ea1
	 *              ^ 0x27497388b6cb0446
	 *              ^ 0x48f396aa155b95ef)
	 * Out[16]: '0x59a4f668f6fbe39L'
	 * hence expecting kc: 059a4f668f6fbe39
	 */

	/* even though vec is not zero-initialized, it should produce the same
	 * result with the same sequence nr */
	aud3g.u.umts.sqn = 31;
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n"
	       "  autn: 8704f5ba55d30000541dde77ea5b1d8c\n"
	       "  ck: f64735036e5871319c679f4742a75ea1\n"
	       "  ik: 27497388b6cb044648f396aa155b95ef\n"
	       "  res: e229c19e791f2e410000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 059a4f668f6fbe39\n"
	       "  sres: 9b36efdf\n"
	       "  auth_types: 03000000\n"
	      );


	fprintf(stderr, "- test AUTS resync\n");
	vec = (struct osmo_auth_vector){};
	aud3g.u.umts.sqn = 31;
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64);

	/* The AUTN sent was 8704f5ba55f30000d2ee44b22c8ea919
	 * with the first 6 bytes being SQN ^ AK.
	 * K = EB215756028D60E3275E613320AEC880
	 * OPC = FB2A3D1B360F599ABAB99DB8669F8308
	 * RAND = 39fa2f4e3d523d8619a73b4f65c3e14d
	 * --milenage-f5-->
	 * AK = 8704f5ba55f3
	 *
	 * The first six bytes are 8704f5ba55f3,
	 * and 8704f5ba55f3 ^ AK = 0.
	 * --> SQN = 0.
	 *
	 * Say the USIM doesn't like that, let's say it is at SQN 23.
	 * SQN_MS = 000000000017
	 *
	 * AUTS = Conc(SQN_MS) || MAC-S
	 * Conc(SQN_MS) = SQN_MS ⊕ f5*[K](RAND)
	 * MAC-S = f1*[K] (SQN MS || RAND || AMF)
	 *
	 * f5*--> Conc(SQN_MS) = 000000000017 ^ 979498b1f73a
	 *                     = 979498b1f72d
	 * AMF = 0000 (TS 33.102 v7.0.0, 6.3.3)
	 *
	 * MAC-S = f1*[K] (000000000017 || 39fa2f4e3d523d8619a73b4f65c3e14d || 0000)
	 *       = 3e28c59fa2e72f9c
	 *
	 * AUTS = 979498b1f72d || 3e28c59fa2e72f9c
	 *
	 * verify valid AUTS resulting in SQN 23 with:
	 * osmo-auc-gen -3 -a milenage -k EB215756028D60E3275E613320AEC880 \
	 *              -o FB2A3D1B360F599ABAB99DB8669F8308 \
	 *              -r 39fa2f4e3d523d8619a73b4f65c3e14d \
	 *              -A 979498b1f72d3e28c59fa2e72f9c
	 */

	/* AUTS response by USIM */
	osmo_hexparse("979498b1f72d3e28c59fa2e72f9c",
		      auts, sizeof(auts));
	/* RAND sent to USIM, which AUTS was generated from */
	osmo_hexparse("39fa2f4e3d523d8619a73b4f65c3e14d",
		      rand_auts, sizeof(rand_auts));
	/* new RAND token for the next key */
	next_rand("897210a0f7de278f0b8213098e098a3f", true);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, rand_auts, auts);
	VERBOSE_ASSERT(rc, == 1, "%d");
	/* The USIM's last sqn was 23, the calculated vector was 24 */
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 24, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: 897210a0f7de278f0b8213098e098a3f\n"
	       "  autn: c6b9790dad4b00000cf322869ea6a481\n"
	       "  ck: e9922bd036718ed9e40bd1d02c3b81a5\n"
	       "  ik: f19c20ca863137f8892326d959ec5e01\n"
	       "  res: 9af5a557902d2db80000000000000000\n"
	       "  res_len: 08\n"
	       "  kc: 7526fc13c5976685\n"
	       "  sres: 0ad888ef\n"
	       "  auth_types: 03000000\n"
	      );


	fprintf(stderr, "- verify N vectors with AUTS resync"
		" == N vectors without AUTS\n"
		"First just set rand and sqn = 23, and compute 3 vectors\n");
	next_rand("897210a0f7de278f0b8213098e098a3f", false);
	aud3g.u.umts.sqn = 23;
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 23, "%"PRIu64);

	memset(vecs, 0, sizeof(vecs));
	rc = auc_compute_vectors(vecs, 3, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 3, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 26, "%"PRIu64);

	_test_gen_vectors_3g_only__expect_vecs(vecs);

	fprintf(stderr, "Now reach sqn = 23 with AUTS and expect the same\n");
	/* AUTS response by USIM */
	osmo_hexparse("979498b1f72d3e28c59fa2e72f9c",
		      auts, sizeof(auts));
	/* RAND sent to USIM, which AUTS was generated from */
	osmo_hexparse("39fa2f4e3d523d8619a73b4f65c3e14d",
		      rand_auts, sizeof(rand_auts));
	next_rand("897210a0f7de278f0b8213098e098a3f", false);
	rc = auc_compute_vectors(vecs, 3, &aud2g, &aud3g, rand_auts, auts);

	_test_gen_vectors_3g_only__expect_vecs(vecs);

	comment_end();
}

/* Subscriber with only 3G (XOR) authentication data,
 * reflects the default configuration of sysmoTSIM-SJAx as well
 * as many "Test USIM" cards. Resulting tuples are suitable for both
 * 2G and 3G authentication */
static void test_gen_vectors_3g_xor(void)
{
	struct osmo_sub_auth_data2 aud2g;
	struct osmo_sub_auth_data2 aud3g;
	struct osmo_auth_vector vec;
	int rc;

	comment_start();

	aud2g = (struct osmo_sub_auth_data2){ 0 };

	aud3g = (struct osmo_sub_auth_data2){
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = OSMO_AUTH_ALG_XOR_3G,
		.u.umts.k_len = 16,
		.u.umts.opc_len = 16,
		.u.umts.sqn = 0,
	};

	osmo_hexparse("000102030405060708090a0b0c0d0e0f",
		      aud3g.u.umts.k, sizeof(aud3g.u.umts.k));
	osmo_hexparse("00000000000000000000000000000000",
		      aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc));
	next_rand("b5039c57e4a75051551d1a390a71ce48", true);

	vec = (struct osmo_auth_vector){ {0} };
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64);
	rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL);
	VERBOSE_ASSERT(rc, == 1, "%d");
	VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64);

	VEC_IS(&vec,
	       "  rand: b5039c57e4a75051551d1a390a71ce48\n"
	       "  autn: 54e0a256565d0000b5029e54e0a25656\n"
	       "  ck: 029e54e0a256565d141032067cc047b5\n"
	       "  ik: 9e54e0a256565d141032067cc047b502\n"
	       "  res: b5029e54e0a256565d141032067cc047\n"
	       "  res_len: 10\n"
	       "  kc: 98e880384887f9fe\n"
	       "  sres: 0ec81877\n"
	       "  auth_types: 03000000\n"
	      );

	comment_end();
}

/* Test a variety of invalid authentication data combinations */
void test_gen_vectors_bad_args(void)
{
	struct osmo_auth_vector vec;
	uint8_t auts[14];
	uint8_t rand_auts[16];
	int rc;
	int i;

	struct osmo_sub_auth_data2 aud2g = {
		.type = OSMO_AUTH_TYPE_GSM,
		.algo = OSMO_AUTH_ALG_COMP128v1,
	};

	struct osmo_sub_auth_data2 aud3g = {
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = OSMO_AUTH_ALG_MILENAGE,
		.u.umts.k_len = 16,
		.u.umts.opc_len = 16,
	};

	struct osmo_sub_auth_data2 aud2g_noalg = {
		.type = OSMO_AUTH_TYPE_GSM,
		.algo = OSMO_AUTH_ALG_NONE,
	};

	struct osmo_sub_auth_data2 aud3g_noalg = {
		.type = OSMO_AUTH_TYPE_UMTS,
		.algo = OSMO_AUTH_ALG_NONE,
		.u.umts.k_len = 16,
		.u.umts.opc_len = 16,
	};

	struct osmo_sub_auth_data2 aud_notype = {
		.type = OSMO_AUTH_TYPE_NONE,
		.algo = OSMO_AUTH_ALG_MILENAGE,
	};

	struct osmo_sub_auth_data2 no_aud = {
		.type = OSMO_AUTH_TYPE_NONE,
		.algo = OSMO_AUTH_ALG_NONE,
	};

	struct {
		struct osmo_sub_auth_data2 *aud2g;
		struct osmo_sub_auth_data2 *aud3g;
		uint8_t *rand_auts;
		uint8_t *auts;
		const char *label;
	} tests[] = {
		{         NULL,         NULL,       NULL,  NULL, "no auth data (a)"},
		{         NULL, &aud3g_noalg,       NULL,  NULL, "no auth data (b)"},
		{         NULL,  &aud_notype,       NULL,  NULL, "no auth data (c)"},
		{         NULL,      &no_aud,       NULL,  NULL, "no auth data (d)"},
		{ &aud2g_noalg,         NULL,       NULL,  NULL, "no auth data (e)"},
		{ &aud2g_noalg, &aud3g_noalg,       NULL,  NULL, "no auth data (f)"},
		{ &aud2g_noalg,  &aud_notype,       NULL,  NULL, "no auth data (g)"},
		{ &aud2g_noalg,      &no_aud,       NULL,  NULL, "no auth data (h)"},
		{  &aud_notype,         NULL,       NULL,  NULL, "no auth data (i)"},
		{  &aud_notype, &aud3g_noalg,       NULL,  NULL, "no auth data (j)"},
		{  &aud_notype,  &aud_notype,       NULL,  NULL, "no auth data (k)"},
		{  &aud_notype,      &no_aud,       NULL,  NULL, "no auth data (l)"},
		{      &no_aud,         NULL,       NULL,  NULL, "no auth data (m)"},
		{      &no_aud, &aud3g_noalg,       NULL,  NULL, "no auth data (n)"},
		{      &no_aud,  &aud_notype,       NULL,  NULL, "no auth data (o)"},
		{      &no_aud,      &no_aud,       NULL,  NULL, "no auth data (p)"},
		{       &aud3g,         NULL,       NULL,  NULL, "wrong auth data type (a)"},
		{       &aud3g, &aud3g_noalg,       NULL,  NULL, "wrong auth data type (b)"},
		{       &aud3g,  &aud_notype,       NULL,  NULL, "wrong auth data type (c)"},
		{       &aud3g,      &no_aud,       NULL,  NULL, "wrong auth data type (d)"},
		{         NULL,       &aud2g,       NULL,  NULL, "wrong auth data type (e)"},
		{ &aud3g_noalg,       &aud2g,       NULL,  NULL, "wrong auth data type (f)"},
		{  &aud_notype,       &aud2g,       NULL,  NULL, "wrong auth data type (g)"},
		{      &no_aud,       &aud2g,       NULL,  NULL, "wrong auth data type (h)"},
		{       &aud3g,       &aud2g,       NULL,  NULL, "wrong auth data type (i)"},
		{       &aud3g,       &aud3g,       NULL,  NULL, "wrong auth data type (j)"},
		{       &aud2g,       &aud2g,       NULL,  NULL, "wrong auth data type (k)"},
		{       &aud2g,         NULL,  rand_auts,  auts, "AUTS for 2G-only (a)"},
		{       &aud2g, &aud3g_noalg,  rand_auts,  auts, "AUTS for 2G-only (b)"},
		{       &aud2g,  &aud_notype,  rand_auts,  auts, "AUTS for 2G-only (c)"},
		{       &aud2g,      &no_aud,  rand_auts,  auts, "AUTS for 2G-only (d)"},
		{         NULL,       &aud3g,       NULL,  auts, "incomplete AUTS (a)"},
		{         NULL,       &aud3g,  rand_auts,  NULL, "incomplete AUTS (b)"},
		{       &aud2g,       &aud3g,       NULL,  auts, "incomplete AUTS (c)"},
		{       &aud2g,       &aud3g,  rand_auts,  NULL, "incomplete AUTS (d)"},
	};

	comment_start();

	for (i = 0; i < ARRAY_SIZE(tests); i++) {
		fprintf(stderr, "\n- %s\n", tests[i].label);
		rc = auc_compute_vectors(&vec, 1,
					 tests[i].aud2g,
					 tests[i].aud3g,
					 tests[i].rand_auts,
					 tests[i].auts);
		VERBOSE_ASSERT(rc, < 0, "%d");
	}

	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("auc_3g_test.c\n");

	handle_options(argc, argv);

	void *tall_ctx = talloc_named_const(NULL, 1, "auc_test");

	osmo_init_logging2(tall_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");

	test_gen_vectors_2g_only();
	test_gen_vectors_2g_plus_3g();
	test_gen_vectors_3g_only();
	test_gen_vectors_3g_xor();
	test_gen_vectors_bad_args();

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