mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-mgw.git
synced 2025-11-02 13:03:33 +00:00
The issue can be reproduced by typing the following 9 or more times. OpenBSC> subscriber id 2 sms sender id 2 send bla For some unknown reason the phone sends us a CP-ERROR for a transaction identifier we have allocated and used but don't remember. Due the way we use the SMC/SMR we 'establish' the machine and this results in a CP-ACK being sent out. But the CP-ERROR is not having the content we want for an establish so we send out a RP-ERROR. This will result in a CP-ERROR because the phone does not know the transaction... Avoid the issue by checking the direction of the transaction. If we do not know the transaction and it is supposed to be allocated by us then just ignore it and do not create a new transaction.
1004 lines
27 KiB
C
1004 lines
27 KiB
C
/* 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-2012 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/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "bscconfig.h"
|
|
|
|
#include <osmocom/core/msgb.h>
|
|
#include <osmocom/core/talloc.h>
|
|
|
|
#include <osmocom/gsm/gsm_utils.h>
|
|
#include <osmocom/gsm/gsm0411_utils.h>
|
|
|
|
#include <openbsc/debug.h>
|
|
#include <openbsc/gsm_data.h>
|
|
#include <openbsc/db.h>
|
|
#include <openbsc/gsm_subscriber.h>
|
|
#include <openbsc/gsm_04_08.h>
|
|
#include <openbsc/abis_rsl.h>
|
|
#include <openbsc/signal.h>
|
|
#include <openbsc/db.h>
|
|
#include <openbsc/transaction.h>
|
|
#include <openbsc/paging.h>
|
|
#include <openbsc/bsc_rll.h>
|
|
#include <openbsc/chan_alloc.h>
|
|
#include <openbsc/bsc_api.h>
|
|
|
|
#ifdef BUILD_SMPP
|
|
#include "smpp_smsc.h"
|
|
extern int smpp_try_deliver(struct gsm_sms *sms);
|
|
#endif
|
|
|
|
void *tall_gsms_ctx;
|
|
static uint32_t new_callref = 0x40000001;
|
|
|
|
|
|
struct gsm_sms *sms_alloc(void)
|
|
{
|
|
return talloc_zero(tall_gsms_ctx, struct gsm_sms);
|
|
}
|
|
|
|
void sms_free(struct gsm_sms *sms)
|
|
{
|
|
/* drop references to subscriber structure */
|
|
if (sms->sender)
|
|
subscr_put(sms->sender);
|
|
if (sms->receiver)
|
|
subscr_put(sms->receiver);
|
|
#ifdef BUILD_SMPP
|
|
if (sms->smpp.esme)
|
|
smpp_esme_put(sms->smpp.esme);
|
|
#endif
|
|
|
|
talloc_free(sms);
|
|
}
|
|
|
|
struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver,
|
|
struct gsm_subscriber *sender,
|
|
int dcs, const char *text)
|
|
{
|
|
struct gsm_sms *sms = sms_alloc();
|
|
|
|
if (!sms)
|
|
return NULL;
|
|
|
|
sms->receiver = subscr_get(receiver);
|
|
strncpy(sms->text, text, sizeof(sms->text)-1);
|
|
|
|
sms->sender = subscr_get(sender);
|
|
strncpy(sms->src.addr, sms->sender->extension, sizeof(sms->src.addr)-1);
|
|
sms->reply_path_req = 0;
|
|
sms->status_rep_req = 0;
|
|
sms->ud_hdr_ind = 0;
|
|
sms->protocol_id = 0; /* implicit */
|
|
sms->data_coding_scheme = dcs;
|
|
strncpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr)-1);
|
|
/* Generate user_data */
|
|
sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text);
|
|
|
|
return sms;
|
|
}
|
|
|
|
|
|
static void send_signal(int sig_no,
|
|
struct gsm_trans *trans,
|
|
struct gsm_sms *sms,
|
|
int paging_result)
|
|
{
|
|
struct sms_signal_data sig;
|
|
sig.trans = trans;
|
|
sig.sms = sms;
|
|
sig.paging_result = paging_result;
|
|
osmo_signal_dispatch(SS_SMS, sig_no, &sig);
|
|
}
|
|
|
|
static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg)
|
|
{
|
|
DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len));
|
|
msg->l3h = msg->data;
|
|
return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1);
|
|
}
|
|
|
|
/* Prefix msg with a 04.08/04.11 CP header */
|
|
static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
|
|
uint8_t msg_type)
|
|
{
|
|
struct gsm48_hdr *gh;
|
|
|
|
gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
|
|
/* Outgoing needs the highest bit set */
|
|
gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
|
|
gh->msg_type = msg_type;
|
|
|
|
DEBUGP(DLSMS, "sending CP message (trans=%x)\n", trans->transaction_id);
|
|
|
|
return gsm411_sendmsg(trans->conn, msg);
|
|
}
|
|
|
|
/* mm_send: receive MMCCSMS sap message from SMC */
|
|
static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type,
|
|
struct msgb *msg, int cp_msg_type)
|
|
{
|
|
struct gsm_trans *trans =
|
|
container_of(inst, struct gsm_trans, sms.smc_inst);
|
|
int rc = 0;
|
|
|
|
switch (msg_type) {
|
|
case GSM411_MMSMS_EST_REQ:
|
|
/* recycle msg */
|
|
rc = gsm411_smc_recv(inst, GSM411_MMSMS_EST_CNF, msg, 0);
|
|
msgb_free(msg); /* upper layer does not free msg */
|
|
break;
|
|
case GSM411_MMSMS_DATA_REQ:
|
|
rc = gsm411_cp_sendmsg(msg, trans, cp_msg_type);
|
|
break;
|
|
case GSM411_MMSMS_REL_REQ:
|
|
DEBUGP(DLSMS, "Got MMSMS_REL_REQ, destroying transaction.\n");
|
|
msgb_free(msg);
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Unhandled MMCCSMS msg 0x%x\n", msg_type);
|
|
msgb_free(msg);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* mm_send: receive MNCCSMS sap message from SMR */
|
|
int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type,
|
|
struct msgb *msg)
|
|
{
|
|
struct gsm_trans *trans =
|
|
container_of(inst, struct gsm_trans, sms.smr_inst);
|
|
|
|
/* forward to SMC */
|
|
return gsm411_smc_send(&trans->sms.smc_inst, msg_type, msg);
|
|
}
|
|
|
|
static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms)
|
|
{
|
|
if (db_sms_store(gsms) != 0) {
|
|
LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
|
|
return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
|
|
}
|
|
/* dispatch a signal to tell higher level about it */
|
|
send_signal(S_SMS_SUBMITTED, NULL, gsms, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
|
|
static int gsm340_gen_oa_sub(uint8_t *oa, unsigned int oa_len,
|
|
const struct gsm_sms_addr *src)
|
|
{
|
|
/* network specific, private numbering plan */
|
|
return gsm340_gen_oa(oa, oa_len, src->ton, src->npi, src->addr);
|
|
}
|
|
|
|
/* generate a msgb containing a TPDU derived from struct gsm_sms,
|
|
* returns total size of TPDU */
|
|
static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms)
|
|
{
|
|
uint8_t *smsp;
|
|
uint8_t oa[12]; /* max len per 03.40 */
|
|
uint8_t oa_len = 0;
|
|
uint8_t octet_len;
|
|
unsigned int old_msg_len = msg->len;
|
|
|
|
/* generate first octet with masked bits */
|
|
smsp = msgb_put(msg, 1);
|
|
/* TP-MTI (message type indicator) */
|
|
*smsp = GSM340_SMS_DELIVER_SC2MS;
|
|
/* TP-MMS (more messages to send) */
|
|
if (0 /* FIXME */)
|
|
*smsp |= 0x04;
|
|
/* TP-SRI(deliver)/SRR(submit) */
|
|
if (sms->status_rep_req)
|
|
*smsp |= 0x20;
|
|
/* TP-UDHI (indicating TP-UD contains a header) */
|
|
if (sms->ud_hdr_ind)
|
|
*smsp |= 0x40;
|
|
|
|
/* generate originator address */
|
|
oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src);
|
|
smsp = msgb_put(msg, oa_len);
|
|
memcpy(smsp, oa, oa_len);
|
|
|
|
/* generate TP-PID */
|
|
smsp = msgb_put(msg, 1);
|
|
*smsp = sms->protocol_id;
|
|
|
|
/* generate TP-DCS */
|
|
smsp = msgb_put(msg, 1);
|
|
*smsp = sms->data_coding_scheme;
|
|
|
|
/* generate TP-SCTS */
|
|
smsp = msgb_put(msg, 7);
|
|
gsm340_gen_scts(smsp, time(NULL));
|
|
|
|
/* generate TP-UDL */
|
|
smsp = msgb_put(msg, 1);
|
|
*smsp = sms->user_data_len;
|
|
|
|
/* generate TP-UD */
|
|
switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
|
|
case DCS_7BIT_DEFAULT:
|
|
octet_len = sms->user_data_len*7/8;
|
|
if (sms->user_data_len*7%8 != 0)
|
|
octet_len++;
|
|
/* Warning, user_data_len indicates the amount of septets
|
|
* (characters), we need amount of octets occupied */
|
|
smsp = msgb_put(msg, octet_len);
|
|
memcpy(smsp, sms->user_data, octet_len);
|
|
break;
|
|
case DCS_UCS2:
|
|
case DCS_8BIT_DATA:
|
|
smsp = msgb_put(msg, sms->user_data_len);
|
|
memcpy(smsp, sms->user_data, sms->user_data_len);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
|
|
sms->data_coding_scheme);
|
|
break;
|
|
}
|
|
|
|
return msg->len - old_msg_len;
|
|
}
|
|
|
|
/* process an incoming TPDU (called from RP-DATA)
|
|
* return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
|
|
static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg)
|
|
{
|
|
uint8_t *smsp = msgb_sms(msg);
|
|
struct gsm_sms *gsms;
|
|
unsigned int sms_alphabet;
|
|
uint8_t sms_mti, sms_mms, sms_vpf, sms_rp;
|
|
uint8_t *sms_vp;
|
|
uint8_t da_len_bytes;
|
|
uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
|
|
int rc = 0;
|
|
|
|
osmo_counter_inc(conn->bts->network->stats.sms.submitted);
|
|
|
|
gsms = sms_alloc();
|
|
if (!gsms)
|
|
return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
|
|
|
|
/* invert those fields where 0 means active/present */
|
|
sms_mti = *smsp & 0x03;
|
|
sms_mms = !!(*smsp & 0x04);
|
|
sms_vpf = (*smsp & 0x18) >> 3;
|
|
gsms->status_rep_req = (*smsp & 0x20);
|
|
gsms->ud_hdr_ind = (*smsp & 0x40);
|
|
sms_rp = (*smsp & 0x80);
|
|
|
|
smsp++;
|
|
gsms->msg_ref = *smsp++;
|
|
|
|
/* length in bytes of the destination address */
|
|
da_len_bytes = 2 + *smsp/2 + *smsp%2;
|
|
if (da_len_bytes > 12) {
|
|
LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
|
|
rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
|
|
goto out;
|
|
} else if (da_len_bytes < 4) {
|
|
LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n");
|
|
rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
|
|
goto out;
|
|
}
|
|
memset(address_lv, 0, sizeof(address_lv));
|
|
memcpy(address_lv, smsp, da_len_bytes);
|
|
/* mangle first byte to reflect length in bytes, not digits */
|
|
address_lv[0] = da_len_bytes - 1;
|
|
|
|
gsms->dst.ton = (address_lv[1] >> 4) & 7;
|
|
gsms->dst.npi = address_lv[1] & 0xF;
|
|
/* convert to real number */
|
|
gsm48_decode_bcd_number(gsms->dst.addr,
|
|
sizeof(gsms->dst.addr), address_lv, 1);
|
|
smsp += da_len_bytes;
|
|
|
|
gsms->protocol_id = *smsp++;
|
|
gsms->data_coding_scheme = *smsp++;
|
|
|
|
sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
|
|
if (sms_alphabet == 0xffffffff) {
|
|
sms_free(gsms);
|
|
return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
|
|
}
|
|
|
|
switch (sms_vpf) {
|
|
case GSM340_TP_VPF_RELATIVE:
|
|
sms_vp = smsp++;
|
|
break;
|
|
case GSM340_TP_VPF_ABSOLUTE:
|
|
case GSM340_TP_VPF_ENHANCED:
|
|
sms_vp = smsp;
|
|
/* the additional functionality indicator... */
|
|
if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
|
|
smsp++;
|
|
smsp += 7;
|
|
break;
|
|
case GSM340_TP_VPF_NONE:
|
|
sms_vp = 0;
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE,
|
|
"SMS Validity period not implemented: 0x%02x\n", sms_vpf);
|
|
return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
|
|
}
|
|
gsms->user_data_len = *smsp++;
|
|
if (gsms->user_data_len) {
|
|
memcpy(gsms->user_data, smsp, gsms->user_data_len);
|
|
|
|
switch (sms_alphabet) {
|
|
case DCS_7BIT_DEFAULT:
|
|
gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len);
|
|
break;
|
|
case DCS_8BIT_DATA:
|
|
case DCS_UCS2:
|
|
case DCS_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
gsms->sender = subscr_get(conn->subscr);
|
|
|
|
LOGP(DLSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
|
|
"MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
|
|
"UserDataLength: 0x%02x, UserData: \"%s\"\n",
|
|
subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref,
|
|
gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr,
|
|
gsms->user_data_len,
|
|
sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
|
|
osmo_hexdump(gsms->user_data, gsms->user_data_len));
|
|
|
|
gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
|
|
|
|
/* FIXME: This looks very wrong */
|
|
send_signal(0, NULL, gsms, 0);
|
|
|
|
/* determine gsms->receiver based on dialled number */
|
|
gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dst.addr);
|
|
if (!gsms->receiver) {
|
|
#ifdef BUILD_SMPP
|
|
rc = smpp_try_deliver(gsms);
|
|
if (rc == 1) {
|
|
rc = 1; /* cause 1: unknown subscriber */
|
|
osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
|
|
} else if (rc < 0) {
|
|
rc = 21; /* cause 21: short message transfer rejected */
|
|
/* FIXME: handle the error somehow? */
|
|
}
|
|
#else
|
|
rc = 1; /* cause 1: unknown subscriber */
|
|
osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
|
|
#endif
|
|
goto out;
|
|
}
|
|
|
|
switch (sms_mti) {
|
|
case GSM340_SMS_SUBMIT_MS2SC:
|
|
/* MS is submitting a SMS */
|
|
rc = gsm340_rx_sms_submit(msg, gsms);
|
|
break;
|
|
case GSM340_SMS_COMMAND_MS2SC:
|
|
case GSM340_SMS_DELIVER_REP_MS2SC:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
|
|
rc = GSM411_RP_CAUSE_IE_NOTEXIST;
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
|
|
rc = GSM411_RP_CAUSE_IE_NOTEXIST;
|
|
break;
|
|
}
|
|
|
|
if (!rc && !gsms->receiver)
|
|
rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
|
|
|
|
out:
|
|
sms_free(gsms);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Prefix msg with a RP-DATA header and send as SMR 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 rl_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 gsm411_smr_send(inst, rl_msg_type, msg);
|
|
}
|
|
|
|
static int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref)
|
|
{
|
|
struct msgb *msg = gsm411_msgb_alloc();
|
|
|
|
DEBUGP(DLSMS, "TX: SMS RP ACK\n");
|
|
|
|
return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, GSM411_MT_RP_ACK_MT,
|
|
msg_ref, GSM411_SM_RL_REPORT_REQ);
|
|
}
|
|
|
|
static int gsm411_send_rp_error(struct gsm_trans *trans,
|
|
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(&trans->sms.smr_inst, msg,
|
|
GSM411_MT_RP_ERROR_MT, msg_ref, GSM411_SM_RL_REPORT_REQ);
|
|
}
|
|
|
|
/* Receive a 04.11 TPDU inside RP-DATA / user data */
|
|
static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
|
|
struct gsm411_rp_hdr *rph,
|
|
uint8_t src_len, uint8_t *src,
|
|
uint8_t dst_len, uint8_t *dst,
|
|
uint8_t tpdu_len, uint8_t *tpdu)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (src_len && src)
|
|
LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
|
|
|
|
if (!dst_len || !dst || !tpdu_len || !tpdu) {
|
|
LOGP(DLSMS, LOGL_ERROR,
|
|
"RP-DATA (MO) without DST or TPDU ?!?\n");
|
|
gsm411_send_rp_error(trans, rph->msg_ref,
|
|
GSM411_RP_CAUSE_INV_MAND_INF);
|
|
return -EIO;
|
|
}
|
|
msg->l4h = tpdu;
|
|
|
|
DEBUGP(DLSMS, "DST(%u,%s)\n", dst_len, osmo_hexdump(dst, dst_len));
|
|
|
|
rc = gsm340_rx_tpdu(trans->conn, msg);
|
|
if (rc == 0)
|
|
return gsm411_send_rp_ack(trans, rph->msg_ref);
|
|
else if (rc > 0)
|
|
return gsm411_send_rp_error(trans, rph->msg_ref, rc);
|
|
else
|
|
return rc;
|
|
}
|
|
|
|
/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
|
|
static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
|
|
struct gsm411_rp_hdr *rph)
|
|
{
|
|
uint8_t src_len, dst_len, rpud_len;
|
|
uint8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
|
|
|
|
/* in the MO case, this should always be zero length */
|
|
src_len = rph->data[0];
|
|
if (src_len)
|
|
src = &rph->data[1];
|
|
|
|
dst_len = rph->data[1+src_len];
|
|
if (dst_len)
|
|
dst = &rph->data[1+src_len+1];
|
|
|
|
rpud_len = rph->data[1+src_len+1+dst_len];
|
|
if (rpud_len)
|
|
rp_ud = &rph->data[1+src_len+1+dst_len+1];
|
|
|
|
DEBUGP(DLSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
|
|
src_len, dst_len, rpud_len);
|
|
return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
|
|
rpud_len, rp_ud);
|
|
}
|
|
|
|
/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
|
|
static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
|
|
struct gsm411_rp_hdr *rph)
|
|
{
|
|
struct gsm_sms *sms = trans->sms.sms;
|
|
|
|
/* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
|
|
* successfully received a SMS. We can now safely mark it as
|
|
* transmitted */
|
|
|
|
if (!sms) {
|
|
LOGP(DLSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
|
|
return gsm411_send_rp_error(trans, rph->msg_ref,
|
|
GSM411_RP_CAUSE_PROTOCOL_ERR);
|
|
}
|
|
|
|
/* mark this SMS as sent in database */
|
|
db_sms_mark_sent(sms);
|
|
|
|
send_signal(S_SMS_DELIVERED, trans, sms, 0);
|
|
|
|
sms_free(sms);
|
|
trans->sms.sms = NULL;
|
|
|
|
/* check for more messages for this subscriber */
|
|
sms = db_sms_get_unsent_for_subscr(trans->subscr);
|
|
if (sms)
|
|
gsm411_send_sms(trans->conn, sms);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
|
|
struct gsm411_rp_hdr *rph)
|
|
{
|
|
struct gsm_network *net = trans->conn->bts->network;
|
|
struct gsm_sms *sms = trans->sms.sms;
|
|
uint8_t cause_len = rph->data[0];
|
|
uint8_t cause = rph->data[1];
|
|
|
|
/* Error in response to MT RP_DATA, i.e. the MS did not
|
|
* successfully receive the SMS. We need to investigate
|
|
* the cause and take action depending on it */
|
|
|
|
LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
|
|
subscr_name(trans->conn->subscr), cause_len, cause,
|
|
get_value_string(gsm411_rp_cause_strs, cause));
|
|
|
|
if (!sms) {
|
|
LOGP(DLSMS, LOGL_ERROR,
|
|
"RX RP-ERR, but no sms in transaction?!?\n");
|
|
return -EINVAL;
|
|
#if 0
|
|
return gsm411_send_rp_error(trans, rph->msg_ref,
|
|
GSM411_RP_CAUSE_PROTOCOL_ERR);
|
|
#endif
|
|
}
|
|
|
|
if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
|
|
/* MS has not enough memory to store the message. We need
|
|
* to store this in our database and wait for a SMMA message */
|
|
/* FIXME */
|
|
send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0);
|
|
osmo_counter_inc(net->stats.sms.rp_err_mem);
|
|
} else {
|
|
send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
|
|
osmo_counter_inc(net->stats.sms.rp_err_other);
|
|
}
|
|
|
|
sms_free(sms);
|
|
trans->sms.sms = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
|
|
struct gsm411_rp_hdr *rph)
|
|
{
|
|
struct gsm_sms *sms;
|
|
int rc;
|
|
|
|
rc = gsm411_send_rp_ack(trans, rph->msg_ref);
|
|
|
|
/* MS tells us that it has memory for more SMS, we need
|
|
* to check if we have any pending messages for it and then
|
|
* transfer those */
|
|
send_signal(S_SMS_SMMA, trans, NULL, 0);
|
|
|
|
/* check for more messages for this subscriber */
|
|
sms = db_sms_get_unsent_for_subscr(trans->subscr);
|
|
if (sms)
|
|
gsm411_send_sms(trans->conn, sms);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* receive RL DATA */
|
|
static int gsm411_rx_rl_data(struct msgb *msg, struct gsm48_hdr *gh,
|
|
struct gsm_trans *trans)
|
|
{
|
|
struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
|
|
uint8_t msg_type = rp_data->msg_type & 0x07;
|
|
int rc = 0;
|
|
|
|
switch (msg_type) {
|
|
case GSM411_MT_RP_DATA_MO:
|
|
DEBUGP(DLSMS, "RX SMS RP-DATA (MO)\n");
|
|
rc = gsm411_rx_rp_data(msg, trans, rp_data);
|
|
break;
|
|
case GSM411_MT_RP_SMMA_MO:
|
|
DEBUGP(DLSMS, "RX SMS RP-SMMA\n");
|
|
rc = gsm411_rx_rp_smma(msg, trans, rp_data);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* receive RL REPORT */
|
|
static int gsm411_rx_rl_report(struct msgb *msg, struct gsm48_hdr *gh,
|
|
struct gsm_trans *trans)
|
|
{
|
|
struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
|
|
uint8_t msg_type = rp_data->msg_type & 0x07;
|
|
int rc = 0;
|
|
|
|
switch (msg_type) {
|
|
case GSM411_MT_RP_ACK_MO:
|
|
DEBUGP(DLSMS, "RX SMS RP-ACK (MO)\n");
|
|
rc = gsm411_rx_rp_ack(msg, trans, rp_data);
|
|
break;
|
|
case GSM411_MT_RP_ERROR_MO:
|
|
DEBUGP(DLSMS, "RX SMS RP-ERROR (MO)\n");
|
|
rc = gsm411_rx_rp_error(msg, trans, rp_data);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* receive SM-RL sap message from SMR
|
|
* NOTE: Message is freed by sender
|
|
*/
|
|
int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type,
|
|
struct msgb *msg)
|
|
{
|
|
struct gsm_trans *trans =
|
|
container_of(inst, struct gsm_trans, sms.smr_inst);
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
int rc = 0;
|
|
|
|
switch (msg_type) {
|
|
case GSM411_SM_RL_DATA_IND:
|
|
rc = gsm411_rx_rl_data(msg, gh, trans);
|
|
break;
|
|
case GSM411_SM_RL_REPORT_IND:
|
|
if (gh)
|
|
rc = gsm411_rx_rl_report(msg, gh, trans);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Unhandled SM-RL message 0x%x\n", msg_type);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* receive MNCCSMS sap message from SMC
|
|
* NOTE: Message is freed by sender
|
|
*/
|
|
static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type,
|
|
struct msgb *msg)
|
|
{
|
|
struct gsm_trans *trans =
|
|
container_of(inst, struct gsm_trans, sms.smc_inst);
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
int rc = 0;
|
|
|
|
switch (msg_type) {
|
|
case GSM411_MNSMS_EST_IND:
|
|
case GSM411_MNSMS_DATA_IND:
|
|
DEBUGP(DLSMS, "MNSMS-DATA/EST-IND\n");
|
|
rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
|
|
break;
|
|
case GSM411_MNSMS_ERROR_IND:
|
|
if (gh)
|
|
DEBUGP(DLSMS, "MNSMS-ERROR-IND, cause %d (%s)\n",
|
|
gh->data[0],
|
|
get_value_string(gsm411_cp_cause_strs,
|
|
gh->data[0]));
|
|
else
|
|
DEBUGP(DLSMS, "MNSMS-ERROR-IND, no cause\n");
|
|
rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_NOTICE, "Unhandled MNCCSMS msg 0x%x\n", msg_type);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
|
|
int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn,
|
|
struct msgb *msg)
|
|
{
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
uint8_t msg_type = gh->msg_type;
|
|
uint8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */
|
|
struct gsm_trans *trans;
|
|
int new_trans = 0;
|
|
int rc = 0;
|
|
|
|
if (!conn->subscr)
|
|
return -EIO;
|
|
/* FIXME: send some error message */
|
|
|
|
DEBUGP(DLSMS, "receiving data (trans_id=%x)\n", transaction_id);
|
|
trans = trans_find_by_id(conn->subscr, GSM48_PDISC_SMS,
|
|
transaction_id);
|
|
|
|
/*
|
|
* A transaction we created but don't know about?
|
|
*/
|
|
if (!trans && (transaction_id & 0x8) == 0) {
|
|
LOGP(DLSMS, LOGL_ERROR, "trans_id=%x allocated by us but known "
|
|
"to us anymore. We are ignoring it, maybe a CP-ERROR "
|
|
"from a MS?\n",
|
|
transaction_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!trans) {
|
|
DEBUGP(DLSMS, " -> (new transaction)\n");
|
|
trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
|
|
transaction_id, new_callref++);
|
|
if (!trans) {
|
|
DEBUGP(DLSMS, " -> No memory for trans\n");
|
|
/* FIXME: send some error message */
|
|
return -ENOMEM;
|
|
}
|
|
gsm411_smc_init(&trans->sms.smc_inst, 0, 1,
|
|
gsm411_mn_recv, gsm411_mm_send);
|
|
gsm411_smr_init(&trans->sms.smr_inst, 0, 1,
|
|
gsm411_rl_recv, gsm411_mn_send);
|
|
|
|
trans->conn = conn;
|
|
|
|
new_trans = 1;
|
|
}
|
|
|
|
/* 5.4: For MO, if a CP-DATA is received for a new
|
|
* transaction, equals reception of an implicit
|
|
* last CP-ACK for previous transaction */
|
|
if (trans->sms.smc_inst.cp_state == GSM411_CPS_IDLE
|
|
&& msg_type == GSM411_MT_CP_DATA) {
|
|
int i;
|
|
struct gsm_trans *ptrans;
|
|
|
|
/* Scan through all remote initiated transactions */
|
|
for (i=8; i<15; i++) {
|
|
if (i == transaction_id)
|
|
continue;
|
|
|
|
ptrans = trans_find_by_id(conn->subscr,
|
|
GSM48_PDISC_SMS, i);
|
|
if (!ptrans)
|
|
continue;
|
|
|
|
DEBUGP(DLSMS, "Implicit CP-ACK for trans_id=%x\n", i);
|
|
|
|
/* Finish it for good */
|
|
trans_free(ptrans);
|
|
}
|
|
}
|
|
|
|
gsm411_smc_recv(&trans->sms.smc_inst,
|
|
(new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
|
|
msg, msg_type);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Take a SMS in gsm_sms structure and send it through an already
|
|
* existing lchan. We also assume that the caller ensured this lchan already
|
|
* has a SAPI3 RLL connection! */
|
|
int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
|
|
{
|
|
struct msgb *msg = gsm411_msgb_alloc();
|
|
struct gsm_trans *trans;
|
|
uint8_t *data, *rp_ud_len;
|
|
uint8_t msg_ref = 42;
|
|
int transaction_id;
|
|
int rc;
|
|
|
|
transaction_id =
|
|
trans_assign_trans_id(conn->subscr, GSM48_PDISC_SMS, 0);
|
|
if (transaction_id == -1) {
|
|
LOGP(DLSMS, LOGL_ERROR, "No available transaction ids\n");
|
|
send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
|
|
sms_free(sms);
|
|
return -EBUSY;
|
|
}
|
|
|
|
DEBUGP(DLSMS, "send_sms_lchan()\n");
|
|
|
|
/* FIXME: allocate transaction with message reference */
|
|
trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
|
|
transaction_id, new_callref++);
|
|
if (!trans) {
|
|
LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n");
|
|
send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
|
|
sms_free(sms);
|
|
/* FIXME: send some error message */
|
|
return -ENOMEM;
|
|
}
|
|
gsm411_smc_init(&trans->sms.smc_inst, sms->id, 1,
|
|
gsm411_mn_recv, gsm411_mm_send);
|
|
gsm411_smr_init(&trans->sms.smr_inst, sms->id, 1,
|
|
gsm411_rl_recv, gsm411_mn_send);
|
|
trans->sms.sms = sms;
|
|
|
|
trans->conn = conn;
|
|
|
|
/* Hardcode SMSC Originating Address for now */
|
|
data = (uint8_t *)msgb_put(msg, 8);
|
|
data[0] = 0x07; /* originator length == 7 */
|
|
data[1] = 0x91; /* type of number: international, ISDN */
|
|
data[2] = 0x44; /* 447785016005 */
|
|
data[3] = 0x77;
|
|
data[4] = 0x58;
|
|
data[5] = 0x10;
|
|
data[6] = 0x06;
|
|
data[7] = 0x50;
|
|
|
|
/* Hardcoded Destination Address */
|
|
data = (uint8_t *)msgb_put(msg, 1);
|
|
data[0] = 0; /* destination length == 0 */
|
|
|
|
/* obtain a pointer for the rp_ud_len, so we can fill it later */
|
|
rp_ud_len = (uint8_t *)msgb_put(msg, 1);
|
|
|
|
/* generate the 03.40 TPDU */
|
|
rc = gsm340_gen_tpdu(msg, sms);
|
|
if (rc < 0) {
|
|
send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
|
|
sms_free(sms);
|
|
trans->sms.sms = NULL;
|
|
trans_free(trans);
|
|
msgb_free(msg);
|
|
return rc;
|
|
}
|
|
|
|
*rp_ud_len = rc;
|
|
|
|
DEBUGP(DLSMS, "TX: SMS DELIVER\n");
|
|
|
|
osmo_counter_inc(conn->bts->network->stats.sms.delivered);
|
|
db_sms_inc_deliver_attempts(trans->sms.sms);
|
|
|
|
return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg,
|
|
GSM411_MT_RP_DATA_MT, msg_ref, GSM411_SM_RL_DATA_REQ);
|
|
}
|
|
|
|
/* paging callback. Here we get called if paging a subscriber has
|
|
* succeeded or failed. */
|
|
static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
|
|
struct msgb *msg, void *_conn, void *_sms)
|
|
{
|
|
struct gsm_subscriber_connection *conn = _conn;
|
|
struct gsm_sms *sms = _sms;
|
|
int rc = 0;
|
|
|
|
DEBUGP(DLSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
|
|
"conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id);
|
|
|
|
if (hooknum != GSM_HOOK_RR_PAGING)
|
|
return -EINVAL;
|
|
|
|
switch (event) {
|
|
case GSM_PAGING_SUCCEEDED:
|
|
gsm411_send_sms(conn, sms);
|
|
break;
|
|
case GSM_PAGING_EXPIRED:
|
|
case GSM_PAGING_OOM:
|
|
case GSM_PAGING_BUSY:
|
|
send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
|
|
sms_free(sms);
|
|
rc = -ETIMEDOUT;
|
|
break;
|
|
default:
|
|
LOGP(DLSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* high-level function to send a SMS to a given subscriber. The function
|
|
* will take care of paging the subscriber, establishing the RLL SAPI3
|
|
* connection, etc. */
|
|
int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
|
|
struct gsm_sms *sms)
|
|
{
|
|
struct gsm_subscriber_connection *conn;
|
|
|
|
/* check if we already have an open lchan to the subscriber.
|
|
* if yes, send the SMS this way */
|
|
conn = connection_for_subscr(subscr);
|
|
if (conn) {
|
|
return gsm411_send_sms(conn, sms);
|
|
}
|
|
|
|
/* if not, we have to start paging */
|
|
subscr_get_channel(subscr, RSL_CHANNEED_SDCCH, paging_cb_send_sms, sms);
|
|
return 0;
|
|
}
|
|
|
|
void _gsm411_sms_trans_free(struct gsm_trans *trans)
|
|
{
|
|
/* cleanup SMS instance */
|
|
gsm411_smr_clear(&trans->sms.smr_inst);
|
|
trans->sms.smr_inst.rl_recv = NULL;
|
|
trans->sms.smr_inst.mn_send = NULL;
|
|
|
|
gsm411_smc_clear(&trans->sms.smc_inst);
|
|
trans->sms.smc_inst.mn_recv = NULL;
|
|
trans->sms.smc_inst.mm_send = NULL;
|
|
|
|
if (trans->sms.sms) {
|
|
LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n");
|
|
send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0);
|
|
sms_free(trans->sms.sms);
|
|
trans->sms.sms = NULL;
|
|
}
|
|
}
|
|
|
|
void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
|
|
{
|
|
struct gsm_network *net;
|
|
struct gsm_trans *trans, *tmp;
|
|
|
|
net = conn->bts->network;
|
|
|
|
llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry)
|
|
if (trans->conn == conn) {
|
|
struct gsm_sms *sms = trans->sms.sms;
|
|
if (!sms) {
|
|
LOGP(DLSMS, LOGL_ERROR, "SAPI Reject but no "
|
|
"SMS.\n");
|
|
continue;
|
|
}
|
|
|
|
send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
|
|
sms_free(sms);
|
|
trans->sms.sms = NULL;
|
|
trans_free(trans);
|
|
}
|
|
}
|
|
|