diff options
Diffstat (limited to 'src/host/layer23/src/mobile/gsm48_cc.c')
-rw-r--r-- | src/host/layer23/src/mobile/gsm48_cc.c | 2139 |
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(¬ify, 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(¬ify.notify, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_NOTIFY_IND, ¬ify); +} + +/* 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; +} + |