bsc_api: Implement the assignment command for the BSC.
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index e6c0a2e..e40b118 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -255,6 +255,11 @@
 	struct gsm_lchan *lchan;
 	struct gsm_lchan *ho_lchan;
 	struct gsm_bts *bts;
+
+	/* for assignment handling */
+	struct timer_list T10;
+	struct gsm_lchan *secondary_lchan;
+
 };
 
 struct gsm_lchan {
diff --git a/openbsc/src/bsc_api.c b/openbsc/src/bsc_api.c
index 6639dae..043006e 100644
--- a/openbsc/src/bsc_api.c
+++ b/openbsc/src/bsc_api.c
@@ -30,16 +30,21 @@
 #include <openbsc/chan_alloc.h>
 #include <openbsc/handover.h>
 #include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
 
 #include <osmocore/protocol/gsm_08_08.h>
 
 #include <osmocore/talloc.h>
 
+#define GSM0808_T10_VALUE    6, 0
+
 static LLIST_HEAD(sub_connections);
 
 static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind);
 static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id);
 static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
 
 /* GSM 08.08 3.2.2.33 */
 static u_int8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
@@ -123,6 +128,81 @@
         return mode;
 }
 
+static void assignment_t10_timeout(void *_conn)
+{
+	struct bsc_api *api;
+	struct gsm_subscriber_connection *conn =
+		(struct gsm_subscriber_connection *) _conn;
+
+	LOGP(DMSC, LOGL_ERROR, "Assigment T10 timeout on %p\n", conn);
+
+	/* normal release on the secondary channel */
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	/* inform them about the failure */
+	api = conn->bts->network->bsc_api;
+	api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+}
+
+/*
+ * Start a new assignment and make sure that it is completed within T10 either
+ * positively, negatively or by the timeout.
+ *
+ *  1.) allocate a new lchan
+ *  2.) copy the encryption key and other data from the
+ *      old to the new channel.
+ *  3.) RSL Channel Activate this channel and wait
+ *
+ * -> Signal handler for the LCHAN
+ *  4.) Send GSM 04.08 assignment command to the MS
+ *
+ * -> Assignment Complete/Assignment Failure
+ *  5.) Release the SDCCH, continue signalling on the new link
+ */
+static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct gsm_lchan *new_lchan;
+	int chan_type;
+
+	chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+
+	new_lchan = lchan_alloc(conn->bts, chan_type, 0);
+
+	if (!new_lchan) {
+		LOGP(DMSC, LOGL_NOTICE, "No free channel.\n");
+		return -1;
+	}
+
+	/* copy old data to the new channel */
+	memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = conn->lchan->ms_power;
+	new_lchan->bs_power = conn->lchan->bs_power;
+
+	/* copy new data to it */
+	new_lchan->tch_mode = chan_mode;
+	new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+
+	/* handle AMR correctly */
+	if (chan_mode == GSM48_CMODE_SPEECH_AMR) {
+		new_lchan->mr_conf.ver = 1;
+		new_lchan->mr_conf.icmi = 1;
+		new_lchan->mr_conf.m5_90 = 1;
+	}
+
+	if (rsl_chan_activate_lchan(new_lchan, 0x1, 0, 0) < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		lchan_free(new_lchan);
+		return -1;
+	}
+
+	/* remember that we have the channel */
+	conn->secondary_lchan = new_lchan;
+	new_lchan->conn = conn;
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	return 0;
+}
 
 struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan)
 {
@@ -163,6 +243,11 @@
 		conn->lchan->conn = NULL;
 	}
 
+	if (conn->secondary_lchan) {
+		LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n");
+		conn->secondary_lchan->conn = NULL;
+	}
+
 	llist_del(&conn->entry);
 	talloc_free(conn);
 }
@@ -213,7 +298,8 @@
 	api = conn->bts->network->bsc_api;
 
 	if (conn->lchan->type == GSM_LCHAN_SDCCH) {
-		api->assign_fail(conn, 0, NULL);
+		if (handle_new_assignment(conn, chan_mode, full_rate) != 0)
+			goto error;
 	} else {
 		LOGP(DMSC, LOGL_NOTICE,
 			"Sending ChanModify for speech %d %d\n", chan_mode, full_rate);
@@ -223,10 +309,18 @@
 			conn->lchan->mr_conf.m5_90 = 1;
 		}
 
-		return gsm48_lchan_modify(conn->lchan, chan_mode);
+		gsm48_lchan_modify(conn->lchan, chan_mode);
 	}
 
+	/* we will now start the timer to complete the assignment */
+	conn->T10.cb = assignment_t10_timeout;
+	conn->T10.data = conn;
+	bsc_schedule_timer(&conn->T10, GSM0808_T10_VALUE);
 	return 0;
+
+error:
+	api->assign_fail(conn, 0, NULL);
+	return -1;
 }
 
 int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len,
