/* * 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 #include #include "L3SupServ.h" #include "L3TranEntry.h" #include "L3MMLayer.h" #include #include #include #include #include #include // 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()) <<" " <>= 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 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 # SEND ' // In temporary mode, to invoke CLIR for a single call enter: ' # 31 # 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::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 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::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) < 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:"<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(l3msg); LOG(ERR) << "Unexpected SS Register message:"<(l3msg); LOG(ERR) << "Unexpected SS Facility message:"<(l3msg); LOG(ERR) << "Unexpected SS Release Complete message:"<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="<l3sendm(ssrelease); } else { L3SupServFacilityMessage ssfac(getL3TI(), facility); channel()->l3sendm(ssfac); } } void startMOSSD(const L3CMServiceRequest*cmsrq,MMContext *mmchan) { LOG(DEBUG) <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)<subscriber()); switch (state) { case L3CASE_MM(CMServiceRequest): { timerStart(TCancel,30*1000,TimerAbortTran); // Just in case. const L3CMServiceRequest *req = dynamic_cast(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(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(l3msg); WATCH("SS Facility " << facp); return MachineStatusOK; } case L3CASE_SS(L3SupServMessage::ReleaseComplete): { const L3SupServFacilityMessage *relp = dynamic_cast(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(sipmsg); if (umsg == NULL) { LOG(ERR) << "USSD client error: could not convert DialogMessage to DialogUssdMessage "<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