ms: Add timer

Currently the MS object is immediately idle when all TBFs are
detached and if no guard is being used. Since the plan is to use the
MS objects to pass information from one TBF to the next one even
across the gap of some seconds of inactivity, a mechanism is needed
to keep the MS objects around for some time.

This commit extends the GprsMs class by a timer that keeps the MS
objects in non-idle state for some time after all TBFs have been
detached. The set_timeout method must be used with a non-zero value
to activate this feature.

Sponsored-by: On-Waves ehf
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
index d6520c3..f5b92d2 100644
--- a/src/gprs_ms.cpp
+++ b/src/gprs_ms.cpp
@@ -40,11 +40,9 @@
 
 static GprsMsDefaultCallback gprs_default_cb;
 
-
-GprsMs::Guard::Guard(GprsMs *ms) : m_ms(ms)
+GprsMs::Guard::Guard(GprsMs *ms) :
+	m_ms(ms ? ms->ref() : NULL)
 {
-	if (m_ms)
-		m_ms->ref();
 }
 
 GprsMs::Guard::~Guard()
@@ -53,6 +51,19 @@
 		m_ms->unref();
 }
 
+void GprsMs::timeout(void *priv_)
+{
+	GprsMs *ms = static_cast<GprsMs *>(priv_);
+
+	LOGP(DRLCMAC, LOGL_INFO, "Timeout for MS object, TLLI = 0x%08x\n",
+		ms->tlli());
+
+	if (ms->m_timer.data) {
+		ms->m_timer.data = NULL;
+		ms->unref();
+	}
+}
+
 GprsMs::GprsMs(uint32_t tlli) :
 	m_cb(&gprs_default_cb),
 	m_ul_tbf(NULL),
@@ -68,12 +79,17 @@
 	LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli);
 
 	m_imsi[0] = 0;
+	memset(&m_timer, 0, sizeof(m_timer));
+	m_timer.cb = GprsMs::timeout;
 }
 
 GprsMs::~GprsMs()
 {
 	LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli());
 
+	if (osmo_timer_pending(&m_timer))
+		osmo_timer_del(&m_timer);
+
 	if (m_ul_tbf) {
 		m_ul_tbf->set_ms(NULL);
 		m_ul_tbf = NULL;
@@ -99,9 +115,10 @@
 	talloc_free(p);
 }
 
-void GprsMs::ref()
+GprsMs *GprsMs::ref()
 {
 	m_ref += 1;
+	return this;
 }
 
 void GprsMs::unref()
@@ -112,6 +129,27 @@
 		update_status();
 }
 
+void GprsMs::start_timer()
+{
+	if (m_delay == 0)
+		return;
+
+	if (!m_timer.data)
+		m_timer.data = ref();
+
+	osmo_timer_schedule(&m_timer, m_delay, 0);
+}
+
+void GprsMs::stop_timer()
+{
+	if (!m_timer.data)
+		return;
+
+	osmo_timer_del(&m_timer);
+	m_timer.data = NULL;
+	unref();
+}
+
 void GprsMs::attach_tbf(struct gprs_rlcmac_tbf *tbf)
 {
 	if (tbf->direction == GPRS_RLCMAC_DL_TBF)
@@ -134,6 +172,9 @@
 		detach_tbf(m_ul_tbf);
 
 	m_ul_tbf = tbf;
+
+	if (tbf)
+		stop_timer();
 }
 
 void GprsMs::attach_dl_tbf(struct gprs_rlcmac_dl_tbf *tbf)
@@ -150,6 +191,9 @@
 		detach_tbf(m_dl_tbf);
 
 	m_dl_tbf = tbf;
+
+	if (tbf)
+		stop_timer();
 }
 
 void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf)
@@ -167,6 +211,9 @@
 	if (tbf->ms() == this)
 		tbf->set_ms(NULL);
 
+	if (!m_dl_tbf && !m_dl_tbf)
+		start_timer();
+
 	update_status();
 }