From fb7be589e6f2a7e8dcbd560a0b0fdbda7d1fd316 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Mon, 31 Oct 2011 18:14:03 +0100 Subject: host/mobile/sms: Adding SMS support for osmocomBB/mobile Both MO and MT SMS are supported. Transmission an reception can be controlled via VTY: en sms 1 All received SMS are stored in "~/.osmocom/bb/sms.txt". SMS transmission is performed on SAPI 3 datalink, using DCCH or ACCH. Written-by: Andreas Eversberg Signed-off-by: Sylvain Munaut --- .../layer23/include/osmocom/bb/mobile/Makefile.am | 5 +- .../layer23/include/osmocom/bb/mobile/gsm411_sms.h | 33 + .../layer23/include/osmocom/bb/mobile/settings.h | 3 + .../include/osmocom/bb/mobile/transaction.h | 12 +- src/host/layer23/src/mobile/Makefile.am | 2 +- src/host/layer23/src/mobile/app_mobile.c | 3 + src/host/layer23/src/mobile/gsm411_sms.c | 940 +++++++++++++++++++++ src/host/layer23/src/mobile/gsm48_mm.c | 5 +- src/host/layer23/src/mobile/main.c | 8 +- src/host/layer23/src/mobile/support.c | 2 +- src/host/layer23/src/mobile/transaction.c | 3 +- src/host/layer23/src/mobile/vty_interface.c | 85 ++ 12 files changed, 1085 insertions(+), 16 deletions(-) create mode 100644 src/host/layer23/include/osmocom/bb/mobile/gsm411_sms.h create mode 100644 src/host/layer23/src/mobile/gsm411_sms.c (limited to 'src') diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am index 65b7ce76..7f49d5e9 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am @@ -1,2 +1,3 @@ -noinst_HEADERS = gsm322.h gsm48_cc.h gsm48_mm.h gsm48_rr.h mncc.h settings.h \ - subscriber.h support.h transaction.h vty.h mncc_sock.h +noinst_HEADERS = gsm322.h gsm411_sms.h gsm48_cc.h gsm48_mm.h gsm48_rr.h mncc.h \ + settings.h subscriber.h support.h transaction.h vty.h \ + mncc_sock.h diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm411_sms.h b/src/host/layer23/include/osmocom/bb/mobile/gsm411_sms.h new file mode 100644 index 00000000..61019de6 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm411_sms.h @@ -0,0 +1,33 @@ +#ifndef _GSM411_SMS_H +#define _GSM411_SMS_H + +#define SMS_HDR_SIZE 128 +#define SMS_TEXT_SIZE 256 + +struct gsm_sms { + unsigned long validity_minutes; + uint8_t reply_path_req; + uint8_t status_rep_req; + uint8_t ud_hdr_ind; + uint8_t protocol_id; + uint8_t data_coding_scheme; + uint8_t msg_ref; + char address[20+1]; /* DA LV is 12 bytes max, i.e. 10 bytes + * BCD == 20 bytes string */ + time_t time; + uint8_t user_data_len; + uint8_t user_data[SMS_TEXT_SIZE]; + + char text[SMS_TEXT_SIZE]; +}; + +int gsm411_sms_init(struct osmocom_ms *ms); +int gsm411_sms_exit(struct osmocom_ms *ms); +struct gsm_sms *sms_alloc(void); +void sms_free(struct gsm_sms *sms); +struct gsm_sms *sms_from_text(const char *receiver, int dcs, const char *text); +int gsm411_tx_sms_submit(struct osmocom_ms *ms, struct gsm_sms *sms); +int gsm411_rcv_sms(struct osmocom_ms *ms, struct msgb *msg); +int sms_send(struct osmocom_ms *ms, const char *number, const char *text); + +#endif /* _GSM411_SMS_H */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/settings.h b/src/host/layer23/include/osmocom/bb/mobile/settings.h index cd1b8001..8442f038 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/settings.h +++ b/src/host/layer23/include/osmocom/bb/mobile/settings.h @@ -23,6 +23,9 @@ struct gsm_settings { int sim_type; /* selects card on power on */ char emergency_imsi[16]; + /* SMS */ + char sms_sca[12]; + /* test card simulator settings */ char test_imsi[16]; uint32_t test_tmsi; diff --git a/src/host/layer23/include/osmocom/bb/mobile/transaction.h b/src/host/layer23/include/osmocom/bb/mobile/transaction.h index aa62f465..b0695ecb 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/transaction.h +++ b/src/host/layer23/include/osmocom/bb/mobile/transaction.h @@ -2,6 +2,8 @@ #define _TRANSACT_H #include +#include +#include /* One transaction */ struct gsm_trans { @@ -38,18 +40,14 @@ struct gsm_trans { struct osmo_timer_list timer; struct gsm_mncc msg; /* stores setup/disconnect/release message */ } cc; -#if 0 struct { - uint8_t link_id; /* RSL Link ID to be used for this trans */ - int is_mt; /* is this a MO (0) or MT (1) transfer */ - enum gsm411_cp_state cp_state; - struct osmo_timer_list cp_timer; + uint8_t sapi; /* SAPI to be used for this trans */ - enum gsm411_rp_state rp_state; + struct gsm411_smc_inst smc_inst; + struct gsm411_smr_inst smr_inst; struct gsm_sms *sms; } sms; -#endif }; }; diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 4cff9efd..4b2059f8 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -3,7 +3,7 @@ AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) noinst_LIBRARIES = libmobile.a -libmobile_a_SOURCES = gsm322.c gsm48_cc.c gsm48_mm.c gsm48_rr.c \ +libmobile_a_SOURCES = gsm322.c gsm411_sms.c gsm48_cc.c gsm48_mm.c gsm48_rr.c \ mnccms.c settings.c subscriber.c support.c \ transaction.c vty_interface.c voice.c mncc_sock.c diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index eeb76a82..c5fe9443 100644 --- a/src/host/layer23/src/mobile/app_mobile.c +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,7 @@ int mobile_exit(struct osmocom_ms *ms, int force) gsm48_rr_exit(ms); gsm_subscr_exit(ms); gsm48_cc_exit(ms); + gsm411_sms_exit(ms); gsm_sim_exit(ms); lapdm_channel_exit(&ms->lapdm_channel); @@ -167,6 +169,7 @@ int mobile_init(struct osmocom_ms *ms) gsm_sim_init(ms); gsm48_cc_init(ms); + gsm411_sms_init(ms); gsm_voice_init(ms); gsm_subscr_init(ms); gsm48_rr_init(ms); diff --git a/src/host/layer23/src/mobile/gsm411_sms.c b/src/host/layer23/src/mobile/gsm411_sms.c new file mode 100644 index 00000000..a08984d1 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm411_sms.c @@ -0,0 +1,940 @@ +/* + * Code based on work of: + * (C) 2008 by Daniel Willmann + * (C) 2009 by Harald Welte + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * (C) 2011 by Andreas Eversberg + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UM_SAPI_SMS 3 + +extern void *l23_ctx; +static uint32_t new_callref = 0x40000001; + +static int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg); +static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg); +static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg, int cp_msg_type); +static int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg); +/* + * init / exit + */ + +int gsm411_sms_init(struct osmocom_ms *ms) +{ + LOGP(DLSMS, LOGL_INFO, "init SMS\n"); + + return 0; +} + +int gsm411_sms_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DLSMS, LOGL_INFO, "exit SMS processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_SMS) { + LOGP(DLSMS, LOGL_NOTICE, "Free pendig " + "SMS-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + +/* + * SMS content + */ + +struct gsm_sms *sms_alloc(void) +{ + return talloc_zero(l23_ctx, struct gsm_sms); +} + +void sms_free(struct gsm_sms *sms) +{ + talloc_free(sms); +} + +struct gsm_sms *sms_from_text(const char *receiver, int dcs, const char *text) +{ + struct gsm_sms *sms = sms_alloc(); + + if (!sms) + return NULL; + + strncpy(sms->text, text, sizeof(sms->text)-1); + + /* FIXME: don't use ID 1 static */ + 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->address, receiver, sizeof(sms->address)-1); + /* Generate user_data */ + sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text); + + return sms; +} + +static int gsm411_sms_report(struct osmocom_ms *ms, struct gsm_sms *sms, + uint8_t cause) +{ + vty_notify(ms, NULL); + if (!cause) + vty_notify(ms, "SMS to %s successfull\n", sms->address); + else + vty_notify(ms, "SMS to %s failed: %s\n", sms->address, + get_value_string(gsm411_rp_cause_strs, cause)); + + return 0; +} +/* + * transaction + */ + +/* SMS Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm411_sms_trans_free(struct gsm_trans *trans) +{ + gsm411_smr_clear(&trans->sms.smr_inst); + gsm411_smc_clear(&trans->sms.smc_inst); + + if (trans->sms.sms) { + LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n"); + gsm411_sms_report(trans->ms, trans->sms.sms, + GSM411_RP_CAUSE_MO_SMS_REJECTED); + sms_free(trans->sms.sms); + trans->sms.sms = NULL; + } +} + +/* release MM connection, free transaction */ +static int gsm411_trans_free(struct gsm_trans *trans) +{ + struct msgb *nmsg; + + /* release MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_REQ, trans->callref, + trans->transaction_id, trans->sms.sapi); + if (!nmsg) + return -ENOMEM; + LOGP(DLSMS, LOGL_INFO, "Sending MMSMS_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* + * receive SMS + */ + +/* now here comes our SMS */ +static int gsm340_rx_sms_deliver(struct osmocom_ms *ms, struct msgb *msg, + struct gsm_sms *gsms) +{ + const char osmocomsms[] = ".osmocom/bb/sms.txt"; + int len; + const char *home; + char *sms_file; + char vty_text[sizeof(gsms->text)], *p; + FILE *fp; + + /* remove linefeeds and show at VTY */ + strcpy(vty_text, gsms->text); + for (p = vty_text; *p; p++) { + if (*p == '\n' || *p == '\r') + *p = ' '; + } + vty_notify(ms, NULL); + vty_notify(ms, "SMS from %s: '%s'\n", gsms->address, vty_text); + + home = getenv("HOME"); + if (!home) { +fail: + fprintf(stderr, "Can't deliver SMS, be sure to create '%s' in " + "your home directory.\n", osmocomsms); + return GSM411_RP_CAUSE_MT_MEM_EXCEEDED; + } + len = strlen(home) + 1 + sizeof(osmocomsms); + sms_file = talloc_size(l23_ctx, len); + if (!sms_file) + goto fail; + snprintf(sms_file, len, "%s/%s", home, osmocomsms); + + fp = fopen(sms_file, "a"); + if (!fp) + goto fail; + fprintf(fp, "[SMS %s]\n%s\n", gsms->address, gsms->text); + fclose(fp); + + talloc_free(sms_file); + + return 0; +} + +/* 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_trans *trans, struct msgb *msg) +{ + uint8_t *smsp = msgb_sms(msg); + struct gsm_sms *gsms; + unsigned int sms_alphabet; + uint8_t sms_mti, sms_mms; + uint8_t oa_len_bytes; + uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ + int rc = 0; + + gsms = sms_alloc(); + + /* invert those fields where 0 means active/present */ + sms_mti = *smsp & 0x03; + sms_mms = !!(*smsp & 0x04); + gsms->status_rep_req = (*smsp & 0x20); + gsms->ud_hdr_ind = (*smsp & 0x40); + gsms->reply_path_req = (*smsp & 0x80); + smsp++; + + /* length in bytes of the originate address */ + oa_len_bytes = 2 + *smsp/2 + *smsp%2; + if (oa_len_bytes > 12) { + LOGP(DLSMS, LOGL_ERROR, "Originate Address > 12 bytes ?!?\n"); + rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; + goto out; + } + memset(address_lv, 0, sizeof(address_lv)); + memcpy(address_lv, smsp, oa_len_bytes); + /* mangle first byte to reflect length in bytes, not digits */ + address_lv[0] = oa_len_bytes - 1; + /* convert to real number */ + if (((smsp[1] & 0x70) >> 4) == 1) + strcpy(gsms->address, "+"); + else if (((smsp[1] & 0x70) >> 4) == 2) + strcpy(gsms->address, "0"); + else + gsms->address[0] = '\0'; + gsm48_decode_bcd_number(gsms->address + strlen(gsms->address), + sizeof(gsms->address) - strlen(gsms->address), address_lv, 1); + smsp += oa_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; + } + + /* get timestamp */ + gsms->time = gsm340_scts(smsp); + smsp += 7; + + /* user data */ + 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; + } + } + + LOGP(DLSMS, LOGL_INFO, "RX SMS: MTI: 0x%02x, " + "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, OA: %s, " + "UserDataLength: 0x%02x, UserData: \"%s\"\n", + sms_mti, gsms->msg_ref, + gsms->protocol_id, gsms->data_coding_scheme, gsms->address, + gsms->user_data_len, + sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text : + osmo_hexdump(gsms->user_data, + gsms->user_data_len)); + + switch (sms_mti) { + case GSM340_SMS_DELIVER_SC2MS: + /* MS is receiving an SMS */ + rc = gsm340_rx_sms_deliver(trans->ms, msg, gsms); + break; + case GSM340_SMS_STATUS_REP_SC2MS: + case GSM340_SMS_SUBMIT_REP_SC2MS: + 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; + } + +out: + sms_free(gsms); + + return rc; +} + +static int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + LOGP(DLSMS, LOGL_INFO, "TX: SMS RP ACK\n"); + + gsm411_push_rp_header(msg, GSM411_MT_RP_ACK_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_REPORT_REQ, + msg); +} + +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)); + + gsm411_push_rp_header(msg, GSM411_MT_RP_ERROR_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_REPORT_REQ, + msg); +} + +/* 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 (dst_len && dst) + LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MT) with DST ?!?\n"); + + if (!src_len || !src || !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; + + LOGP(DLSMS, LOGL_INFO, "DST(%u,%s)\n", src_len, + osmo_hexdump(src, src_len)); + LOGP(DLSMS, LOGL_INFO, "TPDU(%u,%s)\n", msg->tail-msg->l4h, + osmo_hexdump(msg->l4h, msg->tail-msg->l4h)); + + rc = gsm340_rx_tpdu(trans, 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]; + + LOGP(DLSMS, LOGL_INFO, "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 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_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-DATA (MT)\n"); + rc = gsm411_rx_rp_data(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_trans_free(trans); + rc = -EINVAL; + break; + } + + return rc; +} + +/* + * send SMS + */ + +/* 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 osmocom_ms *ms = trans->ms; + 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); + } + + gsm411_sms_report(ms, sms, 0); + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct osmocom_ms *ms = trans->ms; + 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", + trans->ms->name, 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 + } + + gsm411_sms_report(ms, sms, cause); + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +/* 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_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-ACK (MT)\n"); + rc = gsm411_rx_rp_ack(msg, trans, rp_data); + break; + case GSM411_MT_RP_ERROR_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-ERROR (MT)\n"); + rc = gsm411_rx_rp_error(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_trans_free(trans); + rc = -EINVAL; + break; + } + + return rc; +} + +/* 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 da[12]; /* max len per 03.40 */ + uint8_t da_len = 0; + uint8_t octet_len; + unsigned int old_msg_len = msg->len; + uint8_t sms_vpf = GSM340_TP_VPF_NONE; + uint8_t sms_vp; + + /* generate first octet with masked bits */ + smsp = msgb_put(msg, 1); + /* TP-MTI (message type indicator) */ + *smsp = GSM340_SMS_SUBMIT_MS2SC; + /* TP-RD */ + if (0 /* FIXME */) + *smsp |= 0x04; + /* TP-VPF */ + *smsp |= (sms_vpf << 3); + /* 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; + /* TP-RP */ + if (sms->reply_path_req) + *smsp |= 0x80; + + /* generate message ref */ + smsp = msgb_put(msg, 1); + *smsp = sms->msg_ref; + + /* generate destination address */ + if (sms->address[0] == '+') + da_len = gsm340_gen_oa(da, sizeof(da), 0x1, 0x1, + sms->address + 1); + else + da_len = gsm340_gen_oa(da, sizeof(da), 0x0, 0x1, sms->address); + smsp = msgb_put(msg, da_len); + memcpy(smsp, da, da_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-VP */ + switch (sms_vpf) { + case GSM340_TP_VPF_NONE: + sms_vp = 0; + break; + default: + fprintf(stderr, "VPF unsupported, please fix!\n"); + exit(0); + } + smsp = msgb_put(msg, sms_vp); + + /* 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; +} + +/* Take a SMS in gsm_sms structure and send it. */ +int gsm411_tx_sms_submit(struct osmocom_ms *ms, struct gsm_sms *sms) +{ + struct gsm_settings *set = &ms->settings; + struct msgb *msg; + struct gsm_trans *trans; + uint8_t *data, *rp_ud_len; + uint8_t msg_ref = 42; + int rc; + uint8_t transaction_id; + uint8_t sca[11]; /* max len per 03.40 */ + + LOGP(DLSMS, LOGL_INFO, "..._sms_submit()\n"); + + /* no running, no transaction */ + if (!ms->started || ms->shutdown) { + LOGP(DLSMS, LOGL_ERROR, "Phone is down\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -EIO; + } + + /* allocate transaction with dummy reference */ + transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_SMS, 0); + if (transaction_id < 0) { + LOGP(DLSMS, LOGL_ERROR, "No transaction ID available\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_CONGESTION); + sms_free(sms); + return -ENOMEM; + } + trans = trans_alloc(ms, GSM48_PDISC_SMS, transaction_id, new_callref++); + if (!trans) { + LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -ENOMEM; + } + gsm411_smc_init(&trans->sms.smc_inst, 0, gsm411_mn_recv, + gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, 0, gsm411_rl_recv, + gsm411_mn_send); + trans->sms.sms = sms; + trans->sms.sapi = UM_SAPI_SMS; + + msg = gsm411_msgb_alloc(); + + /* no orig Address */ + data = (uint8_t *)msgb_put(msg, 1); + data[0] = 0x00; /* originator length == 0 */ + + /* Destination Address */ + sca[1] = 0x80; /* no extension */ + sca[1] |= ((set->sms_sca[0] == '+') ? 0x01 : 0x00) << 4; /* type */ + sca[1] |= 0x1; /* plan*/ + + rc = gsm48_encode_bcd_number(sca, sizeof(sca), 1, + set->sms_sca + (set->sms_sca[0] == '+')); + if (rc < 0) { +error: + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_SEMANT_INC_MSG); + gsm411_trans_free(trans); + msgb_free(msg); + return rc; + } + data = msgb_put(msg, rc); + memcpy(data, sca, rc); + + /* 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) + goto error; + *rp_ud_len = rc; + + LOGP(DLSMS, LOGL_INFO, "TX: SMS DELIVER\n"); + + gsm411_push_rp_header(msg, GSM411_MT_RP_DATA_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_DATA_REQ, + msg); +} + +/* create and send SMS */ +int sms_send(struct osmocom_ms *ms, const char *number, const char *text) +{ + struct gsm_sms *sms = sms_from_text(number, 0, text); + + if (!sms) + return -ENOMEM; + + return gsm411_tx_sms_submit(ms, sms); +} + +/* + * message flow between layers + */ + +/* push MMSMS header and send to MM */ +static int gsm411_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type) +{ + struct gsm48_mmxx_hdr *mmh; + + /* 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->sapi = trans->sms.sapi; + mmh->emergency = 0; + + /* send message to MM */ + LOGP(DLSMS, LOGL_INFO, "Sending '%s' to MM (callref=%x, " + "transaction_id=%d, sapi=%d)\n", get_mmxx_name(msg_type), + trans->callref, trans->transaction_id, trans->sms.sapi); + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* mm_send: receive MMSMS 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: + gsm411_to_mm(msg, trans, msg_type); + break; + case GSM411_MMSMS_DATA_REQ: + gsm411_push_cp_header(msg, trans->protocol, + trans->transaction_id, cp_msg_type); + msg->l3h = msg->data; + LOGP(DLSMS, LOGL_INFO, "sending CP message (trans=%x)\n", + trans->transaction_id); + rc = gsm411_to_mm(msg, trans, msg_type); + break; + case GSM411_MMSMS_REL_REQ: + LOGP(DLSMS, LOGL_INFO, "Got MMSMS_REL_REQ, destroying " + "transaction.\n"); + gsm411_to_mm(msg, trans, msg_type); + gsm411_trans_free(trans); + break; + default: + msgb_free(msg); + rc = -EINVAL; + } + + return rc; +} + +/* mm_send: receive MNSMS sap message from SMR */ +static 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); +} + +/* receive SM-RL sap message from SMR + * NOTE: Message is freed by sender + */ +static 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) + LOGP(DLSMS, LOGL_INFO, "Release transaction on empty " + "report.\n"); + else { + LOGP(DLSMS, LOGL_INFO, "Release transaction on RL " + "report.\n"); + rc = gsm411_rx_rl_report(msg, gh, trans); + } + break; + default: + rc = -EINVAL; + } + + return rc; +} + +/* receive MNSMS 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: + LOGP(DLSMS, LOGL_INFO, "MNSMS-DATA/EST-IND\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + case GSM411_MNSMS_ERROR_IND: + if (gh) + LOGP(DLSMS, LOGL_INFO, "MNSMS-ERROR-IND, cause %d " + "(%s)\n", gh->data[0], + get_value_string(gsm411_cp_cause_strs, + gh->data[0])); + else + LOGP(DLSMS, LOGL_INFO, "MNSMS-ERROR-IND, no cause\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + default: + rc = -EINVAL; + } + + return rc; +} + +/* receive est/data message from MM layer */ +static int gsm411_mmsms_ind(int mmsms_msg, 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 */ + + /* pull the MMSMS header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + LOGP(DLSMS, LOGL_INFO, "(ms %s) Received est/data '%u'\n", ms->name, + msg_type); + + /* 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(ms, GSM48_PDISC_SMS, i); + if (!ptrans) + continue; + + LOGP(DLSMS, LOGL_INFO, "Implicit CP-ACK for " + "trans_id=%x\n", i); + + /* Finish it for good */ + gsm411_trans_free(ptrans); + } + } + return gsm411_smc_recv(&trans->sms.smc_inst, mmsms_msg, msg, msg_type); +} + +/* receive message from MM layer */ +int gsm411_rcv_sms(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + int sapi = mmh->sapi; + struct gsm_trans *trans; + int rc = 0; + + trans = trans_find_by_callref(ms, mmh->ref); + if (!trans) { + LOGP(DLSMS, LOGL_INFO, " -> (new transaction sapi=%d)\n", sapi); + trans = trans_alloc(ms, GSM48_PDISC_SMS, mmh->transaction_id, + mmh->ref); + if (!trans) + return -ENOMEM; + gsm411_smc_init(&trans->sms.smc_inst, 0, gsm411_mn_recv, + gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, 0, gsm411_rl_recv, + gsm411_mn_send); + trans->sms.sapi = mmh->sapi; + } + + LOGP(DLSMS, LOGL_INFO, "(ms %s) Received '%s' from MM\n", ms->name, + get_mmxx_name(msg_type)); + + switch (msg_type) { + case GSM48_MMSMS_EST_CNF: + rc = gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_EST_CNF, + msg, 0); + break; + case GSM48_MMSMS_EST_IND: + case GSM48_MMSMS_DATA_IND: + rc = gsm411_mmsms_ind(msg_type, trans, msg); + break; + case GSM48_MMSMS_REL_IND: + case GSM48_MMSMS_ERR_IND: + LOGP(DLSMS, LOGL_INFO, "MM connection released.\n"); + trans_free(trans); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Message unhandled.\n"); + rc = -ENOTSUP; + } + + return rc; +} + diff --git a/src/host/layer23/src/mobile/gsm48_mm.c b/src/host/layer23/src/mobile/gsm48_mm.c index 77fa7cff..34bb82f0 100644 --- a/src/host/layer23/src/mobile/gsm48_mm.c +++ b/src/host/layer23/src/mobile/gsm48_mm.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -760,10 +761,10 @@ int gsm48_mmxx_dequeue(struct osmocom_ms *ms) case GSM48_MMSS_CLASS: gsm48_rcv_ss(ms, msg); break; +#endif case GSM48_MMSMS_CLASS: gsm411_rcv_sms(ms, msg); break; -#endif } msgb_free(msg); work = 1; /* work done */ @@ -4055,11 +4056,11 @@ static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) msgb_free(msg); return rc; +#endif case GSM48_PDISC_SMS: rc = gsm411_rcv_sms(ms, msg); msgb_free(msg); return rc; -#endif default: LOGP(DMM, LOGL_NOTICE, "Protocol type 0x%02x unsupported.\n", diff --git a/src/host/layer23/src/mobile/main.c b/src/host/layer23/src/mobile/main.c index 3da8e89f..3590ec61 100644 --- a/src/host/layer23/src/mobile/main.c +++ b/src/host/layer23/src/mobile/main.c @@ -67,6 +67,9 @@ int mobile_work(struct osmocom_ms *ms); int mobile_exit(struct osmocom_ms *ms, int force); +const char *debug_default = + "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DLSMS:DPAG:DSUM"; + const char *openbsc_copyright = "Copyright (C) 2008-2010 ...\n" "Contributions by ...\n\n" @@ -87,7 +90,8 @@ static void print_help() printf(" -i --gsmtap-ip The destination IP used for GSMTAP.\n"); printf(" -v --vty-port The VTY port number to telnet to. " "(default %u)\n", vty_port); - printf(" -d --debug Change debug flags.\n"); + printf(" -d --debug Change debug flags. default: %s\n", + debug_default); printf(" -D --daemonize Run as daemon\n"); printf(" -m --mncc-sock Disable built-in MNCC handler and " "offer socket\n"); @@ -198,7 +202,7 @@ int main(int argc, char **argv) handle_options(argc, argv); if (!debug_set) - log_parse_category_mask(stderr_target, "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DPAG:DSUM"); + log_parse_category_mask(stderr_target, debug_default); log_set_log_level(stderr_target, LOGL_DEBUG); if (gsmtap_ip) { diff --git a/src/host/layer23/src/mobile/support.c b/src/host/layer23/src/mobile/support.c index c4269aca..bfc61805 100644 --- a/src/host/layer23/src/mobile/support.c +++ b/src/host/layer23/src/mobile/support.c @@ -41,7 +41,7 @@ void gsm_support_init(struct osmocom_ms *ms) /* support of VBS */ sup->vbs = 0; /* no */ /* support of SMS */ - sup->sms_ptp = 0; /* no */ + sup->sms_ptp = 1; /* no */ /* screening indicator */ sup->ss_ind = 1; /* phase 2 error handling */ /* pseudo synchronised capability */ diff --git a/src/host/layer23/src/mobile/transaction.c b/src/host/layer23/src/mobile/transaction.c index 35db0eeb..3e4bbf5f 100644 --- a/src/host/layer23/src/mobile/transaction.c +++ b/src/host/layer23/src/mobile/transaction.c @@ -33,6 +33,7 @@ extern void *l23_ctx; void _gsm48_cc_trans_free(struct gsm_trans *trans); +void _gsm411_sms_trans_free(struct gsm_trans *trans); struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, uint8_t proto, uint8_t trans_id) @@ -94,10 +95,10 @@ void trans_free(struct gsm_trans *trans) case GSM48_PDISC_SS: _gsm411_ss_trans_free(trans); break; +#endif case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); break; -#endif } DEBUGP(DCC, "ms %s frees transaction (mem %p)\n", trans->ms->name, diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c index c0a7ceff..aed6cfa5 100644 --- a/src/host/layer23/src/mobile/vty_interface.c +++ b/src/host/layer23/src/mobile/vty_interface.c @@ -37,6 +37,7 @@ #include #include #include +#include #include void *l23_ctx; @@ -837,6 +838,50 @@ DEFUN(call_dtmf, call_dtmf_cmd, "call MS_NAME dtmf DIGITS", return CMD_SUCCESS; } +DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", + "Send an SMS\nName of MS (see \"show ms\")\nPhone number to send SMS " + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "SMS text\n") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct gsm_settings_abbrev *abbrev; + char *number; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + + if (!set->sms_ptp) { + vty_out(vty, "SMS not supported by this mobile, please enable " + "SMS support%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!set->sms_sca[0]) { + vty_out(vty, "SMS sms-service-center not defined in settings%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + number = (char *)argv[1]; + llist_for_each_entry(abbrev, &set->abbrev, list) { + if (!strcmp(number, abbrev->abbrev)) { + number = abbrev->number; + vty_out(vty, "Using number '%s'%s", number, + VTY_NEWLINE); + break; + } + } + if (vty_check_number(vty, number)) + return CMD_WARNING; + + sms_send(ms, number, argv_concat(argv, argc, 2)); + + return CMD_SUCCESS; +} + DEFUN(test_reselection, test_reselection_cmd, "test re-selection NAME", "Manually trigger cell re-selection\nName of MS (see \"show ms\")") { @@ -1208,6 +1253,12 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) else if (!hide_default) vty_out(vty, " no emergency-imsi%s", VTY_NEWLINE); + if (set->sms_sca[0]) + vty_out(vty, " sms-service-center %s%s", set->sms_sca, + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no sms-service-center%s", VTY_NEWLINE); if (!hide_default || set->cw) vty_out(vty, " %scall-waiting%s", (set->cw) ? "" : "no ", VTY_NEWLINE); @@ -1569,6 +1620,37 @@ DEFUN(cfg_ms_no_emerg_imsi, cfg_ms_no_emerg_imsi_cmd, "no emergency-imsi", return CMD_SUCCESS; } +DEFUN(cfg_ms_sms_sca, cfg_ms_sms_sca_cmd, "sms-service-center NUMBER", + "Use Service center address for outgoing SMS\nNumber of service center " + "(Use digits '0123456789*#abc', and '+' to dial international)") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + const char *number = argv[0]; + + if ((strlen(number) > 20 && number[0] != '+') || strlen(number) > 21) { + vty_out(vty, "Number too long%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (vty_check_number(vty, number)) + return CMD_WARNING; + + strcpy(set->sms_sca, number); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_sms_sca, cfg_ms_no_sms_sca_cmd, "no sms-service-center", + NO_STR "Use Service center address for outgoing SMS") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->sms_sca[0] = '\0'; + + return CMD_SUCCESS; +} + DEFUN(cfg_no_cw, cfg_ms_no_cw_cmd, "no call-waiting", NO_STR "Disallow waiting calls") { @@ -2624,6 +2706,7 @@ int ms_vty_init(void) install_element(ENABLE_NODE, &call_cmd); install_element(ENABLE_NODE, &call_retr_cmd); install_element(ENABLE_NODE, &call_dtmf_cmd); + install_element(ENABLE_NODE, &sms_cmd); install_element(ENABLE_NODE, &test_reselection_cmd); install_element(ENABLE_NODE, &delete_forbidden_plmn_cmd); @@ -2657,6 +2740,8 @@ int ms_vty_init(void) install_element(MS_NODE, &cfg_ms_imei_random_cmd); install_element(MS_NODE, &cfg_ms_no_emerg_imsi_cmd); install_element(MS_NODE, &cfg_ms_emerg_imsi_cmd); + install_element(MS_NODE, &cfg_ms_no_sms_sca_cmd); + install_element(MS_NODE, &cfg_ms_sms_sca_cmd); install_element(MS_NODE, &cfg_ms_cw_cmd); install_element(MS_NODE, &cfg_ms_no_cw_cmd); install_element(MS_NODE, &cfg_ms_auto_answer_cmd); -- cgit v1.2.3