ms: Store references to replaced TBFs in the MS object

Currently when calling GprsMs::attach_tbf and a TBF of the same
direction already exists, the old TBF gets detached from the MS
object.

Therefore that TBF object loses access to that MS object including
for instance TLLI and IMSI.

This leads to failing DL TBF reuses, since the downlink assigment
cannot be sent on the PACCH later on because that must be sent on the
old DL TBF which ms() is NULL and the new DL TBF cannot be retrieved.

This commit fixes this bug by changing the GprsMs implementation to
keep a list of replaced (old) TBFs. TBFs are only removed when they
are being detached explicitely (see tbf_free and set_ms).

Addresses:
tbf.cpp:741 We have a schedule for downlink assignment at uplink
TBF(TFI=1 TLLI=0xf35a680e DIR=UL STATE=RELEASING), but there is no
downlink TBF

Sponsored-by: On-Waves ehf
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
index cb7773f..807f345 100644
--- a/src/gprs_ms.cpp
+++ b/src/gprs_ms.cpp
@@ -140,6 +140,8 @@
 
 GprsMs::~GprsMs()
 {
+	LListHead<gprs_rlcmac_tbf> *pos, *tmp;
+
 	LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli());
 
 	set_reserved_slots(NULL, 0, 0);
@@ -156,6 +158,10 @@
 		m_dl_tbf->set_ms(NULL);
 		m_dl_tbf = NULL;
 	}
+
+	llist_for_each_safe(pos, tmp, &m_old_tbfs)
+		pos->entry()->set_ms(NULL);
+
 	m_llc_queue.clear(m_bts);
 }
 
@@ -227,7 +233,7 @@
 	Guard guard(this);
 
 	if (m_ul_tbf)
-		detach_tbf(m_ul_tbf);
+		llist_add_tail(&m_ul_tbf->ms_list(), &m_old_tbfs);
 
 	m_ul_tbf = tbf;
 
@@ -246,7 +252,7 @@
 	Guard guard(this);
 
 	if (m_dl_tbf)
-		detach_tbf(m_dl_tbf);
+		llist_add_tail(&m_dl_tbf->ms_list(), &m_old_tbfs);
 
 	m_dl_tbf = tbf;
 
@@ -256,12 +262,26 @@
 
 void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf)
 {
-	if (m_ul_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf))
+	if (tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf)) {
 		m_ul_tbf = NULL;
-	else if (m_dl_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf))
+	} else if (tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf)) {
 		m_dl_tbf = NULL;
-	else
-		return;
+	} else {
+		bool found = false;
+
+		LListHead<gprs_rlcmac_tbf> *pos, *tmp;
+		llist_for_each_safe(pos, tmp, &m_old_tbfs) {
+			if (pos->entry() == tbf) {
+				llist_del(pos);
+				found = true;
+				break;
+			}
+		}
+
+		/* Protect against recursive calls via set_ms() */
+		if (!found)
+			return;
+	}
 
 	LOGP(DRLCMAC, LOGL_INFO, "Detaching TBF from MS object, TLLI = 0x%08x, TBF = %s\n",
 		tlli(), tbf->name());
diff --git a/src/gprs_ms.h b/src/gprs_ms.h
index c490e7a..1f080ff 100644
--- a/src/gprs_ms.h
+++ b/src/gprs_ms.h
@@ -109,13 +109,14 @@
 
 	void update_error_rate(gprs_rlcmac_tbf *tbf, int percent);
 
-	bool is_idle() const {return !m_ul_tbf && !m_dl_tbf && !m_ref;}
+	bool is_idle() const;
 
 	void* operator new(size_t num);
 	void operator delete(void* p);
 
 	LListHead<GprsMs>& list() {return this->m_list;}
 	const LListHead<GprsMs>& list() const {return this->m_list;}
+	const LListHead<gprs_rlcmac_tbf>& old_tbfs() const {return m_old_tbfs;}
 
 	void update_l1_meas(const pcu_l1_meas *meas);
 	const pcu_l1_meas* l1_meas() const {return &m_l1_meas;};
@@ -136,6 +137,8 @@
 	Callback * m_cb;
 	gprs_rlcmac_ul_tbf *m_ul_tbf;
 	gprs_rlcmac_dl_tbf *m_dl_tbf;
+	LListHead<gprs_rlcmac_tbf> m_old_tbfs;
+
 	uint32_t m_tlli;
 	uint32_t m_new_ul_tlli;
 	uint32_t m_new_dl_tlli;
@@ -167,6 +170,11 @@
 	struct gprs_codel *m_codel_state;
 };
 
+inline bool GprsMs::is_idle() const
+{
+	return !m_ul_tbf && !m_dl_tbf && !m_ref && llist_empty(&m_old_tbfs);
+}
+
 inline uint32_t GprsMs::tlli() const
 {
 	return m_new_ul_tlli ? m_new_ul_tlli :
diff --git a/src/tbf.cpp b/src/tbf.cpp
index 9d4363f..4a59faa 100644
--- a/src/tbf.cpp
+++ b/src/tbf.cpp
@@ -42,7 +42,8 @@
 static void tbf_timer_cb(void *_tbf);
 
 gprs_rlcmac_tbf::gprs_rlcmac_tbf(gprs_rlcmac_tbf_direction dir) :
-	direction(dir)
+	direction(dir),
+	m_ms_list(this)
 {
 }
 
diff --git a/src/tbf.h b/src/tbf.h
index f1484f0..4a92bbf 100644
--- a/src/tbf.h
+++ b/src/tbf.h
@@ -23,6 +23,7 @@
 #include "gprs_rlcmac.h"
 #include "llc.h"
 #include "rlc.h"
+#include "cxx_linuxlist.h"
 #include <gprs_debug.h>
 
 #include <stdint.h>
@@ -165,6 +166,9 @@
 	/* attempt to make things a bit more fair */
 	void rotate_in_list();
 
+	LListHead<gprs_rlcmac_tbf>& ms_list() {return this->m_ms_list;}
+	const LListHead<gprs_rlcmac_tbf>& ms_list() const {return this->m_ms_list;}
+
 	struct llist_pods list;
 	uint32_t state_flags;
 	enum gprs_rlcmac_tbf_direction direction;
@@ -238,6 +242,8 @@
 	uint8_t m_ms_class;
 
 private:
+	LListHead<gprs_rlcmac_tbf> m_ms_list;
+
 	mutable char m_name_buf[60];
 };