osmocom-bb/src/host/layer23/src/mobile/gsm480_ss.c

1279 lines
31 KiB
C

/*
* (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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdint.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <osmocom/core/msgb.h>
#include <osmocom/bb/common/logging.h>
#include <osmocom/bb/common/osmocom_data.h>
#include <osmocom/bb/mobile/mncc.h>
#include <osmocom/bb/mobile/transaction.h>
#include <osmocom/bb/mobile/gsm480_ss.h>
#include <osmocom/core/talloc.h>
#include <osmocom/bb/mobile/vty.h>
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <osmocom/gsm/gsm48.h>
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;
OSMO_STRLCPY_ARRAY(text, response);
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;
}
/*
* encoding
*/
#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;
}
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;
/* GSM 04.08, section 10.5.4.11 */
if (cause) {
uint8_t *tlv = msgb_put(msg, 4);
tlv[0] = GSM48_IE_CAUSE;
tlv[1] = 2;
/* Coding standard defined for the GSM PLMNs,
* location - USER, cause as given by caller,
* no extension, no diagnostics. */
tlv[2] = (1 << 7) | (0x03 << 5) | (GSM48_CAUSE_LOC_USER & 0x0f);
tlv[3] = (1 << 7) | cause;
}
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_push_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 */
gsm_7bit_encode_n_ussd(msg->data, msgb_tailroom(msg), text, &length);
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;
int 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 != MS_SHUTDOWN_NONE) {
gsm480_ss_result(ms, "<phone is down>", 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 (ss_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 (ss_code && to && to[0] == '*') {
OSMO_STRLCPY_ARRAY(dest, to + 1);
return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER,
GSM0480_OP_CODE_REGISTER_SS, ss_code, dest);
}
/* activate */
if (ss_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 (ss_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 (ss_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;
gsm_7bit_decode_n_ussd(text, sizeof(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) == 2)
strcpy(number, "0");
else
number[0] = '\0';
gsm48_decode_bcd_number2(number + strlen(number),
sizeof(number) - strlen(number),
tag_data - 1, tag_len + 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 want 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, "<no result>", 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, "<error received>", 0);
break;
case GSM0480_CTYPE_REJECT:
gsm480_ss_result(trans->ms, "<service rejected>", 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 rc;
}
/* 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;
}