@@ -254,6 +348,72 @@
 	return work;
 }
 
+static void handle_ass_compl(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct bsc_api *api = conn->bts->network->bsc_api;
+
+	if (conn->secondary_lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n");
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %d\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		return;
+	}
+
+	/* swap channels */
+	bsc_del_timer(&conn->T10);
+
+	lchan_release(conn->lchan, 0, 1);
+	conn->lchan = conn->secondary_lchan;
+	conn->secondary_lchan = NULL;
+
+	if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(conn->lchan);
+
+	api->assign_compl(conn, gh->data[0],
+			  lchan_to_chosen_channel(conn->lchan),
+			  conn->lchan->encr.alg_id,
+			  chan_mode_to_speech(conn->lchan));
+}
+
+static void handle_ass_fail(struct gsm_subscriber_connection *conn,
+			    struct msgb *msg)
+{
+	struct bsc_api *api = conn->bts->network->bsc_api;
+	uint8_t *rr_failure;
+	struct gsm48_hdr *gh;
+
+
+	if (conn->lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n");
+		return;
+	}
+
+	/* stop the timer and release it */
+	bsc_del_timer(&conn->T10);
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "assignemnt failure unhandled: %d\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		rr_failure = NULL;
+	} else {
+		rr_failure = &gh->data[0];
+	}
+
+	api->assign_fail(conn,
+			 GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE,
+			 rr_failure);
+}
+
 static void dispatch_dtap(struct gsm_subscriber_connection *conn,
 			  uint8_t link_id, struct msgb *msg)
 {
@@ -278,12 +438,13 @@
 						conn->lchan->encr.alg_id);
 			break;
 		case GSM48_MT_RR_ASS_COMPL:
-			LOGP(DMSC, LOGL_ERROR, "Assignment command is not handled.\n");
+			handle_ass_compl(conn, msg);
 			break;
 		case GSM48_MT_RR_ASS_FAIL:
-			LOGP(DMSC, LOGL_ERROR, "Assignment failure is not handled.\n");
+			handle_ass_fail(conn, msg);
 			break;
 		case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+			bsc_del_timer(&conn->T10);
 			rc = gsm48_rx_rr_modif_ack(msg);
 			if (rc < 0 && api->assign_fail) {
 				api->assign_fail(conn,
@@ -369,13 +530,19 @@
 	if (conn->ho_lchan)
 		bsc_clear_handover(conn);
 
+	if (conn->secondary_lchan)
+		lchan_release(conn->secondary_lchan, 0, 1);
+
 	if (conn->lchan)
 		lchan_release(conn->lchan, 1, 0);
 
 	conn->lchan = NULL;
+	conn->secondary_lchan = NULL;
 	conn->ho_lchan = NULL;
 	conn->bts = NULL;
 
+	bsc_del_timer(&conn->T10);
+
 	return 0;
 }
 
@@ -439,6 +606,12 @@
 	case S_LCHAN_UNEXPECTED_RELEASE:
 		handle_release(lchan->conn, bsc, lchan);
 		break;
+	case S_LCHAN_ACTIVATE_ACK:
+		handle_chan_ack(lchan->conn, bsc, lchan);
+		break;
+	case S_LCHAN_ACTIVATE_NACK:
+		handle_chan_nack(lchan->conn, bsc, lchan);
+		break;
 	}
 
 	return 0;
@@ -449,22 +622,54 @@
 {
 	int destruct = 1;
 
-	if (bsc->clear_request)
-		destruct = bsc->clear_request(conn, 0);
-
 	/* now give up all channels */
 	if (conn->lchan == lchan)
 		conn->lchan = NULL;
 	if (conn->ho_lchan == lchan)
 		conn->ho_lchan = NULL;
+	if (conn->secondary_lchan == lchan) {
+		bsc_del_timer(&conn->T10);
+		conn->secondary_lchan = NULL;
+
+		bsc->assign_fail(conn,
+				 GSM0808_CAUSE_RADIO_INTERFACE_FAILURE,
+				 NULL);
+	}
+
 	lchan->conn = NULL;
 
+	/* clear the connection now */
+	if (bsc->clear_request)
+		destruct = bsc->clear_request(conn, 0);
+
+
 	gsm0808_clear(conn);
 
 	if (destruct)
 		subscr_con_free(conn);
 }
 
+static void handle_chan_ack(struct gsm_subscriber_connection *conn,
+			    struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", lchan);
+	gsm48_send_rr_ass_cmd(conn->lchan, lchan, 0x3);
+}
+
+static void handle_chan_nack(struct gsm_subscriber_connection *conn,
+			     struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n");
+	conn->secondary_lchan->conn = NULL;
+	conn->secondary_lchan = NULL;
+}
+
 static __attribute__((constructor)) void on_dso_load_bsc(void)
 {
 	register_signal_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL);