From 3426063f562892ede1be7f67f302ad5d754f8f4c Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Thu, 3 Nov 2011 11:33:19 +0100 Subject: host/mobile: Adding (partly implemented) supplementary service support Use VTY to request your extension number form OpenBSC: en service 1 *100# Written-by: Andreas Eversberg Signed-off-by: Sylvain Munaut --- .../layer23/include/osmocom/bb/common/logging.h | 1 + .../layer23/include/osmocom/bb/mobile/Makefile.am | 6 +- .../layer23/include/osmocom/bb/mobile/gsm480_ss.h | 9 + .../include/osmocom/bb/mobile/transaction.h | 7 + src/host/layer23/src/common/logging.c | 6 + src/host/layer23/src/mobile/Makefile.am | 4 +- src/host/layer23/src/mobile/app_mobile.c | 3 + src/host/layer23/src/mobile/gsm480_ss.c | 1289 ++++++++++++++++++++ src/host/layer23/src/mobile/gsm48_mm.c | 11 +- src/host/layer23/src/mobile/main.c | 2 +- src/host/layer23/src/mobile/transaction.c | 7 +- src/host/layer23/src/mobile/vty_interface.c | 31 + 12 files changed, 1358 insertions(+), 18 deletions(-) create mode 100644 src/host/layer23/include/osmocom/bb/mobile/gsm480_ss.h create mode 100644 src/host/layer23/src/mobile/gsm480_ss.c diff --git a/src/host/layer23/include/osmocom/bb/common/logging.h b/src/host/layer23/include/osmocom/bb/common/logging.h index 2cb58354..3efa57a5 100644 --- a/src/host/layer23/include/osmocom/bb/common/logging.h +++ b/src/host/layer23/include/osmocom/bb/common/logging.h @@ -12,6 +12,7 @@ enum { DNB, DMM, DCC, + DSS, DSMS, DMNCC, DMEAS, diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am index 7f49d5e9..b58b9529 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am @@ -1,3 +1,3 @@ -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 +noinst_HEADERS = gsm322.h gsm480_ss.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/gsm480_ss.h b/src/host/layer23/include/osmocom/bb/mobile/gsm480_ss.h new file mode 100644 index 00000000..ecd778e4 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm480_ss.h @@ -0,0 +1,9 @@ +#ifndef _GSM480_SS_H +#define _GSM480_SS_H + +int gsm480_ss_init(struct osmocom_ms *ms); +int gsm480_ss_exit(struct osmocom_ms *ms); +int gsm480_rcv_ss(struct osmocom_ms *ms, struct msgb *msg); +int ss_send(struct osmocom_ms *ms, const char *code, int new_trans); + +#endif /* _GSM480_SS_H */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/transaction.h b/src/host/layer23/include/osmocom/bb/mobile/transaction.h index b0695ecb..8c06d5d9 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/transaction.h +++ b/src/host/layer23/include/osmocom/bb/mobile/transaction.h @@ -40,6 +40,13 @@ struct gsm_trans { struct osmo_timer_list timer; struct gsm_mncc msg; /* stores setup/disconnect/release message */ } cc; + struct { + /* current supp.serv. state */ + int state; + + uint8_t invoke_id; + struct msgb *msg; + } ss; struct { uint8_t sapi; /* SAPI to be used for this trans */ diff --git a/src/host/layer23/src/common/logging.c b/src/host/layer23/src/common/logging.c index ce138b7a..d8fd076b 100644 --- a/src/host/layer23/src/common/logging.c +++ b/src/host/layer23/src/common/logging.c @@ -68,6 +68,12 @@ static const struct log_info_cat default_categories[] = { .color = "\033[1;33m", .enabled = 1, .loglevel = LOGL_DEBUG, }, + [DSS] = { + .name = "DSS", + .description = "Supplenmentary Services", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, [DSMS] = { .name = "DSMS", .description = "Short Message Service", diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 4b2059f8..f3365af5 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -3,8 +3,8 @@ 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 gsm411_sms.c gsm48_cc.c gsm48_mm.c gsm48_rr.c \ - mnccms.c settings.c subscriber.c support.c \ +libmobile_a_SOURCES = gsm322.c gsm480_ss.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 bin_PROGRAMS = mobile diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index c5fe9443..da388b22 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 @@ -145,6 +146,7 @@ int mobile_exit(struct osmocom_ms *ms, int force) gsm48_rr_exit(ms); gsm_subscr_exit(ms); gsm48_cc_exit(ms); + gsm480_ss_exit(ms); gsm411_sms_exit(ms); gsm_sim_exit(ms); lapdm_channel_exit(&ms->lapdm_channel); @@ -169,6 +171,7 @@ int mobile_init(struct osmocom_ms *ms) gsm_sim_init(ms); gsm48_cc_init(ms); + gsm480_ss_init(ms); gsm411_sms_init(ms); gsm_voice_init(ms); gsm_subscr_init(ms); diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c new file mode 100644 index 00000000..fda62881 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm480_ss.c @@ -0,0 +1,1289 @@ +/* + * (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 +#include + +static uint32_t new_callref = 0x80000001; + +static int gsm480_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type); +static const struct value_string gsm480_err_names[] = { + { GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER, + "UNKNOWN SUBSCRIBER" }, + { GSM0480_ERR_CODE_ILLEGAL_SUBSCRIBER, + "ILLEGAL SUBSCRIBER" }, + { GSM0480_ERR_CODE_BEARER_SERVICE_NOT_PROVISIONED, + "BEARER SERVICE NOT PROVISIONED" }, + { GSM0480_ERR_CODE_TELESERVICE_NOT_PROVISIONED, + "TELESERVICE NOT PROVISIONED" }, + { GSM0480_ERR_CODE_ILLEGAL_EQUIPMENT, + "ILLEGAL EQUIPMENT" }, + { GSM0480_ERR_CODE_CALL_BARRED, + "CALL BARRED" }, + { GSM0480_ERR_CODE_ILLEGAL_SS_OPERATION, + "ILLEGAL SS OPERATION" }, + { GSM0480_ERR_CODE_SS_ERROR_STATUS, + "SS ERROR STATUS" }, + { GSM0480_ERR_CODE_SS_NOT_AVAILABLE, + "SS NOT AVAILABLE" }, + { GSM0480_ERR_CODE_SS_SUBSCRIPTION_VIOLATION, + "SS SUBSCRIPTION VIOLATION" }, + { GSM0480_ERR_CODE_SS_INCOMPATIBILITY, + "SS INCOMPATIBILITY" }, + { GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED, + "FACILITY NOT SUPPORTED" }, + { GSM0480_ERR_CODE_ABSENT_SUBSCRIBER, + "ABSENT SUBSCRIBER" }, + { GSM0480_ERR_CODE_SYSTEM_FAILURE, + "SYSTEM FAILURE" }, + { GSM0480_ERR_CODE_DATA_MISSING, + "DATA MISSING" }, + { GSM0480_ERR_CODE_UNEXPECTED_DATA_VALUE, + "UNEXPECTED DATA VALUE" }, + { GSM0480_ERR_CODE_PW_REGISTRATION_FAILURE, + "PW REGISTRATION FAILURE" }, + { GSM0480_ERR_CODE_NEGATIVE_PW_CHECK, + "NEGATIVE PW CHECK" }, + { GSM0480_ERR_CODE_NUM_PW_ATTEMPTS_VIOLATION, + "NUM PW ATTEMPTS VIOLATION" }, + { GSM0480_ERR_CODE_UNKNOWN_ALPHABET, + "UNKNOWN ALPHABET" }, + { GSM0480_ERR_CODE_USSD_BUSY, + "USSD BUSY" }, + { GSM0480_ERR_CODE_MAX_MPTY_PARTICIPANTS, + "MAX MPTY PARTICIPANTS" }, + { GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE, + "RESOURCES NOT AVAILABLE" }, + {0, NULL } +}; + +/* taken from Wireshark */ +static const struct value_string Teleservice_vals[] = { + {0x00, "allTeleservices" }, + {0x10, "allSpeechTransmissionServices" }, + {0x11, "telephony" }, + {0x12, "emergencyCalls" }, + {0x20, "allShortMessageServices" }, + {0x21, "shortMessageMT-PP" }, + {0x22, "shortMessageMO-PP" }, + {0x60, "allFacsimileTransmissionServices" }, + {0x61, "facsimileGroup3AndAlterSpeech" }, + {0x62, "automaticFacsimileGroup3" }, + {0x63, "facsimileGroup4" }, + + {0x70, "allDataTeleservices" }, + {0x80, "allTeleservices-ExeptSMS" }, + + {0x90, "allVoiceGroupCallServices" }, + {0x91, "voiceGroupCall" }, + {0x92, "voiceBroadcastCall" }, + + {0xd0, "allPLMN-specificTS" }, + {0xd1, "plmn-specificTS-1" }, + {0xd2, "plmn-specificTS-2" }, + {0xd3, "plmn-specificTS-3" }, + {0xd4, "plmn-specificTS-4" }, + {0xd5, "plmn-specificTS-5" }, + {0xd6, "plmn-specificTS-6" }, + {0xd7, "plmn-specificTS-7" }, + {0xd8, "plmn-specificTS-8" }, + {0xd9, "plmn-specificTS-9" }, + {0xda, "plmn-specificTS-A" }, + {0xdb, "plmn-specificTS-B" }, + {0xdc, "plmn-specificTS-C" }, + {0xdd, "plmn-specificTS-D" }, + {0xde, "plmn-specificTS-E" }, + {0xdf, "plmn-specificTS-F" }, + { 0, NULL } +}; + +/* taken from Wireshark */ +static const struct value_string Bearerservice_vals[] = { + {0x00, "allBearerServices" }, + {0x10, "allDataCDA-Services" }, + {0x11, "dataCDA-300bps" }, + {0x12, "dataCDA-1200bps" }, + {0x13, "dataCDA-1200-75bps" }, + {0x14, "dataCDA-2400bps" }, + {0x15, "dataCDA-4800bps" }, + {0x16, "dataCDA-9600bps" }, + {0x17, "general-dataCDA" }, + + {0x18, "allDataCDS-Services" }, + {0x1A, "dataCDS-1200bps" }, + {0x1C, "dataCDS-2400bps" }, + {0x1D, "dataCDS-4800bps" }, + {0x1E, "dataCDS-9600bps" }, + {0x1F, "general-dataCDS" }, + + {0x20, "allPadAccessCA-Services" }, + {0x21, "padAccessCA-300bps" }, + {0x22, "padAccessCA-1200bps" }, + {0x23, "padAccessCA-1200-75bps" }, + {0x24, "padAccessCA-2400bps" }, + {0x25, "padAccessCA-4800bps" }, + {0x26, "padAccessCA-9600bps" }, + {0x27, "general-padAccessCA" }, + + {0x28, "allDataPDS-Services" }, + {0x2C, "dataPDS-2400bps" }, + {0x2D, "dataPDS-4800bps" }, + {0x2E, "dataPDS-9600bps" }, + {0x2F, "general-dataPDS" }, + + {0x30, "allAlternateSpeech-DataCDA" }, + {0x38, "allAlternateSpeech-DataCDS" }, + {0x40, "allSpeechFollowedByDataCDA" }, + {0x48, "allSpeechFollowedByDataCDS" }, + + {0x50, "allDataCircuitAsynchronous" }, + {0x60, "allAsynchronousServices" }, + {0x58, "allDataCircuitSynchronous" }, + {0x68, "allSynchronousServices" }, + + {0xD0, "allPLMN-specificBS" }, + {0xD1, "plmn-specificBS-1" }, + {0xD2, "plmn-specificBS-2" }, + {0xD3, "plmn-specificBS-3" }, + {0xD4, "plmn-specificBS-4" }, + {0xD5, "plmn-specificBS-5" }, + {0xD6, "plmn-specificBS-6" }, + {0xD7, "plmn-specificBS-7" }, + {0xD8, "plmn-specificBS-8" }, + {0xD9, "plmn-specificBS-9" }, + {0xDA, "plmn-specificBS-A" }, + {0xDB, "plmn-specificBS-B" }, + {0xDC, "plmn-specificBS-C" }, + {0xDD, "plmn-specificBS-D" }, + {0xDE, "plmn-specificBS-E" }, + {0xDF, "plmn-specificBS-F" }, + { 0, NULL } +}; + +static int gsm480_ss_result(struct osmocom_ms *ms, const char *response, + uint8_t error) +{ + vty_notify(ms, NULL); + if (response) { + char text[256], *t = text, *s; + + strncpy(text, response, sizeof(text) - 1); + text[sizeof(text) - 1] = '\0'; + while ((s = strchr(text, '\r'))) + *s = '\n'; + while ((s = strsep(&t, "\n"))) { + vty_notify(ms, "Service response: %s\n", s); + } + } else if (error) + vty_notify(ms, "Service request failed: %s\n", + get_value_string(gsm480_err_names, error)); + else + vty_notify(ms, "Service request failed.\n"); + + return 0; +} + +enum { + GSM480_SS_ST_IDLE = 0, + GSM480_SS_ST_REGISTER, + GSM480_SS_ST_ACTIVE, +}; + +/* + * init / exit + */ + +int gsm480_ss_init(struct osmocom_ms *ms) +{ + LOGP(DSS, LOGL_INFO, "init SS\n"); + + return 0; +} + +int gsm480_ss_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DSS, LOGL_INFO, "exit SS processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_NC_SS) { + LOGP(DSS, LOGL_NOTICE, "Free pendig " + "SS-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + +/* + * transaction + */ + +/* SS Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm480_ss_trans_free(struct gsm_trans *trans) +{ + if (trans->ss.msg) { + LOGP(DSS, LOGL_INFO, "Free pending SS request\n"); + msgb_free(trans->ss.msg); + trans->ss.msg = NULL; + } + vty_notify(trans->ms, NULL); + vty_notify(trans->ms, "Service connection terminated.\n"); +} + +/* release MM connection, free transaction */ +static int gsm480_trans_free(struct gsm_trans *trans) +{ + struct msgb *nmsg; + + /* release MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_REQ, trans->callref, + trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOGP(DSS, LOGL_INFO, "Sending MMSS_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* + * endcoding + */ + +#define GSM480_ALLOC_SIZE 512+128 +#define GSM480_ALLOC_HEADROOM 128 + +struct msgb *gsm480_msgb_alloc(void) +{ + return msgb_alloc_headroom(GSM480_ALLOC_SIZE, GSM480_ALLOC_HEADROOM, + "GSM 04.80"); +} + +static inline unsigned char *msgb_wrap_with_L(struct msgb *msgb) +{ + uint8_t *data = msgb_push(msgb, 1); + + data[0] = msgb->len - 1; + return data; +} + +/* support function taken from OpenBSC */ +static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) +{ + uint8_t *data = msgb_push(msgb, 2); + + data[0] = tag; + data[1] = msgb->len - 2; + return data; +} + +static inline void msgb_wrap_with_TL_asn(struct msgb *msg, uint8_t tag) +{ + int len = msg->len; + uint8_t *data = msgb_push(msg, (len >= 128) ? 3 : 2); + + *data++ = tag; + if (len >= 128) + *data++ = 0x81; + *data = len; + return; +} + +/* support function taken from OpenBSC */ +static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, +uint8_t value) +{ + uint8_t *data = msgb_push(msgb, 3); + + data[0] = tag; + data[1] = 1; + data[2] = value; + return data; +} + +static const char *ss_code_by_char(const char *code, uint8_t *ss_code) +{ + if (!strncmp(code, "21", 2)) { + *ss_code = 33; + return code + 2; + } + if (!strncmp(code, "67", 2)) { + *ss_code = 41; + return code + 2; + } + if (!strncmp(code, "61", 2)) { + *ss_code = 42; + return code + 2; + } + if (!strncmp(code, "62", 2)) { + *ss_code = 43; + return code + 2; + } + if (!strncmp(code, "002", 3)) { + *ss_code = 32; + return code + 3; + } + if (!strncmp(code, "004", 3)) { + *ss_code = 40; + return code + 3; + } + + return NULL; +} + +static const char *decode_ss_code(uint8_t ss_code) +{ + static char unknown[16]; + + switch (ss_code) { + case 33: + return "CFU"; + case 41: + return "CFB"; + case 42: + return "CFNR"; + case 43: + return "CF Not Reachable"; + case 32: + return "All CF"; + case 40: + return "All conditional CF"; + default: + sprintf(unknown, "Unknown %d", ss_code); + return unknown; + } +} + +static int gsm480_tx_release_compl(struct gsm_trans *trans, uint8_t cause) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm480_msgb_alloc(); + if (!msg) + return -ENOMEM; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | (trans->transaction_id << 4); + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + if (cause) { + uint8_t *tlv = msgb_put(msg, 4); + *tlv = GSM48_IE_CAUSE; + *tlv = 2; + *tlv = 0x80 | cause; + *tlv = 0x80 | GSM48_CAUSE_LOC_USER; + } + return gsm480_to_mm(msg, trans, GSM48_MMSS_DATA_REQ); +} + +static int return_imei(struct osmocom_ms *ms) +{ + char text[32]; + struct gsm_settings *set = &ms->settings; + + sprintf(text, "IMEI: %s SV: %s", set->imei, + set->imeisv + strlen(set->imei)); + gsm480_ss_result(ms, text, 0); + + return 0; +} + +/* prepend invoke-id, facility IE and facility message */ +static int gsm480_tx_invoke(struct gsm_trans *trans, struct msgb *msg, + uint8_t msg_type) +{ + struct gsm48_hdr *gh; + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, trans->ss.invoke_id); + + /* Wrap this up as invoke vomponent */ + if (msg_type == GSM0480_MTYPE_FACILITY) + msgb_wrap_with_TL_asn(msg, GSM0480_CTYPE_RETURN_RESULT); + else + msgb_wrap_with_TL_asn(msg, GSM0480_CTYPE_INVOKE); + + /* Wrap this up as facility IE */ + if (msg_type == GSM0480_MTYPE_FACILITY) + msgb_wrap_with_L(msg); + else + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* FIXME: If phase 2, we need SSVERSION to be added */ + + /* Push L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | (trans->transaction_id << 4); + gh->msg_type = msg_type; + + if (msg_type == GSM0480_MTYPE_FACILITY) { + /* directly transmit data on established connection */ + return gsm480_to_mm(msg, trans, GSM48_MMSS_DATA_REQ); + } else { + /* store header until our MM connection is established */ + trans->ss.msg = msg; + + /* request establishment */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + return gsm480_to_mm(msg, trans, GSM48_MMSS_EST_REQ); + } +} + +static int gsm480_tx_cf(struct gsm_trans *trans, uint8_t msg_type, + uint8_t op_code, uint8_t ss_code, const char *dest) +{ + struct msgb *msg; + + /* allocate message */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + + if (dest) { + uint8_t tlv[32]; + int rc; + + /* Forwarding To address */ + tlv[0] = 0x84; + tlv[2] = 0x80; /* no extension */ + tlv[2] |= ((dest[0] == '+') ? 0x01 : 0x00) << 4; /* type */ + tlv[2] |= 0x1; /* plan*/ + rc = gsm48_encode_bcd_number(tlv + 1, sizeof(tlv) - 1, 1, + dest + (dest[0] == '+')); + if (rc < 0) { + msgb_free(msg); + trans_free(trans); + return -EINVAL; + } + memcpy(msgb_put(msg, rc + 1), tlv, rc + 1); + } + + /* Encode ss-Code */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, ss_code); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, op_code); + + return gsm480_tx_invoke(trans, msg, msg_type); +} + +static int gsm480_tx_ussd(struct gsm_trans *trans, uint8_t msg_type, + const char *text) +{ + struct msgb *msg; + int length; + + /* allocate message */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + + /* Encode service request */ + length = gsm_7bit_encode(msg->data, text); + msgb_put(msg, length); + + /* Then wrap it as an Octet String */ + msgb_wrap_with_TL_asn(msg, ASN1_OCTET_STRING_TAG); + + /* Pre-pend the DCS octet string */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + + if (msg_type == GSM0480_MTYPE_FACILITY) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_USS_REQUEST); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + } else { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_PROCESS_USS_REQ); + } + + return gsm480_tx_invoke(trans, msg, msg_type); +} + +/* create and send service code */ +int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) +{ + struct gsm_trans *trans = NULL, *transt; + uint8_t transaction_id; + + /* look for an old transaction */ + if (!new_trans) { + llist_for_each_entry(transt, &ms->trans_list, entry) { + if (transt->protocol == GSM48_PDISC_NC_SS) { + trans = transt; + break; + } + } + } + + /* if there is an old transaction, check if we can send data */ + if (trans) { + if (trans->ss.state != GSM480_SS_ST_ACTIVE) { + LOGP(DSS, LOGL_INFO, "Pending trans not active.\n"); + gsm480_ss_result(trans->ms, "Current service pending", + 0); + return 0; + } + if (!strcmp(code, "hangup")) { + gsm480_tx_release_compl(trans, 0); + gsm480_trans_free(trans); + return 0; + } + LOGP(DSS, LOGL_INFO, "Existing transaction.\n"); + return gsm480_tx_ussd(trans, GSM0480_MTYPE_FACILITY, code); + } + + /* do nothing, if hangup is received */ + if (!strcmp(code, "hangup")) + return 0; + + /* internal codes */ + if (!strcmp(code, "*#06#")) { + return return_imei(ms); + } + + /* no running, no transaction */ + if (!ms->started || ms->shutdown) { + gsm480_ss_result(ms, "", 0); + return -EIO; + } + + /* allocate transaction with dummy reference */ + transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_NC_SS, + 0); + if (transaction_id < 0) { + LOGP(DSS, LOGL_ERROR, "No transaction ID available\n"); + gsm480_ss_result(ms, NULL, + GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE); + return -ENOMEM; + } + trans = trans_alloc(ms, GSM48_PDISC_NC_SS, transaction_id, + new_callref++); + if (!trans) { + LOGP(DSS, LOGL_ERROR, "No memory for trans\n"); + gsm480_ss_result(ms, NULL, + GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE); + return -ENOMEM; + } + + /* go register sent state */ + trans->ss.state = GSM480_SS_ST_REGISTER; + + /* FIXME: generate invoke ID */ + trans->ss.invoke_id = 5; + + /* interrogate */ + if (code[0] == '*' && code[1] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 2, &ss_code); + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_INTERROGATE_SS, ss_code, NULL); + } else + /* register / activate */ + if (code[0] == '*' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + const char *to; + char dest[32]; + + /* double star */ + if (code[1] == '*') + code++; + + to = ss_code_by_char(code + 1, &ss_code); + + /* register */ + if (code && to && to[0] == '*') { + strncpy(dest, to + 1, sizeof(dest) - 1); + dest[sizeof(dest) - 1] = '\0'; + dest[strlen(dest) - 1] = '\0'; + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_REGISTER_SS, ss_code, dest); + } + /* activate */ + if (code && to && to[0] == '#') { + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_ACTIVATE_SS, ss_code, NULL); + } + } else + /* erasure */ + if (code[0] == '#' && code[1] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 2, &ss_code); + + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_ERASE_SS, ss_code, NULL); + } else + /* deactivate */ + if (code[0] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 1, &ss_code); + + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_DEACTIVATE_SS, ss_code, NULL); + } + + /* other codes */ + return gsm480_tx_ussd(trans, GSM0480_MTYPE_REGISTER, code); +} + +/* + * decoding + */ + +static int parse_tag_asn1(const uint8_t *data, int len, + const uint8_t **tag_data, int *tag_len) +{ + /* at least 2 bytes (tag + len) */ + if (len < 2) + return -1; + + /* extended length */ + if (data[1] == 0x81) { + /* at least 2 bytes (tag + 0x81 + len) */ + if (len < 3) + return -1; + *tag_len = data[2]; + *tag_data = data + 3; + len -= 3; + } else { + *tag_len = data[1]; + *tag_data = data + 2; + len -= 2; + } + + /* check for buffer overflow */ + if (len < *tag_len) + return -1; + + /* return length */ + return len; +} + +static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + int num_chars; + char text[256]; + int i; + const uint8_t *tag_data; + int tag_len; + + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "2. Sequence tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting 2. Sequence Tag\n"); + return -EINVAL; + } + len = tag_len; + data = tag_data; + + /* DSC */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "DSC tag too short\n"); + return -EINVAL; + } + if (data[0] != ASN1_OCTET_STRING_TAG || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting DSC tag\n"); + return -EINVAL; + } + if (tag_data[0] != 0x0f) { + LOGP(DSS, LOGL_NOTICE, "DSC not 0x0f\n"); + return -EINVAL; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + /* text */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Text tag too short\n"); + return -EINVAL; + } + if (data[0] != ASN1_OCTET_STRING_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting text tag\n"); + return -EINVAL; + } + num_chars = tag_len * 8 / 7; + /* Prevent a mobile-originated buffer-overrun! */ + if (num_chars > sizeof(text) - 1) + num_chars = sizeof(text) - 1; + text[sizeof(text) - 1] = '\0'; + gsm_7bit_decode(text, tag_data, num_chars); + + for (i = 0; text[i]; i++) { + if (text[i] == '\r') + text[i] = '\n'; + } + /* remove last CR, if exists */ + if (text[0] && text[strlen(text) - 1] == '\n') + text[strlen(text) - 1] = '\0'; + gsm480_ss_result(trans->ms, text, 0); + + return 0; +} + +static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + struct osmocom_ms *ms = trans->ms; + const uint8_t *tag_data, *data2; + int tag_len, len2; + char number[32]; + + LOGP(DSS, LOGL_INFO, "call forwarding reply: len %d data %s\n", len, + osmo_hexdump(data, len)); + + vty_notify(ms, NULL); + + /* forwarding feature list */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] == 0x80) { + if ((tag_data[0] & 0x01)) + vty_notify(ms, "Status: activated\n"); + else + vty_notify(ms, "Status: deactivated\n"); + return 0; + } + + switch(data[0]) { + case 0xa3: + len = tag_len; + data = tag_data; + break; + case 0xa0: /* forwarding info */ + len = tag_len; + data = tag_data; + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + /* check for SS code */ + if (data[0] != 0x04) + break; + vty_notify(ms, "Reply for %s\n", decode_ss_code(tag_data[0])); + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting sequence tag\n"); + return -EINVAL; + } + len = tag_len; + data = tag_data; + break; + default: + vty_notify(ms, "Call Forwarding reply unsupported.\n"); + return 0; + } + + while (len) { + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + LOGP(DSS, LOGL_NOTICE, "Skipping tag 0x%x\n", data[0]); + continue; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + len2 = tag_len; + data2 = tag_data; + + while (len2) { + /* tags in sequence */ + if (parse_tag_asn1(data2, len2, &tag_data, &tag_len) + < 1) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + LOGP(DSS, LOGL_INFO, "Tag: len %d data %s\n", tag_len, + osmo_hexdump(tag_data, tag_len)); + switch (data2[0]) { + case 0x82: + vty_notify(ms, "Bearer Service: %s\n", + get_value_string(Bearerservice_vals, + tag_data[0])); + break; + case 0x83: + vty_notify(ms, "Teleservice: %s\n", + get_value_string(Teleservice_vals, + tag_data[0])); + break; + case 0x84: + if ((tag_data[0] & 0x01)) + vty_notify(ms, "Status: activated\n"); + else + vty_notify(ms, "Status: deactivated\n"); + break; + case 0x85: + if (((tag_data[0] & 0x70) >> 4) == 1) + strcpy(number, "+"); + else if (((tag_data[0] & 0x70) >> 4) == 1) + strcpy(number, "+"); + else + number[0] = '\0'; + gsm48_decode_bcd_number(number + strlen(number), + sizeof(number) - strlen(number), + tag_data - 1, 1); + vty_notify(ms, "Destination: %s\n", number); + break; + } + len2 -= tag_data - data2 + tag_len; + data2 = tag_data + tag_len; + } + } + + return 0; +} + +static int gsm480_rx_result(struct gsm_trans *trans, const uint8_t *data, + int len, int msg_type) +{ + const uint8_t *tag_data; + int tag_len; + int rc = 0; + + LOGP(DSS, LOGL_INFO, "Result received (len %d)\n", len); + + if (len && data[0] == 0x8d) { + LOGP(DSS, LOGL_NOTICE, "Skipping mysterious 0x8d\n"); + len--; + data++; + } + + /* invoke ID */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Invoke ID too short\n"); + return -EINVAL; + } + if (data[0] != GSM0480_COMPIDTAG_INVOKE_ID || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting invoke ID\n"); + return -EINVAL; + } + + if (msg_type == GSM0480_CTYPE_RETURN_RESULT) { + if (trans->ss.invoke_id != data[2]) { + LOGP(DSS, LOGL_NOTICE, "Invoke ID mismatch\n"); + } + } + /* Store invoke ID, in case we wan't to send a result. */ + trans->ss.invoke_id = tag_data[0]; + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + if (!len) { + gsm480_ss_result(trans->ms, "", 0); + return 0; + } + + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Sequence tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting Sequence Tag, trying " + "Operation Tag\n"); + goto operation; + } + len = tag_len; + data = tag_data; + + /* operation */ +operation: + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Operation too short\n"); + return -EINVAL; + } + if (data[0] != GSM0480_OPERATION_CODE || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting Operation Code\n"); + return -EINVAL; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + switch (tag_data[0]) { + case GSM0480_OP_CODE_PROCESS_USS_REQ: + case GSM0480_OP_CODE_USS_REQUEST: + rc = gsm480_rx_ussd(trans, data, len); + break; + case GSM0480_OP_CODE_INTERROGATE_SS: + case GSM0480_OP_CODE_REGISTER_SS: + case GSM0480_OP_CODE_ACTIVATE_SS: + case GSM0480_OP_CODE_DEACTIVATE_SS: + case GSM0480_OP_CODE_ERASE_SS: + rc = gsm480_rx_cf(trans, data, len); + break; + default: + LOGP(DSS, LOGL_NOTICE, "Operation code not USS\n"); + rc = -EINVAL; + } + + return rc; +} + +/* facility from BSC */ +static int gsm480_rx_fac_ie(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + int rc = 0; + const uint8_t *tag_data; + int tag_len; + + LOGP(DSS, LOGL_INFO, "Facility received (len %d)\n", len); + + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Facility too short\n"); + return -EINVAL; + } + + switch (data[0]) { + case GSM0480_CTYPE_INVOKE: + case GSM0480_CTYPE_RETURN_RESULT: + rc = gsm480_rx_result(trans, tag_data, tag_len, data[0]); + break; + case GSM0480_CTYPE_RETURN_ERROR: + // FIXME: return error code + gsm480_ss_result(trans->ms, "", 0); + break; + case GSM0480_CTYPE_REJECT: + gsm480_ss_result(trans->ms, "", 0); + break; + default: + LOGP(DSS, LOGL_NOTICE, "CTYPE unknown\n"); + rc = -EINVAL; + } + + return rc; +} + +static int gsm480_rx_cause_ie(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + uint8_t value; + + LOGP(DSS, LOGL_INFO, "Cause received (len %d)\n", len); + + if (len < 2) { + LOGP(DSS, LOGL_NOTICE, "Cause too short\n"); + return -EINVAL; + } + if (!(data[1] & 0x80)) { + if (len < 3) { + LOGP(DSS, LOGL_NOTICE, "Cause too short\n"); + return -EINVAL; + } + value = data[3] & 0x7f; + } else + value = data[2] & 0x7f; + + LOGP(DSS, LOGL_INFO, "Received Cause %d\n", value); + + /* this is an error */ + return -EINVAL; +} + +/* release complete from BSC */ +static int gsm480_rx_release_comp(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; + int rc = 0; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + /* facility optional */ + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rc = gsm480_rx_cause_ie(trans, + TLVP_VAL(&tp, GSM48_IE_CAUSE), + *(TLVP_VAL(&tp, GSM48_IE_CAUSE)-1)); + } + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + /* remote releases */ + gsm480_trans_free(trans); + + return rc; +} + +/* facility from BSC */ +static int gsm480_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 tlv_parsed tp; + int rc = 0; + + /* go register state */ + trans->ss.state = GSM480_SS_ST_ACTIVE; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_FACILITY, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + /* release 3.7.5 */ + gsm480_tx_release_compl(trans, 96); + /* local releases */ + gsm480_trans_free(trans); + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + return rc; +} + +/* regisster from BSC */ +static int gsm480_rx_register(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; + int rc = 0; + + /* go register state */ + trans->ss.state = GSM480_SS_ST_ACTIVE; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + /* facility optional */ + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + /* release 3.7.5 */ + gsm480_tx_release_compl(trans, 96); + /* local releases */ + gsm480_trans_free(trans); + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + return rc; +} + +/* + * message handling + */ + +/* push MMSS header and send to MM */ +static int gsm480_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type) +{ + struct gsm48_mmxx_hdr *mmh; + + /* set l3H */ + msg->l3h = msg->data; + + /* 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 = 0; + mmh->emergency = 0; + + /* send message to MM */ + LOGP(DSS, LOGL_INFO, "Sending '%s' to MM (callref=%x, " + "transaction_id=%d)\n", get_mmxx_name(msg_type), trans->callref, + trans->transaction_id); + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* receive est confirm from MM layer */ +static int gsm480_mmss_est(int mmss_msg, struct gsm_trans *trans, + struct msgb *msg) +{ + struct osmocom_ms *ms = trans->ms; + struct msgb *temp; + + LOGP(DSS, LOGL_INFO, "(ms %s) Received confirm, sending pending SS\n", + ms->name); + + /* remove transaction, if no SS message */ + if (!trans->ss.msg) { + LOGP(DSS, LOGL_ERROR, "(ms %s) No pending SS!\n", ms->name); + gsm480_trans_free(trans); + return -EINVAL; + } + + /* detach message and then send */ + temp = trans->ss.msg; + trans->ss.msg = NULL; + return gsm480_to_mm(temp, trans, GSM48_MMSS_DATA_REQ); +} + +/* receive data indication from MM layer */ +static int gsm480_mmss_ind(int mmss_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; + int rc = 0; + + /* pull the MMSS header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + LOGP(DSS, LOGL_INFO, "(ms %s) Received est/data '%u'\n", ms->name, + msg_type); + + switch (msg_type) { + case GSM0480_MTYPE_RELEASE_COMPLETE: + rc = gsm480_rx_release_comp(trans, msg); + break; + case GSM0480_MTYPE_FACILITY: + rc = gsm480_rx_facility(trans, msg); + break; + case GSM0480_MTYPE_REGISTER: + rc = gsm480_rx_register(trans, msg); + break; + default: + LOGP(DSS, LOGL_NOTICE, "Message unhandled.\n"); + /* release 3.7.4 */ + gsm480_tx_release_compl(trans, 97); + gsm480_trans_free(trans); + rc = -ENOTSUP; + } + return 0; +} + +/* receive message from MM layer */ +int gsm480_rcv_ss(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) { + LOGP(DSS, LOGL_INFO, " -> (new transaction)\n"); + trans = trans_alloc(ms, GSM48_PDISC_NC_SS, mmh->transaction_id, + mmh->ref); + if (!trans) + return -ENOMEM; + } + + LOGP(DSS, LOGL_INFO, "(ms %s) Received '%s' from MM\n", ms->name, + get_mmxx_name(msg_type)); + + switch (msg_type) { + case GSM48_MMSS_EST_CNF: + rc = gsm480_mmss_est(msg_type, trans, msg); + break; + case GSM48_MMSS_EST_IND: + case GSM48_MMSS_DATA_IND: + rc = gsm480_mmss_ind(msg_type, trans, msg); + break; + case GSM48_MMSS_REL_IND: + case GSM48_MMSS_ERR_IND: + LOGP(DSS, LOGL_INFO, "MM connection released.\n"); + trans_free(trans); + break; + default: + LOGP(DSS, 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 34bb82f0..2153abac 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 #include @@ -757,11 +758,9 @@ int gsm48_mmxx_dequeue(struct osmocom_ms *ms) case GSM48_MMCC_CLASS: gsm48_rcv_cc(ms, msg); break; -#if 0 case GSM48_MMSS_CLASS: - gsm48_rcv_ss(ms, msg); + gsm480_rcv_ss(ms, msg); break; -#endif case GSM48_MMSMS_CLASS: gsm411_rcv_sms(ms, msg); break; @@ -3982,12 +3981,10 @@ static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) rr_prim = GSM48_MMCC_DATA_IND; rr_est = GSM48_MMCC_EST_IND; break; -#if 0 case GSM48_PDISC_NC_SS: rr_prim = GSM48_MMSS_DATA_IND; rr_est = GSM48_MMSS_EST_IND; break; -#endif case GSM48_PDISC_SMS: rr_prim = GSM48_MMSMS_DATA_IND; rr_est = GSM48_MMSMS_EST_IND; @@ -4050,13 +4047,11 @@ static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) msgb_free(msg); return rc; -#if 0 case GSM48_PDISC_NC_SS: - rc = gsm48_rcv_ss(ms, msg); + rc = gsm480_rcv_ss(ms, msg); msgb_free(msg); return rc; -#endif case GSM48_PDISC_SMS: rc = gsm411_rcv_sms(ms, msg); msgb_free(msg); diff --git a/src/host/layer23/src/mobile/main.c b/src/host/layer23/src/mobile/main.c index 3590ec61..12140773 100644 --- a/src/host/layer23/src/mobile/main.c +++ b/src/host/layer23/src/mobile/main.c @@ -68,7 +68,7 @@ int mobile_exit(struct osmocom_ms *ms, int force); const char *debug_default = - "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DLSMS:DPAG:DSUM"; + "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM"; const char *openbsc_copyright = "Copyright (C) 2008-2010 ...\n" diff --git a/src/host/layer23/src/mobile/transaction.c b/src/host/layer23/src/mobile/transaction.c index 3e4bbf5f..45bf2b41 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 _gsm480_ss_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, @@ -91,11 +92,9 @@ void trans_free(struct gsm_trans *trans) case GSM48_PDISC_CC: _gsm48_cc_trans_free(trans); break; -#if 0 - case GSM48_PDISC_SS: - _gsm411_ss_trans_free(trans); + case GSM48_PDISC_NC_SS: + _gsm480_ss_trans_free(trans); break; -#endif case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); break; diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c index 5c8d60b7..ca1c5828 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 #include @@ -887,6 +888,35 @@ DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", return CMD_SUCCESS; } +DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" + "|*#002#|*#004#|*xx*number#|*xx#|#xx#|##xx#|STRING|hangup)", + "Send a Supplementary Service request\nName of MS (see \"show ms\")\n" + "Query IMSI\n" + "Query Call Forwarding Unconditional (CFU)\n" + "Query Call Forwarding when Busy (CFB)\n" + "Query Call Forwarding when No Response (CFNR)\n" + "Query Call Forwarding when Not Reachable\n" + "Query all Call Forwardings\n" + "Query all conditional Call Forwardings\n" + "Set and activate Call Forwarding (xx = Service Code, see above)\n" + "Activate Call Forwarding (xx = Service Code, see above)\n" + "Deactivate Call Forwarding (xx = Service Code, see above)\n" + "Erase and deactivate Call Forwarding (xx = Service Code, see above)\n" + "Service string " + "(Example: '*100#' requests account balace on some networks.)\n" + "Hangup existing service connection") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + ss_send(ms, argv[1], 0); + + return CMD_SUCCESS; +} + DEFUN(test_reselection, test_reselection_cmd, "test re-selection NAME", "Manually trigger cell re-selection\nName of MS (see \"show ms\")") { @@ -2712,6 +2742,7 @@ int ms_vty_init(void) install_element(ENABLE_NODE, &call_retr_cmd); install_element(ENABLE_NODE, &call_dtmf_cmd); install_element(ENABLE_NODE, &sms_cmd); + install_element(ENABLE_NODE, &service_cmd); install_element(ENABLE_NODE, &test_reselection_cmd); install_element(ENABLE_NODE, &delete_forbidden_plmn_cmd); -- cgit v1.2.3