diff options
Diffstat (limited to 'src/gsm/gsm0411_smr.c')
-rw-r--r-- | src/gsm/gsm0411_smr.c | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/src/gsm/gsm0411_smr.c b/src/gsm/gsm0411_smr.c new file mode 100644 index 00000000..d5ca9238 --- /dev/null +++ b/src/gsm/gsm0411_smr.c @@ -0,0 +1,451 @@ +/* Point-to-Point (PP) Short Message Service (SMS) + * Support on Mobile Radio Interface + * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */ + +/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * (C) 2011 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 Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* Notes on msg: + * + * Messages from lower layer are freed by lower layer. + * + * Messages to upper layer are freed after upper layer call returns, so upper + * layer cannot use data after returning. Upper layer must not free the msg. + * + * This implies: Lower layer messages can be forwarded to upper layer. + * + * Upper layer messages are freed by lower layer, so they must not be freed + * after calling lower layer. + * + * + * Notes on release: + * + * Sending Abort/Release (MNSMS-ABORT-REQ or MNSMS-REL-REQ) may cause the + * lower layer to become IDLE. Then it is allowed to destroy this instance, + * so sending this this MUST be the last thing that is done. + * + */ + + +#include <string.h> +#include <errno.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/tlv.h> + +#include <osmocom/gsm/gsm0411_utils.h> +#include <osmocom/gsm/gsm0411_smc.h> +#include <osmocom/gsm/gsm0411_smr.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +static void rp_timer_expired(void *data); + +/* init a new instance */ +void gsm411_smr_init(struct gsm411_smr_inst *inst, int network, + int (*rl_recv) (struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg), + int (*mn_send) (struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg)) +{ + memset(inst, 0, sizeof(*inst)); + inst->network = network; + inst->rp_state = GSM411_RPS_IDLE; + inst->rl_recv = rl_recv; + inst->mn_send = mn_send; + inst->rp_timer.data = inst; + inst->rp_timer.cb = rp_timer_expired; + + LOGP(DLSMS, LOGL_INFO, "New SMR instance created\n"); +} + +/* clear instance */ +void gsm411_smr_clear(struct gsm411_smr_inst *inst) +{ + LOGP(DLSMS, LOGL_INFO, "Clear SMR instance\n"); + + osmo_timer_del(&inst->rp_timer); +} + +const char *smr_state_names[] = { + "IDLE", + "WAIT_FOR_RP_ACK", + "illegal state 2" + "WAIT_TO_TX_RP_ACK", + "WAIT_FOR_RETRANS_T", +}; + +const struct value_string gsm411_rp_cause_strs[] = { + { GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" }, + { GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" }, + { GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" }, + { GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" }, + { GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" }, + { GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" }, + { GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" }, + { GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" }, + { GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" }, + { GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" }, + { GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" }, + { GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" }, + { GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" }, + { GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" }, + { GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" }, + /* valid only for MT */ + { GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" }, + /* valid for both directions */ + { GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" }, + { GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" }, + { GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" }, + { GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" }, + { GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" }, + { GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" }, + { GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" }, + { 0, NULL } +}; + +static void new_rp_state(struct gsm411_smr_inst *inst, + enum gsm411_rp_state state) +{ + LOGP(DLSMS, LOGL_INFO, "New RP state %s -> %s\n", + smr_state_names[inst->rp_state], smr_state_names[state]); + inst->rp_state = state; + + /* stop timer when going idle */ + if (state == GSM411_RPS_IDLE) + osmo_timer_del(&inst->rp_timer); +} + +/* Prefix msg with a RP-DATA header and send as CP-DATA */ +static int gsm411_rp_sendmsg(struct gsm411_smr_inst *inst, struct msgb *msg, + uint8_t rp_msg_type, uint8_t rp_msg_ref, + int mnsms_msg_type) +{ + struct gsm411_rp_hdr *rp; + uint8_t len = msg->len; + + /* GSM 04.11 RP-DATA header */ + rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp)); + rp->len = len + 2; + rp->msg_type = rp_msg_type; + rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */ + + return inst->mn_send(inst, mnsms_msg_type, msg); +} + +static int gsm411_send_rp_error(struct gsm411_smr_inst *inst, + uint8_t msg_ref, uint8_t cause) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + msgb_tv_put(msg, 1, cause); + + LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause, + get_value_string(gsm411_rp_cause_strs, cause)); + + return gsm411_rp_sendmsg(inst, msg, + (inst->network) ? GSM411_MT_RP_ERROR_MT : GSM411_MT_RP_ERROR_MO, + msg_ref, GSM411_MNSMS_DATA_REQ); +} + +static int gsm411_send_release(struct gsm411_smr_inst *inst) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + LOGP(DLSMS, LOGL_NOTICE, "TX: MNSMS-REL-REQ\n"); + + return inst->mn_send(inst, GSM411_MNSMS_REL_REQ, msg); +} + +static int gsm411_send_abort(struct gsm411_smr_inst *inst) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + msgb_tv_put(msg, 1, 111); //FIXME: better idea ? */ + LOGP(DLSMS, LOGL_NOTICE, "TX: MNSMS-ABORT-REQ\n"); + + return inst->mn_send(inst, GSM411_MNSMS_ABORT_REQ, msg); +} + +static int gsm411_send_report(struct gsm411_smr_inst *inst) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + LOGP(DLSMS, LOGL_NOTICE, "send empty SM_RL_REPORT_IND\n"); + + return inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg); +} + +static int gsm411_rl_data_req(struct gsm411_smr_inst *inst, struct msgb *msg) +{ + LOGP(DLSMS, LOGL_DEBUG, "TX SMS RP-DATA\n"); + /* start TR1N and enter 'wait for RP-ACK state' */ + osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR1M); + new_rp_state(inst, GSM411_RPS_WAIT_FOR_RP_ACK); + + return inst->mn_send(inst, GSM411_MNSMS_EST_REQ, msg); +} + +static int gsm411_rl_report_req(struct gsm411_smr_inst *inst, struct msgb *msg) +{ + LOGP(DLSMS, LOGL_DEBUG, "TX SMS REPORT\n"); + new_rp_state(inst, GSM411_RPS_IDLE); + + inst->mn_send(inst, GSM411_MNSMS_DATA_REQ, msg); + gsm411_send_release(inst); + return 0; +} + +static int gsm411_mnsms_est_ind(struct gsm411_smr_inst *inst, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr*)msg->l3h; + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc; + + /* check direction */ + if (inst->network == (msg_type & 1)) { + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_send_rp_error(inst, rp_data->msg_ref, + GSM411_RP_CAUSE_MSG_INCOMP_STATE); + new_rp_state(inst, GSM411_RPS_IDLE); + gsm411_send_release(inst); + return -EINVAL; + } + + switch (msg_type) { + case GSM411_MT_RP_DATA_MT: + case GSM411_MT_RP_DATA_MO: + LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-DATA\n"); + /* start TR2N and enter 'wait to send RP-ACK state' */ + osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR2M); + new_rp_state(inst, GSM411_RPS_WAIT_TO_TX_RP_ACK); + rc = inst->rl_recv(inst, GSM411_SM_RL_DATA_IND, msg); + break; + case GSM411_MT_RP_SMMA_MO: + LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-SMMA\n"); + /* start TR2N and enter 'wait to send RP-ACK state' */ + osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR2M); + new_rp_state(inst, GSM411_RPS_WAIT_TO_TX_RP_ACK); + rc = inst->rl_recv(inst, GSM411_SM_RL_DATA_IND, msg); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_send_rp_error(inst, rp_data->msg_ref, + GSM411_RP_CAUSE_MSGTYPE_NOTEXIST); + new_rp_state(inst, GSM411_RPS_IDLE); + rc = -EINVAL; + break; + } + + return rc; +} + +static int gsm411_mnsms_data_ind_tx(struct gsm411_smr_inst *inst, + struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr*)msg->l3h; + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc; + + /* check direction */ + if (inst->network == (msg_type & 1)) { + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_send_rp_error(inst, rp_data->msg_ref, + GSM411_RP_CAUSE_MSG_INCOMP_STATE); + new_rp_state(inst, GSM411_RPS_IDLE); + gsm411_send_release(inst); + return -EINVAL; + } + + switch (msg_type) { + case GSM411_MT_RP_ACK_MO: + case GSM411_MT_RP_ACK_MT: + LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-ACK\n"); + new_rp_state(inst, GSM411_RPS_IDLE); + inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg); + gsm411_send_release(inst); + return 0; + case GSM411_MT_RP_ERROR_MO: + case GSM411_MT_RP_ERROR_MT: + LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-ERROR\n"); + new_rp_state(inst, GSM411_RPS_IDLE); + inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg); + gsm411_send_release(inst); + return 0; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_send_rp_error(inst, rp_data->msg_ref, + GSM411_RP_CAUSE_MSGTYPE_NOTEXIST); + new_rp_state(inst, GSM411_RPS_IDLE); + gsm411_send_release(inst); + return -EINVAL; + } + + return rc; +} + +static int gsm411_mnsms_error_ind_tx(struct gsm411_smr_inst *inst, + struct msgb *msg) +{ + LOGP(DLSMS, LOGL_DEBUG, "RX SMS MNSMS-ERROR-IND\n"); + new_rp_state(inst, GSM411_RPS_IDLE); + inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg); + gsm411_send_release(inst); + return 0; +} + +static int gsm411_mnsms_error_ind_rx(struct gsm411_smr_inst *inst, + struct msgb *msg) +{ + LOGP(DLSMS, LOGL_DEBUG, "RX SMS MNSMS-ERROR-IND\n"); + new_rp_state(inst, GSM411_RPS_IDLE); + return inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg); +} + +/* SMR TR1* is expired */ +static void rp_timer_expired(void *data) +{ + struct gsm411_smr_inst *inst = data; + + if (inst->rp_state == GSM411_RPS_WAIT_TO_TX_RP_ACK) + LOGP(DLSMS, LOGL_DEBUG, "TR2N\n"); + else + LOGP(DLSMS, LOGL_DEBUG, "TR1N\n"); + gsm411_send_report(inst); + gsm411_send_abort(inst); +} + +/* statefull handling for SM-RL SAP messages */ +static struct smrdownstate { + uint32_t states; + int type; + const char *name; + int (*rout) (struct gsm411_smr_inst *inst, + struct msgb *msg); +} smrdownstatelist[] = { + /* data request */ + {SBIT(GSM411_RPS_IDLE), + GSM411_SM_RL_DATA_REQ, + "SM-RL-DATA_REQ", gsm411_rl_data_req}, + + /* report request */ + {SBIT(GSM411_RPS_WAIT_TO_TX_RP_ACK), + GSM411_SM_RL_REPORT_REQ, + "SM-RL-REPORT_REQ", gsm411_rl_report_req}, +}; + +#define SMRDOWNSLLEN \ + (sizeof(smrdownstatelist) / sizeof(struct smrdownstate)) + +/* message from upper layer */ +int gsm411_smr_send(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + int i, rc; + + /* find function for current state and message */ + for (i = 0; i < SMRDOWNSLLEN; i++) { + if ((msg_type == smrdownstatelist[i].type) + && (SBIT(inst->rp_state) & smrdownstatelist[i].states)) + break; + } + if (i == SMRDOWNSLLEN) { + LOGP(DLSMS, LOGL_NOTICE, "Message %u unhandled at this state " + "%s.\n", msg_type, smr_state_names[inst->rp_state]); + msgb_free(msg); + return 0; + } + + LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n", + smrdownstatelist[i].name, smr_state_names[inst->rp_state]); + + rc = smrdownstatelist[i].rout(inst, msg); + + return rc; +} + +/* statefull handling for MMSMS SAP messages */ +static struct smrdatastate { + uint32_t states; + int type; + const char *name; + int (*rout) (struct gsm411_smr_inst *inst, + struct msgb *msg); +} smrdatastatelist[] = { + /* establish indication */ + {SBIT(GSM411_RPS_IDLE), + GSM411_MNSMS_EST_IND, + "MNSMS-EST-IND", gsm411_mnsms_est_ind}, + + /* data indication */ + {SBIT(GSM411_RPS_WAIT_FOR_RP_ACK), + GSM411_MNSMS_DATA_IND, + "MNSMS-DATA-IND", gsm411_mnsms_data_ind_tx}, + + /* error indication */ + {SBIT(GSM411_RPS_WAIT_FOR_RP_ACK), + GSM411_MNSMS_ERROR_IND, + "MNSMS-ERROR-IND", gsm411_mnsms_error_ind_tx}, + + /* error indication */ + {SBIT(GSM411_RPS_WAIT_TO_TX_RP_ACK), + GSM411_MNSMS_ERROR_IND, + "MNSMS-ERROR-IND", gsm411_mnsms_error_ind_rx}, + +}; + +#define SMRDATASLLEN \ + (sizeof(smrdatastatelist) / sizeof(struct smrdatastate)) + +/* message from lower layer + * WARNING: We must not free msg, since it will be performed by the + * lower layer. */ +int gsm411_smr_recv(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + int i, rc; + + /* find function for current state and message */ + for (i = 0; i < SMRDATASLLEN; i++) { + /* state must machtch, MM message must match + * CP msg must match only in case of MMSMS_DATA_IND + */ + if ((msg_type == smrdatastatelist[i].type) + && (SBIT(inst->rp_state) & smrdatastatelist[i].states)) + break; + } + if (i == SMRDATASLLEN) { + LOGP(DLSMS, LOGL_NOTICE, "Message %u unhandled at this state " + "%s.\n", msg_type, smr_state_names[inst->rp_state]); + return 0; + } + + LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n", + smrdatastatelist[i].name, smr_state_names[inst->rp_state]); + + rc = smrdatastatelist[i].rout(inst, msg); + + return rc; +} |