mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-24 16:43:58 +00:00
732 lines
26 KiB
C++
732 lines
26 KiB
C++
/*
|
|
* Copyright 2013, 2014 Range Networks, Inc.
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
|
|
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.
|
|
*/
|
|
|
|
// Written by Pat Thompson
|
|
|
|
#define LOG_GROUP LogGroup::Control
|
|
|
|
#include <string>
|
|
#include <list>
|
|
#include "L3SupServ.h"
|
|
#include "L3TranEntry.h"
|
|
#include "L3MMLayer.h"
|
|
#include <GSMCommon.h>
|
|
#include <GSML3Message.h>
|
|
#include <GSML3SSMessages.h>
|
|
#include <GSMLogicalChannel.h>
|
|
#include <SIPDialog.h>
|
|
#include <Globals.h>
|
|
|
|
|
|
// Generic SS messages are transferred by the Facility IE, whose content is described by 24.080 4.61
|
|
// and by an ASN description in the 24.080 appendix. This encoding is actually part of the GSM MAP reference
|
|
// lifted out of ITU Q.773. The MAP messages are in 3GPP 29.002 11.9 through 11.11.
|
|
// The Facility IE may appear in the 'call-related' messages Alerting, Call Proceeding, Setup, etc. described in 24.008,
|
|
// or in 'call-independent' L3 messages described in 24.080: Facility, Register, Release Complete.
|
|
// However, the unstructured USupServ operations may only appear in call-independent messages, except that phase 1 MS
|
|
// may send them in call-dependent locations, so we better just recognize them anywhere.
|
|
|
|
// I am not going to suck in the ASN description, rather just pull out the tiny pieces we need.
|
|
// The USupServ version 1 command is processUnstructuredSS-Data (3GPP 4.80) and has one argument: (ss-UserData)
|
|
// The USupServ version 2 command is processUnstructuredSS-Request (3GPP 24.80) and has two arguments: (uSupServ-DataCodingScheme, uSupServ-String)
|
|
|
|
// It was difficult to find the operation codes. The ASN referenced in 24.080 is not up to date, but 4.80 is!
|
|
// Then you have to look in 24.090 to find out what these names mean.
|
|
// ProcessUnstructuredSS-Data ::= localValue 19 // USupServ version 1 MS->network request.
|
|
// ProcessUnstructuredSS-Request ::= localValue 59 // USupServ version 2 MS->network request.
|
|
// UnstructuredSS-Request ::= localValue 60 // USupServ version 2 network->MS request.
|
|
// UnstructuredSS-Notify ::= localValue 61 // USupServ version 2 network->MS notification.
|
|
|
|
// 22.030 6.5.2: man-machine interface (MMI) for USupServ.
|
|
// These are the key presses: SC = Service Code (2 or 3 digits) SI = Supplementary Information
|
|
// Activation: *SC*SI#
|
|
// Deactivation: #SC*SI#
|
|
// Interrogation: *#SC*SI#
|
|
// Registration: *SC*SI# and **SC*SI#
|
|
// Erasure: ##SC*SI#
|
|
// UE Determines whether single * is activation or registration,
|
|
// "For example, a call forwarding request with a single * would be interpreted as registration if containing
|
|
// a forwarded-to number, or an activation if not."
|
|
// The *SI may be absent, or *SIA*SIB*SIC (followed by #).
|
|
// In the above, SIA or SIB or SIC may be absent, for example *SC***SIC#
|
|
|
|
// The service codes are in GSM 02.30 annex B.
|
|
// The SS-operation-codes are in from http://cpansearch.perl.org/src/REHSACK/Net-Radio-Location-SUPL-Test-0.001/asn1src/MAP-SS-Code.asc
|
|
// Here is a register message: 0b7b1c0da10b0201010201 0c 30 03 04 01 91 7f 01 00
|
|
// 0b // PD=0xb.
|
|
// 7b MTI (0x3b) ored with 0x80.
|
|
// 1c FacilityIEI
|
|
// 0d length of facility contents == 13
|
|
// a1 component type tag = Invoke (24.080 3.6.2)
|
|
// 0b component length == 12
|
|
// 02 Component id tag == InvokeID
|
|
// 01 invoke id length
|
|
// 01 invoke id
|
|
// Optional LinkedID tag 0x80 not present.
|
|
// 02 OperationCodeTag
|
|
// 01 OperationCode length
|
|
// 0c operation code 12 == activateSS.
|
|
// parameter list is from 4.80 4.5 ASN: activateSS OPERATION ARGUMENT
|
|
// I thought I typed *34#, which came out like this:
|
|
// ss codes from http://cpansearch.perl.org/src/REHSACK/Net-Radio-Location-SUPL-Test-0.001/asn1src/MAP-SS-Code.asn
|
|
// or from 3GPP 9.02 7.6.4 page 268.
|
|
// 3GPP 9.02 says: 0x30 is "allCallOfferingSS" in decimal == 48.
|
|
// 30 Sequence tag.
|
|
// 03 length of sequence
|
|
// 04 - octet?
|
|
// 01 length of param
|
|
// 91 the activateSS argument data, maybe "BarringOfOutgoingCalls SS-Code == 91. Selected by *33.
|
|
// 7f version indicator IEI
|
|
// 01 24.080 3.7.2: value 1 indicates SS-Protocol version 3 and phase 2 error handling.
|
|
// 00 what is this? Maybe it has to be word aligned?
|
|
// -------------
|
|
// Here is another, I typed *78#: 1b7b1c13a1110201010201 3b 30 09 04 01 0f 04 04 aa 1b 6e 04 7f 01 00
|
|
// This time: operation code 0x3b == processUnstructuredSSRequest
|
|
// 30 Sequence Tag
|
|
// 09 sequence length (guessing)
|
|
// 04 - octet?
|
|
// 01 length of param
|
|
// 0f == CBS Data Coding scheme language unspecified. (23.038 5)
|
|
// 04 - octet?
|
|
// 04 length of param
|
|
// aa1b6e04 is 23.038 6.1.2.3 UUSD packing of "*78#"
|
|
|
|
// Here is #78#:
|
|
// 0b7b1c13a11102010002013b300904010f0404a31b6e047f0100
|
|
// ...
|
|
// 30 sequence tag
|
|
// 09 seq len
|
|
// 04 first param is octet
|
|
// 01 first param len
|
|
// 0f
|
|
// 04 second param it octet
|
|
// 04 second param len
|
|
// a31b6e04
|
|
// 7f0100 what is this?
|
|
|
|
// =======
|
|
// This is what the blackberry sent after being sent a Facility message:
|
|
// rawdata=a203020100
|
|
// a2 is ReturnResult
|
|
// 03 length
|
|
// 02 Component ID = invokeId
|
|
// 01 length
|
|
// 00 invokeID was 0.
|
|
// and no args, so I guess not much result, huh.
|
|
|
|
|
|
namespace Control {
|
|
using namespace SIP;
|
|
|
|
struct SSParseExcept {
|
|
string mErrorMessage;
|
|
SSParseExcept(string message) : mErrorMessage(message) {}
|
|
};
|
|
|
|
// From GSM 4.80 Annex A.
|
|
enum SSOperationCode {
|
|
registerSS = 10,
|
|
eraseSS = 11,
|
|
activateSS = 12,
|
|
deactivateSS = 13,
|
|
interrogateSS = 14,
|
|
notifySS = 16,
|
|
registerPassword = 17,
|
|
getPassword = 18,
|
|
processUnstructuredSSData = 19, // 0x13 USSD version 1 MS->network request.
|
|
forwardCheckSSIndication = 38,
|
|
processUnstructuredSSRequest = 59, // 0x3b USSD version 2 MS->network request.
|
|
unstructuredSSRequest = 60,
|
|
unstructuredSSNotify = 61, // version 2 notification.
|
|
eraseCCEntry = 77,
|
|
callDeflection = 117,
|
|
userUserService = 118,
|
|
accessRegisterCCEntry = 119,
|
|
forwardCUGInfo = 120,
|
|
splitMPTY = 121,
|
|
retrieveMPTY = 122,
|
|
holdMPTY = 123,
|
|
buildMPTY = 124,
|
|
forwardChargeAdvice = 125,
|
|
explicitCT = 126,
|
|
lcsLocationNotification = 116,
|
|
lcsMOLR = 115
|
|
};
|
|
|
|
// Decode the unstructured data string as per 23.038, 7bit chars to 8bit chars.
|
|
static string ussdDecode(string in)
|
|
{
|
|
LOG(DEBUG) << LOGVAR(in.size()) <<" " <<data2hex(in.data(),in.size());
|
|
// The maximum size is very limited so we can use a presized buffer.
|
|
unsigned char result[250], *rp = result;
|
|
unsigned offset = 0;
|
|
unsigned accum = 0;
|
|
const unsigned char *indata = (const unsigned char*)in.data();
|
|
unsigned indatalen = in.size();
|
|
for (unsigned i = 0; i < indatalen; i++) {
|
|
accum = accum | (indata[i] << offset);
|
|
//LOG(DEBUG) <<LOGHEX(i)<<LOGHEX(offset)<<LOGHEX(accum)<<LOGHEX(indata[i]);
|
|
*rp++ = decodeGSMChar(accum & 0x7f);
|
|
accum >>= 7;
|
|
// When we have accumulated two 7-bit characters, we output them both.
|
|
if (++offset == 7) {
|
|
*rp++ = decodeGSMChar(accum & 0x7f);
|
|
accum >>= 7;
|
|
offset = 0;
|
|
}
|
|
}
|
|
// Special goofy case as per 23.038 6.1.2.3 - If there are exactly 7 bits at the end they are padded with a CR,
|
|
// which we must remove. Update: I dont think we are required to remove the extra CR, could just leave it.
|
|
int len = rp - result;
|
|
if (len && len % 8 == 0 && result[len-1] == '\r') { len--; }
|
|
return string((char*)result,len);
|
|
};
|
|
|
|
// Encode the string as per 23.038, packing 8bit chars into 7bit chars packed tightly.
|
|
static string ussdEncode(string in)
|
|
{
|
|
unsigned char result[280], *rp = result;
|
|
const unsigned char *indata = (const unsigned char*)in.data();
|
|
unsigned indatalen = in.size();
|
|
unsigned offset = 0;
|
|
unsigned accum = 0;
|
|
for (unsigned i = 0; i < indatalen; i++) {
|
|
accum = accum | (encodeGSMChar(indata[i] & 0x7f) << offset);
|
|
if (offset == 0) {
|
|
offset = 7;
|
|
} else {
|
|
*rp++ = accum & 0xff;
|
|
accum >>= 8;
|
|
offset--;
|
|
}
|
|
}
|
|
if (offset == 1) {
|
|
// Special case as per 23.038 6.1.2.3 - If there are exactly 7 bits at the end they are padded with a CR.
|
|
accum |= ('\r' << 1);
|
|
}
|
|
if (offset) {
|
|
*rp++ = accum & 0xff;
|
|
}
|
|
return string((char*)result,rp-result);
|
|
}
|
|
|
|
|
|
// This class is just a wrapper to conveniently contain the ssCodes map.
|
|
class SupServCodes
|
|
{
|
|
map<unsigned,string> ssCodes;
|
|
|
|
void addSSCode(unsigned ssCode,string serviceCodeDigits)
|
|
{
|
|
ssCodes[ssCode] = serviceCodeDigits;
|
|
}
|
|
|
|
|
|
void ssCodesInit()
|
|
{
|
|
// the mapId is a MAP-SS-Code 22.030 Annex B and
|
|
// from http://cpansearch.perl.org/src/REHSACK/Net-Radio-Location-SUPL-Test-0.001/asn1src/MAP-SS-Code.asn
|
|
addSSCode(0xa1,"75"); // eMLPP. 22.067 Also 75n n=0..4 ehanced Multilevel Precedence Pre-emption service.
|
|
addSSCode(0x24,"66"); // CD. Call Deflection 22.072
|
|
addSSCode(0x11, "30"); // CLIP 22.081 Calling Line Identification Presentation
|
|
addSSCode(0x12, "31"); // CLIR Calling Line Identification Restriction
|
|
addSSCode(0x13, "76"); // COLP Connected Line Identification Presentation
|
|
addSSCode(0x14, "77"); // COLR Connected Line Identification Restriction
|
|
// In temporary mode, to suppress CLIR for a single call, enter: ' * 31 # <called number> SEND '
|
|
// In temporary mode, to invoke CLIR for a single call enter: ' # 31 # <called number> SEND '
|
|
addSSCode(0x41, "21"); // CFU 22.082
|
|
addSSCode(0x29, "67"); // CFB call forward Busy
|
|
addSSCode(0x2a, "61"); // cfnry CF No Reply
|
|
addSSCode(0x2b,"62"); // cfnrc CF Not Reachable
|
|
addSSCode(0x20,"002"); // all CallForwardingSS
|
|
addSSCode(0x28,"004"); // all conditional CF
|
|
addSSCode(0x41,"43"); // WAIT 22.083 Call waiting?
|
|
// I think HOLD and MultiParty are handled by L3 messages at the MS level, and these MAP-SS-Codes are used only at the MSC level.
|
|
//addSSCode(0x42,??); // HOLD see section 4.5.5. Ha ha, there is no such section in either 22.030 or 22.083. It is in 2.30.
|
|
//addSSCode(0x51, ??); // MPTTY see 4.5.5 22.084
|
|
addSSCode(0x81,"361"); // UUS Service 1 22.087
|
|
addSSCode(0x82,"362"); // UUS Service 2 22.087
|
|
addSSCode(0x83,"363"); // UUS Service 3 22.087
|
|
addSSCode(0x80, "360"); // all UUS Services. allAdiitionalInfoTransferSS reserved for future use?
|
|
|
|
// SS-Codes 0x1a to 0x1f are reserved for future use.
|
|
|
|
|
|
addSSCode(0x90, "330"); // all Barring Serv.
|
|
addSSCode(0x91, "??"); // barringOfOutgoingCalls. This has a separate code other than baoc.
|
|
addSSCode(0x92, "33"); // BAOC 22.088 barring of outgoing calls.
|
|
addSSCode(0x93,"331"); // BAOIC barring of outgoing international calls.
|
|
addSSCode(0x94, "332"); // BAOIC exc home country
|
|
addSSCode(0x9a, "35"); // BAIC barring of incoming calls
|
|
addSSCode(0x9b, "351"); // BAIC roaming
|
|
//addSSCode "333"); // Outg. Barr. Serv.
|
|
//addSSCode "353"); // Inc. Barr. Serv.
|
|
addSSCode(0x31,"96"); // ECT 22.091 Explicit Call Transfer
|
|
addSSCode(0x43, "37"); // CCBS-A 22.093 Completion of Call to Busy Subscribers.
|
|
// CCBS-A SS-code is used only in insert,delete,interrogate SS
|
|
addSSCode(0x44, "37"); // CCBS-B This SS-code is used only in insert,delete,interrogate SS
|
|
// addSSCode(??,"214"); // FM 22.094
|
|
addSSCode(0x19, "300"); // CNAP 22.096
|
|
//addSSCode(??, "591"); // MSP 22.097 also 592,593,594
|
|
// 0x51 CUG closed user group
|
|
addSSCode(0x45,"88"); // MC 22.135 Multicall
|
|
// 22.030 Annex C
|
|
addSSCode(0xb0,"50");
|
|
addSSCode(0xb1,"51");
|
|
addSSCode(0xb2,"52");
|
|
addSSCode(0xb3,"53");
|
|
addSSCode(0xb4,"54");
|
|
addSSCode(0xb5,"55");
|
|
addSSCode(0xb6,"56");
|
|
addSSCode(0xb7,"57");
|
|
addSSCode(0xb8,"58");
|
|
addSSCode(0xb9,"59");
|
|
addSSCode(0xba,"60");
|
|
addSSCode(0xbb,"61");
|
|
addSSCode(0xbc,"62");
|
|
addSSCode(0xbd,"63");
|
|
addSSCode(0xbe,"64");
|
|
addSSCode(0xbf,"65");
|
|
|
|
// aoci advice of charge information??
|
|
// aocc advice of charge charging??
|
|
// A bunch of location services.
|
|
}
|
|
|
|
public:
|
|
string ssMapIdToServiceCodeDigits(unsigned mapId)
|
|
{
|
|
if (ssCodes.size() == 0) ssCodesInit();
|
|
map<unsigned,string>::const_iterator foo = ssCodes.find(mapId);
|
|
if (foo != ssCodes.end()) { return foo->second; }
|
|
return format("unknownMapId(0x%x)",mapId);
|
|
}
|
|
} supServCodes; // The one and only instance of this class.
|
|
|
|
class SSMapCommand
|
|
{
|
|
enum SSComponentTypeTag {
|
|
ssInvokeTag = 0xa1,
|
|
ssReturnResultTag = 0xa2,
|
|
ssReturnErrorTag = 0xa3,
|
|
ssRejectTag = 0xa4
|
|
};
|
|
|
|
enum SSComponentIdTag {
|
|
ssInvokeIDTag = 0x02,
|
|
ssLinkedIDTag = 0x80,
|
|
};
|
|
|
|
static const unsigned ssOperationCodeTag = 0x02;
|
|
|
|
enum SSParameterListTag {
|
|
ssSequenceTag = 0x30,
|
|
ssSetTag = 0x31,
|
|
};
|
|
enum SSParameterTypeTag {
|
|
ssOctetParam = 0x04,
|
|
};
|
|
|
|
|
|
SSComponentTypeTag ssComponentType;
|
|
SSOperationCode ssOpCode;
|
|
vector<string> ssParams;
|
|
Bool_z ssParseSuccess;
|
|
|
|
public:
|
|
unsigned ssInvokeID;
|
|
unsigned ssLinkID;
|
|
|
|
string text()
|
|
{
|
|
string result = format("ComponentType=0x%x invokeID=0x%x linkID=0x%x opCode=0x%x ok=%d",
|
|
ssComponentType,ssInvokeID,ssLinkID,ssOpCode,(int)ssParseSuccess);
|
|
for (vector<string>::iterator it = ssParams.begin(); it != ssParams.end(); it++) {
|
|
result += " <" + data2hex(it->data(),it->size()) + ">";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
string getParam(unsigned paramNum,const char *opname)
|
|
{
|
|
if (paramNum >= ssParams.size()) {
|
|
throw SSParseExcept(format("%s wrong number of params (%u)",opname,ssParams.size()));
|
|
}
|
|
return ssParams[paramNum];
|
|
}
|
|
|
|
unsigned ssParseLinkedID(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
// This is an optional componenent, whatever that means
|
|
if (data[datai] != ssLinkedIDTag) { return 0; }
|
|
datai++;
|
|
unsigned linkIDLen = data[datai++];
|
|
if (linkIDLen != 1) { throw SSParseExcept(format("unexpected link ID length: %d",linkIDLen)); }
|
|
unsigned linkID = data[datai++];
|
|
return linkID;
|
|
}
|
|
unsigned ssParseInvokeID(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
if (data[datai++] != ssInvokeIDTag) { throw SSParseExcept("missing required Invoke ID Tag"); }
|
|
unsigned invokeIDLen = data[datai++];
|
|
if (invokeIDLen != 1) { throw SSParseExcept(format("unexpected invoke ID length: %u",invokeIDLen)); }
|
|
unsigned invokeID = data[datai++];
|
|
return invokeID;
|
|
}
|
|
|
|
SSOperationCode ssParseOperationCode(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
if (data[datai++] != ssOperationCodeTag) { throw SSParseExcept("no operation code tag"); }
|
|
unsigned operationCodeLen = data[datai++];
|
|
if (operationCodeLen != 1) { throw SSParseExcept(format("unexpected operation code length: %u at %u",operationCodeLen,datai)); }
|
|
SSOperationCode ssOpCode = (SSOperationCode) data[datai++]; // Finally, the piece of data we want.
|
|
return ssOpCode;
|
|
}
|
|
|
|
string ssParseParameter(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
SSParameterTypeTag tag = (SSParameterTypeTag) data[datai++];
|
|
if (tag != ssOctetParam) {
|
|
throw SSParseExcept(format("Unexpected parameter type %u, expected %u at %u",tag,ssOctetParam,datai));
|
|
}
|
|
unsigned paramLen = data[datai++];
|
|
if (paramLen > datalen-datai) { throw SSParseExcept(format("parameter len (%u) exceeds parameter list len (%u)",paramLen,datalen)); }
|
|
// Finally, a parameter:
|
|
string result = string((char*)&data[datai],paramLen);
|
|
LOG(DEBUG) <<LOGVAR(paramLen)<<LOGVAR(datalen)<<LOGVAR(datai)<<LOGVAR(result.size());
|
|
datai += paramLen;
|
|
return result;
|
|
}
|
|
|
|
void ssParseParameterList(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
SSParameterListTag tag = (SSParameterListTag) data[datai++];
|
|
if (tag != ssSequenceTag && tag != ssSetTag) { throw SSParseExcept(format("unexpected parameter tag: %u",tag)); }
|
|
unsigned paramsLen = data[datai++];
|
|
if (paramsLen < datalen-datai) { throw SSParseExcept(format("parameter len (%u) exceeds component len (%u) at %u",paramsLen,datalen-datai,datai)); }
|
|
ssParams.clear();
|
|
ssParams.reserve(6);
|
|
unsigned adjustedLen = datai + paramsLen;
|
|
LOG(DEBUG)<<LOGVAR(datai)<<LOGVAR(datalen)<<LOGVAR(paramsLen)<<LOGVAR(adjustedLen);
|
|
while (datai < adjustedLen) {
|
|
ssParams.push_back(ssParseParameter(data,adjustedLen,datai));
|
|
LOG(DEBUG)<<LOGVAR(datai)<<LOGVAR(datalen)<<LOGVAR(paramsLen)<<LOGVAR(adjustedLen);
|
|
};
|
|
#if 0
|
|
const char*paramsStart = data+datai;
|
|
unsigned parami = 0;
|
|
while (parami < paramsLen) {
|
|
LOG(DEBUG)<<LOGVAR(datai)<<LOGVAR(datalen)<<LOGVAR(paramsLen);
|
|
ssParams.push_back(ssParseParameter(paramsStart,paramsLen,parami));
|
|
};
|
|
datai += parami;
|
|
#endif
|
|
}
|
|
|
|
// Parse the map message in the SS Facility IE.
|
|
void ssParseDataInternal(const unsigned char *data, unsigned datalen, unsigned &datai)
|
|
{
|
|
if (datalen < 2) {
|
|
throw SSParseExcept("data too short");
|
|
}
|
|
|
|
ssComponentType = (SSComponentTypeTag) data[datai++];
|
|
unsigned componentLen = data[datai++];
|
|
if (componentLen > datalen-datai) { throw SSParseExcept(format("component len (%u) exceeds data len-2 (%u) at %u",componentLen,datalen,datai)); }
|
|
ssInvokeID = ssParseInvokeID(data,componentLen,datai);
|
|
ssLinkID = ssParseLinkedID(data,datalen,datai);
|
|
ssOpCode = ssParseOperationCode(data,componentLen,datai);
|
|
ssParseParameterList(data,componentLen,datai);
|
|
}
|
|
|
|
public:
|
|
|
|
// The data is the 'components" section of the facility IE.
|
|
bool ssParseData(const unsigned char *data, unsigned datalen)
|
|
{
|
|
try {
|
|
unsigned datai = 0;
|
|
ssParseDataInternal(data, datalen, datai);
|
|
ssParseSuccess = true;
|
|
return true;
|
|
} catch (SSParseExcept &err) {
|
|
LOG(ERR) << "Error parsing Supplementary Service message:"<<err.mErrorMessage;
|
|
} catch (...) {
|
|
LOG(ERR) << "Unknown error parsing Supplementary Service message"; // should never happen.
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SSMapCommand(const unsigned char *data, unsigned datalen) { ssParseData(data, datalen); }
|
|
|
|
// Pull the USSD string out of the map command and return it.
|
|
string ssGetUssd()
|
|
{
|
|
if (!ssParseSuccess) { return ""; }
|
|
// TODO: something about this?
|
|
switch (ssComponentType) {
|
|
case ssInvokeTag:
|
|
case ssReturnResultTag:
|
|
case ssReturnErrorTag:
|
|
case ssRejectTag:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
string ussd;
|
|
const char *opname;
|
|
switch (ssOpCode) {
|
|
case processUnstructuredSSData: // USSD version 1 MS->network request.
|
|
opname = "processUnstructuredSSData";
|
|
ussd = ussdDecode(getParam(0,opname));
|
|
break;
|
|
case unstructuredSSNotify: // version 2 notification.
|
|
opname = "unstructuredSSNotify";
|
|
ussd = ussdDecode(getParam(1,opname));
|
|
break;
|
|
case processUnstructuredSSRequest: // USSD version 1 MS->network request.
|
|
opname = "processUnstructuredSSRequest";
|
|
ussd = ussdDecode(getParam(1,opname));
|
|
break;
|
|
case activateSS:
|
|
opname = "activateSS";
|
|
ussd = format("*%s#",supServCodes.ssMapIdToServiceCodeDigits(getParam(0,opname)[0]));
|
|
break;
|
|
case registerSS:
|
|
opname = "registerSS";
|
|
ussd = "**";
|
|
addParams:
|
|
ussd += supServCodes.ssMapIdToServiceCodeDigits(getParam(0,opname)[0]);
|
|
for (unsigned i = 1; i < ssParams.size(); i++) {
|
|
ussd += "*";
|
|
ussd += getParam(i,opname);
|
|
}
|
|
ussd += "#";
|
|
break;
|
|
case deactivateSS:
|
|
opname = "deactivateSS";
|
|
ussd = "#";
|
|
goto addParams;
|
|
case interrogateSS:
|
|
opname = "interrogateSS";
|
|
ussd = "*#";
|
|
goto addParams;
|
|
case eraseSS:
|
|
opname = "eraseSS";
|
|
ussd = "##";
|
|
goto addParams;
|
|
default:
|
|
ussd = format("(unimplemented MAP SS-OP-CODE 0x%x)",ssOpCode);
|
|
}
|
|
return ussd;
|
|
}
|
|
};
|
|
|
|
|
|
#if TODO_IN_PROGRESS
|
|
// There is a huge NonCall SS message parser in features/ussd, but USSD uses only a tiny subset
|
|
// of the SS Facility, so we will code it directly here.
|
|
// 3GPP 24.80 3.6
|
|
class L3USSDMessage {
|
|
string ussdDataCodingScheme;
|
|
string ussdString;
|
|
bool ussdParse(int len, const unsigned char *components) {
|
|
}
|
|
};
|
|
#endif
|
|
|
|
string ssMap2Ussd(const unsigned char *mapcmd,unsigned maplen)
|
|
{
|
|
SSMapCommand cmd(mapcmd,maplen);
|
|
LOG(DEBUG) << cmd.text();
|
|
return cmd.ssGetUssd();
|
|
}
|
|
|
|
// SS can be in-call or stand-alone via CMServiceRequest. This is the latter.
|
|
class MOSSDMachine : public SSDBase {
|
|
enum State { // These are the machineRunState states for our State Machine.
|
|
stateStartUnused, // unused.
|
|
stateSSIdentResult
|
|
};
|
|
bool mIdentifyResult;
|
|
unsigned mInvokeId; // Copied from the USSD request to the USSD response.
|
|
public:
|
|
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
|
|
MOSSDMachine(TranEntry *wTran) : SSDBase(wTran) {}
|
|
const char *debugName() const { return "MOSSDMachine"; }
|
|
void sendUssdMsg(string ussdin, bool final);
|
|
};
|
|
|
|
// (pat) Used for SS messages arriving in Call Control messsages.
|
|
// Should be called mishandle, since I dont know what to do about this case.
|
|
// When I tried to send USSD messages within a call on the Blackberry, it disallowed it,
|
|
// so I dont really know what SS messages might show up within call control messages.
|
|
MachineStatus SSDBase::handleSSMessage(const GSM::L3Message*l3msg)
|
|
{
|
|
// TODO: If there is an SS machine running, send the message there, otherwise error out.
|
|
switch (l3msg->MTI()) {
|
|
case L3SupServMessage::Register: {
|
|
const L3SupServRegisterMessage *ssregmsg = dynamic_cast<typeof(ssregmsg)>(l3msg);
|
|
LOG(ERR) << "Unexpected SS Register message:"<<ssregmsg;
|
|
return MachineStatusOK;
|
|
}
|
|
case L3SupServMessage::Facility: {
|
|
const L3SupServFacilityMessage *ssfacmsg = dynamic_cast<typeof(ssfacmsg)>(l3msg);
|
|
LOG(ERR) << "Unexpected SS Facility message:"<<ssfacmsg;
|
|
return MachineStatusOK;
|
|
}
|
|
case L3SupServMessage::ReleaseComplete: {
|
|
const L3SupServReleaseCompleteMessage *ssrelmsg = dynamic_cast<typeof(ssrelmsg)>(l3msg);
|
|
LOG(ERR) << "Unexpected SS Release Complete message:"<<ssrelmsg;
|
|
return MachineStatusOK;
|
|
}
|
|
}
|
|
return MachineStatusOK; // not reached but makes g++ happy
|
|
}
|
|
|
|
void MOSSDMachine::sendUssdMsg(string ussdin, bool final)
|
|
{
|
|
string ussd = ussdEncode(ussdin);
|
|
|
|
// Make the stupid MAP message.
|
|
char mapbuf[260];
|
|
mapbuf[0] = 0xa1; // component type tag = Invoke (24.080 3.6.2)
|
|
mapbuf[1] = ussd.size() + 13; // component length.
|
|
mapbuf[2] = 0x02; // component id tag == InvokeID
|
|
mapbuf[3] = 0x01; // invoke id length
|
|
mapbuf[4] = this->mInvokeId; // Must be copied from original USSD request.
|
|
mapbuf[5] = 0x02; // OperationCodeTag
|
|
mapbuf[6] = 0x01; // OperationCode length
|
|
mapbuf[7] = unstructuredSSNotify; // The MAP message type.
|
|
mapbuf[8] = 0x30; // Sequence tag.
|
|
mapbuf[9] = ussd.size() + 5; // Sequence length.
|
|
// First argument is language type.
|
|
mapbuf[10] = 0x04; // octet data?
|
|
mapbuf[11] = 1; // length of param
|
|
mapbuf[12] = 0x0f; // Default alphabet. TODO - check this.
|
|
// Second argument is the encoded ussd data.
|
|
mapbuf[13] = 0x04; // octet data?
|
|
mapbuf[14] = ussd.size(); // length of param
|
|
memcpy(&mapbuf[15],ussd.data(),ussd.size());
|
|
|
|
string components = string(mapbuf,15+ussd.size());
|
|
LOG(DEBUG) << "map="<<data2hex(components.data(),components.size());
|
|
L3SupServFacilityIE facility(components);
|
|
if (final) { // Is it the final USSD message?
|
|
L3SupServReleaseCompleteMessage ssrelease(getL3TI(), facility);
|
|
channel()->l3sendm(ssrelease);
|
|
} else {
|
|
L3SupServFacilityMessage ssfac(getL3TI(), facility);
|
|
channel()->l3sendm(ssfac);
|
|
}
|
|
}
|
|
|
|
|
|
void startMOSSD(const L3CMServiceRequest*cmsrq,MMContext *mmchan)
|
|
{
|
|
LOG(DEBUG) <<mmchan;
|
|
TranEntry *tran = TranEntry::newMOSSD(mmchan);
|
|
string proxyUssd = gConfig.getStr("SIP.Proxy.USSD");
|
|
if (proxyUssd.size() == 0) {
|
|
// Disabled. Reject USSD immediately.
|
|
mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported));
|
|
return;
|
|
}
|
|
|
|
MOSSDMachine *ssmp = new MOSSDMachine(tran);
|
|
// The message is CMServiceRequest.
|
|
tran->lockAndStart(ssmp,(GSM::L3Message*)cmsrq);
|
|
}
|
|
|
|
MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
|
|
switch (state) {
|
|
case L3CASE_MM(CMServiceRequest): {
|
|
timerStart(TCancel,30*1000,TimerAbortTran); // Just in case.
|
|
const L3CMServiceRequest *req = dynamic_cast<typeof(req)>(l3msg);
|
|
const GSM::L3MobileIdentity &mobileID = req->mobileID(); // Reference ok - the SM is going to copy it.
|
|
|
|
// FIXME: We should only identify this the FIRST time.
|
|
return machPush(new L3IdentifyMachine(tran(),mobileID, &mIdentifyResult), stateSSIdentResult);
|
|
}
|
|
case stateSSIdentResult: {
|
|
if (! mIdentifyResult) {
|
|
//const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information);
|
|
channel()->l3sendm(L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information));
|
|
return MachineStatus::QuitTran(TermCause::Local(L3RejectCause::Invalid_Mandatory_Information));
|
|
}
|
|
|
|
PROCLOG(DEBUG) << "sending CMServiceAccept";
|
|
WATCH("SS CMServiceAccept");
|
|
channel()->l3sendm(GSM::L3CMServiceAccept()); // On SAPI0
|
|
|
|
gReports.incr("OpenBTS.GSM.SMS.MOSMS.Start");
|
|
return MachineStatusOK;
|
|
}
|
|
|
|
case L3CASE_SS(L3SupServMessage::Register): {
|
|
const L3SupServRegisterMessage *regp = dynamic_cast<typeof(regp)>(l3msg);
|
|
// The TI comes from the other side, so we have to set the high bit when we respond.
|
|
tran()->setL3TI(0x8 | regp->TI());
|
|
WATCH("SS Register " << regp);
|
|
string content = regp->getMapComponents();
|
|
SSMapCommand mapcmd((const unsigned char *)content.data(),content.size());
|
|
string ussd = mapcmd.ssGetUssd();
|
|
this->mInvokeId = mapcmd.ssInvokeID;
|
|
SIP::SipDialog::newSipDialogMOUssd(tran()->tranID(),tran()->subscriber(),ussd,channel());
|
|
return MachineStatusOK;
|
|
}
|
|
case L3CASE_SS(L3SupServMessage::Facility): {
|
|
const L3SupServFacilityMessage *facp = dynamic_cast<typeof(facp)>(l3msg);
|
|
WATCH("SS Facility " << facp);
|
|
return MachineStatusOK;
|
|
}
|
|
case L3CASE_SS(L3SupServMessage::ReleaseComplete): {
|
|
const L3SupServFacilityMessage *relp = dynamic_cast<typeof(relp)>(l3msg);
|
|
WATCH("SS ReleaseComplete" << relp);
|
|
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success));
|
|
}
|
|
|
|
case L3CASE_SIP(dialogBye): {
|
|
if (sipmsg == NULL) {
|
|
LOG(ERR) << "USSD client error: missing BYE message";
|
|
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Error));
|
|
}
|
|
const DialogUssdMessage *umsg = dynamic_cast<typeof(umsg)>(sipmsg);
|
|
if (umsg == NULL) {
|
|
LOG(ERR) << "USSD client error: could not convert DialogMessage to DialogUssdMessage "<<sipmsg;
|
|
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Error));
|
|
}
|
|
string result = umsg->dmMsgPayload;
|
|
// Send it to the MS.
|
|
// Sending the message in the Facility IE of ReleaseComplete did not work,
|
|
// but sending a separate Facility message does, so fine, do it that way.
|
|
sendUssdMsg(result, false); // Send an L3Facility message.
|
|
L3SupServReleaseCompleteMessage ssrelease(getL3TI());
|
|
channel()->l3sendm(ssrelease);
|
|
//sendUssdMsg(result, true);
|
|
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success));
|
|
}
|
|
|
|
default:
|
|
LOG(DEBUG) << "unexpected state";
|
|
return unexpectedState(state,l3msg);
|
|
}
|
|
}
|
|
|
|
}; // namespace
|