gbproxy: Refactor gb_proxy.c into several files

This patch moves several functions and declarations out of gb_proxy.c
to make them reusable by other components and to separate them by
context and task.

Counter enums (prefix is changed to gbproxy_):
  enum gbprox_global_ctr -> gprs/gb_proxy.h
  enum gbprox_peer_ctr -> gprs/gb_proxy.h

Generic Gb parsing (prefix is changed to gprs_gb_):
  struct gbproxy_parse_context -> openbsc/gprs_gb_parse.h
  gbprox_parse_dtap() -> gprs/gprs_gb_parse.c
  gbprox_parse_llc() -> gprs/gprs_gb_parse.c
  gbprox_parse_bssgp() -> gprs/gprs_gb_parse.c
  gbprox_log_parse_context() -> gprs/gprs_gb_parse.c
  *_shift(), *_match() -> gprs/gprs_gb_parse.c (no prefix)
  gbprox_parse_gmm_* -> gprs/gprs_gb_parse.c (static)
  gbprox_parse_gsm_* -> gprs/gprs_gb_parse.c (static)

MI testing/parsing (prefix gprs_ added):
  is_mi_tmsi() -> gprs/gprs_utils.c
  is_mi_imsi() -> gprs/gprs_utils.c
  parse_mi_tmsi() -> gprs/gprs_utils.c

TLLI state handling (prefix is changed to gbproxy_):
  gbprox_*tlli* -> gprs/gb_proxy_tlli.c
  (except gbprox_patch_tlli, gbproxy_make_sgsn_tlli)

