lynxis lazus has submitted this change. ( 
https://gerrit.osmocom.org/c/osmo-sgsn/+/37864?usp=email )

 (

17 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted 
one.
 )Change subject: Add Routing Areas
......................................................................

Add Routing Areas

Add a routing area layer which tracks routing area and
cells within a routing area.

Change-Id: I2474b19a7471a1dea3c863ddf8372b16180211aa
---
M configure.ac
M include/osmocom/sgsn/Makefile.am
M include/osmocom/sgsn/debug.h
A include/osmocom/sgsn/gprs_routing_area.h
M include/osmocom/sgsn/sgsn.h
M src/sgsn/Makefile.am
M src/sgsn/gprs_bssgp.c
M src/sgsn/gprs_ns.c
A src/sgsn/gprs_routing_area.c
M src/sgsn/sgsn.c
M src/sgsn/sgsn_main.c
M tests/Makefile.am
A tests/gprs_routing_area/Makefile.am
A tests/gprs_routing_area/gprs_routing_area_test.c
A tests/gprs_routing_area/gprs_routing_area_test.ok
M tests/sgsn/Makefile.am
M tests/testsuite.at
17 files changed, 987 insertions(+), 0 deletions(-)

Approvals:
  laforge: Looks good to me, approved
  pespin: Looks good to me, but someone else must approve
  daniel: Looks good to me, but someone else must approve
  Jenkins Builder: Verified




diff --git a/configure.ac b/configure.ac
index 5286d2e..17a0236 100644
--- a/configure.ac
+++ b/configure.ac
@@ -243,6 +243,7 @@
     tests/Makefile
     tests/atlocal
     tests/gprs/Makefile
+    tests/gprs_routing_area/Makefile
     tests/sgsn/Makefile
     tests/gtphub/Makefile
     tests/xid/Makefile
diff --git a/include/osmocom/sgsn/Makefile.am b/include/osmocom/sgsn/Makefile.am
index aa6cd0f..9f87fd8 100644
--- a/include/osmocom/sgsn/Makefile.am
+++ b/include/osmocom/sgsn/Makefile.am
@@ -14,6 +14,7 @@
        gprs_llc.h \
        gprs_llc_xid.h \
        gprs_ranap.h \
+       gprs_routing_area.h \
        gprs_sm.h \
        gprs_sndcp_comp.h \
        gprs_sndcp_dcomp.h \
diff --git a/include/osmocom/sgsn/debug.h b/include/osmocom/sgsn/debug.h
index 80c5d9f..04ba3c0 100644
--- a/include/osmocom/sgsn/debug.h
+++ b/include/osmocom/sgsn/debug.h
@@ -27,6 +27,7 @@
        DGTP,
        DOBJ,
        DRIM,
+       DRA, /* Routing Area handling */
        Debug_LastEntry,
 };

diff --git a/include/osmocom/sgsn/gprs_routing_area.h 
b/include/osmocom/sgsn/gprs_routing_area.h
new file mode 100644
index 0000000..34e29cb
--- /dev/null
+++ b/include/osmocom/sgsn/gprs_routing_area.h
@@ -0,0 +1,88 @@
+/*! \file gprs_routing_area.h */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm23003.h>
+
+struct sgsn_instance;
+
+struct sgsn_ra_global {
+       /* list of struct sgsn_ra */
+       struct llist_head ra_list;
+};
+
+struct sgsn_ra {
+       /* Entry in sgsn_ra_global->ra_list */
+       struct llist_head list;
+
+       struct osmo_routing_area_id rai;
+       /* cells contains a list of sgsn_ra_cells which are alive */
+       struct llist_head cells;
+};
+
+enum sgsn_ra_ran_type {
+       RA_TYPE_GERAN_Gb,
+       RA_TYPE_UTRAN_Iu,
+};
+
+struct sgsn_ra_cell {
+       /* Entry in sgsn_ra->cells */
+       struct llist_head list;
+
+       /*! link back to the parent */
+       struct sgsn_ra *ra;
+
+       enum sgsn_ra_ran_type ran_type;
+
+       uint16_t cell_id;
+       union {
+               struct {
+                       uint16_t nsei;
+                       uint16_t bvci;
+               } geran;
+               struct {
+                       /* TODO: unused */
+                       uint16_t rncid;
+                       uint16_t sac;
+               } utran;
+       } u;
+};
+
+void sgsn_ra_init(struct sgsn_instance *inst);
+
+struct sgsn_ra *sgsn_ra_alloc(const struct osmo_routing_area_id *rai);
+void sgsn_ra_free(struct sgsn_ra *ra);
+struct sgsn_ra_cell *sgsn_ra_cell_alloc_geran(struct sgsn_ra *ra, uint16_t 
cell_id, uint16_t nsei, uint16_t bvci);
+void sgsn_ra_cell_free(struct sgsn_ra_cell *cell);
+
+/* Called by BSSGP layer to inform about a reset on a BVCI */
+int sgsn_ra_bvc_reset_ind(uint16_t nsei, uint16_t bvci, struct 
osmo_cell_global_id_ps *cgi_ps);
+/* FIXME: handle BVC BLOCK/UNBLOCK/UNAVAILABLE */
+/* Called by NS-VC layer to inform about an unavailable NSEI (and all BVCI on 
them) */
+int sgsn_ra_nsei_failure_ind(uint16_t nsei);
+
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi_ps(const struct 
osmo_cell_global_id_ps *cgi_ps);
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_lai(const struct 
osmo_location_area_id *lai, uint16_t cell_id);
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi(const struct osmo_cell_global_id 
*cgi);
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_ra(const struct sgsn_ra *ra, uint16_t 
cell_id);
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_gb(uint16_t nsei, uint16_t bvci);
+struct sgsn_ra *sgsn_ra_get_ra(const struct osmo_routing_area_id *ra_id);
+
+
+/*
+ * return value for callbacks.
+ * STOP: stop calling the callback for the remaining cells, 
sgsn_ra_foreach_ra() returns 0
+ * CONT: continue to call the callback for remaining cells
+ * ABORT: stop calling the callback for the remaining cells, 
sgsn_ra_foreach_ra() returns -1
+ */
+#define SGSN_RA_CB_STOP 1
+#define SGSN_RA_CB_CONT 0
+#define SGSN_RA_CB_ERROR -1
+
+typedef int (sgsn_ra_cb_t)(struct sgsn_ra_cell *ra_cell, void *cb_data);
+int sgsn_ra_foreach_cell(struct sgsn_ra *ra, sgsn_ra_cb_t *cb, void *cb_data);
+int sgsn_ra_foreach_cell2(struct osmo_routing_area_id *rai, sgsn_ra_cb_t *cb, 
void *cb_data);
diff --git a/include/osmocom/sgsn/sgsn.h b/include/osmocom/sgsn/sgsn.h
index 9e09184..9190a61 100644
--- a/include/osmocom/sgsn/sgsn.h
+++ b/include/osmocom/sgsn/sgsn.h
@@ -152,6 +152,9 @@
        ares_channel ares_channel;
        struct ares_addr_node *ares_servers;

