summaryrefslogtreecommitdiffstats
path: root/src/host/layer23/src/mobile/gsm48_cc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/host/layer23/src/mobile/gsm48_cc.c')
-rw-r--r--src/host/layer23/src/mobile/gsm48_cc.c2139
1 files changed, 2139 insertions, 0 deletions
diff --git a/src/host/layer23/src/mobile/gsm48_cc.c b/src/host/layer23/src/mobile/gsm48_cc.c
new file mode 100644
index 00000000..820ce665
--- /dev/null
+++ b/src/host/layer23/src/mobile/gsm48_cc.c
@@ -0,0 +1,2139 @@
+/*
+ * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm48.h>
+#include <osmocore/talloc.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/osmocom_data.h>
+#include <osmocom/mncc.h>
+#include <osmocom/transaction.h>
+#include <osmocom/gsm48_cc.h>
+
+extern void *l23_ctx;
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_rel_null_free(struct gsm_trans *trans);
+int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans,
+ u_int32_t callref, int location, int value);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg);
+
+/*
+ * init
+ */
+
+int gsm48_cc_init(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+
+ cc->ms = ms;
+
+ if (!cc->mncc_upqueue.next == 0)
+ return 0;
+
+ LOGP(DCC, LOGL_INFO, "init Call Control\n");
+
+ INIT_LLIST_HEAD(&cc->mncc_upqueue);
+
+ return 0;
+}
+
+int gsm48_cc_exit(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct gsm_trans *trans, *trans2;
+ struct msgb *msg;
+
+ LOGP(DCC, LOGL_INFO, "exit Call Control processes for %s\n", ms->name);
+
+ llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) {
+ if (trans->protocol == GSM48_PDISC_CC)
+ LOGP(DCC, LOGL_NOTICE, "Free pendig CC-transaction.\n");
+ trans_free(trans);
+ }
+
+ while ((msg = msgb_dequeue(&cc->mncc_upqueue)))
+ msgb_free(msg);
+
+ return 0;
+}
+
+/*
+ * messages
+ */
+
+/* names of MNCC-SAP */
+static const struct value_string gsm_mncc_names[] = {
+ { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" },
+ { MNCC_SETUP_IND, "MNCC_SETUP_IND" },
+ { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" },
+ { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" },
+ { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" },
+ { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" },
+ { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" },
+ { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" },
+ { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" },
+ { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" },
+ { MNCC_ALERT_IND, "MNCC_ALERT_IND" },
+ { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" },
+ { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" },
+ { MNCC_DISC_REQ, "MNCC_DISC_REQ" },
+ { MNCC_DISC_IND, "MNCC_DISC_IND" },
+ { MNCC_REL_REQ, "MNCC_REL_REQ" },
+ { MNCC_REL_IND, "MNCC_REL_IND" },
+ { MNCC_REL_CNF, "MNCC_REL_CNF" },
+ { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" },
+ { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" },
+ { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" },
+ { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" },
+ { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" },
+ { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" },
+ { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" },
+ { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" },
+ { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" },
+ { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" },
+ { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" },
+ { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" },
+ { MNCC_HOLD_IND, "MNCC_HOLD_IND" },
+ { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" },
+ { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" },
+ { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" },
+ { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" },
+ { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" },
+ { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" },
+ { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" },
+ { MNCC_REJ_REQ, "MNCC_REJ_REQ" },
+ { MNCC_REJ_IND, "MNCC_REJ_IND" },
+ { MNCC_PROGRESS_IND, "MNCC_PROGRESS_IND" },
+ { MNCC_CALL_PROC_IND, "MNCC_CALL_PROC_IND" },
+ { MNCC_CALL_CONF_REQ, "MNCC_CALL_CONF_REQ" },
+ { MNCC_START_DTMF_REQ, "MNCC_START_DTMF_REQ" },
+ { MNCC_STOP_DTMF_REQ, "MNCC_STOP_DTMF_REQ" },
+ { MNCC_HOLD_REQ, "MNCC_HOLD_REQ " },
+ { MNCC_RETRIEVE_REQ, "MNCC_RETRIEVE_REQ" },
+ { 0, NULL }
+};
+
+const char *get_mncc_name(int value)
+{
+ return get_value_string(gsm_mncc_names, value);
+}
+
+/* push MMCC header and send to MM */
+static int gsm48_cc_to_mm(struct msgb *msg, struct gsm_trans *trans,
+ int msg_type)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_mmxx_hdr *mmh;
+ int emergency = 0;
+
+ /* Add protocol type and transaction ID */
+ gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+
+ /* indicate emergency setup to MM layer */
+ if (gh->msg_type == GSM48_MT_CC_EMERG_SETUP)
+ emergency = 1;
+
+ /* push RR header */
+ msgb_push(msg, sizeof(struct gsm48_mmxx_hdr));
+ mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ mmh->msg_type = msg_type;
+ mmh->ref = trans->callref;
+ mmh->transaction_id = trans->transaction_id;
+ mmh->emergency = emergency;
+
+ /* send message to MM */
+ LOGP(DCC, LOGL_INFO, "Sending '%s' using %s (callref=%x, "
+ "transaction_id=%d)\n", gsm48_cc_msg_name(gh->msg_type),
+ get_mmxx_name(msg_type), trans->callref, trans->transaction_id);
+ return gsm48_mmxx_downmsg(trans->ms, msg);
+}
+
+/* enqueue message to application (MNCC-SAP) */
+static int mncc_recvmsg(struct osmocom_ms *ms, struct gsm_trans *trans,
+ int msg_type, struct gsm_mncc *mncc)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct msgb *msg;
+
+ if (trans)
+ LOGP(DCC, LOGL_INFO, "(ms %s ti %x) Sending '%s' to MNCC.\n",
+ ms->name, trans->transaction_id,
+ get_mncc_name(msg_type));
+ else
+ LOGP(DCC, LOGL_INFO, "(ms %s ti -) Sending '%s' to MNCC.\n",
+ ms->name, get_mncc_name(msg_type));
+
+ mncc->msg_type = msg_type;
+
+ msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+ if (!msg)
+ return -ENOMEM;
+ memcpy(msg->data, mncc, sizeof(struct gsm_mncc));
+ msgb_enqueue(&cc->mncc_upqueue, msg);
+
+ return 0;
+}
+
+/* dequeue messages to layer 4 */
+int mncc_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct gsm_mncc *mncc;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&cc->mncc_upqueue))) {
+ mncc = (struct gsm_mncc *)msg->data;
+ if (cc->mncc_recv)
+ cc->mncc_recv(ms, mncc->msg_type, mncc);
+ work = 1; /* work done */
+ talloc_free(msg);
+ }
+
+ return work;
+}
+
+
+/*
+ * state transition
+ */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+ if (state > 31 || state < 0)
+ return;
+
+ DEBUGP(DCC, "new state %s -> %s\n",
+ gsm48_cc_state_name(trans->cc.state),
+ gsm48_cc_state_name(state));
+
+ trans->cc.state = state;
+}
+
+/*
+ * timers
+ */
+
+/* timeout events of all timers */
+static void gsm48_cc_timeout(void *arg)
+{
+ struct gsm_trans *trans = arg;
+ int disconnect = 0, release = 0, abort = 1;
+ int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+ int mo_location = GSM48_CAUSE_LOC_PRN_S_LU;
+ int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+ int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+ struct gsm_mncc mo_rel, l4_rel;
+
+ memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+ mo_rel.callref = trans->callref;
+ memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+ l4_rel.callref = trans->callref;
+
+ LOGP(DCC, LOGL_INFO, "Timer T%x has fired.\n", trans->cc.Tcurrent);
+
+ switch(trans->cc.Tcurrent) {
+ case 0x303:
+ /* abort if connection is not already esablished */
+ if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND)
+ abort = 1;
+ else
+ release = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x305:
+ release = 1;
+ mo_cause = trans->cc.msg.cause.value;
+ mo_location = trans->cc.msg.cause.location;
+ break;
+ case 0x308:
+ if (!trans->cc.T308_second) {
+ /* restart T308 a second time */
+ gsm48_cc_tx_release(trans, &trans->cc.msg);
+ trans->cc.T308_second = 1;
+ break; /* stay in release state */
+ }
+ /* release MM conn, got NULL state, free trans */
+ gsm48_rel_null_free(trans);
+
+ return;
+ case 0x310:
+ disconnect = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x313:
+ disconnect = 1;
+ /* unknown, did not find it in the specs */
+ break;
+ default:
+ release = 1;
+ }
+
+ if ((release || abort) && trans->callref) {
+ /* process release towards layer 4 */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ l4_location, l4_cause);
+ }
+
+ if (disconnect && trans->callref) {
+ /* process disconnect towards layer 4 */
+ mncc_set_cause(&l4_rel, l4_location, l4_cause);
+ mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &l4_rel);
+ }
+
+ /* process disconnect towards mobile station */
+ if (disconnect || release || abort) {
+ mncc_set_cause(&mo_rel, mo_location, mo_cause);
+ mo_rel.cause.diag[0] =
+ ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+ mo_rel.cause.diag[1] =
+ ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+ mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+ mo_rel.cause.diag_len = 3;
+
+ if (disconnect)
+ gsm48_cc_tx_disconnect(trans, &mo_rel);
+ if (release)
+ gsm48_cc_tx_release(trans, &mo_rel);
+ if (abort) {
+ /* release MM conn, got NULL state, free trans */
+ gsm48_rel_null_free(trans);
+ }
+ }
+}
+
+/* start various timers */
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+ int sec, int micro)
+{
+ LOGP(DCC, LOGL_INFO, "starting timer T%x with %d seconds\n", current,
+ sec);
+ trans->cc.timer.cb = gsm48_cc_timeout;
+ trans->cc.timer.data = trans;
+ bsc_schedule_timer(&trans->cc.timer, sec, micro);
+ trans->cc.Tcurrent = current;
+}
+
+/* stop various timers */
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+ if (bsc_timer_pending(&trans->cc.timer)) {
+ LOGP(DCC, LOGL_INFO, "stopping pending timer T%x\n",
+ trans->cc.Tcurrent);
+ bsc_del_timer(&trans->cc.timer);
+ trans->cc.Tcurrent = 0;
+ }
+}
+
+/*
+ * process handlers (misc)
+ */
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF!
+ */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+ gsm48_stop_cc_timer(trans);
+
+ /* send release to L4, if callref still exists */
+ if (trans->callref) {
+ /* Ressource unavailable */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ }
+ if (trans->cc.state != GSM_CSTATE_NULL)
+ new_cc_state(trans, GSM_CSTATE_NULL);
+}
+
+/* release MM connection, go NULL state, free transaction */
+static int gsm48_rel_null_free(struct gsm_trans *trans)
+{
+ struct msgb *nmsg;
+
+ /* release MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n");
+ gsm48_mmxx_downmsg(trans->ms, nmsg);
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return 0;
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+ data->fields |= MNCC_F_CAUSE;
+ data->cause.location = loc;
+ data->cause.value = val;
+}
+
+/* send release indication to upper layer */
+int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans,
+ u_int32_t callref, int location, int value)
+{
+ struct gsm_mncc rel;
+
+ memset(&rel, 0, sizeof(rel));
+ rel.callref = callref;
+ mncc_set_cause(&rel, location, value);
+ return mncc_recvmsg(ms, trans, MNCC_REL_IND, &rel);
+}
+
+/* sending status message in response to unknown message */
+static int gsm48_cc_tx_status(struct gsm_trans *trans, int cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ uint8_t *cause_ie, *call_state_ie;
+
+ LOGP(DCC, LOGL_INFO, "sending STATUS (cause %d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_STATUS;
+
+ cause_ie = msgb_put(nmsg, 3);
+ cause_ie[0] = 2;
+ cause_ie[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_PRN_S_LU;
+ cause_ie[2] = 0x80 | cause;
+
+ call_state_ie = msgb_put(nmsg, 1);
+ call_state_ie[0] = 0xc0 | trans->cc.state;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* reply status enquiry */
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+ LOGP(DCC, LOGL_INFO, "received STATUS ENQUIREY\n");
+
+ return gsm48_cc_tx_status(trans, GSM48_CC_CAUSE_RESP_STATUS_INQ);
+}
+
+/*
+ * process handlers (mobile originating call establish)
+ */
+
+/* on SETUP request from L4, init MM connection */
+static int gsm48_cc_init_mm(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm_mncc *data = arg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* store setup message */
+ memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+ new_cc_state(trans, GSM_CSTATE_MM_CONNECTION_PEND);
+
+ /* establish MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *) nmsg->data;
+ if (data->emergency)
+ nmmh->emergency = 1;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_EST_REQ\n");
+ return gsm48_mmxx_downmsg(trans->ms, nmsg);
+}
+
+/* abort connection prior SETUP */
+static int gsm48_cc_abort_mm(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+
+ /* abort MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n");
+ gsm48_mmxx_downmsg(trans->ms, nmsg);
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return 0;
+}
+
+/* setup message from upper layer */
+static int gsm48_cc_tx_setup(struct gsm_trans *trans)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm_mncc *setup = &trans->cc.msg;
+ int rc, transaction_id;
+ uint8_t *ie;
+
+ LOGP(DCC, LOGL_INFO, "sending SETUP\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ /* transaction id must not be assigned */
+ if (trans->transaction_id != 0xff) { /* unasssigned */
+ LOGP(DCC, LOGL_NOTICE, "TX Setup with assigned transaction. "
+ "This is not allowed!\n");
+ /* Temporarily out of order */
+ rc = mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_NORMAL_UNSPEC);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+
+ /* Get free transaction_id */
+ transaction_id = trans_assign_trans_id(trans->ms, GSM48_PDISC_CC, 0);
+ if (transaction_id < 0) {
+ /* no free transaction ID */
+ rc = mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+ trans->transaction_id = transaction_id;
+
+ gh->msg_type = (setup->emergency) ? GSM48_MT_CC_EMERG_SETUP :
+ GSM48_MT_CC_SETUP;
+
+ /* actually we have to start it when CM SERVICE REQUEST has been sent,
+ * but there is no primitive for that defined. i think it is ok to
+ * do it here rather than inventing MMCC-NOTIFY-IND.
+ */
+ gsm48_start_cc_timer(trans, 0x303, GSM48_T303_MS);
+
+ if (!setup->emergency) {
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 0, &setup->bearer_cap);
+ /* facility */
+ if (setup->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &setup->facility);
+ /* called party BCD number */
+ if (setup->fields & MNCC_F_CALLED)
+ gsm48_encode_called(nmsg, &setup->called);
+ /* user-user */
+ if (setup->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &setup->useruser);
+ /* ss version */
+ if (setup->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &setup->ssversion);
+ /* CLIR suppression */
+ if (setup->clir.sup) {
+ ie = msgb_put(nmsg, 1);
+ ie[0] = GSM48_IE_CLIR_SUPP;
+ }
+ /* CLIR invocation */
+ if (setup->clir.inv) {
+ ie = msgb_put(nmsg, 1);
+ ie[0] = GSM48_IE_CLIR_INVOC;
+ }
+ /* cc cap */
+ if (setup->fields & MNCC_F_CCCAP)
+ gsm48_encode_cccap(nmsg, &setup->cccap);
+ }
+
+ /* actually MM CONNECTION PENDING */
+ new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* progress is received from lower layer */
+static int gsm48_cc_rx_progress(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc progress;
+
+ LOGP(DCC, LOGL_INFO, "received PROGRESS\n");
+
+ memset(&progress, 0, sizeof(struct gsm_mncc));
+ progress.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_PROGR_IND, 0);
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ progress.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&progress.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ /* store last progress indicator */
+ trans->cc.prog_ind = progress.progress.descr;
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ progress.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&progress.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_PROGRESS_IND, &progress);
+}
+
+/* call proceeding is received from lower layer */
+static int gsm48_cc_rx_call_proceeding(struct gsm_trans *trans,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc call_proc;
+
+ LOGP(DCC, LOGL_INFO, "sending CALL PROCEEDING\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&call_proc, 0, sizeof(struct gsm_mncc));
+ call_proc.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+ /* repeat */
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+ call_conf.repeat = 1;
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+ call_conf.repeat = 2;
+#endif
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ call_proc.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&call_proc.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ call_proc.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&call_proc.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ call_proc.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&call_proc.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ /* store last progress indicator */
+ trans->cc.prog_ind = call_proc.progress.descr;
+ }
+
+ /* start T310, if last progress indicator was 1 or 2 or 64 */
+ if (trans->cc.prog_ind == 1
+ || trans->cc.prog_ind == 2
+ || trans->cc.prog_ind == 64)
+ gsm48_start_cc_timer(trans, 0x310, GSM48_T310_MS);
+
+ new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_CALL_PROC_IND,
+ &call_proc);
+}
+
+/* alerting is received by the lower layer */
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc alerting;
+
+ LOGP(DCC, LOGL_INFO, "sending ALERTING\n");
+
+ gsm48_stop_cc_timer(trans);
+ /* no T301 in MS call control */
+
+ memset(&alerting, 0, sizeof(struct gsm_mncc));
+ alerting.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ alerting.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&alerting.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ alerting.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&alerting.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ alerting.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&alerting.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_ALERT_IND,
+ &alerting);
+}
+
+/* connect is received from lower layer */
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc connect;
+
+ LOGP(DCC, LOGL_INFO, "received CONNECT\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&connect, 0, sizeof(struct gsm_mncc));
+ connect.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ connect.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&connect.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* connected */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CONN_BCD)) {
+ connect.fields |= MNCC_F_CONNECTED;
+ gsm48_decode_connected(&connect.connected,
+ TLVP_VAL(&tp, GSM48_IE_CONN_BCD)-1);
+ }
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ connect.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&connect.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ connect.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&connect.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ /* ACTIVE state is set during this: */
+ gsm48_cc_tx_connect_ack(trans, NULL);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_CNF, &connect);
+}
+
+/* connect ack message from upper layer */
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending CONNECT ACKNOWLEDGE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/*
+ * process handlers (mobile terminating call establish)
+ */
+
+/* setup is received from lower layer */
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc setup;
+
+ LOGP(DCC, LOGL_INFO, "sending SETUP\n");
+
+ memset(&setup, 0, sizeof(struct gsm_mncc));
+ setup.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ setup.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&setup.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ setup.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&setup.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ setup.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&setup.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* signal */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SIGNAL)) {
+ setup.fields |= MNCC_F_SIGNAL;
+ gsm48_decode_signal(&setup.signal,
+ TLVP_VAL(&tp, GSM48_IE_SIGNAL)-1);
+ }
+ /* calling party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CALLING_BCD)) {
+ setup.fields |= MNCC_F_CALLING;
+ gsm48_decode_calling(&setup.calling,
+ TLVP_VAL(&tp, GSM48_IE_CALLING_BCD)-1);
+ }
+ /* called party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+ setup.fields |= MNCC_F_CALLED;
+ gsm48_decode_called(&setup.called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+ }
+ /* redirecting party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_REDIR_BCD)) {
+ setup.fields |= MNCC_F_REDIRECTING;
+ gsm48_decode_redirecting(&setup.redirecting,
+ TLVP_VAL(&tp, GSM48_IE_REDIR_BCD)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ setup.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&setup.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+ /* indicate setup to MNCC */
+ mncc_recvmsg(trans->ms, trans, MNCC_SETUP_IND, &setup);
+
+ return 0;
+}
+
+/* call conf message from upper layer */
+static int gsm48_cc_tx_call_conf(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *confirm = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending CALL CONFIRMED (proceeding)\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CALL_CONF;
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+ /* bearer capability */
+ if (confirm->fields & MNCC_F_BEARER_CAP)
+ gsm48_encode_bearer_cap(nmsg, 0, &confirm->bearer_cap);
+ /* cause */
+ if (confirm->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &confirm->cause);
+ /* cc cap */
+ if (confirm->fields & MNCC_F_CCCAP)
+ gsm48_encode_cccap(nmsg, &confirm->cccap);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* alerting message from upper layer */
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *alerting = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending ALERTING\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_ALERTING;
+
+ /* facility */
+ if (alerting->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &alerting->facility);
+ /* user-user */
+ if (alerting->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &alerting->useruser);
+ /* ss version */
+ if (alerting->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &alerting->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* connect message from upper layer */
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *connect = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending CONNECT\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x313, GSM48_T313_MS);
+
+ /* facility */
+ if (connect->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &connect->facility);
+ /* user-user */
+ if (connect->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &connect->useruser);
+ /* ss version */
+ if (connect->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &connect->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* connect ack is received from lower layer */
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc connect_ack;
+
+ LOGP(DCC, LOGL_INFO, "received CONNECT ACKNOWLEDGE\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+ connect_ack.callref = trans->callref;
+ return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_COMPL_IND,
+ &connect_ack);
+}
+
+/*
+ * process handlers (during active state)
+ */
+
+/* notify message from upper layer */
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *notify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending NOTIFY\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+ /* notify */
+ gsm48_encode_notify(nmsg, notify->notify);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* notify is received from lower layer */
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc notify;
+
+ LOGP(DCC, LOGL_INFO, "received NOTIFY\n");
+
+ memset(&notify, 0, sizeof(struct gsm_mncc));
+ notify.callref = trans->callref;
+ /* notify */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of notify message error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_notify(&notify.notify, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+/* start dtmf message from upper layer */
+static int gsm48_cc_tx_start_dtmf(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *dtmf = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending START DTMF\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_START_DTMF;
+
+ /* keypad */
+ gsm48_encode_keypad(nmsg, dtmf->keypad);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* start dtmf ack is received from lower layer */
+static int gsm48_cc_rx_start_dtmf_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc dtmf;
+
+ LOGP(DCC, LOGL_INFO, "received START DTMF ACKNOWLEDGE\n");
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* keypad facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+ dtmf.fields |= MNCC_F_KEYPAD;
+ gsm48_decode_keypad(&dtmf.keypad,
+ TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+ }
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_RSP, &dtmf);
+}
+
+/* start dtmf rej is received from lower layer */
+static int gsm48_cc_rx_start_dtmf_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc dtmf;
+
+ LOGP(DCC, LOGL_INFO, "received START DTMF REJECT\n");
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of dtmf reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&dtmf.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_REJ, &dtmf);
+}
+
+/* stop dtmf message from upper layer */
+static int gsm48_cc_tx_stop_dtmf(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending STOP DTMF\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_STOP_DTMF;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* stop dtmf ack is received from lower layer */
+static int gsm48_cc_rx_stop_dtmf_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc dtmf;
+
+ LOGP(DCC, LOGL_INFO, "received STOP DTMF ACKNOWLEDGE\n");
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_STOP_DTMF_RSP, &dtmf);
+}
+
+/* hold message from upper layer */
+static int gsm48_cc_tx_hold(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending HOLD\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_HOLD;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* hold ack is received from lower layer */
+static int gsm48_cc_rx_hold_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc hold;
+
+ LOGP(DCC, LOGL_INFO, "received HOLD ACKNOWLEDGE\n");
+
+ memset(&hold, 0, sizeof(struct gsm_mncc));
+ hold.callref = trans->callref;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_CNF, &hold);
+}
+
+/* hold rej is received from lower layer */
+static int gsm48_cc_rx_hold_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc hold;
+
+ LOGP(DCC, LOGL_INFO, "received HOLD REJECT\n");
+
+ memset(&hold, 0, sizeof(struct gsm_mncc));
+ hold.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of hold reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&hold.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_REJ, &hold);
+}
+
+/* retrieve message from upper layer */
+static int gsm48_cc_tx_retrieve(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending RETRIEVE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RETR;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* retrieve ack is received from lower layer */
+static int gsm48_cc_rx_retrieve_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc retrieve;
+
+ LOGP(DCC, LOGL_INFO, "received RETRIEVE ACKNOWLEDGE\n");
+
+ memset(&retrieve, 0, sizeof(struct gsm_mncc));
+ retrieve.callref = trans->callref;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_CNF, &retrieve);
+}
+
+/* retrieve rej is received from lower layer */
+static int gsm48_cc_rx_retrieve_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc retrieve;
+
+ LOGP(DCC, LOGL_INFO, "received RETRIEVE REJECT\n");
+
+ memset(&retrieve, 0, sizeof(struct gsm_mncc));
+ retrieve.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of retrieve reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&retrieve.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_REJ, &retrieve);
+}
+
+/* facility message from upper layer or from timer event */
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *fac = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending FACILITY\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_FACILITY;
+
+ /* facility */
+ gsm48_encode_facility(nmsg, 1, &fac->facility);
+ /* ss version */
+ if (fac->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &fac->ssversion);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* facility is received from lower layer */
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc fac;
+
+ LOGP(DCC, LOGL_INFO, "received FACILITY\n");
+
+ memset(&fac, 0, sizeof(struct gsm_mncc));
+ fac.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of facility message "
+ "error.\n");
+ return -EINVAL;
+ }
+ /* facility */
+ gsm48_decode_facility(&fac.facility, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_FACILITY_IND, &fac);
+}
+
+/* user info message from upper layer or from timer event */
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *user = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending USERINFO\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+ /* user-user */
+ if (user->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 1, &user->useruser);
+ /* more data */
+ if (user->more)
+ gsm48_encode_more(nmsg);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* user info is received from lower layer */
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc user;
+
+ LOGP(DCC, LOGL_INFO, "received USERINFO\n");
+
+ memset(&user, 0, sizeof(struct gsm_mncc));
+ user.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of userinfo message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_USER_USER, 0);
+ /* user-user */
+ gsm48_decode_useruser(&user.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ /* more data */
+ if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+ user.more = 1;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_USERINFO_IND, &user);
+}
+
+/* modify message from upper layer or from timer event */
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending MODIFY\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY;
+
+ gsm48_start_cc_timer(trans, 0x323, GSM48_T323_MS);
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* modify complete is received from lower layer */
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc modify;
+
+ LOGP(DCC, LOGL_INFO, "received MODIFY COMPLETE\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify complete message "
+ "error.\n");
+ return -EINVAL;
+ }
+ /* bearer capability */
+ gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+/* modify reject is received from lower layer */
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc modify;
+
+ LOGP(DCC, LOGL_INFO, "received MODIFY REJECT\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ modify.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&modify.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ modify.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&modify.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+/* modify is received from lower layer */
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc modify;
+
+ LOGP(DCC, LOGL_INFO, "received MODIFY\n");
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify message error.\n");
+ return -EINVAL;
+ }
+ /* bearer capability */
+ gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data);
+
+ new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_IND, &modify);
+}
+
+/* modify complete message from upper layer or from timer event */
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending MODIFY COMPLETE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* modify reject message from upper layer or from timer event */
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending MODIFY REJECT\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+ /* cause */
+ gsm48_encode_cause(nmsg, 1, &modify->cause);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/*
+ * process handlers (call clearing)
+ */
+
+static struct gsm_mncc_cause default_cause = {
+ .location = GSM48_CAUSE_LOC_PRN_S_LU,
+ .coding = 0,
+ .rec = 0,
+ .rec_val = 0,
+ .value = GSM48_CC_CAUSE_NORMAL_UNSPEC,
+ .diag_len = 0,
+ .diag = { 0 },
+};
+
+/* disconnect message from upper layer or from timer event */
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *disc = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending DISCONNECT\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x305, GSM48_T305_MS);
+
+ /* cause */
+ if (disc->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 1, &disc->cause);
+ else
+ gsm48_encode_cause(nmsg, 1, &default_cause);
+
+ /* facility */
+ if (disc->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &disc->facility);
+ /* progress */
+ if (disc->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(nmsg, 0, &disc->progress);
+ /* user-user */
+ if (disc->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &disc->useruser);
+ /* ss version */
+ if (disc->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &disc->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* release message from upper layer or from timer event */
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending RELEASE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x308, GSM48_T308_MS);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &rel->useruser);
+ /* ss version */
+ if (rel->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &rel->ssversion);
+
+ trans->cc.T308_second = 0;
+ memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+ if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+ new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+ gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+
+#if 0
+ /* release without sending MMCC_REL_REQ */
+ new_cc_state(trans, GSM_CSTATE_NULL);
+ trans->callref = 0;
+ trans_free(trans);
+#endif
+
+ return 0;
+}
+
+/* reject message from upper layer */
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ LOGP(DCC, LOGL_INFO, "sending RELEASE COMPLETE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+ gsm48_stop_cc_timer(trans);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &rel->useruser);
+ /* ss version */
+ if (rel->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &rel->ssversion);
+
+ /* release without sending MMCC_REL_REQ */
+ new_cc_state(trans, GSM_CSTATE_NULL);
+ trans->callref = 0;
+ trans_free(trans);
+
+ return 0;
+}
+
+/* disconnect is received from lower layer */
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc disc;
+
+ LOGP(DCC, LOGL_INFO, "received DISCONNECT\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+ memset(&disc, 0, sizeof(struct gsm_mncc));
+ disc.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_CAUSE, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ disc.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&disc.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ disc.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&disc.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ disc.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&disc.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ disc.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&disc.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ /* store disconnect cause for T305 expiry */
+ memcpy(&trans->cc.msg, &disc, sizeof(struct gsm_mncc));
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &disc);
+}
+
+/* release is received from lower layer */
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+
+ LOGP(DCC, LOGL_INFO, "received RELEASE\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ /* in case we receive a relase, when we are already in NULL state */
+ if (trans->cc.state == GSM_CSTATE_NULL) {
+ LOGP(DCC, LOGL_INFO, "ignoring RELEASE in NULL state\n");
+ /* release MM conn, free trans */
+ return gsm48_rel_null_free(trans);
+ }
+ if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+ /* release collision 5.4.5 */
+ mncc_recvmsg(trans->ms, trans, MNCC_REL_CNF, &rel);
+ } else {
+ struct msgb *nmsg;
+
+ /* forward cause only */
+ LOGP(DCC, LOGL_INFO, "sending RELEASE COMPLETE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+ if (rel.fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &rel.cause);
+
+ gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+
+ /* release indication */
+ mncc_recvmsg(trans->ms, trans, MNCC_REL_IND, &rel);
+ }
+
+ /* release MM conn, got NULL state, free trans */
+ return gsm48_rel_null_free(trans);
+}
+
+/* release complete is received from lower layer */
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+
+ LOGP(DCC, LOGL_INFO, "received RELEASE COMPLETE\n");
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ if (trans->callref) {
+ switch (trans->cc.state) {
+ case GSM_CSTATE_CALL_PRESENT:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REJ_IND, &rel);
+ break;
+ case GSM_CSTATE_RELEASE_REQ:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REL_CNF, &rel);
+ break;
+ default:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REL_IND, &rel);
+ }
+ }
+
+ /* release MM conn, got NULL state, free trans */
+ return gsm48_rel_null_free(trans);
+}
+
+/*
+ * state machines
+ */
+
+/* state trasitions for MNCC messages (upper layer) */
+static struct downstate {
+ u_int32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.1 */
+ MNCC_SETUP_REQ, gsm48_cc_init_mm},
+
+ {SBIT(GSM_CSTATE_MM_CONNECTION_PEND), /* 5.2.1 */
+ MNCC_REL_REQ, gsm48_cc_abort_mm},
+
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.1 */
+ MNCC_CALL_CONF_REQ, gsm48_cc_tx_call_conf},
+
+ {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* 5.2.2.3.2 */
+ MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+
+ {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) |
+ SBIT(GSM_CSTATE_CALL_RECEIVED), /* 5.2.2.5 */
+ MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+
+ /* signalling during call */
+ {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */
+ MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+
+ {ALL_STATES, /* 5.5.7.1 */
+ MNCC_START_DTMF_REQ, gsm48_cc_tx_start_dtmf},
+
+ {ALL_STATES, /* 5.5.7.3 */
+ MNCC_STOP_DTMF_REQ, gsm48_cc_tx_stop_dtmf},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_HOLD_REQ, gsm48_cc_tx_hold},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_RETRIEVE_REQ, gsm48_cc_tx_retrieve},
+
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+ MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+
+ /* clearing */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) -
+ SBIT(GSM_CSTATE_RELEASE_REQ) -
+ SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.3.1 */
+ MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+
+ {SBIT(GSM_CSTATE_INITIATED),
+ MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) -
+ SBIT(GSM_CSTATE_RELEASE_REQ), /* ??? */
+ MNCC_REL_REQ, gsm48_cc_tx_release},
+
+ /* modify */
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+};
+
+#define DOWNSLLEN \
+ (sizeof(downstatelist) / sizeof(struct downstate))
+
+int mncc_send(struct osmocom_ms *ms, int msg_type, void *arg)
+{
+ struct gsm_mncc *data = arg;
+ struct gsm_trans *trans;
+ int i, rc;
+
+ /* Find callref */
+ trans = trans_find_by_callref(ms, data->callref);
+
+ if (!trans) {
+ /* check for SETUP message */
+ if (msg_type != MNCC_SETUP_REQ) {
+ /* Invalid call reference */
+ LOGP(DCC, LOGL_NOTICE, "transaction not found\n");
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INVAL_TRANS_ID);
+ }
+ if (data->callref >= 0x40000000) {
+ LOGP(DCC, LOGL_FATAL, "MNCC ref wrong.\n");
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INVAL_TRANS_ID);
+ }
+
+ /* Create transaction */
+ trans = trans_alloc(ms, GSM48_PDISC_CC, 0xff, data->callref);
+ if (!trans) {
+ /* No memory or whatever */
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ }
+ }
+
+ switch (msg_type) {
+ case GSM_TCHF_FRAME:
+ printf("TCH/F frame ignored!\n");
+ return -EINVAL;
+ }
+
+ /* Find function for current state and message */
+ for (i = 0; i < DOWNSLLEN; i++)
+ if ((msg_type == downstatelist[i].type)
+ && ((1 << trans->cc.state) & downstatelist[i].states))
+ break;
+ if (i == DOWNSLLEN) {
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return 0;
+ }
+
+ rc = downstatelist[i].rout(trans, arg);
+
+ return rc;
+}
+
+/* state trasitions for call control messages (lower layer) */
+static struct datastate {
+ u_int32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.3 */
+ GSM48_MT_CC_CALL_PROC, gsm48_cc_rx_call_proceeding},
+
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) |
+ SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.4.1 */
+ MNCC_PROGRESS_REQ, gsm48_cc_rx_progress},
+
+ {SBIT(GSM_CSTATE_INITIATED) |
+ SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.5 */
+ GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) |
+ SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.6 */
+ GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+ GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+
+ {SBIT(GSM_CSTATE_CONNECT_REQUEST), /* 5.2.2.6 */
+ GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+
+ /* signalling during call */
+ {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */
+ GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+
+ {ALL_STATES, /* 8.4 */
+ GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+
+ {ALL_STATES, /* 5.5.7.2 */
+ GSM48_MT_CC_START_DTMF_ACK, gsm48_cc_rx_start_dtmf_ack},
+
+ {ALL_STATES, /* 5.5.7.2 */
+ GSM48_MT_CC_START_DTMF_REJ, gsm48_cc_rx_start_dtmf_rej},
+
+ {ALL_STATES, /* 5.5.7.4 */
+ GSM48_MT_CC_STOP_DTMF_ACK, gsm48_cc_rx_stop_dtmf_ack},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_HOLD_ACK, gsm48_cc_rx_hold_ack},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_HOLD_REJ, gsm48_cc_rx_hold_rej},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_RETR_ACK, gsm48_cc_rx_retrieve_ack},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_RETR_REJ, gsm48_cc_rx_retrieve_rej},
+
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL),
+ GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+
+ /* clearing */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ) -
+ SBIT(GSM_CSTATE_DISCONNECT_IND), /* 5.4.4.1.1 */
+ GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+
+ {ALL_STATES, /* 5.4.3.3 & 5.4.5!!!*/
+ GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+
+ {ALL_STATES, /* 5.4.4.1.3 */
+ GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+
+ /* modify */
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+};
+
+#define DATASLLEN \
+ (sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm48_cc_data_ind(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct osmocom_ms *ms = trans->ms;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int msg_type = gh->msg_type & 0xbf;
+ uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4;
+ /* flip */
+ int msg_supported = 0; /* determine, if message is supported at all */
+ int i, rc;
+
+ /* set transaction ID, if not already */
+ trans->transaction_id = transaction_id;
+
+ /* pull the MMCC header */
+ msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));
+
+ LOGP(DCC, LOGL_INFO, "(ms %s) Received '%s' in CC state %s\n", ms->name,
+ gsm48_cc_msg_name(msg_type),
+ gsm48_cc_state_name(trans->cc.state));
+
+ /* find function for current state and message */
+ for (i = 0; i < DATASLLEN; i++) {
+ if (msg_type == datastatelist[i].type)
+ msg_supported = 1;
+ if ((msg_type == datastatelist[i].type)
+ && ((1 << trans->cc.state) & datastatelist[i].states))
+ break;
+ }
+ if (i == DATASLLEN) {
+ if (msg_supported) {
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return gsm48_cc_tx_status(trans,
+ GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE);
+ } else {
+ LOGP(DCC, LOGL_NOTICE, "Message not supported.\n");
+ return gsm48_cc_tx_status(trans,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+ }
+
+ rc = datastatelist[i].rout(trans, msg);
+
+ return rc;
+}
+
+/* receive message from MM layer */
+int gsm48_rcv_cc(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ int msg_type = mmh->msg_type;
+ struct gsm_trans *trans;
+ int rc = 0;
+
+ trans = trans_find_by_callref(ms, mmh->ref);
+ if (!trans) {
+ trans = trans_alloc(ms, GSM48_PDISC_CC, mmh->transaction_id,
+ mmh->ref);
+ if (!trans)
+ return -ENOMEM;
+ }
+
+ LOGP(DCC, LOGL_INFO, "(ms %s) Received '%s' in CC state %s\n", ms->name,
+ get_mmxx_name(msg_type),
+ gsm48_cc_state_name(trans->cc.state));
+
+ switch (msg_type) {
+ case GSM48_MMCC_EST_IND:
+ /* data included */
+ rc = gsm48_cc_data_ind(trans, msg);
+ break;
+ case GSM48_MMCC_EST_CNF:
+ /* send setup after confirm */
+ if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND)
+ rc = gsm48_cc_tx_setup(trans);
+ else
+ LOGP(DCC, LOGL_ERROR, "Oops, MMCC-EST-CONF in state "
+ "%d?\n", trans->cc.state);
+ break;
+ case GSM48_MMCC_ERR_IND: /* no supporting re-establishment */
+ case GSM48_MMCC_REL_IND:
+ /* release L4, release transaction */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU, mmh->cause);
+ /* release without sending MMCC_REL_REQ */
+ new_cc_state(trans, GSM_CSTATE_NULL);
+ trans->callref = 0;
+ trans_free(trans);
+ break;
+ case GSM48_MMCC_DATA_IND:
+ rc = gsm48_cc_data_ind(trans, msg);
+ break;
+ case GSM48_MMCC_UNIT_DATA_IND:
+ break;
+ case GSM48_MMCC_SYNC_IND:
+ break;
+ default:
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled.\n");
+ rc = -ENOTSUP;
+ }
+
+ return rc;
+}
+