Message patching (prefix is changed to gbproxy_):
  gbprox_*patch* -> gprs/gb_proxy_patch.c
  gbprox_check_imsi -> gprs/gb_proxy_patch.c

Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy_tlli.c b/openbsc/src/gprs/gb_proxy_tlli.c
new file mode 100644
index 0000000..ccd118e
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_tlli.c
@@ -0,0 +1,544 @@
+/* Gb-proxy TLLI state handling */
+
+/* (C) 2014 by On-Waves
+ * 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 <openbsc/gb_proxy.h>
+
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_gb_parse.h>
+
+#include <openbsc/gsm_data_shared.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/talloc.h>
+
+struct gbproxy_tlli_info *gbproxy_find_tlli(struct gbproxy_peer *peer,
+					    uint32_t tlli)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
+		if (tlli_info->tlli.current == tlli ||
+		    tlli_info->tlli.assigned == tlli)
+			return tlli_info;
+
+	return NULL;
+}
+
+struct gbproxy_tlli_info *gbproxy_find_tlli_by_ptmsi(
+	struct gbproxy_peer *peer,
+	uint32_t ptmsi)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
+		if (tlli_info->tlli.ptmsi == ptmsi)
+			return tlli_info;
+
+	return NULL;
+}
+
+struct gbproxy_tlli_info *gbproxy_find_tlli_by_sgsn_tlli(
+	struct gbproxy_peer *peer,
+	uint32_t tlli)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
+		if (tlli_info->sgsn_tlli.current == tlli ||
+		    tlli_info->sgsn_tlli.assigned == tlli)
+			return tlli_info;
+
+	return NULL;
+}
+
+struct gbproxy_tlli_info *gbproxy_find_tlli_by_mi(
+	struct gbproxy_peer *peer,
+	const uint8_t *mi_data,
+	size_t mi_data_len)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	if (!gprs_is_mi_imsi(mi_data, mi_data_len))
+		return NULL;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list) {
+		if (tlli_info->mi_data_len != mi_data_len)
+			continue;
+		if (memcmp(tlli_info->mi_data, mi_data, mi_data_len) != 0)
+			continue;
+
+		return tlli_info;
+	}
+
+	return NULL;
+}
+
+void gbproxy_delete_tlli(struct gbproxy_peer *peer,
+			 struct gbproxy_tlli_info *tlli_info)
+{
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_del(&tlli_info->list);
+	talloc_free(tlli_info);
+	state->enabled_tllis_count -= 1;
+
+	peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+		state->enabled_tllis_count;
+}
+
+void gbproxy_delete_tllis(struct gbproxy_peer *peer)
+{
+	struct gbproxy_tlli_info *tlli_info, *nxt;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry_safe(tlli_info, nxt, &state->enabled_tllis, list)
+		gbproxy_delete_tlli(peer, tlli_info);
+
+	OSMO_ASSERT(state->enabled_tllis_count == 0);
+	OSMO_ASSERT(llist_empty(&state->enabled_tllis));
+}
+
+static void gbproxy_attach_tlli_info(struct gbproxy_peer *peer, time_t now,
+				     struct gbproxy_tlli_info *tlli_info)
+{
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	tlli_info->timestamp = now;
+	llist_add(&tlli_info->list, &state->enabled_tllis);
+	state->enabled_tllis_count += 1;
+
+	peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+		state->enabled_tllis_count;
+}
+
+int gbproxy_remove_stale_tllis(struct gbproxy_peer *peer, time_t now)
+{
+	struct gbproxy_patch_state *state = &peer->patch_state;
+	int exceeded_max_len = 0;
+	int deleted_count = 0;
+	int check_for_age;
+
+	if (peer->cfg->tlli_max_len > 0)
+		exceeded_max_len =
+			state->enabled_tllis_count - peer->cfg->tlli_max_len;
+
+	check_for_age = peer->cfg->tlli_max_age > 0;
+
+	for (; exceeded_max_len > 0; exceeded_max_len--) {
+		struct gbproxy_tlli_info *tlli_info;
+		OSMO_ASSERT(!llist_empty(&state->enabled_tllis));
+		tlli_info = llist_entry(state->enabled_tllis.prev,
+					struct gbproxy_tlli_info,
+					list);
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list "
+		     "(stale, length %d, max_len exceeded)\n",
+		     tlli_info->tlli.current, state->enabled_tllis_count);
+
+		gbproxy_delete_tlli(peer, tlli_info);
+		deleted_count += 1;
+	}
+
+	while (check_for_age && !llist_empty(&state->enabled_tllis)) {
+		time_t age;
+		struct gbproxy_tlli_info *tlli_info;
+		tlli_info = llist_entry(state->enabled_tllis.prev,
+					struct gbproxy_tlli_info,
+					list);
+		age = now - tlli_info->timestamp;
+		/* age < 0 only happens after system time jumps, discard entry */
+		if (age <= peer->cfg->tlli_max_age && age >= 0) {
+			check_for_age = 0;
+			continue;
+		}
+
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list "
+		     "(stale, age %d, max_age exceeded)\n",
+		     tlli_info->tlli.current, (int)age);
+
+		gbproxy_delete_tlli(peer, tlli_info);
+		deleted_count += 1;
+	}
+
+	return deleted_count;
+}
+
+static struct gbproxy_tlli_info *gbproxy_tlli_info_alloc(
+	struct gbproxy_peer *peer)
+{
+	struct gbproxy_tlli_info *tlli_info;
+
+	tlli_info = talloc_zero(peer, struct gbproxy_tlli_info);
+	tlli_info->tlli.ptmsi = GSM_RESERVED_TMSI;
+	tlli_info->sgsn_tlli.ptmsi = GSM_RESERVED_TMSI;
+
+	return tlli_info;
+}
+
+static void gbproxy_detach_tlli_info(
+	struct gbproxy_peer *peer,
+	struct gbproxy_tlli_info *tlli_info)
+{
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_del(&tlli_info->list);
+	OSMO_ASSERT(state->enabled_tllis_count > 0);
+	state->enabled_tllis_count -= 1;
+
+	peer->ctrg->ctr[GBPROX_PEER_CTR_TLLI_CACHE_SIZE].current =
+		state->enabled_tllis_count;
+}
+
+static void gbproxy_update_tlli_info(struct gbproxy_tlli_info *tlli_info,
+				     const uint8_t *imsi, size_t imsi_len)
+{
+	if (!gprs_is_mi_imsi(imsi, imsi_len))
+		return;
+
+	tlli_info->mi_data_len = imsi_len;
+	tlli_info->mi_data =
+		talloc_realloc_size(tlli_info, tlli_info->mi_data, imsi_len);
+	OSMO_ASSERT(tlli_info->mi_data != NULL);
+	memcpy(tlli_info->mi_data, imsi, imsi_len);
+}
+
+void gbproxy_reassign_tlli(struct gbproxy_tlli_state *tlli_state,
+			   struct gbproxy_peer *peer, uint32_t new_tlli)
+{
+	if (new_tlli == tlli_state->current)
+		return;
+
+	LOGP(DGPRS, LOGL_INFO,
+	     "The TLLI has been reassigned from %08x to %08x\n",
+	     tlli_state->current, new_tlli);
+
+	/* Remember assigned TLLI */
+	tlli_state->assigned = new_tlli;
+	tlli_state->bss_validated = 0;
+	tlli_state->net_validated = 0;
+}
+
+uint32_t gbproxy_map_tlli(uint32_t other_tlli,
+			  struct gbproxy_tlli_info *tlli_info, int to_bss)
+{
+	uint32_t tlli = 0;
+	struct gbproxy_tlli_state *src, *dst;
+	if (to_bss) {
+		src = &tlli_info->sgsn_tlli;
+		dst = &tlli_info->tlli;
+	} else {
+		src = &tlli_info->tlli;
+		dst = &tlli_info->sgsn_tlli;
+	}
+	if (src->current == other_tlli)
+		tlli = dst->current;
+	else if (src->assigned == other_tlli)
+		tlli = dst->assigned;
+
+	return tlli;
+}
+
+static void gbproxy_validate_tlli(struct gbproxy_tlli_state *tlli_state,
+				  uint32_t tlli, int to_bss)
+{
+	LOGP(DGPRS, LOGL_DEBUG,
+	     "%s({current = %08x, assigned = %08x, net_vld = %d, bss_vld = %d}, %08x)\n",
+	     __func__, tlli_state->current, tlli_state->assigned,
+	     tlli_state->net_validated, tlli_state->bss_validated, tlli);
+
+	if (!tlli_state->assigned || tlli_state->assigned != tlli)
+		return;
+
+	/* TODO: Is this ok? Check spec */
+	if (gprs_tlli_type(tlli) != TLLI_LOCAL)
+		return;
+
+	/* See GSM 04.08, 4.7.1.5 */
+	if (to_bss)
+		tlli_state->net_validated = 1;
+	else
+		tlli_state->bss_validated = 1;
+
+	if (!tlli_state->bss_validated || !tlli_state->net_validated)
+		return;
+
+	LOGP(DGPRS, LOGL_INFO,
+	     "The TLLI %08x has been validated (was %08x)\n",
+	     tlli_state->assigned, tlli_state->current);
+
+	tlli_state->current = tlli;
+	tlli_state->assigned = 0;
+}
+
+void gbproxy_touch_tlli(struct gbproxy_peer *peer,
+			struct gbproxy_tlli_info *tlli_info, time_t now)
+{
+	gbproxy_detach_tlli_info(peer, tlli_info);
+	gbproxy_attach_tlli_info(peer, now, tlli_info);
+}
+
+struct gbproxy_tlli_info *gbproxy_register_tlli(
+	struct gbproxy_peer *peer, uint32_t tlli,
+	const uint8_t *imsi, size_t imsi_len, time_t now)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	int enable_patching = -1;
+	int tlli_already_known;
+
+	/* Check, whether the IMSI matches */
+	if (gprs_is_mi_imsi(imsi, imsi_len)) {
+		enable_patching = gbproxy_check_imsi(peer, imsi, imsi_len);
+		if (enable_patching < 0)
+			return NULL;
+	}
+
+	tlli_info = gbproxy_find_tlli(peer, tlli);
+
+	if (!tlli_info) {
+		tlli_info = gbproxy_find_tlli_by_mi(peer, imsi, imsi_len);
+
+		if (tlli_info) {
+			/* TLLI has changed somehow, adjust it */
+			LOGP(DGPRS, LOGL_INFO,
+			     "The TLLI has changed from %08x to %08x\n",
+			     tlli_info->tlli.current, tlli);
+			tlli_info->tlli.current = tlli;
+		}
+	}
+
+	if (!tlli_info) {
+		tlli_info = gbproxy_tlli_info_alloc(peer);
+		tlli_info->tlli.current = tlli;
+	} else {
+		gbproxy_detach_tlli_info(peer, tlli_info);
+		tlli_already_known = 1;
+	}
+
+	OSMO_ASSERT(tlli_info != NULL);
+
+	if (!tlli_already_known)
+		LOGP(DGPRS, LOGL_INFO, "Adding TLLI %08x to list\n", tlli);
+
+	gbproxy_attach_tlli_info(peer, now, tlli_info);
+	gbproxy_update_tlli_info(tlli_info, imsi, imsi_len);
+
+	if (enable_patching >= 0)
+		tlli_info->enable_patching = enable_patching;
+
+	return tlli_info;
+}
+
+static void gbproxy_unregister_tlli(struct gbproxy_peer *peer, uint32_t tlli)
+{
+	struct gbproxy_tlli_info *tlli_info;
+
+	tlli_info = gbproxy_find_tlli(peer, tlli);
+	if (tlli_info) {
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list\n",
+		     tlli);
+		gbproxy_delete_tlli(peer, tlli_info);
+	}
+}
+
+int gbproxy_check_tlli(struct gbproxy_peer *peer, uint32_t tlli)
+{
+	struct gbproxy_tlli_info *tlli_info;
+
+	LOGP(DGPRS, LOGL_INFO, "Checking TLLI %08x, class: %d\n",
+	     tlli, gprs_tlli_type(tlli));
+
+	if (!peer->cfg->check_imsi)
+		return 1;
+
+	tlli_info = gbproxy_find_tlli(peer, tlli);
+
+	return tlli_info != NULL && tlli_info->enable_patching;
+}
+
+struct gbproxy_tlli_info *gbproxy_update_tlli_state_ul(
+	struct gbproxy_peer *peer,
+	time_t now,
+	struct gprs_gb_parse_context *parse_ctx)
+{
+	struct gbproxy_tlli_info *tlli_info = NULL;
+
+	if (parse_ctx->tlli_enc)
+		tlli_info = gbproxy_find_tlli(peer, parse_ctx->tlli);
+
+	if (parse_ctx->tlli_enc && parse_ctx->llc) {
+		uint32_t sgsn_tlli;
+		if (!tlli_info) {
+			tlli_info =
+				gbproxy_register_tlli(peer, parse_ctx->tlli,
+						      parse_ctx->imsi,
+						      parse_ctx->imsi_len, now);
+			/* Setup TLLIs */
+			sgsn_tlli = gbproxy_make_sgsn_tlli(peer, tlli_info,
+							   parse_ctx->tlli);
+			tlli_info->sgsn_tlli.current = sgsn_tlli;
+		} else {
+			sgsn_tlli = gbproxy_map_tlli(parse_ctx->tlli, tlli_info, 0);
+			if (!sgsn_tlli)
+				sgsn_tlli = gbproxy_make_sgsn_tlli(peer, tlli_info,
+								   parse_ctx->tlli);
+
+			gbproxy_validate_tlli(&tlli_info->tlli,
+					      parse_ctx->tlli, 0);
+			gbproxy_validate_tlli(&tlli_info->sgsn_tlli,
+					      sgsn_tlli, 0);
+			gbproxy_touch_tlli(peer, tlli_info, now);
+		}
+	} else if (tlli_info) {
+		gbproxy_touch_tlli(peer, tlli_info, now);
+	}
+
+	if (parse_ctx->imsi && tlli_info && tlli_info->mi_data_len == 0) {
+		int enable_patching;
+		gbproxy_update_tlli_info(tlli_info,
+					 parse_ctx->imsi, parse_ctx->imsi_len);
+
+		/* Check, whether the IMSI matches */
+		enable_patching = gbproxy_check_imsi(peer, parse_ctx->imsi,
+						     parse_ctx->imsi_len);
+		if (enable_patching >= 0)
+			tlli_info->enable_patching = enable_patching;
+	}
+
+	return tlli_info;
+}
+
+struct gbproxy_tlli_info *gbproxy_update_tlli_state_dl(
+	struct gbproxy_peer *peer,
+	time_t now,
+	struct gprs_gb_parse_context *parse_ctx)
+{
+	struct gbproxy_tlli_info *tlli_info = NULL;
+
+	if (parse_ctx->tlli_enc)
+		tlli_info = gbproxy_find_tlli_by_sgsn_tlli(peer, parse_ctx->tlli);
+
+	if (parse_ctx->tlli_enc && parse_ctx->new_ptmsi_enc) {
+		/* A new PTMSI has been signaled in the message,
+		 * register new TLLI */
+		uint32_t new_sgsn_ptmsi;
+		uint32_t new_sgsn_tlli;
+		uint32_t new_bss_ptmsi;
+		uint32_t new_bss_tlli = 0;
+		if (!gprs_parse_mi_tmsi(parse_ctx->new_ptmsi_enc, GSM48_TMSI_LEN,
+					&new_sgsn_ptmsi)) {
+			LOGP(DGPRS, LOGL_ERROR,
+			     "Failed to parse new TLLI/PTMSI (current is %08x)\n",
+			     parse_ctx->tlli);
+			return tlli_info;
+		}
+		new_sgsn_tlli = gprs_tmsi2tlli(new_sgsn_ptmsi, TLLI_LOCAL);
+		new_bss_ptmsi = gbproxy_make_bss_ptmsi(peer, new_sgsn_ptmsi);
+		if (new_bss_ptmsi != GSM_RESERVED_TMSI)
+			new_bss_tlli = gprs_tmsi2tlli(new_bss_ptmsi, TLLI_LOCAL);
+		LOGP(DGPRS, LOGL_INFO,
+		     "Got new TLLI(PTMSI) %08x(%08x) from SGSN, using %08x(%08x)\n",
+		     new_sgsn_tlli, new_sgsn_ptmsi, new_bss_tlli, new_bss_ptmsi);
+		if (tlli_info) {
+			gbproxy_reassign_tlli(&tlli_info->sgsn_tlli,
+					      peer, new_sgsn_tlli);
+			gbproxy_reassign_tlli(&tlli_info->tlli,
+					      peer, new_bss_tlli);
+			gbproxy_touch_tlli(peer, tlli_info, now);
+		} else {
+			tlli_info = gbproxy_tlli_info_alloc(peer);
+			LOGP(DGPRS, LOGL_INFO,
+			     "Adding TLLI %08x to list (SGSN, new P-TMSI)\n",
+			     new_sgsn_tlli);
+
+			gbproxy_attach_tlli_info(peer, now, tlli_info);
+			/* Setup TLLIs */
+			tlli_info->sgsn_tlli.current = new_sgsn_tlli;
+		}
+		/* Setup PTMSIs */
+		tlli_info->sgsn_tlli.ptmsi = new_sgsn_ptmsi;
+		tlli_info->tlli.ptmsi = new_bss_ptmsi;
+	} else if (parse_ctx->tlli_enc && parse_ctx->llc && !tlli_info) {
+		/* Unknown SGSN TLLI */
+		tlli_info = gbproxy_tlli_info_alloc(peer);
+		LOGP(DGPRS, LOGL_INFO, "Adding TLLI %08x to list (SGSN)\n",
+		     parse_ctx->tlli);
+
+		gbproxy_attach_tlli_info(peer, now, tlli_info);
+		/* Setup TLLIs */
+		tlli_info->sgsn_tlli.current = parse_ctx->tlli;
+		if (peer->cfg->patch_ptmsi) {
+			/* TODO: We don't know the local TLLI here, perhaps add
+			 * a workaround that derives a PTMSI from the SGSN TLLI
+			 * and use that to get the missing values. This may
+			 * only happen when the gbproxy has been restarted or a
+			 * tlli_info has been discarded due to age or queue
+			 * length.
+			 */
+			tlli_info->tlli.current = 0;
+		} else {
+			tlli_info->tlli.current = tlli_info->sgsn_tlli.current;
+		}
+	} else if (parse_ctx->tlli_enc && parse_ctx->llc && tlli_info) {
+		uint32_t bss_tlli = gbproxy_map_tlli(parse_ctx->tlli,
+						     tlli_info, 1);
+		gbproxy_validate_tlli(&tlli_info->sgsn_tlli, parse_ctx->tlli, 1);
+		gbproxy_validate_tlli(&tlli_info->tlli, bss_tlli, 1);
+		gbproxy_touch_tlli(peer, tlli_info, now);
+	} else if (tlli_info) {
+		gbproxy_touch_tlli(peer, tlli_info, now);
+	}
+
+	if (parse_ctx->imsi && tlli_info && tlli_info->mi_data_len == 0) {
+		int enable_patching;
+		gbproxy_update_tlli_info(tlli_info,
+					 parse_ctx->imsi, parse_ctx->imsi_len);
+
+		/* Check, whether the IMSI matches */
+		enable_patching = gbproxy_check_imsi(peer, parse_ctx->imsi,
+						     parse_ctx->imsi_len);
+		if (enable_patching >= 0)
+			tlli_info->enable_patching = enable_patching;
+	}
+
+	return tlli_info;
+}
+
+void gbproxy_update_tlli_state_after(
+	struct gbproxy_peer *peer,
+	struct gbproxy_tlli_info *tlli_info,
+	time_t now,
+	struct gprs_gb_parse_context *parse_ctx)
+{
+	if (parse_ctx->invalidate_tlli)
+		gbproxy_unregister_tlli(peer, parse_ctx->tlli);
+
+	gbproxy_remove_stale_tllis(peer, now);
+}
+
+