+       /* Routing areas */
+       struct sgsn_ra_global *routing_area;
+
        struct rate_ctr_group *rate_ctrs;

        struct llist_head apn_list; /* list of struct sgsn_apn_ctx */
diff --git a/src/sgsn/Makefile.am b/src/sgsn/Makefile.am
index 487dead..c363a48 100644
--- a/src/sgsn/Makefile.am
+++ b/src/sgsn/Makefile.am
@@ -47,6 +47,7 @@
        gprs_gmm_fsm.c \
        gprs_mm_state_gb_fsm.c \
        gprs_ns.c \
+       gprs_routing_area.c \
        gprs_sm.c \
        gprs_sndcp.c \
        gprs_sndcp_comp.c \
diff --git a/src/sgsn/gprs_bssgp.c b/src/sgsn/gprs_bssgp.c
index 0a9bb91..fd82b15 100644
--- a/src/sgsn/gprs_bssgp.c
+++ b/src/sgsn/gprs_bssgp.c
@@ -20,6 +20,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
+
 #include <osmocom/core/prim.h>
 #include <osmocom/core/rate_ctr.h>

@@ -30,9 +31,26 @@

 #include <osmocom/sgsn/gprs_llc.h>
 #include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/gprs_routing_area.h>
 #include <osmocom/sgsn/sgsn_rim.h>
 #include <osmocom/sgsn/mmctx.h>

+#include <osmocom/sgsn/debug.h>
+
+static int bssgp_nm_bvc_reset_ind(struct osmo_bssgp_prim *bp)
+{
+       struct osmo_cell_global_id_ps cgi_ps = {};
+
+       if (!bp->tp)
+               return -EINVAL;
+
+       if (!TLVP_PRES_LEN(bp->tp, BSSGP_IE_CELL_ID, 8))
+               return -EINVAL;
+
+       bssgp_parse_cell_id2(&cgi_ps.rai, &cgi_ps.cell_identity, 
TLVP_VAL(bp->tp, BSSGP_IE_CELL_ID), 8);
+       return sgsn_ra_bvc_reset_ind(bp->nsei, bp->bvci, &cgi_ps);
+}
+
 /* call-back function for the BSSGP protocol */
 int sgsn_bssgp_rx_prim(struct osmo_prim_hdr *oph)
 {
@@ -58,6 +76,18 @@
                }
                break;
        case SAP_BSSGP_NM:
+               switch (oph->primitive) {
+               case PRIM_NM_BVC_RESET:
+                       if (oph->operation == PRIM_OP_INDICATION)
+                               bssgp_nm_bvc_reset_ind(bp);
+                       break;
+               case PRIM_NM_BVC_BLOCK:
+               case PRIM_NM_BVC_UNBLOCK:
+               case PRIM_NM_STATUS:
+               case PRIM_NM_LLC_DISCARDED:
+                       break;
+               }
+
                break;
        case SAP_BSSGP_RIM:
                return sgsn_rim_rx_from_gb(bp, oph->msg);
diff --git a/src/sgsn/gprs_ns.c b/src/sgsn/gprs_ns.c
index eb447fa..3968128 100644
--- a/src/sgsn/gprs_ns.c
+++ b/src/sgsn/gprs_ns.c
@@ -28,6 +28,7 @@
 #include <osmocom/gprs/gprs_ns2.h>
 #include <osmocom/gprs/gprs_bssgp_bss.h>
 #include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/gprs_routing_area.h>

 #include "config.h"

@@ -52,6 +53,7 @@
                break;
        case GPRS_NS2_AFF_CAUSE_FAILURE:
                LOGP(DGPRS, LOGL_NOTICE, "NS-E %d became unavailable\n", 
nsp->nsei);
+               sgsn_ra_nsei_failure_ind(nsp->nsei);
                break;
        default:
                LOGP(DGPRS, LOGL_NOTICE, "NS: %s Unknown prim %d from NS\n",
diff --git a/src/sgsn/gprs_routing_area.c b/src/sgsn/gprs_routing_area.c
new file mode 100644
index 0000000..977caa2
--- /dev/null
+++ b/src/sgsn/gprs_routing_area.c
@@ -0,0 +1,340 @@
+/* SGSN Routing Area for 2G */
+
+/* (C) 2024 sysmocom s.f.m.c. GmbH <[email protected]>
+ * Author: Alexander Couzens <[email protected]>
+ *
+ * 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 <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/sgsn/debug.h>
+
+
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/sgsn/sgsn.h>
+
+#include <osmocom/sgsn/gprs_routing_area.h>
+
+static void _sgsn_ra_cell_free(struct sgsn_ra_cell *cell, bool drop_empty_ra)
+{
+       struct sgsn_ra *ra;
+
+       if (!cell)
+               return;
+
+       llist_del(&cell->list);
+       /* to prevent double free of the Cell when freeing a Routing Area */
+       if (!drop_empty_ra) {
+               talloc_free(cell);
+               return;
+       }
+
+       ra = cell->ra;
+       talloc_free(cell);
+
+       if (llist_empty(&ra->cells))
+               sgsn_ra_free(ra);
+}
+
+void sgsn_ra_cell_free(struct sgsn_ra_cell *cell)
+{
+       _sgsn_ra_cell_free(cell, true);
+}
+
+void sgsn_ra_free(struct sgsn_ra *ra)
+{
+       struct sgsn_ra_cell *cell, *cell2;
+
+       if (!ra)
+               return;
+
+       llist_for_each_entry_safe(cell, cell2, &ra->cells, list) {
+               _sgsn_ra_cell_free(cell, false);
+       }
+
+       llist_del(&ra->list);
+       talloc_free(ra);
+}
+
+struct sgsn_ra *sgsn_ra_alloc(const struct osmo_routing_area_id *rai)
+{
+       struct sgsn_ra *ra;
+       ra = talloc_zero(sgsn->routing_area, struct sgsn_ra);
+       if (!ra)
+               return NULL;
+
+       INIT_LLIST_HEAD(&ra->cells);
+       ra->rai = *rai;
+       llist_add(&ra->list, &sgsn->routing_area->ra_list);
+       return ra;
+}
+
+struct sgsn_ra_cell *sgsn_ra_cell_alloc_geran(struct sgsn_ra *ra, uint16_t 
cell_id, uint16_t nsei, uint16_t bvci)
+{
+       struct sgsn_ra_cell *cell;
+
+       cell = talloc_zero(ra, struct sgsn_ra_cell);
+       if (!cell)
+               return NULL;
+
+       cell->ra = ra;
+       cell->cell_id = cell_id;
+       cell->ran_type = RA_TYPE_GERAN_Gb;
+       cell->u.geran.bvci = bvci;
+       cell->u.geran.nsei = nsei;
+
+       llist_add(&cell->list, &ra->cells);
+
+       return cell;
+}
+
+struct sgsn_ra *sgsn_ra_get_ra(const struct osmo_routing_area_id *ra_id)
+{
+       struct sgsn_ra *ra;
+
+       llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list)
+               if (osmo_rai_cmp(&ra->rai, ra_id) == 0)
+                       return ra;
+
+       return NULL;
+}
+
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_gb(uint16_t nsei, uint16_t bvci)
+{
+       struct sgsn_ra *ra;
+       struct sgsn_ra_cell *cell;
+
+       /* BVCI = 0 is invalid, only valid for signalling within the BSSGP, not 
for a single cell */
+       if (bvci == 0)
+               return NULL;
+
+       llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list) {
+               llist_for_each_entry(cell, &ra->cells, list) {
+                       if (cell->ran_type != RA_TYPE_GERAN_Gb)
+                               continue;
+
+                       if (cell->u.geran.bvci == bvci && cell->u.geran.nsei == 
nsei)
+                               return cell;
+               }
+       }
+
+       return NULL;
+}
+
+int sgsn_ra_foreach_cell(struct sgsn_ra *ra, sgsn_ra_cb_t *cb, void *cb_data)
+{
+       struct sgsn_ra_cell *cell, *tmp;
+       int ret = -ENOENT;
+
+       OSMO_ASSERT(cb);
+
+       llist_for_each_entry_safe(cell, tmp, &ra->cells, list) {
+               ret = cb(cell, cb_data);
+               switch (ret) {
+               case SGSN_RA_CB_CONT:
+                       continue;
+               case SGSN_RA_CB_STOP:
+                       return 0;
+               case SGSN_RA_CB_ERROR:
+                       return -1;
+               default:
+                       OSMO_ASSERT(0);
+               }
+       }
+
+       return ret;
+}
+
+int sgsn_ra_foreach_cell2(struct osmo_routing_area_id *ra_id, sgsn_ra_cb_t 
*cb, void *cb_data)
+{
+       struct sgsn_ra *ra;
+       OSMO_ASSERT(ra_id);
+       OSMO_ASSERT(cb);
+
+       ra = sgsn_ra_get_ra(ra_id);
+       if (!ra)
+               return -ENOENT;
+
+       return sgsn_ra_foreach_cell(ra, cb, cb_data);
+}
+
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_ra(const struct sgsn_ra *ra, uint16_t 
cell_id)
+{
+       struct sgsn_ra_cell *cell;
+
+       llist_for_each_entry(cell, &ra->cells, list) {
+               if (cell->cell_id == cell_id)
+                       return cell;
+       }
+
+       return NULL;
+}
+
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_lai(const struct 
osmo_location_area_id *lai, uint16_t cell_id)
+{
+       struct sgsn_ra *ra;
+       struct sgsn_ra_cell *cell;
+
+       /* This is a little bit in-efficient. A more performance way, but more 
complex would
+        * adding a llist for LAC on top of the routing areas */
+       llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list) {
+               if (osmo_lai_cmp(&ra->rai.lac, lai) != 0)
+                       continue;
+
+               llist_for_each_entry(cell, &ra->cells, list) {
+                       if (cell->cell_id == cell_id)
+                               return cell;
+               }
+       }
+
+       return NULL;
+}
+
+/*! Return the cell by searching for the RA, when found, search the cell 
within the RA
+ *
+ * \param cgi_ps
+ * \return the cell or NULL if not found
+ */
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi_ps(const struct 
osmo_cell_global_id_ps *cgi_ps)
+{
+       struct sgsn_ra *ra;
+
+       OSMO_ASSERT(cgi_ps);
+
+       ra = sgsn_ra_get_ra(&cgi_ps->rai);
+       if (!ra)
+               return NULL;
+
+       return sgsn_ra_get_cell_by_ra(ra, cgi_ps->cell_identity);
+}
+
+struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi(const struct osmo_cell_global_id 
*cgi)
+{
+       OSMO_ASSERT(cgi);
+
+       return sgsn_ra_get_cell_by_lai(&cgi->lai, cgi->cell_identity);
+}
+
+/*! Callback from the BSSGP layer on NM RESET IND
+ *
+ * \param nsei
+ * \param bvci
+ * \param cgi_ps
+ * \return 0 on success or -ENOMEM
+ */
+int sgsn_ra_bvc_reset_ind(uint16_t nsei, uint16_t bvci, struct 
osmo_cell_global_id_ps *cgi_ps)
+{
+       struct sgsn_ra *ra;
+       struct sgsn_ra_cell *cell;
+       bool ra_created = false;
+       OSMO_ASSERT(cgi_ps);
+
+       /* TODO: do we have to move all MS to GMM IDLE state when this happens 
for a alive cell which got reseted? */
+       ra = sgsn_ra_get_ra(&cgi_ps->rai);
+       if (!ra) {
+               ra = sgsn_ra_alloc(&cgi_ps->rai);
+               if (!ra)
+                       return -ENOMEM;
+               ra_created = true;
+       }
+
+       if (!ra_created) {
+               cell = sgsn_ra_get_cell_by_ra(ra, cgi_ps->cell_identity);
+               if (cell && cell->ran_type == RA_TYPE_GERAN_Gb) {
+                       /* Cell already exist, update NSEI/BVCI */
+                       if (cell->u.geran.bvci != bvci || cell->u.geran.nsei != 
nsei) {
+                               LOGP(DRA, LOGL_INFO, "GERAN Cell changed DLCI. 
Old: nsei/bvci %05u/%05u New: nsei/bvci %05u/%05u\n",
+                                    cell->u.geran.nsei, cell->u.geran.bvci, 
nsei, bvci);
+                               cell->u.geran.bvci = bvci;
+                               cell->u.geran.nsei = nsei;
+                       }
+                       return 0;
+               }
+
+               if (cell && cell->ran_type != RA_TYPE_GERAN_Gb) {
+                       /* How can we have here a RA change? Must be a 
configuration error. */
+                       LOGP(DRA, LOGL_INFO, "CGI %s: RAN change detected to 
GERAN!", osmo_cgi_ps_name(cgi_ps));
+                       _sgsn_ra_cell_free(cell, false);
+                       cell = NULL;
+               }
+
+               if (!cell) {
+                       char old_ra[32];
+                       char new_ra[32];
+                       /* check for the same cell id within the location area. 
The cell id is also unique for the cell within the LAC
+                        * This should only happen when a Cell is changing 
routing areas */
+                       cell = sgsn_ra_get_cell_by_lai(&cgi_ps->rai.lac, 
cgi_ps->cell_identity);
+                       if (cell) {
+                               LOGP(DRA, LOGL_INFO, "CGI %s: changed Routing 
Area. Old: %s, New: %s\n",
+                                    osmo_cgi_ps_name(cgi_ps),
+                                    osmo_rai_name2_buf(old_ra, sizeof(old_ra), 
&cell->ra->rai),
+                                    osmo_rai_name2_buf(new_ra, sizeof(new_ra), 
&cgi_ps->rai));
+
+                               OSMO_ASSERT(cell->ra != ra);
+
+                               /* the old RA is definitive not our ra! Drop 
the old ra */
+                               _sgsn_ra_cell_free(cell, true);
+                               cell = NULL;
+                       }
+               }
+       }
+
+       cell = sgsn_ra_cell_alloc_geran(ra, cgi_ps->cell_identity, nsei, bvci);
+       if (!cell)
+               return -ENOMEM;
+
+       LOGP(DRA, LOGL_INFO, "New cell registered %s via nsei/bvci 
%05u/%05u\n", osmo_cgi_ps_name(cgi_ps), nsei, bvci);
+
+       return 0;
+}
+
+/* FIXME: call it on BSSGP BLOCK + unavailable with BVCI */
+int sgsn_ra_nsei_failure_ind(uint16_t nsei)
+{
+       struct sgsn_ra *ra, *ra2;
+       struct sgsn_ra_cell *cell, *cell2;
+       bool found = false;
+
+       llist_for_each_entry_safe(ra, ra2, &sgsn->routing_area->ra_list, list) {
+               llist_for_each_entry_safe(cell, cell2, &ra->cells, list) {
+                       if (cell->ran_type != RA_TYPE_GERAN_Gb)
+                               continue;
+
+                       if (cell->u.geran.nsei == nsei) {
+                               found = true;
+                               _sgsn_ra_cell_free(cell, false);
+                       }
+               }
+
+               if (llist_empty(&ra->cells))
+                       sgsn_ra_free(ra);
+
+       }
+
+       return found ? 0 : -ENOENT;
+}
+
+
+void sgsn_ra_init(struct sgsn_instance *inst)
+{
+       inst->routing_area = talloc_zero(inst, struct sgsn_ra_global);
+       OSMO_ASSERT(inst->routing_area);
+
+       INIT_LLIST_HEAD(&inst->routing_area->ra_list);
+}
diff --git a/src/sgsn/sgsn.c b/src/sgsn/sgsn.c
index 57a2c38..1c637b3 100644
--- a/src/sgsn/sgsn.c
+++ b/src/sgsn/sgsn.c
@@ -55,6 +55,7 @@
 #include <osmocom/sgsn/gtp_ggsn.h>
 #include <osmocom/sgsn/gtp.h>
 #include <osmocom/sgsn/pdpctx.h>
+#include <osmocom/sgsn/gprs_routing_area.h>

 #include <pdp.h>

@@ -189,6 +190,7 @@
        /* These are mostly setting up stuff not related to VTY cfg, so they 
can be set up here: */
        sgsn_auth_init(inst);
        sgsn_cdr_init(inst);
+       sgsn_ra_init(inst);
        return inst;
 }

diff --git a/src/sgsn/sgsn_main.c b/src/sgsn/sgsn_main.c
index d6afdef..86c3561 100644
--- a/src/sgsn/sgsn_main.c
+++ b/src/sgsn/sgsn_main.c
@@ -64,6 +64,7 @@
 #include <osmocom/sgsn/gprs_ranap.h>
 #include <osmocom/sgsn/gprs_ns.h>
 #include <osmocom/sgsn/gprs_bssgp.h>
+#include <osmocom/sgsn/gprs_routing_area.h>
 #include <osmocom/sgsn/gprs_subscriber.h>
 #include <osmocom/sgsn/gtp.h>

@@ -348,6 +349,11 @@
                .description = "RAN Information Management (RIM)",
                .enabled = 1, .loglevel = LOGL_NOTICE,
        },
+       [DRA] = {
+               .name = "DRA",
+               .description = "Routing Area",
+               .enabled = 1, .loglevel = LOGL_NOTICE,
+       },
 };

 static const struct log_info gprs_log_info = {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index fc5af0b..9d429d7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,6 +1,7 @@
 SUBDIRS = \
        gprs \
        gtphub \
+       gprs_routing_area \
        sgsn \
        xid \
        sndcp_xid \
diff --git a/tests/gprs_routing_area/Makefile.am 
b/tests/gprs_routing_area/Makefile.am
new file mode 100644
index 0000000..a61f866
--- /dev/null
+++ b/tests/gprs_routing_area/Makefile.am
@@ -0,0 +1,93 @@
+AM_CPPFLAGS = \
+       $(all_includes) \
+       -I$(top_srcdir)/include \
+       $(NULL)
+
+AM_CFLAGS = \
+       -Wall \
+       -ggdb3 \
+       $(LIBOSMOCORE_CFLAGS) \
+       $(LIBOSMOCTRL_CFLAGS) \
+       $(LIBOSMOABIS_CFLAGS) \
+       $(LIBOSMOGSM_CFLAGS) \
+       $(LIBOSMOGSUPCLIENT_CFLAGS) \
+       $(LIBCARES_CFLAGS) \
+       $(LIBGTP_CFLAGS) \
+       $(NULL)
+if BUILD_IU
+AM_CFLAGS += \
+       $(LIBASN1C_CFLAGS) \
+       $(LIBOSMOSIGTRAN_CFLAGS) \
+       $(LIBOSMORANAP_CFLAGS) \
+       $(NULL)
+endif
+
+AM_LDFLAGS = -no-install
+
+EXTRA_DIST = \
+       gprs_routing_area_test.ok \
+       $(NULL)
+
+check_PROGRAMS = \
+       gprs_routing_area_test \
+       $(NULL)
+
+gprs_routing_area_test_SOURCES = \
+       gprs_routing_area_test.c \
+       $(NULL)
+
+gprs_routing_area_test_LDADD = \
+       $(top_builddir)/src/sgsn/apn.o \
+       $(top_builddir)/src/sgsn/gprs_bssgp.o \
+       $(top_builddir)/src/sgsn/gprs_llc.o \
+       $(top_builddir)/src/sgsn/gprs_ns.o \
+       $(top_builddir)/src/sgsn/gprs_sndcp.o \
+       $(top_builddir)/src/sgsn/gprs_gmm_attach.o \
+       $(top_builddir)/src/sgsn/gprs_gmm.o \
+       $(top_builddir)/src/sgsn/gprs_gmm_fsm.o \
+       $(top_builddir)/src/sgsn/gprs_mm_state_gb_fsm.o \
+       $(top_builddir)/src/sgsn/gprs_routing_area.o \
+       $(top_builddir)/src/sgsn/gtp_ggsn.o \
+       $(top_builddir)/src/sgsn/gtp_mme.o \
+       $(top_builddir)/src/sgsn/mmctx.o \
+       $(top_builddir)/src/sgsn/pdpctx.o \
+       $(top_builddir)/src/sgsn/sgsn.o \
+       $(top_builddir)/src/sgsn/sgsn_cdr.o \
+       $(top_builddir)/src/sgsn/sgsn_ctrl.o \
+       $(top_builddir)/src/sgsn/sgsn_vty.o \
+       $(top_builddir)/src/sgsn/sgsn_libgtp.o \
+       $(top_builddir)/src/sgsn/sgsn_auth.o \
+       $(top_builddir)/src/sgsn/gprs_subscriber.o \
+        $(top_builddir)/src/sgsn/gprs_llc_xid.o \
+       $(top_builddir)/src/sgsn/gprs_sndcp_xid.o \
+        $(top_builddir)/src/sgsn/slhc.o \
+       $(top_builddir)/src/sgsn/gprs_sm.o \
+        $(top_builddir)/src/sgsn/gprs_sndcp_comp.o \
+        $(top_builddir)/src/sgsn/gprs_sndcp_pcomp.o \
+        $(top_builddir)/src/sgsn/v42bis.o \
+        $(top_builddir)/src/sgsn/gprs_sndcp_dcomp.o \
+       $(top_builddir)/src/sgsn/sgsn_rim.o \
+       $(top_builddir)/src/gprs/gprs_utils.o \
+       $(top_builddir)/src/gprs/gprs_llc_parse.o \
+       $(top_builddir)/src/gprs/crc24.o \
+       $(top_builddir)/src/gprs/sgsn_ares.o \
+       $(LIBOSMOCORE_LIBS) \
+       $(LIBOSMOCTRL_LIBS) \
+       $(LIBOSMOGSM_LIBS) \
+       $(LIBOSMOGB_LIBS) \
+       $(LIBOSMOGSUPCLIENT_LIBS) \
+       $(LIBCARES_LIBS) \
+       $(LIBGTP_LIBS) \
+       -lrt \
+       -lm \
+       $(NULL)
+
+if BUILD_IU
+gprs_routing_area_test_LDADD += \
+       $(top_builddir)/src/sgsn/gprs_ranap.o \
+       $(top_builddir)/src/sgsn/gprs_mm_state_iu_fsm.o \
+       $(LIBOSMORANAP_LIBS) \
+       $(LIBOSMOSIGTRAN_LIBS) \
+       $(LIBASN1C_LIBS) \
+       $(NULL)
+endif
diff --git a/tests/gprs_routing_area/gprs_routing_area_test.c 
b/tests/gprs_routing_area/gprs_routing_area_test.c
new file mode 100644
index 0000000..d879f39
--- /dev/null
+++ b/tests/gprs_routing_area/gprs_routing_area_test.c
@@ -0,0 +1,404 @@
+/* Test the SGSN routing ares */
+/*
+ * (C) 2024 by sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ * Author: Alexander Couzens <[email protected]>
+ *
+ * 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 <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/gsupclient/gsup_client.h>
+
+#include <osmocom/sgsn/gprs_llc.h>
+#include <osmocom/sgsn/mmctx.h>
+#include <osmocom/sgsn/sgsn.h>
+#include <osmocom/sgsn/gprs_gmm.h>
+#include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/gprs_routing_area.h>
+
+#include <stdio.h>
+
+
+void *tall_sgsn_ctx;
+struct sgsn_instance *sgsn;
+
+static void cleanup_test(void)
+{
+       TALLOC_FREE(sgsn);
+}
+
+/* Create RA, free RA */
+static void test_routing_area_create(void)
+{
+       struct sgsn_ra *ra;
+       struct osmo_routing_area_id raid = {
+               .lac = {
+                       .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false 
},
+                       .lac = 23
+               },
+               .rac = 42
+       };
+
+       printf("Testing Routing Area create/free\n");
+
+       sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
+       ra = sgsn_ra_alloc(&raid);
+       OSMO_ASSERT(ra);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       sgsn_ra_free(ra);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       /* Cleanup */
+       cleanup_test();
+}
+
+static void test_routing_area_free_empty(void)
+{
+
+       struct sgsn_ra *ra;
+       struct sgsn_ra_cell *cell_a;
+       struct osmo_routing_area_id raid = {
+               .lac = {
+                       .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false 
},
+                       .lac = 24
+               },
+               .rac = 43
+       };
+
+       uint16_t cell_id = 9999;
+       uint16_t nsei = 2, bvci = 3;
+
+       printf("Testing Routing Area create/free\n");
+
+       sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
+       ra = sgsn_ra_alloc(&raid);
+       OSMO_ASSERT(ra);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci);
+       OSMO_ASSERT(cell_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+       OSMO_ASSERT(llist_count(&ra->cells) == 1);
+
+       sgsn_ra_free(ra);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       ra = sgsn_ra_alloc(&raid);
+       OSMO_ASSERT(ra);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci);
+       OSMO_ASSERT(cell_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+       OSMO_ASSERT(llist_count(&ra->cells) == 1);
+
+       sgsn_ra_free(ra);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       cleanup_test();
+}
+
+/* Create RA, use different find functiosn, free RA */
+static void test_routing_area_find(void)
+{
+       struct sgsn_ra *ra_a, *ra_b;
+       struct sgsn_ra_cell *cell_a, *cell_b;
+       struct osmo_routing_area_id ra_id = {
+               .lac = {
+                       .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false 
},
+                       .lac = 24
+               },
+               .rac = 43
+       };
+
+       uint16_t cell_id = 9999, cell_id_not_found = 44;
+       struct osmo_cell_global_id_ps cgi_ps = {
+               .rai = ra_id,
+               .cell_identity = cell_id,
+       };
+       struct osmo_cell_global_id cgi = {
+               .lai = ra_id.lac,
+               .cell_identity = cell_id
+       };
+
+       uint16_t nsei = 2, bvci = 3;
+
+       printf("Testing Routing Area find\n");
+
+       sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
+       ra_a = sgsn_ra_alloc(&ra_id);
+       OSMO_ASSERT(ra_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       ra_b = sgsn_ra_get_ra(&ra_id);
+       OSMO_ASSERT(ra_a == ra_b);
+
+       cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci);
+       OSMO_ASSERT(cell_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
+       OSMO_ASSERT(cell_b);
+       OSMO_ASSERT(cell_b == cell_a);
+
+       cell_b = sgsn_ra_get_cell_by_ra(ra_a, cgi.cell_identity);
+       OSMO_ASSERT(cell_b);
+       OSMO_ASSERT(cell_b == cell_a);
+
+       cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
+       OSMO_ASSERT(cell_b);
+       OSMO_ASSERT(cell_b == cell_a);
+
+       cell_b = sgsn_ra_get_cell_by_lai(&cgi.lai, cgi.cell_identity);
+       OSMO_ASSERT(cell_b);
+       OSMO_ASSERT(cell_b == cell_a);
+
+       sgsn_ra_free(ra_a);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       /* try to search for a cell id which isn't present */
+       cgi.cell_identity = cell_id_not_found;
+       cgi_ps.cell_identity = cell_id_not_found;
+
+       ra_a = sgsn_ra_alloc(&ra_id);
+       OSMO_ASSERT(ra_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci);
+       OSMO_ASSERT(cell_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
+       OSMO_ASSERT(!cell_b);
+
+       cell_b = sgsn_ra_get_cell_by_ra(ra_a, cgi_ps.cell_identity);
+       OSMO_ASSERT(!cell_b);
+
+       cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
+       OSMO_ASSERT(!cell_b);
+
+       cell_b = sgsn_ra_get_cell_by_lai(&cgi.lai, cgi.cell_identity);
+       OSMO_ASSERT(!cell_b);
+
+       /* try to find for a different RAC */
+       cgi_ps.rai.rac = 45;
+       ra_id.rac = 46;
+
+       cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
+       OSMO_ASSERT(!cell_b);
+
+       ra_b = sgsn_ra_get_ra(&ra_id);
+       OSMO_ASSERT(!ra_b);
+
+       /* try to find for different LAC */
+       cgi.lai.lac = 46;
+       cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
+       OSMO_ASSERT(!cell_b);
+
+       sgsn_ra_free(ra_a);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       cleanup_test();
+}
+
+static void test_routing_area_reset_ind(void)
+{
+       struct sgsn_ra *ra_a;
+       struct sgsn_ra_cell *cell_a, *cell_b;
+       struct osmo_routing_area_id ra_id = {
+               .lac = {
+                       .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false 
},
+                       .lac = 24
+               },
+               .rac = 43
+       };
+
+       uint16_t cell_id = 9999;
+       struct osmo_cell_global_id_ps cgi_ps = {
+               .rai = ra_id,
+               .cell_identity = cell_id,
+       };
+       struct osmo_cell_global_id cgi = {
+               .lai = ra_id.lac,
+               .cell_identity = cell_id
+       };
+
+       uint16_t nsei = 2, bvci = 3;
+       int rc;
+
+       printf("Testing Routing Area BSSGP BVC RESET IND\n");
+
+       sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
+       ra_a = sgsn_ra_alloc(&ra_id);
+       OSMO_ASSERT(ra_a);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+       OSMO_ASSERT(llist_count(&ra_a->cells) == 0);
+
+       rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
+       OSMO_ASSERT(rc == 0);
+       OSMO_ASSERT(llist_count(&ra_a->cells) == 1);
+
+       cell_a = sgsn_ra_get_cell_by_cgi(&cgi);
+       OSMO_ASSERT(cell_a);
+
+       rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
+       OSMO_ASSERT(rc == 0);
+
+       cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
+       OSMO_ASSERT(cell_b);
+       OSMO_ASSERT(cell_a == cell_b);
+
+       sgsn_ra_free(ra_a);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
+       OSMO_ASSERT(rc == 0);
+       OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
+
+       ra_a = sgsn_ra_get_ra(&cgi_ps.rai);
+       sgsn_ra_free(ra_a);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       cleanup_test();
+}
+
+void test_routing_area_nsei_free(void)
+{
+       struct sgsn_ra *ra_a;
+       struct osmo_routing_area_id ra_id = {
+               .lac = {
+                       .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false 
},
+                       .lac = 24
+               },
+               .rac = 43
+       };
+
+       uint16_t cell_id = 9999;
+       struct osmo_cell_global_id_ps cgi_ps = {
+               .rai = ra_id,
+               .cell_identity = cell_id,
+       };
+
+       uint16_t nsei = 2, bvci = 3;
+       int rc;
+
+       printf("Testing Routing Area nsei failure\n");
+
+       sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
+
+       rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
+       OSMO_ASSERT(rc == 0);
+
+       ra_a = sgsn_ra_get_ra(&cgi_ps.rai);
+       OSMO_ASSERT(llist_count(&ra_a->cells) == 1);
+
+       rc = sgsn_ra_nsei_failure_ind(nsei);
+       OSMO_ASSERT(rc == 0);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       rc = sgsn_ra_nsei_failure_ind(nsei);
+       OSMO_ASSERT(rc == -ENOENT);
+       OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
+
+       cleanup_test();
+}
+
+static struct log_info_cat gprs_categories[] = {
+       [DMM] = {
+               .name = "DMM",
+               .description = "Layer3 Mobility Management (MM)",
+               .color = "\033[1;33m",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
+       [DPAG]  = {
+               .name = "DPAG",
+               .description = "Paging Subsystem",
+               .color = "\033[1;38m",
+               .enabled = 1, .loglevel = LOGL_NOTICE,
+       },
+       [DREF] = {
+               .name = "DREF",
+               .description = "Reference Counting",
+               .enabled = 0, .loglevel = LOGL_NOTICE,
+       },
+       [DGPRS] = {
+               .name = "DGPRS",
+               .description = "GPRS Packet Service",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
+       [DLLC] = {
+               .name = "DLLC",
+               .description = "GPRS Logical Link Control Protocol (LLC)",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
+       [DRA] = {
+               .name = "DRA",
+               .description = "Routing Area",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
+};
+
+static struct log_info info = {
+       .cat = gprs_categories,
+       .num_cat = ARRAY_SIZE(gprs_categories),
+};
+
+static struct vty_app_info vty_info = {
+       .name = "testSGSN",
+};
+
+int main(int argc, char **argv)
+{
+       void *osmo_sgsn_ctx;
+       void *msgb_ctx;
+
+       osmo_sgsn_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
+       osmo_init_logging2(osmo_sgsn_ctx, &info);
+       tall_sgsn_ctx = talloc_named_const(osmo_sgsn_ctx, 0, "sgsn");
+       msgb_ctx = msgb_talloc_ctx_init(osmo_sgsn_ctx, 0);
+
+       vty_init(&vty_info);
+
+       test_routing_area_create();
+       test_routing_area_find();
+       test_routing_area_free_empty();
+       test_routing_area_reset_ind();
+       test_routing_area_nsei_free();
+       printf("Done\n");
+
+       talloc_report_full(osmo_sgsn_ctx, stderr);
+       OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1);
+       OSMO_ASSERT(talloc_total_blocks(tall_sgsn_ctx) == 1);
+       return 0;
+}
+
+
+/* stubs */
+struct osmo_prim_hdr;
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+       abort();
+}
diff --git a/tests/gprs_routing_area/gprs_routing_area_test.ok 
b/tests/gprs_routing_area/gprs_routing_area_test.ok
new file mode 100644
index 0000000..dccbb5e
--- /dev/null
+++ b/tests/gprs_routing_area/gprs_routing_area_test.ok
@@ -0,0 +1,6 @@
+Testing Routing Area create/free
+Testing Routing Area find
+Testing Routing Area create/free
+Testing Routing Area BSSGP BVC RESET IND
+Testing Routing Area nsei failure
+Done
diff --git a/tests/sgsn/Makefile.am b/tests/sgsn/Makefile.am
index 9cffdfd..db40f99 100644
--- a/tests/sgsn/Makefile.am
+++ b/tests/sgsn/Makefile.am
@@ -60,6 +60,7 @@
        $(top_builddir)/src/sgsn/gprs_gmm.o \
        $(top_builddir)/src/sgsn/gprs_gmm_fsm.o \
        $(top_builddir)/src/sgsn/gprs_mm_state_gb_fsm.o \
+       $(top_builddir)/src/sgsn/gprs_routing_area.o \
        $(top_builddir)/src/sgsn/gtp_ggsn.o \
        $(top_builddir)/src/sgsn/gtp_mme.o \
        $(top_builddir)/src/sgsn/mmctx.o \
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 637d853..242ab26 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -14,6 +14,13 @@
 AT_CHECK([$abs_top_builddir/tests/sgsn/sgsn_test], [], [expout], [ignore])
 AT_CLEANUP

+AT_SETUP([gprs_routing_area])
+AT_KEYWORDS([gprs_routing_area])
+AT_CHECK([test "$enable_gprs_routing_area_test" != no || exit 77])
+cat $abs_srcdir/gprs_routing_area/gprs_routing_area_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gprs_routing_area/gprs_routing_area_test], 
[], [expout], [ignore])
+AT_CLEANUP
+
 AT_SETUP([gtphub])
 AT_KEYWORDS([gtphub])
 AT_CHECK([test "$enable_gtphub_test" != no || exit 77])

--
To view, visit https://gerrit.osmocom.org/c/osmo-sgsn/+/37864?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: osmo-sgsn
Gerrit-Branch: master
Gerrit-Change-Id: I2474b19a7471a1dea3c863ddf8372b16180211aa
Gerrit-Change-Number: 37864
Gerrit-PatchSet: 18
Gerrit-Owner: lynxis lazus <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: daniel <[email protected]>
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: lynxis lazus <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>
Gerrit-CC: fixeria <[email protected]>
Gerrit-CC: osmith <[email protected]>

Reply via email to