diff --git a/.gitmodules b/.gitmodules index fe8d14b..65bd2bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "SR"] - path = SR - url = git@github.com:RangeNetworks/subscriberRegistry.git - branch = master [submodule "CommonLibs"] path = CommonLibs url = git@github.com:RangeNetworks/CommonLibs.git @@ -10,3 +6,7 @@ path = sqlite3 url = git@github.com:RangeNetworks/sqlite3.git branch = master +[submodule "NodeManager"] + path = NodeManager + url = git@github.com:RangeNetworks/NodeManager.git + branch = 4.0 diff --git a/CLI/CLI.cpp b/CLI/CLI.cpp index 5a06b69..1f49173 100644 --- a/CLI/CLI.cpp +++ b/CLI/CLI.cpp @@ -1,7 +1,7 @@ /* * Copyright 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -24,7 +24,7 @@ #include #include #include -#include +#include // for sort() #include @@ -33,20 +33,26 @@ #include #include -#include -#include +#include +#include #include #include #include #include -#include #include #include #include #include +#include +#include +#include +#include + +std::string getARFCNsString(unsigned band); namespace SGSN { - extern int sgsnCLI(int argc, char **argv, std::ostream &os); + // Hack. + extern CommandLine::CLIStatus sgsnCLI(int argc, char **argv, std::ostream &os); }; #include @@ -55,18 +61,12 @@ namespace SGSN { #undef WARNING -using namespace std; -using namespace CommandLine; - -#define SUCCESS 0 -#define BAD_NUM_ARGS 1 -#define BAD_VALUE 2 -#define NOT_FOUND 3 -#define TOO_MANY_ARGS 4 -#define FAILURE 5 - extern TransceiverManager gTRX; +namespace CommandLine { +using namespace std; +using namespace Control; + /** Standard responses in the CLI, much mach erorrCode enum. */ static const char* standardResponses[] = { "success", // 0 @@ -78,50 +78,147 @@ static const char* standardResponses[] = { }; +struct CLIParseError { + string msg; + CLIParseError(string wMsg) : msg(wMsg) {} +}; -int Parser::execute(char* line, ostream& os) const + +CLIStatus Parser::execute(char* line, ostream& os) const { LOG(INFO) << "executing console command: " << line; // tokenize char *argv[mMaxArgs]; int argc = 0; - char **ap; // This is (almost) straight from the man page for strsep. - for (ap=argv; (*ap=strsep(&line," ")) != NULL; ) { - if (**ap != '\0') { - if (++ap >= &argv[mMaxArgs]) break; - else argc++; + while (line && argc < mMaxArgs) { + while (*line == ' ') { line++; } + if (! *line) { break; } + char *anarg = line; + if (*line == '"') { // We allow a quoted string as a single argument. Quotes themselves are removed. + line++; anarg++; + char *endquote = strchr(line,'"'); + if (endquote == NULL) { + os << "error: Missing quote."<= &argv[mMaxArgs]) break; + // else argc++; + // } + //} // Blank line? if (!argc) return SUCCESS; // Find the command. + //printf("args=%d\n",argc); + //for (int i = 0; i < argc; i++) { printf("argv[%d]=%s\n",i,argv[i]); } ParseTable::const_iterator cfp = mParseTable.find(argv[0]); if (cfp == mParseTable.end()) { return NOT_FOUND; } - int (*func)(int,char**,ostream&); + CLICommand func; func = cfp->second; // Do it. - int retVal = (*func)(argc,argv,os); + CLIStatus retVal; + try { + retVal = (*func)(argc,argv,os); + } catch (CLIParseError &pe) { + os << pe.msg << endl; + retVal = SUCCESS; // Dont print any further messages. + } // Give hint on bad # args. if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl; return retVal; } -int Parser::process(const char* line, ostream& os) const +// This is called from a runloop in apps/OpenBTS.cpp +// If it returns a negative number OpenBTS exists. +CLIStatus Parser::process(const char* line, ostream& os) const { + static Mutex oneCommandAtATime; + ScopedLock lock(oneCommandAtATime); char *newLine = strdup(line); - int retVal = execute(newLine,os); + CLIStatus retVal = execute(newLine,os); free(newLine); - if (retVal>0) os << standardResponses[retVal] << endl; + if (retVal < 0 || retVal > FAILURE) { + os << "Unrecognized CLI command exit status: "< cliParse(int &argc, char **&argv, ostream &os, const char *optstring) +{ + map options; // The result + // Skip the command name. + argc--, argv++; + // Parse args. + for ( ; argc > 0; argc--, argv++ ) { + char *arg = argv[0]; + // The argv to match may not contain ':' to prevent the pathological case, for example, where optionlist contains "-a:" and command line arg is "-a:" + if (strchr(arg,':')) { return options; } // Can't parse this, too dangerous. + const char *op = strstr(optstring,arg); + if (op && (op == optstring || op[-1] == ' ')) { + const char *ep = op + strlen(arg); + if (*ep == ':') { + // This valid option requires an argument. + argc--, argv++; + if (argc <= 0) { throw CLIParseError(format("expected argument after: %s",arg)); } + options[arg] = string(argv[0]); + continue; + } else if (*ep == 0 || *ep == ' ') { + // This valid option does not require an argument. + options[arg] = string("true"); + continue; + } else { + // Partial match of something in optstring; drop through to treat it like any other argument. + } + } else { + break; // Return when we find the first non-option. + } + // An argument beginning with - and not in optstring is an unrecognized option and is an error. + if (*arg == '-') { throw CLIParseError(format("unrecognized argument: %s",arg)); } + return options; + } + return options; +} + /**@name Commands for the CLI. */ //@{ // forward refs -int printStats(int argc, char** argv, ostream& os); +static CLIStatus printStats(int argc, char** argv, ostream& os); /* A CLI command takes the argument in an array. @@ -143,7 +281,7 @@ int printStats(int argc, char** argv, ostream& os); */ /** Display system uptime and current GSM frame number. */ -int uptime(int argc, char** argv, ostream& os) +static CLIStatus uptime(int argc, char** argv, ostream& os) { if (argc!=1) return BAD_NUM_ARGS; os.precision(2); @@ -178,7 +316,7 @@ int uptime(int argc, char** argv, ostream& os) /** Give a list of available commands or describe a specific command. */ -int showHelp(int argc, char** argv, ostream& os) +static CLIStatus showHelp(int argc, char** argv, ostream& os) { if (argc==2) { os << argv[1] << " " << gParser.help(argv[1]) << endl; @@ -204,7 +342,7 @@ int showHelp(int argc, char** argv, ostream& os) /** A function to return -1, the exit code for the caller. */ -int exit_function(int argc, char** argv, ostream& os) +static CLIStatus exit_function(int argc, char** argv, ostream& os) { unsigned wait =0; if (argc>2) return BAD_NUM_ARGS; @@ -236,54 +374,95 @@ int exit_function(int argc, char** argv, ostream& os) os << endl << "exiting with loads:" << endl; printStats(1,NULL,os); } - os << endl << "exiting..." << endl; - return -1; + LOG(ALERT) << "exiting OpenBTS as directed by command line..."; // This is sent to the log file. + os << endl << "exiting..." << endl; // This is sent to OpenBTSCLI + // We have to return CLI_EXIT rather than just exiting so we can send the result to OpenBTSCLI. + return CLI_EXIT; } - -// Forward ref. -int tmsis(int argc, char** argv, ostream& os); - -/** Dump TMSI table to a text file. */ -int dumpTMSIs(const char* filename) -{ - ofstream fileout; - fileout.open(filename, ios::out); // erases existing! - // FIXME -- Check that the file really opened. - // Fake an argument list to call printTMSIs. - const char* subargv[] = {"tmsis", NULL}; - int subargc = 1; - // (pat) Cast makes gcc happy about const conversion. - return tmsis(subargc, const_cast(subargv), fileout); -} - - - - /** Print or clear the TMSI table. */ -int tmsis(int argc, char** argv, ostream& os) +static const char *tmsisHelp = "[-a | -l | -ll | -r | clear | dump [-l] | delete -tmsi | delete -imsi | query set name=value] --\n" + " default print the TMSI table; -l or -ll gives longer listing;\n" + " -a lists all TMSIs, default is to show most recent 100 in table\n" + " -r raw TMSI table listing\n" + " clear - clear the TMSI table;\n" + " dump - dump the TMSI table to specified filename;\n" + " delete - delete entry for specified imsi or tmsi;\n" + " set name=value - set TMSI database field name to value. If value is a string use apostrophes, eg: set IMSI='12345678901234'\n" + " query - run sql query, which may be quoted, eg: tmsis query \"UPDATE TMSI_TABLE SET AUTH=0 WHERE IMSI=='123456789012'\" This option may be removed in future." + ; +static CLIStatus tmsis(int argc, char** argv, ostream& os) { - if (argc>=2) { - // Clear? - if (strcmp(argv[1],"clear")==0) { - if (argc!=2) return BAD_NUM_ARGS; - os << "clearing TMSI table" << endl; - gTMSITable.clear(); - return SUCCESS; - } - // Dump? - if (strcmp(argv[1],"dump")==0) { - if (argc!=3) return BAD_NUM_ARGS; - os << "dumping TMSI table to " << argv[2] << endl; - return dumpTMSIs(argv[2]); - } - return BAD_VALUE; + // (pat) We used to allow just "dump" or "clear", so be backward compatible for a while. + map options = cliParse(argc,argv,os,"-a -l -ll -r dump: clear delete -imsi: -tmsi: query: set:"); + string imsiopt = options["-imsi"]; + string tmsiopt = options["-tmsi"]; + unsigned tmsi = strtoul(tmsiopt.c_str(),NULL,0); // No bad effect if option is empty. + string myquery; + if (argc) return BAD_NUM_ARGS; + int verbose = 0; + if (options.count("-l")) { verbose = 1; } + if (options.count("-ll")) { verbose = 2; } + bool showAll = options.count("-a"); + if (options.count("clear")) { + os << "clearing TMSI table" << endl; + gTMSITable.tmsiTabClear(); + return SUCCESS; } - - if (argc!=1) return BAD_NUM_ARGS; - os << "TMSI IMSI age used" << endl; - gTMSITable.dump(os); + if (options.count("dump")) { + ofstream fileout; + string filename = options["dump"]; + if (filename.size() == 0) { os << "bad filename"<;tag=%d\n" - "To: sip:IMSI%s@127.0.0.1\n" - "Call-ID: %x@127.0.0.1:%d\n" - "CSeq: 1 MESSAGE\n" - "Content-Type: text/plain\nContent-Length: %u\n" - "\n%s\n"; + "MESSAGE sip:IMSI%s@127.0.0.1 SIP/2.0\r\n" + "Via: SIP/2.0/TCP 127.0.0.1:%d;branch=%x\r\n" // (pat) The via MUST contain the port. + "Max-Forwards: 2\r\n" + "From: %s ;tag=%d\r\n" + "To: sip:IMSI%s@127.0.0.1\r\n" + "Call-ID: %x@127.0.0.1:%d\r\n" + "CSeq: 1 MESSAGE\r\n" + "Content-Type: text/plain\nContent-Length: %u\r\n" + "\r\n%s\r\n"; static char buffer[1500]; snprintf(buffer,1499,form, - IMSI, (unsigned)random(), srcAddr,srcAddr,sock.port(),(unsigned)random(), IMSI, (unsigned)random(),sock.port(), strlen(txtBuf), txtBuf); + IMSI, sock.port(), // via + (unsigned)random(), // branch + srcAddr,srcAddr,sock.port(), // from + (unsigned)random(), // tag + IMSI, // to imsi + (unsigned)random(), sock.port(), // Call-ID + strlen(txtBuf), txtBuf); // Content-Type and content. sock.write(buffer); os << "message submitted for delivery" << endl; @@ -351,8 +536,8 @@ int sendsimple(int argc, char** argv, ostream& os) } -/** Submit an SMS for delivery to an IMSI. */ -int sendsms(int argc, char** argv, ostream& os) +/** Submit an SMS for delivery to an IMSI on this BTS. */ +static CLIStatus sendsms(int argc, char** argv, ostream& os) { if (argc<4) return BAD_NUM_ARGS; @@ -360,40 +545,38 @@ int sendsms(int argc, char** argv, ostream& os) char *srcAddr = argv[2]; string rest = ""; for (int i=3; imessageType("text/plain"); - Control::initiateMTTransaction(transaction,GSM::SDCCHType,30000); + // We just use the IMSI, dont try to find a tmsi. + FullMobileId msid(IMSI); + Control::TranEntry *tran = Control::TranEntry::newMTSMS( + NULL, // No SIPDialog + msid, + GSM::L3CallingPartyBCDNumber(srcAddr), + rest, // message body + string("text/plain")); // messate content type + Control::gMMLayer.mmAddMT(tran); os << "message submitted for delivery" << endl; return SUCCESS; } /** Print current usage loads. */ -int printStats(int argc, char** argv, ostream& os) +static CLIStatus printStats(int argc, char** argv, ostream& os) { // FIXME -- This needs to take GPRS channels into account. See #762. if (argc!=1) return BAD_NUM_ARGS; os << "== GSM ==" << endl; os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl; os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl; - os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl; + os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << " (target <= 3)" << endl; // paging table size os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl; - os << "Transactions: " << gTransactionTable.size() << endl; + os << "Transactions: " << gNewTransactionTable.size() << endl; // 3122 timer current value (the number of seconds an MS should hold off the next RACH) os << "T3122: " << gBTS.T3122() << " ms (target " << gConfig.getNum("GSM.Radio.PowerManager.TargetT3122") << " ms)" << endl; os << "== GPRS ==" << endl; @@ -410,7 +593,7 @@ int printStats(int argc, char** argv, ostream& os) /** Get/Set MCC, MNC, LAC, CI. */ -int cellID(int argc, char** argv, ostream& os) +static CLIStatus cellID(int argc, char** argv, ostream& os) { if (argc==1) { os << "MCC=" << gConfig.getStr("GSM.Identity.MCC") @@ -441,7 +624,7 @@ int cellID(int argc, char** argv, ostream& os) return BAD_VALUE; } - gTMSITable.clear(); + gTMSITable.tmsiTabClear(); gConfig.set("GSM.Identity.MCC",argv[1]); gConfig.set("GSM.Identity.MNC",argv[2]); gConfig.set("GSM.Identity.LAC",argv[3]); @@ -449,24 +632,116 @@ int cellID(int argc, char** argv, ostream& os) return SUCCESS; } +static CLIStatus handover(int argc, char** argv, ostream& os) +{ + if (argc!=3) return BAD_NUM_ARGS; + string imsi(strncasecmp(argv[1],"IMSI",4)==0 ? argv[1]+4 : argv[1]); // Allow "IMSI1234..." or just "1234..." + RefCntPointer tran = gMMLayer.mmFindVoiceTranByImsi(imsi); + if (tran == 0) { + os << "IMSI not found:"< neighbors = gConfig.getVectorOfStrings("GSM.Neighbors"); + if (nth < 0 || nth >= (int)neighbors.size()) { + os << format("Specified neighbor index '%d' out of bounds. There are %d neighbors.",nth,neighbors.size()); + return BAD_VALUE; + } + peer = neighbors[nth]; + } else { + peer = string(argv[2]); + } + + if (! gPeerInterface.sendHandoverRequest(peer,tran)) { + return BAD_VALUE; + } + return SUCCESS; // success of the handover CLI command, not success of the handover. +} /** Print table of current transactions. */ -int calls(int argc, char** argv, ostream& os) +// (pat) In version 4 this dumps the MM layer. +static CLIStatus calls(int argc, char** argv, ostream& os) { + map options = cliParse(argc,argv,os,"-a -all -t -m -s"); + if (argc) return BAD_NUM_ARGS; + bool showAll = options.count("-a") || options.count("-all"); // -a and -all are synonyms. + bool trans = options.count("-t"); + bool mm = options.count("-m"); + bool sip = options.count("-s"); +#if 0 bool showAll = false; - if (argc==2) showAll = true; - if (argc>2) return BAD_NUM_ARGS; - size_t count = gTransactionTable.dump(os,showAll); - os << endl << count << " transactions in table" << endl; + bool trans = false; + bool mm = false; + bool sip = false; + // Parse args. + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + for (char*op = argv[i]+1; *op; op++) { + switch (*op) { + case 'm': mm = true; continue; + case 'a': showAll = true; continue; + case 's': sip = true; continue; + case 't': trans = true; continue; + case 'l': continue; // Allows -all without complaint. + default: + os << "unrecognized argument:"< options = cliParse(argc,argv,os,"purge"); + if (argc>1) { + return BAD_NUM_ARGS; + } + + TranEntry::header(os); + size_t count = gStaleTransactionTable.dumpTable(os); + os << endl << count << " completed transaction records in table" << endl; + + if (options.count("purge")) { + gStaleTransactionTable.clearTable(); + os << "records purged" << endl; + } + return SUCCESS; } /** Print or modify the global configuration table. */ -int rawconfig(int argc, char** argv, ostream& os) +static CLIStatus rawconfig(int argc, char** argv, ostream& os) { // no args, just print if (argc==1) { @@ -506,7 +781,7 @@ int rawconfig(int argc, char** argv, ostream& os) return SUCCESS; } -int trxfactory(int argc, char** argv, ostream& os) +static CLIStatus trxfactory(int argc, char** argv, ostream& os) { if (argc!=1) return BAD_NUM_ARGS; @@ -543,7 +818,7 @@ int trxfactory(int argc, char** argv, ostream& os) } /** Audit the current configuration. */ -int audit(int argc, char** argv, ostream& os) +static CLIStatus audit(int argc, char** argv, ostream& os) { ConfigurationKeyMap::iterator mp; stringstream ss; @@ -655,6 +930,7 @@ int audit(int argc, char** argv, ostream& os) ss.str(""); } + // non-default values mp = gConfig.mSchema.begin(); while (mp != gConfig.mSchema.end()) { @@ -702,7 +978,7 @@ int audit(int argc, char** argv, ostream& os) } /** Print or modify the global configuration table. */ -int _config(string mode, int argc, char** argv, ostream& os) +CLIStatus _config(string mode, int argc, char** argv, ostream& os) { // no args, just print if (argc==1) { @@ -809,6 +1085,9 @@ int _config(string mode, int argc, char** argv, ostream& os) return FAILURE; } // } + if (string(argv[1]).compare("GSM.Radio.Band") == 0) { + gConfig.mSchema["GSM.Radio.C0"].updateValidValues(getARFCNsString(gConfig.getNum("GSM.Radio.Band"))); + } vector warnings = gConfig.crossCheck(argv[1]); vector::iterator warning = warnings.begin(); while (warning != warnings.end()) { @@ -824,19 +1103,19 @@ int _config(string mode, int argc, char** argv, ostream& os) } /** Print or modify the global configuration table. Customer access. */ -int config(int argc, char** argv, ostream& os) +static CLIStatus config(int argc, char** argv, ostream& os) { return _config("customer", argc, argv, os); } /** Print or modify the global configuration table. Developer/factory access. */ -int devconfig(int argc, char** argv, ostream& os) +static CLIStatus devconfig(int argc, char** argv, ostream& os) { return _config("developer", argc, argv, os); } /** Disable a configuration key. */ -int unconfig(int argc, char** argv, ostream& os) +static CLIStatus unconfig(int argc, char** argv, ostream& os) { if (argc!=2) return BAD_NUM_ARGS; @@ -862,7 +1141,7 @@ int unconfig(int argc, char** argv, ostream& os) /** Set a configuration value back to default or remove from table if custom key. */ -int rmconfig(int argc, char** argv, ostream& os) +static CLIStatus rmconfig(int argc, char** argv, ostream& os) { if (argc!=2) return BAD_NUM_ARGS; @@ -904,7 +1183,7 @@ int rmconfig(int argc, char** argv, ostream& os) /** Change the registration timers. */ -int regperiod(int argc, char** argv, ostream& os) +static CLIStatus regperiod(int argc, char** argv, ostream& os) { if (argc==1) { os << "T3212 is " << gConfig.getNum("GSM.Timer.T3212") << " minutes" << endl; @@ -942,7 +1221,7 @@ int regperiod(int argc, char** argv, ostream& os) /** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << */ -int alarms(int argc, char** argv, ostream& os) +static CLIStatus alarms(int argc, char** argv, ostream& os) { std::ostream_iterator output( os, "\n" ); std::list alarms = gGetLoggerAlarms(); @@ -952,7 +1231,7 @@ int alarms(int argc, char** argv, ostream& os) /** Version string. */ -int version(int argc, char **argv, ostream& os) +static CLIStatus version(int argc, char **argv, ostream& os) { if (argc!=1) return BAD_NUM_ARGS; os << gVersionString << endl; @@ -960,24 +1239,44 @@ int version(int argc, char **argv, ostream& os) } /** Show start-up notices. */ -int notices(int argc, char **argv, ostream& os) +static CLIStatus notices(int argc, char **argv, ostream& os) { if (argc!=1) return BAD_NUM_ARGS; os << endl << gOpenBTSWelcome << endl; return SUCCESS; } -int page(int argc, char **argv, ostream& os) +static CLIStatus page(int argc, char **argv, ostream& os) { if (argc==1) { - gBTS.pager().dump(os); + Control::gMMLayer.printPages(os); return SUCCESS; } return BAD_NUM_ARGS; + + // (pat 7-30-2013) I think David removed the page command because it did not work in rev 3, + // but I had already added rev 4 code, and here it is in case someone wants to test it: + + if (argc!=3) return BAD_NUM_ARGS; + char *IMSI = argv[1]; + if (strlen(IMSI)>15) { + os << IMSI << " is not a valid IMSI" << endl; + return BAD_VALUE; + } + // (pat) Implement pat by just sending an SMS. + Control::FullMobileId msid(IMSI); + Control::TranEntry *tran = Control::TranEntry::newMTSMS( + NULL, // No SIPDialog + msid, + GSM::L3CallingPartyBCDNumber("0"), + string(""), // message body + string("")); // messate content type + Control::gMMLayer.mmAddMT(tran); + return SUCCESS; } -int testcall(int argc, char **argv, ostream& os) +static CLIStatus testcall(int argc, char **argv, ostream& os) { if (argc!=3) return BAD_NUM_ARGS; char *IMSI = argv[1]; @@ -985,34 +1284,261 @@ int testcall(int argc, char **argv, ostream& os) os << IMSI << " is not a valid IMSI" << endl; return BAD_VALUE; } - Control::TransactionEntry *transaction = new Control::TransactionEntry( - gConfig.getStr("SIP.Proxy.Speech").c_str(), - GSM::L3MobileIdentity(IMSI), - NULL, - GSM::L3CMServiceType::TestCall, - GSM::L3CallingPartyBCDNumber("0"), - GSM::Paging); - Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2])); + FullMobileId msid(IMSI); + TranEntry *tran = TranEntry::newMTC( + NULL, // No SIPDialog + msid, + GSM::L3CMServiceType::TestCall, + string("0")); + //GSM::L3CallingPartyBCDNumber("0")); + Control::gMMLayer.mmAddMT(tran); // start paging. return SUCCESS; } -int endcall(int argc, char **argv, ostream& os) + +// Return the Transaction Id or zero is an invalid value. +static TranEntryId transactionId(const char *id) +{ + unsigned result; + if (toupper(*id) == 'T') result = atoi(id+1); + else result = atoi(id); + return (TranEntryId) result; +} + + +static CLIStatus endcall(int argc, char **argv, ostream& os) { if (argc!=2) return BAD_NUM_ARGS; - unsigned transID = atoi(argv[1]); - Control::TransactionEntry* target = gTransactionTable.find(transID); - if (!target) { - os << transID << " not found in table"; + TranEntryId tid = transactionId(argv[1]); + if (tid == 0) { + os << argv[1] << " is not a valid transaction id"; + return BAD_VALUE; + } + if (! gNewTransactionTable.ttTerminate(tid)) { + os << argv[1] << " not found in transaction table"; return BAD_VALUE; } - target->terminate(); return SUCCESS; } +static void addGprsInfo(vector &row, const GSM::L2LogicalChannel* chan) +{ + // "CN TN chan transaction active recyc UPFER RSSI TXPWR TXTA DNLEV DNBER Neighbor Neighbor"; + row.clear(); + string empty("-"); + row.push_back(format("%d",chan->CN())); // CN + row.push_back(format("%d",chan->TN())); // TN + row.push_back(string("GPRS")); // chan type + row.push_back(empty); // transaction + row.push_back(string(chan->active()?"true":"false")); // active + row.push_back(string(chan->recyclable()?"true":"false")); // recyclable + // TODO: We could report gprs chan->PDCHCommon::FER() and we should keep gprs chan utilization per channel. +} + +// Return a descriptive string of the current channel state. +static string chanState(const L2LogicalChannel *chan) +{ + if (! chan->active()) { return string("inactive"); } + LAPDState lstate = chan->getLapdmState(); + if (lstate) { + ostringstream ss; ss << lstate; + return ss.str(); + } else { + return string("active"); + } +} + +// This allocates a ton of tiny strings. It doesnt matter; this function is only called interactively. +static void addChanInfo(vector &row, TranEntryList tids, const GSM::L2LogicalChannel* chan, const vector &fields) +{ + row.clear(); + for (unsigned i = 0; i < fields.size(); i++) { + string field = fields[i]; + MSPhysReportInfo *phys = NULL; + GSM::L3MeasurementResults meas = chan->SACCH()->measurementResults(); + if (chan->SACCH()) { meas = chan->SACCH()->measurementResults(); } + + if (field == "CN") { + row.push_back(format("%d",chan->CN())); + } else if (field == "TN") { + row.push_back(format("%d",chan->TN())); + } else if (field == "chan") { + ostringstream foo; foo << chan->typeAndOffset(); + row.push_back(foo.str()); + } else if (field == "transaction") { + // Add all the transaction ids. + string tranids; + for (TranEntryList::iterator it = tids.begin(); it != tids.end(); it++) { + if (tranids.size()) tranids += " "; + tranids += format("T%d",*it); + } + row.push_back(tranids); + + if (chan->inUseByGPRS()) { + row.push_back(string("GPRS")); + // (pat) Thats all we can say about this channel, although we could show the aggregate FER of all MS using the channel. + return; + } + } else if (field == "LAPDm") { + row.push_back(chanState(chan)); + } else if (field == "recyc") { + row.push_back(string(chan->recyclable()?"true":"false")); + } else if (field == "FER") { + row.push_back(format("%-5.2f",100.0*chan->FER())); + } else if (field == "RSSI") { + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(format("%-4d",(int)round(phys->RSSI()))); + } else if (field == "TA") { // The TA measured by the BTS. + // (pat) This was requested by Mark to get a more accurate distance measure of the MS. + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(format("%-.1f",phys->timingError())); + } else if (field == "TXPWR") { + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(format("%-5d",(int)round(phys->actualMSPower()))); + } else if (field == "TXTA") { // The TA reported by the MS. + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(format("%-4d",(int)round(phys->actualMSTiming()))); + } else if (field == "DNLEV") { + if (chan->SACCH() && meas.MEAS_VALID() == 0) { // Yes, 0 means meas_valid. + row.push_back(format("%-5d", meas.RXLEV_FULL_SERVING_CELL_dBm())); + } else { + row.push_back(""); + } + } else if (field == "DNBER") { + if (chan->SACCH() && meas.MEAS_VALID() == 0) { // Yes, 0 means meas_valid. + row.push_back(format("%-5.2f", 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER())); + } else { + row.push_back(""); + } + } else if (field == "Neighbor") { // Print Neighbor ARFCN and Neighbor dBm + if (chan->SACCH() && meas.NO_NCELL()) { + unsigned CN = meas.BCCH_FREQ_NCELL(0); + std::vector ARFCNList = gNeighborTable.ARFCNList(); + if (CN>=ARFCNList.size()) { + LOG(NOTICE) << "BCCH index " << CN << " does not match ARFCN list of size " << ARFCNList.size(); + goto skipneighbors; + } + row.push_back(format("%-8u", ARFCNList[CN])); + row.push_back(format("%-8d",meas.RXLEV_NCELL_dBm(0))); + } else { + skipneighbors: + row.push_back(""); + row.push_back(""); + } + i++; // We printed both Neighbor fields so skip over. + assert(fields[i] == "Neighbor"); + } else if (field == "IMSI") { + row.push_back(chan->chanGetImsi(true)); + } else if (field == "Frames") { + DecoderStats ds = chan->getDecoderStats(); + row.push_back(format("%d/%d/%d",ds.mStatBadFrames,ds.mStatStolenFrames,ds.mStatTotalFrames)); + } else if (field == "SNR") { + DecoderStats ds = chan->getDecoderStats(); + row.push_back(format("%.3g",ds.mAveSNR)); + } else if (field == "BER") { + DecoderStats ds = chan->getDecoderStats(); + row.push_back(format("%.3g",100.0 * ds.mAveBER)); + } + } +} + + // old: " active - true if the channel is or recently was in use; +static const char *chansHelp = "[-a -l -tab] -- report PHY status for active channels, or if -a all channels.\n" + " -l for longer listing, -tab for tab-separated output format\n" + " CN - Channel Number; TN - Timeslot Number; chan type - the dedicated channel type, or GPRS if reserved for Packet Services;\n" + " transaction id - One or more Layer 3 transactions running on this channel;\n" + " LAPDm state - The current acknowledged message state, if any, otherwise 'active' or 'inactive';\n" + " recyc - true if channel is recyclable, ie, can be reused now;\n" + " RSSI - Uplink signal level dB above noise floor measured by BTS, should be near config parameter GSM.Radio.RSSITarget;\n" + " SNR - Signal to Noise Ratio measured by BTS, higher is better, less than 10 is probably unusable;\n" + " BER - Bit Error Rate before decoding measured by BTS, as a percentage;\n" + " FER - voice frame loss rate as a percentage measured by BTS;\n" + " TA - Timing advance in symbol periods measured by the BTS;\n" + " TXPWR - Uplink transmit power dB reported by MS;\n" + " TXTA - Timing advance in symbol periods reported by MS;\n" + " DNLEV - Downlink signal level dB reported by MS;\n" + " DNBER - Downlink Bit Error rate percentage reported by MS;\n" + " Neighbor ARFCN and dBm - One of the neighbors channel and downlink RSSI reported by the MS;\n" + " may also be: 'no-MMContext' to indicate the layer2 channel is open but has not yet sent any layer3 messages;\n" + " or 'no-MMUser' to indicate that layer3 is connected but the IMSI is not yet known.\n" + " IMSI - International Mobile Subscriber Id of the MS on this channel, reported only if known;\n" + " Frames - number of bad, stolen, and total frames sent, only for traffic channels;\n" + ; + +CLIStatus printChansV4(std::ostream& os,bool showAll, bool longList, bool tabSeparated) +{ + // The "active" field needs to be a big enough for the longest lapdm states here: + // LinkEstablished + // ContentionResolve + // AwaitingEstablish + // The spacing in these headers no longer matters. + const char *header1, *header2; + + // The spaces in these headers are removed later; they are there only to make the columns line up so + // we can make sure we have the two column headers correct. + if (longList) { + header1 = "CN TN chan transaction LAPDm recyc RSSI SNR FER BER TA TXPWR TXTA DNLEV DNBER IMSI Frames Neighbor Neighbor"; + header2 = "_ _ type id state _ dB _ pct pct sym dBm sym dBm pct _ bad/st/tot ARFCN dBm"; + } else { + header1 = "CN TN chan transaction LAPDm recyc RSSI SNR FER BER TXPWR TXTA DNLEV DNBER IMSI"; + header2 = "_ _ type id state _ dB _ pct pct dBm sym dBm pct "; + // This is the original version 4 list: + //header1 = "CN TN chan transaction LAPDm recyc FER RSSI TXPWR TXTA DNLEV DNBER Neighbor Neighbor IMSI"; + //header2 = "_ _ type id state _ pct dB dBm sym dBm pct ARFCN dBm"; + } + + LOG(DEBUG); + prettyTable_t tab; + vector vh1, vh2; + tab.push_back(stringSplit(vh1,header1)); + tab.push_back(stringSplit(vh2,header2)); -void printChanInfo(unsigned transID, const GSM::LogicalChannel* chan, ostream& os) + using namespace GSM; + + //gPhysStatus.dump(os); + //os << endl << "Old data reporting: " << endl; + + L2ChanList chans; + gBTS.getChanVector(chans); + LOG(DEBUG); + Control::TranEntryList tids; + for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) { + const L2LogicalChannel *chan = *it; + LOG(DEBUG); + chan->getTranIds(tids); // Get transactions using this channel. + LOG(DEBUG); + // It would be a bug to have a non-empty tids on an inactive channel, but in that case we really want to show it. + if (chan->active() || ! tids.empty() || showAll) { + vector row; + addChanInfo(row,tids,chan,vh1); + tab.push_back(row); + } else if (chan->inUseByGPRS() && showAll) { + vector row; + addGprsInfo(row,chan); + tab.push_back(row); + } + } + printPrettyTable(tab,os,tabSeparated); + return SUCCESS; +} + +static CLIStatus chans(int argc, char **argv, ostream& os) +{ + // bool showAll = false; + // if (argc==2) showAll = true; + map options = cliParse(argc,argv,os,"-a -l -tab"); + bool showAll = options.count("-a"); + bool longList = options.count("-l"); + bool tabSeparated = options.count("-tab"); + if (argc) return BAD_NUM_ARGS; + printChansV4(os,showAll,longList,tabSeparated); + return SUCCESS; +} + +#if OLD_VERSION +void printChanInfo(unsigned transID, const GSM::L2LogicalChannel* chan, ostream& os) { os << setw(2) << chan->CN() << " " << chan->TN(); os << " " << setw(9) << chan->typeAndOffset(); @@ -1059,56 +1585,45 @@ void printChanInfo(unsigned transID, const GSM::LogicalChannel* chan, ostream& o } - -int chans(int argc, char **argv, ostream& os) +CLIStatus printChansV4(std::ostream& os,bool showAll) { - bool showAll = false; - if (argc==2) showAll = true; - if (argc>2) return BAD_NUM_ARGS; - + using namespace GSM; os << "CN TN chan transaction active recyc UPFER RSSI TXPWR TXTA DNLEV DNBER Neighbor Neighbor" << endl; os << "CN TN type id pct dB dBm sym dBm pct ARFCN dBm" << endl; //gPhysStatus.dump(os); //os << endl << "Old data reporting: " << endl; - // SDCCHs - GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin(); - while (sChanItr != gBTS.SDCCHPool().end()) { - const GSM::SDCCHLogicalChannel* sChan = *sChanItr; - if (sChan->active() || showAll) { - Control::TransactionEntry *trans = gTransactionTable.find(sChan); - int tid = 0; - if (trans) tid = trans->ID(); - printChanInfo(tid,sChan,os); - //if (showAll) printChanInfo(tid,sChan->SACCH(),os); + L2ChanList chans; + gBTS.getChanVector(chans); + Control::TranEntryList tids; + for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) { + const L2LogicalChannel *chan = *it; + chan->getTranIds(tids); + // It would be a bug to have a non-empty tids on an active channel, but in that case we really want to show it. + if (chan->active() || ! tids.empty() || showAll) { + // TODO: There could be multiple TIDs; we should print them all. + printChanInfo(tids.empty()?0:tids.front(),chan,os); } - ++sChanItr; } - - // TCHs - GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin(); - while (tChanItr != gBTS.TCHPool().end()) { - const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr; - if (tChan->active() || showAll) { - Control::TransactionEntry *trans = gTransactionTable.find(tChan); - int tid = 0; - if (trans) tid = trans->ID(); - printChanInfo(tid,tChan,os); - //if (showAll) printChanInfo(tid,tChan->SACCH(),os); - } - ++tChanItr; - } - os << endl; - return SUCCESS; } +static CLIStatus chans(int argc, char **argv, ostream& os) +{ + bool showAll = false; + if (argc==2) showAll = true; + if (argc>2) return BAD_NUM_ARGS; + printChansV4(os,showAll); + return SUCCESS; +} +#endif -int power(int argc, char **argv, ostream& os) + +static CLIStatus power(int argc, char **argv, ostream& os) { os << "current downlink power " << gBTS.powerManager().power() << " dB wrt full scale" << endl; os << "current attenuation bounds " @@ -1151,7 +1666,7 @@ int power(int argc, char **argv, ostream& os) } -int rxgain(int argc, char** argv, ostream& os) +static CLIStatus rxgain(int argc, char** argv, ostream& os) { os << "current RX gain is " << gConfig.getNum("GSM.Radio.RxGain") << " dB" << endl; if (argc==1) return SUCCESS; @@ -1171,7 +1686,7 @@ int rxgain(int argc, char** argv, ostream& os) return SUCCESS; } -int txatten(int argc, char** argv, ostream& os) +static CLIStatus txatten(int argc, char** argv, ostream& os) { os << "current TX attenuation is " << gConfig.getNum("TRX.TxAttenOffset") << " dB" << endl; if (argc==1) return SUCCESS; @@ -1192,7 +1707,7 @@ int txatten(int argc, char** argv, ostream& os) } -int freqcorr(int argc, char** argv, ostream& os) +static CLIStatus freqcorr(int argc, char** argv, ostream& os) { os << "current freq. offset is " << gConfig.getNum("TRX.RadioFrequencyOffset") << endl; if (argc==1) return SUCCESS; @@ -1214,18 +1729,28 @@ int freqcorr(int argc, char** argv, ostream& os) -int noise(int argc, char** argv, ostream& os) +static CLIStatus noise(int argc, char** argv, ostream& os) { - if (argc!=1) return BAD_NUM_ARGS; + if (argc!=1) return BAD_NUM_ARGS; - int noise = gTRX.ARFCN(0)->getNoiseLevel(); - os << "noise RSSI is -" << noise << " dB wrt full scale" << endl; - os << "MS RSSI target is " << gConfig.getNum("GSM.Radio.RSSITarget") << " dB wrt full scale" << endl; + int noise = gTRX.ARFCN(0)->getNoiseLevel(); + int target = abs(gConfig.getNum("GSM.Radio.RSSITarget")); + int diff = noise - target; - return SUCCESS; + os << "noise RSSI is -" << noise << " dB wrt full scale" << endl; + os << "MS RSSI target is -" << target << " dB wrt full scale" << endl; + if (diff <= 0) { + os << "WARNING: the current noise level exceeds the MS RSSI target, uplink connectivity will be impossible." << endl; + } else if (diff <= 10) { + os << "WARNING: the current noise level is approaching the MS RSSI target, uplink connectivity will be extremely limited." << endl; + } else { + os << "INFO: the current noise level is acceptable." << endl; + } + + return SUCCESS; } -int sysinfo(int argc, char** argv, ostream& os) +static CLIStatus sysinfo(int argc, char** argv, ostream& os) { if (argc!=1) return BAD_NUM_ARGS; @@ -1246,13 +1771,75 @@ int sysinfo(int argc, char** argv, ostream& os) } -int neighbors(int argc, char** argv, ostream& os) +static CLIStatus neighbors(int argc, char** argv, ostream& os) { - - os << "host C0 BSIC" << endl; + map options = cliParse(argc,argv,os,"-tab"); + if (argc) return BAD_NUM_ARGS; + bool tabSeparated = options.count("-tab"); + FILE *result = NULL; char cmd[200]; - sprintf(cmd,"sqlite3 -separator ' ' %s 'select IPADDRESS,C0,BSIC from neighbor_table'", + snprintf(cmd,200,"sqlite3 -separator ' ' %s 'select IPADDRESS,C0,BSIC from neighbor_table'", gConfig.getStr("Peering.NeighborTable.Path").c_str()); + result = popen(cmd,"r"); +#if 1 // pat was here. + prettyTable_t tab; + vector vh; + tab.push_back(stringSplit(vh,"host C0 BSIC")); vh.clear(); + tab.push_back(stringSplit(vh,"---- --- ----")); vh.clear(); + if (result) { + char line[202]; + while (fgets(line, 200, result)) { + tab.push_back(stringSplit(vh,line)); vh.clear(); + } + } + printPrettyTable(tab,os,tabSeparated); +#else + os << "host C0 BSIC" << endl; + char *line = (char*)malloc(200); + while (!feof(result)) { + if (!fgets(line, 200, result)) break; + os << line; + } + free(line); + os << endl; +#endif + if (result) pclose(result); + return SUCCESS; +} + + +static CLIStatus crashme(int argc, char** argv, ostream& os) +{ + char *nullp = 0x0; + // we actually have to output this, + // or the compiler will optimize it out + os << *nullp; + return FAILURE; +} + + +static CLIStatus stats(int argc, char** argv, ostream& os) +{ + char cmd[BUFSIZ]; + if (argc==2) { + if (strcmp(argv[1],"clear")==0) { + gReports.clear(); + os << "stats table (gReporting) cleared" << endl; + return SUCCESS; + } + snprintf(cmd,sizeof(cmd)-1, + "sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting where name like \"%%%s%%\";'", + gConfig.getStr("Control.Reporting.StatsTable").c_str(), + time(NULL), argv[1]); + } + else if (argc==1) + { + snprintf(cmd,sizeof(cmd)-1,"sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting;'", + gConfig.getStr("Control.Reporting.StatsTable").c_str(), time(NULL)); + } else + { + return BAD_NUM_ARGS; + } FILE *result = popen(cmd,"r"); char *line = (char*)malloc(200); while (!feof(result)) { @@ -1266,42 +1853,13 @@ int neighbors(int argc, char** argv, ostream& os) } -int crashme(int argc, char** argv, ostream& os) +static CLIStatus memStat(int argc, char** argv, ostream& os) { - char *nullp = 0x0; - // we actually have to output this, - // or the compiler will optimize it out - os << *nullp; - return FAILURE; -} - - -int stats(int argc, char** argv, ostream& os) -{ - - char cmd[200]; - if (argc==2) { - if (strcmp(argv[1],"clear")==0) { - gReports.clear(); - os << "stats table (gReporting) cleared" << endl; - return SUCCESS; - } - sprintf(cmd,"sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting where name like \"%%%s%%\";'", - gConfig.getStr("Control.Reporting.StatsTable").c_str(), time(NULL), argv[1]); - } - else if (argc==1) - sprintf(cmd,"sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting;'", - gConfig.getStr("Control.Reporting.StatsTable").c_str(), time(NULL)); - else return BAD_NUM_ARGS; - FILE *result = popen(cmd,"r"); - char *line = (char*)malloc(200); - while (!feof(result)) { - if (!fgets(line, 200, result)) break; - os << line; - } - free(line); - os << endl; - pclose(result); + // These counters are always available. + os << "Count"< ParseTable; +typedef CLIStatus (*CLICommand)(int,char**,std::ostream&); +typedef std::map ParseTable; /** The help table. */ typedef std::map HelpTable; @@ -47,10 +57,11 @@ class Parser { Process a command line. @return 0 on sucess, -1 on exit request, error codes otherwise */ - int process(const char* line, std::ostream& os) const; + CLIStatus process(const char* line, std::ostream& os) const; + void startCommandLine(); // (pat) Start a simple command line processor. /** Add a command to the parsing table. */ - void addCommand(const char* name, int (*func)(int,char**,std::ostream&), const char* helpString) + void addCommand(const char* name, CLICommand func, const char* helpString) { mParseTable[name] = func; mHelpTable[name]=helpString; } ParseTable::const_iterator begin() const { return mParseTable.begin(); } @@ -68,14 +79,13 @@ class Parser { @os output stream @return status code */ - int execute(char* line, std::ostream& os) const; + CLIStatus execute(char* line, std::ostream& os) const; }; +extern CLIStatus printChansV4(std::ostream& os,bool showAll, bool longList = false, bool tabSeparated = false); - -} // CLI - +} // namespace CommandLine #endif diff --git a/CommonLibs b/CommonLibs index bd94d0a..abec8da 160000 --- a/CommonLibs +++ b/CommonLibs @@ -1 +1 @@ -Subproject commit bd94d0a9883002daa8310b601f28eed3b0d87d2f +Subproject commit abec8dabb69f8f2bdefdb9bcca340b89f43ce419 diff --git a/Control/CallControl.cpp b/Control/CallControl.cpp deleted file mode 100644 index 3e531f9..0000000 --- a/Control/CallControl.cpp +++ /dev/null @@ -1,1419 +0,0 @@ -/**@file GSM/SIP Call Control -- GSM 04.08, ISDN ITU-T Q.931, SIP IETF RFC-3261, RTP IETF RFC-3550. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -/* - Abbreviations: - MTC -- Mobile Terminated Connect (someone calling the mobile) - MOC -- Mobile Originated Connect (mobile calling out) - MTD -- Mobile Terminated Disconnect (other party hangs up) - MOD -- Mobile Originated Disconnect (mobile hangs up) -*/ - - -#include - -#include "ControlCommon.h" -#include "TransactionTable.h" -#include "MobilityManagement.h" -#include "SMSControl.h" -#include "CallControl.h" -#include "RRLPServer.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#undef WARNING - -using namespace std; -using namespace Control; - - - -// Forward refs. - -bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChannel* LCH, const GSM::L3Message *message); - - - -/** - Return an even UDP port number for the RTP even/odd pair. -*/ -unsigned allocateRTPPorts() -{ - const unsigned base = gConfig.getNum("RTP.Start"); - const unsigned range = gConfig.getNum("RTP.Range"); - const unsigned top = base+range; - static Mutex lock; - // Pick a random starting point. - static unsigned port = base + 2*(random()%(range/2)); - unsigned retVal; - lock.lock(); - //This is a little hacky as RTPAvail is O(n) - do { - retVal = port; - port += 2; - if (port>=top) port=base; - } while (!gTransactionTable.RTPAvailable(retVal)); - lock.unlock(); - return retVal; -} - - - - - - -/** - Force clearing on the GSM side. - @param transaction The call transaction record. - @param LCH The logical channel. - @param cause The L3 abort cause. -*/ -void forceGSMClearing(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause) -{ - LOG(DEBUG); - LOG(INFO) << "Q.931 state " << transaction->GSMState(); - // Already cleared? - if (transaction->GSMState()==GSM::NullState) return; - // Clearing not started? Start it. - if (!transaction->clearingGSM()) LCH->send(GSM::L3Disconnect(transaction->L3TI(),cause)); - // Force the rest. - LOG(DEBUG); - LCH->send(GSM::L3ReleaseComplete(transaction->L3TI())); - LCH->send(GSM::L3ChannelRelease()); - LOG(DEBUG); - transaction->resetTimers(); - transaction->GSMState(GSM::NullState); - LOG(DEBUG); - //LCH->send(GSM::RELEASE); - //LOG(DEBUG); -} - - -/** - Force clearing on the SIP side. - @param transaction The call transaction record. -*/ -void forceSIPClearing(TransactionEntry *transaction) -{ - if (transaction->deadOrRemoved()) { - LOG(ERR) << "aborting transaction that is already removed or defunct"; - return; - } - - LOG(DEBUG); - SIP::SIPState state = transaction->SIPState(); - LOG(INFO) << "SIP state " << state; - //why aren't we checking for failed here? -kurtis ; we are now. -david - if (transaction->SIPFinished()) return; - if (state==SIP::Active){ - //Changes state to clearing - transaction->MODSendBYE(); - //then cleared - transaction->MODWaitForBYEOK(); - } else if (transaction->instigator()){ //hasn't started yet, need to cancel - //Changes state to canceling - transaction->MODSendCANCEL(); - //then canceled - transaction->MODWaitForCANCELOK(); - } - else { //we received, respond and then don't send ok - //changed state immediately to canceling - transaction->MODSendERROR(NULL, 480, "Temporarily Unavailable", true); - //then canceled - transaction->MODWaitForERRORACK(true); - } -} - - - -/** - Abort the call. Does not remove the transaction from the table. - @param transaction The call transaction record. - @param LCH The logical channel. - @param cause The L3 abort cause. -*/ -void abortCall(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause) -{ - LOG(DEBUG); - if (!transaction->deadOrRemoved()) { - LOG(INFO) << "cause: " << cause << ", transaction: " << *transaction; - if (LCH) forceGSMClearing(transaction,LCH,cause); - forceSIPClearing(transaction); - } - else { - if (LCH) { - LOG(ERR) << "aborting transaction that is already removed or defunct on " << *LCH; - forceGSMClearing(transaction,LCH,cause); - } - else { - LOG(ERR) << "aborting transaction that is already removed or defunct, no channel"; - } - - } -} - - -/** - Abort the call and remove the transaction. - @param transaction The call transaction record. - @param LCH The logical channel. - @param cause The L3 abort cause. -*/ -void abortAndRemoveCall(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause) -{ - if (transaction->deadOrRemoved()) { - if (LCH) { - LOG(ERR) << "aborting transaction that is already removed or defunct on " << *LCH; - } - else { - LOG(ERR) << "aborting transaction that is already removed or defunct, no channel"; - } - return; - } - LOG(NOTICE) << "cause: " << cause << ", transaction: " << *transaction; - abortCall(transaction,LCH,cause); - gTransactionTable.remove(transaction); -} - - - - -/** - Allocate a TCH and clean up any failure. - @param DCCH The DCCH that will be used to send the assignment. - @return A pointer to the TCH or NULL on failure. -*/ -GSM::TCHFACCHLogicalChannel *allocateTCH(GSM::LogicalChannel *DCCH) -{ - // Get TCH will open the channel. - GSM::TCHFACCHLogicalChannel *TCH = gBTS.getTCH(); - if (!TCH) { - LOG(WARNING) << "congestion, no TCH available for assignment"; - // Cause 0x16 is "congestion". - DCCH->send(GSM::L3CMServiceReject(0x16)); - DCCH->send(GSM::L3ChannelRelease()); - } - return TCH; -} - - - - - -/** - Assign a full rate traffic channel and clean up any failures. - @param DCCH The DCCH on which to send the assignment. - @param TCH The TCH to be assigned. - @bool True on successful transfer. -*/ -bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::TCHFACCHLogicalChannel *TCH) -{ - TCH->open(); - TCH->setPhy(*DCCH); - - // We retry this loop in case there are stray messages in the channel. - // On some phones, we see repeated Call Confirmed messages on MTC. - - GSM::Z100Timer retry(GSM::T3101ms-1000); - retry.set(); - while (!retry.expired()) { - - // Send the assignment. - transaction->channel(TCH); - LOG(DEBUG) << "updated transaction " << *transaction; - LOG(INFO) << "sending AssignmentCommand for " << *TCH << " on " << *DCCH; - // FIXME - We should probably be setting the initial power here. - DCCH->send(GSM::L3AssignmentCommand(TCH->channelDescription(),GSM::L3ChannelMode(GSM::L3ChannelMode::SpeechV1))); - - // This read is SUPPOSED to time out if the assignment was successful. - // Pad the timeout just in case there's a large latency somewhere. - GSM::L3Frame *result = DCCH->recv(GSM::T3107ms+2000); - if (!result) { - LOG(INFO) << "sucessful assignment; exiting normally"; - DCCH->send(GSM::HARDRELEASE); - return true; - } - - // If we got here, the assignment failed, or there was a message backlog in L3. - GSM::L3Message *msg = parseL3(*result); - if (!msg) { LOG(NOTICE) << "waiting for assignment complete, received unparsed L3 frame " << *result; } - delete result; - if (!msg) continue; - LOG(NOTICE) << "waiting for assignment complete, received " << *msg; - callManagementDispatchGSM(transaction,DCCH,msg); - } - - // Turn off the TCH. - TCH->send(GSM::RELEASE); - - // RR Cause 0x04 -- "abnormal release, no activity on the radio path" - DCCH->send(GSM::L3ChannelRelease(0x04)); - - // Dissociate channel from the transaction. - // The tranaction no longer has a channel. - transaction->channel(NULL); - - // Shut down the SIP side of the call. - forceSIPClearing(transaction); - - // Indicate failure. - return false; -} - - - - -/** - Process a message received from the phone during a call. - This function processes all deviations from the "call connected" state. - For now, we handle call clearing and politely reject everything else. - @param transaction The transaction record for this call. - @param LCH The logical channel for the transaction. - @param message A pointer to the receiver message. - @return true If the call has been cleared and the channel released. -*/ -bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChannel* LCH, const GSM::L3Message *message) -{ - if (message) { LOG(DEBUG) << "from " << transaction->subscriber() << " message " << *message; } - - // FIXME -- This dispatch section should be something more efficient with PD and MTI swtiches. - - // Actually check state before taking action. - //if (transaction->SIPState()==SIP::Cleared) return true; - //if (transaction->GSMState()==GSM::NullState) return true; - - // Call connection steps. - - // Connect Acknowledge - if (dynamic_cast(message)) { - LOG(INFO) << "GSM Connect Acknowledge " << *transaction; - transaction->resetTimers(); - transaction->GSMState(GSM::Active); - return false; - } - - // Connect - // GSM 04.08 5.2.2.5 and 5.2.2.6 - if (dynamic_cast(message)) { - LOG(INFO) << "GSM Connect " << *transaction; - transaction->resetTimers(); - transaction->GSMState(GSM::Active); - return false; - } - - // Call Confirmed - // GSM 04.08 5.2.2.3.2 - // "Call Confirmed" is the GSM MTC counterpart to "Call Proceeding" - if (dynamic_cast(message)) { - LOG(INFO) << "GSM Call Confirmed " << *transaction; - transaction->resetTimer("303"); - transaction->setTimer("301"); - transaction->GSMState(GSM::MTCConfirmed); - return false; - } - - // Alerting - // GSM 04.08 5.2.2.3.2 - if (dynamic_cast(message)) { - LOG(INFO) << "GSM Alerting " << *transaction; - transaction->resetTimer("310"); - transaction->setTimer("301"); - transaction->GSMState(GSM::CallReceived); - return false; - } - - // Call clearing steps. - // Good diagrams in GSM 04.08 7.3.4 - - // FIXME -- We should be checking TI values against the transaction object. - - // Disconnect (1st step of MOD) - // GSM 04.08 5.4.3.2 - if (const GSM::L3Disconnect* disc = dynamic_cast(message)) { - LOG(INFO) << "GSM Disconnect " << *transaction; - gReports.incr("OpenBTS.GSM.CC.MOD.Disconnect"); - bool early = transaction->GSMState() != GSM::Active; - bool normal = (disc->cause().cause() <= 0x10); - if (!normal) { - LOG(NOTICE) << "abnormal terminatation: " << *disc; - } - /* late RLLP request */ - if (normal && !early && gConfig.getBool("Control.Call.QueryRRLP.Late")) { - // Query for RRLP - if (!sendRRLP(transaction->subscriber(), LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - transaction->resetTimers(); - LCH->send(GSM::L3Release(transaction->L3TI())); - transaction->setTimer("308"); - transaction->GSMState(GSM::ReleaseRequest); - //bug #172 fixed - if (transaction->SIPState()==SIP::Active){ - transaction->MODSendBYE(); - transaction->MODWaitForBYEOK(); - } - else { //this is the end if the call isn't setup yet in the SIP domain - if (transaction->instigator()){ //if we instigated the call, send a cancel - transaction->MODSendCANCEL(); - transaction->MODWaitForCANCELOK(); - //if we cancel the call, Switch might send 487 Request Terminated - //listen for that - transaction->MODWaitFor487(); - // TODO: Asterisk fires off two SIP packets, OK and 487. We may not receive them - // in that order. We will want to use the code below to eat both of the - // packets, but accept them in any order. - /*vector valid(2); - valid.push_back(200); - valid.push_back(487); - transaction->MODWaitForResponse(&valid); - transaction->MODWaitForResponse(&valid);*/ - } - else { //if we received it, send a 4** instead - //transaction->MODSendERROR(NULL, 480, "Temporarily Unavailable", true); - transaction->MODSendERROR(NULL, 486, "Busy Here", true); - transaction->MODWaitForERRORACK(true); - } - //transaction->GSMState(GSM::NullState); - //return true; - } - return false; - } - - // Release (2nd step of MTD) - if (const GSM::L3Release *rls = dynamic_cast(message)) { - LOG(INFO) << "GSM Release " << *transaction; - gReports.incr("OpenBTS.GSM.CC.MTD.Release"); - if (rls->haveCause() && (rls->cause().cause() > 0x10)) { - LOG(NOTICE) << "abnormal terminatation: " << *rls; - } - /* late RLLP request */ - if (gConfig.getBool("Control.Call.QueryRRLP.Late")) { - // Query for RRLP - if (!sendRRLP(transaction->subscriber(), LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - transaction->resetTimers(); - LCH->send(GSM::L3ReleaseComplete(transaction->L3TI())); - LCH->send(GSM::L3ChannelRelease()); - transaction->GSMState(GSM::NullState); - transaction->MTDSendBYEOK(); - return true; - } - - // Release Complete (3nd step of MOD) - // GSM 04.08 5.4.3.4 - if (dynamic_cast(message)) { - LOG(INFO) << "GSM Release Complete " << *transaction; - transaction->resetTimers(); - LCH->send(GSM::L3ChannelRelease()); - transaction->GSMState(GSM::NullState); - return true; - } - - // IMSI Detach -- the phone is shutting off. - if (const GSM::L3IMSIDetachIndication* detach = dynamic_cast(message)) { - // The IMSI detach procedure will release the LCH. - LOG(INFO) << "GSM IMSI Detach " << *transaction; - IMSIDetachController(detach,LCH); - forceSIPClearing(transaction); - return true; - } - - // Start DTMF - // Transalate to RFC-2967 or RFC-2833. - if (const GSM::L3StartDTMF* startDTMF = dynamic_cast(message)) { - char key = startDTMF->key().IA5(); - LOG(INFO) << "DMTF key=" << key << ' ' << *transaction; - bool success = false; - if (gConfig.defines("SIP.DTMF.RFC2833")) { - bool s = transaction->startDTMF(key); - if (!s) LOG(ERR) << "DTMF RFC-28333 failed."; - success |= s; - } - if (gConfig.defines("SIP.DTMF.RFC2967")) { - unsigned bcd = GSM::encodeBCDChar(key); - bool s = transaction->sendINFOAndWaitForOK(bcd); - if (!s) LOG(ERR) << "DTMF RFC-2967 failed."; - success |= s; - } - if (success) { - LCH->send(GSM::L3StartDTMFAcknowledge(transaction->L3TI(),startDTMF->key())); - } else { - LOG (CRIT) << "DTMF sending attempt failed; is any DTMF method defined?"; - // Cause 0x3f means "service or option not available". - LCH->send(GSM::L3StartDTMFReject(transaction->L3TI(),0x3f)); - } - return false; - } - - // Stop DTMF - // RFC-2967 or RFC-2833 - if (dynamic_cast(message)) { - transaction->stopDTMF(); - LCH->send(GSM::L3StopDTMFAcknowledge(transaction->L3TI())); - return false; - } - - // CM Service Request - if (const GSM::L3CMServiceRequest *cmsrq = dynamic_cast(message)) { - // SMS submission? The rest will happen on the SACCH. - if (cmsrq->serviceType().type() == GSM::L3CMServiceType::ShortMessage) { - LOG (INFO) << "in call SMS submission on " << *LCH; - InCallMOSMSStarter(transaction); - LCH->send(GSM::L3CMServiceAccept()); - return false; - } - // For now, we are rejecting anything else. - LOG(NOTICE) << "cannot accept additional CM Service Request from " << transaction->subscriber(); - // Cause 0x20 means "serivce not supported". - LCH->send(GSM::L3CMServiceReject(0x20)); - return false; - } - -//#if 0 - -//This needs to work, but putting it in causes heap corruption. - - // Status - // If we get this message, is is probably carrying an error code. - if (const GSM::L3CCStatus* status = dynamic_cast(message)) { - LOG(NOTICE) << "unsolicited status message: " << *status; - unsigned callState = status->callState().callState(); - // See GSM 04.08 Table 10.5.117. - if (callState>10) { - // Just cancel on the SIP side. - // FIXME -- We should really try to translate the error cause. - transaction->MODSendCANCEL(); - transaction->resetTimers(); - LCH->send(GSM::L3Release(transaction->L3TI())); - transaction->setTimer("308"); - transaction->GSMState(GSM::ReleaseRequest); - return true; - } - } -//#endif - - // We don't process Assignment Failurehere, but catch it to avoid misleading log message. - if (dynamic_cast(message)) { - return false; - } - - if (dynamic_cast(message)) { - LOG(DEBUG) << "received Ciphering Mode Complete on " << *LCH << " for " << transaction->subscriber(); - // Although the spec (04.08 3.4.7) says you can start ciphering the downlink at this time, - // it also says you can start when you successfully decrypt an uplink layer 2 frame, - // which is what we do. - return false; - } - - // Stubs for unsupported features. - // We need to answer the handset so it doesn't hang. - - // Hold - if (dynamic_cast(message)) { - LOG(NOTICE) << "rejecting hold request from " << transaction->subscriber(); - // Default cause is 0x3f, option not available - LCH->send(GSM::L3HoldReject(transaction->L3TI(),0x3f)); - return false; - } - - if (message) { - LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber(); - } else { - LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber(); - } - - - // If we got here, we're ignoring the message. - return false; -} - - - - - - -/** - Update vocoder data transfers in both directions. - @param transaction The transaction object for this call. - @param TCH The traffic channel for this call. - @return True if anything was transferred. -*/ -bool updateCallTraffic(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH) -{ - bool activity = false; - - // Transfer in the downlink direction (RTP->GSM). - // Blocking call. On average returns 1 time per 20 ms. - // Returns non-zero if anything really happened. - // Make the rxFrame buffer big enough for G.711. - unsigned char rxFrame[160]; - if (transaction->rxFrame(rxFrame)) { - activity = true; - TCH->sendTCH(rxFrame); - } - - // Transfer in the uplink direction (GSM->RTP). - // Flush FIFO to limit latency. - unsigned maxQ = gConfig.getNum("GSM.MaxSpeechLatency"); - while (TCH->queueSize()>maxQ) delete[] TCH->recvTCH(); - if (unsigned char *txFrame = TCH->recvTCH()) { - activity = true; - // Send on RTP. - transaction->txFrame(txFrame); - delete[] txFrame; - } - - // Return a flag so the caller will know if anything transferred. - return activity; -} - - - - -/** - Check GSM signalling. - Can block for up to 52 GSM L1 frames (240 ms) because LCH::send is blocking. - @param transaction The call's TransactionEntry. - @param LCH The call's logical channel (TCH/FACCH or SDCCH). - @return true If the call was cleared, but the transaction is still there. -*/ -bool updateGSMSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, unsigned timeout=0) -{ - if (transaction->GSMState()==GSM::NullState) return true; - - // Any Q.931 timer expired? - if (transaction->anyTimerExpired()) { - // Cause 0x66, "recover on timer expiry" - abortCall(transaction,LCH,GSM::L3Cause(0x66)); - return true; - } - - // Look for a control message from MS side. - if (GSM::L3Frame *l3 = LCH->recv(timeout)) { - // Check for lower-layer error. - if (l3->primitive() == GSM::ERROR) return true; - // Parse and dispatch. - GSM::L3Message *l3msg = parseL3(*l3); - delete l3; - bool cleared = false; - if (l3msg) { - LOG(DEBUG) << "received " << *l3msg; - cleared = callManagementDispatchGSM(transaction, LCH, l3msg); - delete l3msg; - } - return cleared; - } - - // If we are here, we have timed out, but assume the call is still running. - return false; -} - - - -/** - Check SIP signalling. - @param transaction The call's TransactionEntry. - @param LCH The call's GSM logical channel (TCH/FACCH or SDCCH). - @param GSMCleared True if the call is already cleared in the GSM domain. - @return true If the call is cleared in the SIP domain. -*/ -bool updateSIPSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, bool GSMCleared) -{ - - // The main purpose of this code is to initiate disconnects from the SIP side. - - if (transaction->SIPFinished()) return true; - - bool GSMClearedOrClearing = GSMCleared || transaction->clearingGSM(); - - //only checking for Clearing because the call is active at this state. Should not cancel - if (transaction->MTDCheckBYE() == SIP::MTDClearing) { - LOG(DEBUG) << "got SIP BYE " << *transaction; - if (!GSMClearedOrClearing) { - // Initiate clearing in the GSM side. - LCH->send(GSM::L3Disconnect(transaction->L3TI())); - transaction->setTimer("305"); - transaction->GSMState(GSM::DisconnectIndication); - } else { - // GSM already cleared? - // Ack the BYE and end the call. - transaction->MTDSendBYEOK(); - } - } - - return (transaction->SIPFinished()); -} - - - -/** - Check SIP and GSM signalling. - Can block for up to 52 GSM L1 frames (240 ms) because LCH::send is blocking. - @param transaction The call's TransactionEntry. - @param LCH The call's logical channel (TCH/FACCH or SDCCH). - @return true If the call is cleared in both domains. -*/ -bool updateSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, unsigned timeout=0) -{ - - bool GSMCleared = (updateGSMSignalling(transaction,LCH,timeout)); - bool SIPFinished = updateSIPSignalling(transaction,LCH,GSMCleared); - return GSMCleared && SIPFinished; -} - - - - -bool outboundHandoverTransfer(TransactionEntry* transaction, GSM::TCHFACCHLogicalChannel *TCH) -{ - // By returning true, this function indicates to its caller that the call is cleared - // and no longer needs a channel on this BTS. - - // In this method, we are "BS1" in the ladder diagram. - // BS2 has alrady accepted the handover request. - - // Send the handover command. - TCH->send(GSM::L3HandoverCommand( - transaction->outboundCell(), - transaction->outboundChannel(), - transaction->outboundReference(), - transaction->outboundPowerCmd(), - transaction->outboundSynch() - )); - - // Start a timer for T3103, the handover failure timer. - GSM::Z100Timer T3103(gConfig.getNum("GSM.Timer.T3103")); - T3103.set(); - - // The next step for the MS is to send Handover Access to BS2. - // The next step for us is to wait for the Handover Complete message - // and see that the phone doesn't come back to us. - // BS2 is doing most of the work now. - // We will get a handover complete once it's over, but we don't really need it. - - // Q: What about transferring audio packets? - // A: There should not be any after we send the Handover Command. - - // Get the response. - // This is supposed to time out on successful handover, similar to the early assignment channel transfer.. - GSM::L3Frame *result = TCH->recv(T3103.remaining()); - if (result) { - // If we got here, the handover failed and we just keep running the call. - LOG(NOTICE) << "failed handover, received " << *result; - delete result; - // Restore the call state. - transaction->GSMState(GSM::Active); - return false; - } - - // If the phone doesn't come back, either the handover succeeded or - // the phone dropped the connection. Either way, we are clearing the call. - - // Invalidate local cache entry for this IMSI in the subscriber registry. - string imsi = string("IMSI").append(transaction->subscriber().digits()); - gSubscriberRegistry.removeUser(imsi.c_str()); - - LOG(INFO) "timeout following outbound handover; exiting normally"; - TCH->send(GSM::HARDRELEASE); - return true; -} - - -/** - Poll for activity while in a call. - Sleep if needed to prevent fast spinning. - Will block for up to 250 ms. - @param transaction The call's TransactionEntry. - @param TCH The call's TCH+FACCH. - @return true If the call was cleared. -*/ -bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH) -{ - - // See if the radio link disappeared. - if (TCH->radioFailure()) { - LOG(NOTICE) << "radio link failure, dropped call"; - gReports.incr("OpenBTS.GSM.CC.DroppedCalls"); - forceSIPClearing(transaction); - return true; - } - - // Process pending SIP and GSM signalling. - // If this returns true, it means the call is fully cleared. - if (updateSignalling(transaction,TCH)) return true; - - // Check for outbound handover. - if (transaction->GSMState() == GSM::HandoverOutbound) - return outboundHandoverTransfer(transaction,TCH); - - // Did an outside process request a termination? - if (transaction->terminationRequested()) { - // Cause 25 is "pre-emptive clearing". - abortCall(transaction,TCH,25); - // Do the hard release to short-cut the timers. - // If something else is requesting termination, - // it's probably because we need the channel for - // something else (like an emegency call) right away. - //TCH->send(GSM::HARDRELEASE); - return true; - } - - // Transfer vocoder data. - // If anything happened, then the call is still up. - // This is a blocking call, blocking 20 ms on average. - if (updateCallTraffic(transaction,TCH)) return false; - - // If nothing happened, sleep so we don't burn up the CPU cycles. - msleep(50); - return false; -} - - -/** - Pause for a given time while managing the connection. - Returns on timeout or call clearing. - Used for debugging to simulate ringing at terminating end. - @param transaction The transaction record for the call. - @param TCH The TCH+FACCH sed for this call. - @param waitTime_ms The maximum time to wait, in ms. - @return true If the call is cleared during the wait. -*/ -bool waitInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH, unsigned waitTime_ms) -{ - Timeval targetTime(waitTime_ms); - LOG(DEBUG); - while (!targetTime.passed()) { - if (pollInCall(transaction,TCH)) return true; - } - return false; -} - - - -/** - This is the standard call manangement loop, regardless of the origination type. - This function returns when the call is cleared and the channel is released. - @param transaction The transaction record for this call, will be cleared on exit. - @param TCH The TCH+FACCH for the call. -*/ -void Control::callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH) -{ - LOG(INFO) << " call connected " << *transaction; - if (gConfig.getBool("GSM.Cipher.Encrypt")) { - int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(transaction->subscriber().digits()); - if (!encryptionAlgorithm) { - LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber(); - } else if (TCH->decryptUplink_maybe(transaction->subscriber().digits(), encryptionAlgorithm)) { - // send Ciphering Mode Command - // start reception in new mode (GSM 04.08, 3.4.7) - // The spec says to start decrypting uplink at this time, but that would cause us to - // start decrypting before the Ciphering Mode Command is acknowledged, so we start - // maybe decrypting - try decoding without decrypting, and when a frame comes along - // that fails, we try decrypting, and if that passes than we start decrypting everything. - LOG(DEBUG) << "sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber(); - TCH->send(GSM::L3CipheringModeCommand( - GSM::L3CipheringModeSetting(true, encryptionAlgorithm), - GSM::L3CipheringModeResponse(false))); - } else { - LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber(); - } - } - gReports.incr("OpenBTS.GSM.CC.CallMinutes"); - // poll everything until the call is finished - // A rough count of frames. - size_t fCount = 0; - while (!pollInCall(transaction,TCH)) { - - if (transaction->deadOrRemoved()) { - LOG(ERR) << "attempting to use a defunct transaction"; - TCH->send(GSM::L3ChannelRelease()); - return; - } - - fCount++; - // On average, pollInCall blocks for 20 ms. - // Every minute, reset the watchdog timer. - if ((fCount%(60*50))==0) { - LOG(DEBUG) << fCount << " cycles of call management loop; resetting watchdog"; - gResetWatchdog(); - gReports.incr("OpenBTS.GSM.CC.CallMinutes"); - } - } - gTransactionTable.remove(transaction); -} - - - - -/** - This function starts MOC on the SDCCH to the point of TCH assignment. - @param req The CM Service Request that started all of this. - @param LCH The logical used to initiate call setup. -*/ -void Control::MOCStarter(const GSM::L3CMServiceRequest* req, GSM::LogicalChannel *LCH) -{ - assert(LCH); - assert(req); - LOG(INFO) << *req; - - // Determine if very early assignment already happened. - bool veryEarly = (LCH->type()==GSM::FACCHType); - - // If we got a TMSI, find the IMSI. - // Note that this is a copy, not a reference. - GSM::L3MobileIdentity mobileID = req->mobileID(); - resolveIMSI(mobileID,LCH); - - - // FIXME -- At this point, verify the that subscriber has access to this service. - // If the subscriber isn't authorized, send a CM Service Reject with - // cause code, 0x41, "requested service option not subscribed", - // followed by a Channel Release with cause code 0x6f, "unspecified". - // Otherwise, proceed to the next section of code. - // For now, we are assuming that the phone won't make a call if it didn't - // get registered. - - // Allocate a TCH for the call, if we don't have it already. - GSM::TCHFACCHLogicalChannel *TCH = NULL; - if (!veryEarly) { - TCH = allocateTCH(dynamic_cast(LCH)); - // It's OK to just return on failure; allocateTCH cleaned up already, - // and the SIP side and transaction record don't exist yet. - if (TCH==NULL) return; - } - - // Let the phone know we're going ahead with the transaction. - LOG(INFO) << "sending CMServiceAccept"; - LCH->send(GSM::L3CMServiceAccept()); - - // Get the Setup message. - // GSM 04.08 5.2.1.2 - GSM::L3Message* msg_setup = getMessage(LCH); - - // Check for abort, if so close and cancel - if (const GSM::L3CMServiceAbort *cmsab = dynamic_cast(msg_setup)) { - LOG(INFO) << "received CMServiceAbort, closing channel and clearing"; - //SIP Engine not started, just close the channel and exit - LCH->send(GSM::L3ChannelRelease()); - delete cmsab; - return; - } - - const GSM::L3Setup *setup = dynamic_cast(msg_setup); - if (!setup) { - if (msg_setup) { - LOG(WARNING) << "Unexpected message " << *msg_setup; - delete msg_setup; - } - throw UnexpectedMessage(); - } - gReports.incr("OpenBTS.GSM.CC.MOC.Setup"); - - /* early RLLP request */ - /* this seems to need to be sent after initial call setup - -kurtis */ - if (gConfig.getBool("Control.Call.QueryRRLP.Early")) { - // Query for RRLP - if (!sendRRLP(mobileID, LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - - LOG(INFO) << *setup; - // Pull out the L3 short transaction information now. - // See GSM 04.07 11.2.3.1.3. - // Set the high bit, since this TI came from the MS. - unsigned L3TI = setup->TI() | 0x08; - if (!setup->haveCalledPartyBCDNumber()) { - // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. - LOG(WARNING) << "MOC setup with no number"; - // Cause 0x60 "Invalid mandatory information" - LCH->send(GSM::L3ReleaseComplete(L3TI,0x60)); - LCH->send(GSM::L3ChannelRelease()); - // The SIP side and transaction record don't exist yet. - // So we're done. - delete msg_setup; - return; - } - - LOG(DEBUG) << "SIP start engine"; - // Get the users sip_uri by pulling out the IMSI. - //const char *IMSI = mobileID.digits(); - // Pull out Number user is trying to call and use as the sip_uri. - const char *bcdDigits = setup->calledPartyBCDNumber().digits(); - - // Create a transaction table entry so the TCH controller knows what to do later. - // The transaction on the TCH will be a continuation of this one. - TransactionEntry *transaction = new TransactionEntry( - gConfig.getStr("SIP.Proxy.Speech").c_str(), - mobileID, - LCH, - req->serviceType(), - L3TI, - setup->calledPartyBCDNumber()); - LOG(DEBUG) << "transaction: " << *transaction; - gTransactionTable.add(transaction); - - // At this point, we have enough information start the SIP call setup. - // We also have a SIP side and a transaction that will need to be - // cleaned up on abort or clearing. - - // Now start a call by contacting asterisk. - // Engine methods will return their current state. - // The remote party will start ringing soon. - LOG(DEBUG) << "starting SIP (INVITE) Calling "<MOCSendINVITE(bcdDigits,gConfig.getStr("SIP.Local.IP").c_str(),basePort,SIP::RTPGSM610); - LOG(DEBUG) << "transaction: " << *transaction; - - // Once we can start SIP call setup, send Call Proceeding. - LOG(INFO) << "Sending Call Proceeding"; - LCH->send(GSM::L3CallProceeding(L3TI)); - transaction->GSMState(GSM::MOCProceeding); - // Finally done with the Setup message. - delete msg_setup; - - // The transaction is moving on to the MOCController. - // If we need a TCH assignment, we do it here. - LOG(DEBUG) << "transaction: " << *transaction; - if (veryEarly) { - // For very early assignment, we need a mode change. - static const GSM::L3ChannelMode mode(GSM::L3ChannelMode::SpeechV1); - LCH->send(GSM::L3ChannelModeModify(LCH->channelDescription(),mode)); - GSM::L3Message *msg_ack = getMessage(LCH); - const GSM::L3ChannelModeModifyAcknowledge *ack = - dynamic_cast(msg_ack); - if (!ack) { - // FIXME -- We need this in a loop calling the GSM disptach function. - if (msg_ack) { - LOG(WARNING) << "Unexpected message " << *msg_ack; - delete msg_ack; - } - throw UnexpectedMessage(transaction->ID()); - } - // Cause 0x06 is "channel unacceptable" - bool modeOK = (ack->mode()==mode); - delete msg_ack; - if (!modeOK) return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x06)); - MOCController(transaction,dynamic_cast(LCH)); - } else { - // For late assignment, send the TCH assignment now. - // This dispatcher on the next channel will continue the transaction. - assignTCHF(transaction,LCH,TCH); - } -} - - - - - -/** - Continue MOC process on the TCH. - @param transaction The call state and SIP interface. - @param TCH The traffic channel to be used. -*/ -void Control::MOCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH) -{ - assert(transaction); - assert(TCH); - if (transaction->deadOrRemoved()) { - LOG(ERR) << "dead or defunct transaciton on " << *TCH; - TCH->send(GSM::L3ChannelRelease()); - return; - } - LOG(DEBUG) << "transaction: " << *transaction; - unsigned L3TI = transaction->L3TI(); - assert(L3TI>7); - - - // Look for RINGING or OK from the SIP side. - // There's a T310 running on the phone now. - // The phone will initiate clearing if it expires. - // FIXME -- We should also have a SIP.Timer.B timeout on this end. - SIP::SIPState prevState = transaction->SIPState(); - while (transaction->GSMState()!=GSM::CallReceived) { - - if (transaction->deadOrRemoved()) { - LOG(ERR) << "attempting to use a defunct transaction"; - TCH->send(GSM::L3ChannelRelease()); - return; - } - - if (updateGSMSignalling(transaction,TCH)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - if (transaction->clearingGSM()) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - - LOG(INFO) << "wait for Ringing or OK"; - SIP::SIPState state = transaction->MOCCheckForOK(); - LOG(DEBUG) << "SIP state="<MOCSendACK(); - forceGSMClearing(transaction,TCH,GSM::L3Cause(0x11)); - gTransactionTable.remove(transaction); - return; - case SIP::Fail: - LOG(NOTICE) << "SIP:Fail, abort"; - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - case SIP::Ringing: - LOG(INFO) << "SIP:Ringing, send Alerting and move on"; - TCH->send(GSM::L3Alerting(L3TI)); - transaction->GSMState(GSM::CallReceived); - break; - case SIP::Active: - LOG(DEBUG) << "SIP:Active, move on"; - transaction->GSMState(GSM::CallReceived); - break; - case SIP::Proceeding: - if (state != prevState) { - LOG(DEBUG) << "SIP::Proceeding, state change, sending L3 progress"; - TCH->send(GSM::L3Progress(L3TI)); - } - break; - case SIP::Timeout: - // This is CRIT instead of ALERT because it could also be due to packet loss. - LOG(CRIT) << "MOC INVITE Timed out. Is SIP.Proxy.Speech (" << gConfig.getStr("SIP.Proxy.Speech") << ") configured correctly?"; - state = transaction->MOCResendINVITE(); - break; - default: - LOG(NOTICE) << "SIP unexpected state " << state; - break; - } - prevState = state; - } - - // There's a question here of what entity is generating the "patterns" - // (ringing, busy signal, etc.) during call set-up. For now, we're ignoring - // that question and hoping the phone will make its own ringing pattern. - - - // Wait for the SIP session to start. - // There's a timer on the phone that will initiate clearing if it expires. - LOG(INFO) << "wait for SIP OKAY"; - SIP::SIPState state = transaction->SIPState(); - while (state!=SIP::Active) { - - if (transaction->deadOrRemoved()) { - LOG(ERR) << "attempting to use a defunct transaction"; - TCH->send(GSM::L3ChannelRelease()); - return; - } - - LOG(DEBUG) << "wait for SIP session start"; - state = transaction->MOCCheckForOK(); - LOG(DEBUG) << "SIP state "<< state; - - // check GSM state - if (updateGSMSignalling(transaction,TCH)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - if (transaction->clearingGSM()) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - - // parse out SIP state - switch (state) { - case SIP::Busy: - // Should this be possible at this point? - LOG(INFO) << "SIP:Busy, abort"; - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x11)); - case SIP::Fail: - LOG(INFO) << "SIP:Fail, abort"; - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - case SIP::Proceeding: - LOG(DEBUG) << "SIP:Proceeding, NOT sending progress"; - //TCH->send(GSM::L3Progress(L3TI)); - break; - // For these cases, do nothing. - case SIP::Timeout: - // FIXME We should abort if this happens too often. - // For now, we are relying on the phone, which may have bugs of its own. - case SIP::Active: - default: - break; - } - } - - // Let the phone know the call is connected. - LOG(INFO) << "sending Connect to handset"; - TCH->send(GSM::L3Connect(L3TI)); - transaction->setTimer("313"); - transaction->GSMState(GSM::ConnectIndication); - - // The call is open. - transaction->MOCInitRTP(); - transaction->MOCSendACK(); - - // FIXME -- We need to watch for a repeated OK in case the ACK got lost. - - // Get the Connect Acknowledge message. - while (transaction->GSMState()!=GSM::Active) { - - if (transaction->deadOrRemoved()) { - LOG(ERR) << "attempting to use a defunct transaction"; - TCH->send(GSM::L3ChannelRelease()); - return; - } - - LOG(DEBUG) << "MOC Q.931 state=" << transaction->GSMState(); - if (updateGSMSignalling(transaction,TCH,T313ms)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - } - - // At this point, everything is ready to run the call. - callManagementLoop(transaction,TCH); - - // The radio link should have been cleared with the call. - // So just return. -} - - - - -void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH) -{ - assert(LCH); - LOG(INFO) << "MTC on " << LCH->type() << " transaction: "<< *transaction; - - // Determine if very early assigment already happened. - bool veryEarly = false; - if (LCH->type()==GSM::FACCHType) veryEarly=true; - - /* early RLLP request */ - if (gConfig.getBool("Control.Call.QueryRRLP.Early")) { - // Query for RRLP - if (!sendRRLP(transaction->subscriber(), LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - - // Allocate a TCH for the call. - GSM::TCHFACCHLogicalChannel *TCH = NULL; - if (!veryEarly) { - TCH = allocateTCH(dynamic_cast(LCH)); - // It's OK to just return on failure; allocateTCH cleaned up already. - // The orphaned transaction will be cleared automatically later. - if (TCH==NULL) return; - } - - - // Get transaction identifiers. - // This transaction was created by the SIPInterface when it - // processed the INVITE that started this call. - unsigned L3TI = transaction->L3TI(); - assert(L3TI<7); - - // GSM 04.08 5.2.2.1 - LOG(INFO) << "sending GSM Setup to call " << transaction->calling(); - LCH->send(GSM::L3Setup(L3TI,GSM::L3CallingPartyBCDNumber(transaction->calling()))); - gReports.incr("OpenBTS.GSM.CC.MTC.Setup"); - transaction->setTimer("303"); - transaction->GSMState(GSM::CallPresent); - - // Wait for Call Confirmed message. - LOG(DEBUG) << "wait for GSM Call Confirmed"; - while (transaction->GSMState()!=GSM::MTCConfirmed) { - if (transaction->MTCSendTrying()==SIP::Fail) { - LOG(NOTICE) << "call failed on SIP side"; - if (TCH) TCH->send(GSM::HARDRELEASE); - // Cause 0x03 is "no route to destination" - return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x03)); - } - // FIXME -- What's the proper timeout here? - // It's the SIP TRYING timeout, whatever that is. - if (updateGSMSignalling(transaction,LCH,1000)) { - LOG(INFO) << "Release from GSM side"; - if (TCH) TCH->send(GSM::HARDRELEASE); - LCH->send(GSM::RELEASE); - return; - } - // Check for SIP cancel, too. - if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) { - LOG(INFO) << "call cancelled on SIP side"; - if (TCH) TCH->send(GSM::HARDRELEASE); - transaction->MTDSendCANCELOK(); - //should probably send a 487 here - // Cause 0x15 is "rejected" - return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x15)); - } - //lastly check if we're toast - if(transaction->SIPState()==SIP::Fail) { - LOG(DEBUG) << "Call failed"; - if (TCH) TCH->send(GSM::HARDRELEASE); - return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x7F)); - } - } - - // The transaction is moving to the MTCController. - // Once this update happens, don't change the transaction object again in this function. - LOG(DEBUG) << "transaction: " << *transaction; - if (veryEarly) { - // For very early assignment, we need a mode change. - static const GSM::L3ChannelMode mode(GSM::L3ChannelMode::SpeechV1); - LCH->send(GSM::L3ChannelModeModify(LCH->channelDescription(),mode)); - // FIXME - We should call this in a loop in case there are stray messages in the channel. - GSM::L3Message* msg_ack = getMessage(LCH); - const GSM::L3ChannelModeModifyAcknowledge *ack = - dynamic_cast(msg_ack); - if (!ack) { - if (msg_ack) { - LOG(WARNING) << "Unexpected message " << *msg_ack; - delete msg_ack; - } - throw UnexpectedMessage(transaction->ID()); - } - // Cause 0x06 is "channel unacceptable" - bool modeOK = (ack->mode()==mode); - delete msg_ack; - if (!modeOK) return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x06)); - MTCController(transaction,dynamic_cast(LCH)); - } - else { - // For late assignment, send the TCH assignment now. - // The dispatcher on the next channel will continue the transaction. - assert(TCH); - assignTCHF(transaction,LCH,TCH); - } -} - - -void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH) -{ - // Early Assignment Mobile Terminated Call. - // Transaction table in 04.08 7.3.3 figure 7.10a - - LOG(DEBUG) << "transaction: " << *transaction; - unsigned L3TI = transaction->L3TI(); - assert(L3TI<7); - assert(TCH); - - // Get the alerting message. - LOG(INFO) << "waiting for GSM Alerting and Connect"; - while (transaction->GSMState()!=GSM::Active) { - if (updateGSMSignalling(transaction,TCH,1000)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - if (transaction->GSMState()==GSM::Active) break; - if (transaction->GSMState()==GSM::CallReceived) { - LOG(DEBUG) << "sending SIP Ringing"; - transaction->MTCSendRinging(); - } - //this should probably check if GSM has ended as well - if (transaction->SIPFinished()){ - //Call was canceled during setup, just remove it - LOG(INFO) << "Call canceled during setup, removing"; - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - } - // Check for SIP cancel, too. - if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) { - LOG(INFO) << "MTCCheckForCancel return Canceling"; - transaction->MTDSendCANCELOK(); - //should probably send a 487 here - // Cause 0x15 is "rejected" - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - } - //check if we're toast - if (transaction->SIPState()==SIP::Fail){ - LOG(DEBUG) << "Call failed"; - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - } - } - - // FIXME -- We should also have a SIP.Timer.F timeout here. - LOG(INFO) << "allocating port and sending SIP OKAY"; - unsigned RTPPorts = allocateRTPPorts(); - SIP::SIPState state = transaction->MTCSendOK(RTPPorts,SIP::RTPGSM610); - while (state!=SIP::Active) { - LOG(DEBUG) << "wait for SIP OKAY-ACK"; - if (updateGSMSignalling(transaction,TCH)) return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - state = transaction->MTCCheckForACK(); - LOG(DEBUG) << "SIP call state "<< state; - switch (state) { - case SIP::Active: - break; - case SIP::Fail: - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x7F)); - case SIP::Timeout: - state = transaction->MTCSendOK(RTPPorts,SIP::RTPGSM610); - break; - case SIP::Connecting: - break; - case SIP::MTDCanceling: - state = transaction->MTDSendCANCELOK(); - //should probably send a 487 here - // Cause 0x15 is "rejected" - return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); - default: - LOG(NOTICE) << "SIP unexpected state " << state; - break; - } - } - transaction->MTCInitRTP(); - - // Send Connect Ack to make it all official. - LOG(DEBUG) << "MTC send GSM Connect Acknowledge"; - TCH->send(GSM::L3ConnectAcknowledge(L3TI)); - - // At this point, everything is ready to run for the call. - // The radio link should have been cleared with the call. - callManagementLoop(transaction,TCH); -} - - -void Control::TestCall(TransactionEntry *transaction, GSM::LogicalChannel *LCH) -{ - assert(LCH); - LOG(INFO) << LCH->type() << " transaction: "<< *transaction; - assert(transaction->L3TI()<7); - - // Mark the call as active. - transaction->GSMState(GSM::Active); - - LOG(INFO) << "starting test call"; - while (!transaction->terminationRequested()) { sleep(1); } - LOG(INFO) << "ending test call"; - LCH->send(GSM::L3ChannelRelease()); - gTransactionTable.remove(transaction); -} - - - - - -void Control::initiateMTTransaction(TransactionEntry *transaction, GSM::ChannelType chanType, unsigned pageTime) -{ - gTransactionTable.add(transaction); - transaction->GSMState(GSM::Paging); - gBTS.pager().addID(transaction->subscriber(),chanType,*transaction,pageTime); -} - - - - - -// vim: ts=4 sw=4 diff --git a/Control/CallControl.h b/Control/CallControl.h deleted file mode 100644 index 1cbc5da..0000000 --- a/Control/CallControl.h +++ /dev/null @@ -1,73 +0,0 @@ -/**@file GSM/SIP Call Control -- GSM 04.08, ISDN ITU-T Q.931, SIP IETF RFC-3261, RTP IETF RFC-3550. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. -* -* 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. - -*/ - -#ifndef CALLCONTROL_H -#define CALLCONTROL_H - - -namespace GSM { -class LogicalChannel; -class TCHFACCHLogicalChannel; -class L3CMServiceRequest; -}; - -namespace Control { - -class TransactionEntry; - - - -/**@name MOC */ -//@{ -/** Run the MOC to the point of alerting, doing early assignment if needed. */ -void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*); -/** Complete the MOC connection. */ -void MOCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*); -//@} - - -/**@name MTC */ -//@{ -/** Run the MTC to the point of alerting, doing early assignment if needed. */ -void MTCStarter(TransactionEntry*, GSM::LogicalChannel*); -/** Complete the MTC connection. */ -void MTCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*); -//@} - - -/**@name Test Call */ -//@{ -/** Run the test call. */ -void TestCall(TransactionEntry*, GSM::LogicalChannel*); -//@} - -/** Create a new transaction entry and start paging. */ -void initiateMTTransaction(TransactionEntry* transaction, - GSM::ChannelType chanType, unsigned pageTime); - - -/** - This is the standard call manangement loop, regardless of the origination type. - This function returns when the call is cleared and the channel is released. - @param transaction The transaction record for this call, will be cleared on exit. - @param TCH The TCH+FACCH for the call. -*/ -void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH); - -} - - -#endif diff --git a/Control/ControlCommon.cpp b/Control/ControlCommon.cpp deleted file mode 100644 index 313a3c4..0000000 --- a/Control/ControlCommon.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/**@file Common-use functions for the control layer. */ - -/* -* Copyright 2008, 2010 Free Software Foundation, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -#include "ControlCommon.h" -#include "TransactionTable.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#undef WARNING - - -using namespace std; -using namespace GSM; -using namespace Control; - - - - - -// FIXME -- getMessage should return an L3Frame, not an L3Message. -// This will mean moving all of the parsing into the control layer. -// FIXME -- This needs an adjustable timeout. - -L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI) -{ - unsigned timeout_ms = LCH->N200() * T200ms; - L3Frame *rcv = LCH->recv(timeout_ms,SAPI); - if (rcv==NULL) { - LOG(NOTICE) << "L3 read timeout"; - throw ChannelReadTimeout(); - } - LOG(DEBUG) << "received " << *rcv; - Primitive primitive = rcv->primitive(); - if (primitive!=DATA) { - LOG(ERR) << "unexpected primitive " << primitive; - delete rcv; - throw UnexpectedPrimitive(); - } - L3Message *msg = parseL3(*rcv); - if (msg==NULL) { - LOG(WARNING) << "unparsed message:" << *rcv; - delete rcv; - return NULL; - //throw UnsupportedMessage(); - } - delete rcv; - LOG(DEBUG) << *msg; - return msg; -} - - -// FIXME -- getMessage should return an L3Frame, not an L3Message. -// This will mean moving all of the parsing into the control layer. -// FIXME -- This needs an adjustable timeout. - -L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI) -{ - L3Message *msg = getMessageCore(LCH,SAPI); - // Handsets should not be sending us GPRS suspension requests when GPRS support is not enabled. - // But if they do, we should ignore them. - // They should not send more than one in any case, but we need to be - // ready for whatever crazy behavior they throw at us. - - // The suspend procedure includes MS<->BSS and BSS<->SGSN messages. - // GSM44.018 3.4.25 GPRS Suspension Procedure and 9.1.13b: GPRS Suspension Request message. - // Also 23.060 16.2.1.1 Suspend/Resume procedure general. - // GSM08.18: Suspend Procedure talks about communication between the BSS and SGSN, - // and is not applicable to us when using the internal SGSN. - // Note: When call is finished the RR is supposed to include a GPRS resumption IE, but if it does not, - // 23.060 16.2.1.1.1 says the MS will do a GPRS RoutingAreaUpdate to get the - // GPRS service back, so we are not worrying about it. - // (pat 3-2012) Send the message to the internal SGSN. - // It returns true if GPRS and the internal SGSN are enabled. - // If we are using an external SGSN, we could send the GPRS suspend request to the SGSN via the BSSG, - // but that has no hope of doing anything useful. See ticket #613. - // First, We are supposed to automatically detect when we should do the Resume procedure. - // Second: An RA-UPDATE, which gets send to the SGSN, does something to the CC state - // that I dont understand yet. - // We dont do any of the above. - unsigned count = gConfig.getNum("GSM.Control.GPRSMaxIgnore"); - const GSM::L3GPRSSuspensionRequest *srmsg; - while (count && (srmsg = dynamic_cast(msg))) { - if (! SGSN::Sgsn::handleGprsSuspensionRequest(srmsg->mTLLI,srmsg->mRaId)) { - LOG(NOTICE) << "ignoring GPRS suspension request"; - } - msg = getMessageCore(LCH,SAPI); - count--; - } - return msg; -} - - - - - - -/* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */ -unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, LogicalChannel* LCH) -{ - // Returns known or assigned TMSI. - assert(LCH); - LOG(DEBUG) << "resolving mobile ID " << mobileID << ", sameLAI: " << sameLAI; - - // IMSI already? See if there's a TMSI already, too. - if (mobileID.type()==IMSIType) { - GPRS::GPRSNotifyGsmActivity(mobileID.digits()); - return gTMSITable.TMSI(mobileID.digits()); - } - - // IMEI? WTF?! - // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". - if (mobileID.type()==IMEIType) throw UnexpectedMessage(); - - // Must be a TMSI. - // Look in the table to see if it's one we assigned. - unsigned TMSI = mobileID.TMSI(); - char* IMSI = NULL; - if (sameLAI) IMSI = gTMSITable.IMSI(TMSI); - if (IMSI) { - // We assigned this TMSI already; the TMSI/IMSI pair is already in the table. - GPRS::GPRSNotifyGsmActivity(IMSI); - mobileID = L3MobileIdentity(IMSI); - LOG(DEBUG) << "resolving mobile ID (table): " << mobileID; - free(IMSI); - return TMSI; - } - // Not our TMSI. - // Phones are not supposed to do this, but many will. - // If the IMSI's not in the table, ASK for it. - LCH->send(L3IdentityRequest(IMSIType)); - // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. - L3Message* msg = getMessage(LCH); - L3IdentityResponse *resp = dynamic_cast(msg); - if (!resp) { - if (msg) delete msg; - throw UnexpectedMessage(); - } - mobileID = resp->mobileID(); - LOG(INFO) << resp; - delete msg; - LOG(DEBUG) << "resolving mobile ID (requested): " << mobileID; - // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". - if (mobileID.type()!=IMSIType) throw UnexpectedMessage(); - // Return 0 to indicate that we have not yet assigned our own TMSI for this phone. - return 0; -} - - - -/* Resolve a mobile ID to an IMSI. */ -void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH) -{ - // Are we done already? - if (mobileIdentity.type()==IMSIType) return; - - // If we got a TMSI, find the IMSI. - if (mobileIdentity.type()==TMSIType) { - char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI()); - if (IMSI) { - GPRS::GPRSNotifyGsmActivity(IMSI); - mobileIdentity = L3MobileIdentity(IMSI); - } - free(IMSI); - } - - // Still no IMSI? Ask for one. - if (mobileIdentity.type()!=IMSIType) { - LOG(NOTICE) << "MOC with no IMSI or valid TMSI. Reqesting IMSI."; - LCH->send(L3IdentityRequest(IMSIType)); - // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. - L3Message* msg = getMessage(LCH); - L3IdentityResponse *resp = dynamic_cast(msg); - if (!resp) { - if (msg) delete msg; - throw UnexpectedMessage(); - } - mobileIdentity = resp->mobileID(); - if (mobileIdentity.type()==IMSIType) { - GPRS::GPRSNotifyGsmActivity(mobileIdentity.digits()); - } - delete msg; - } - - // Still no IMSI?? - if (mobileIdentity.type()!=IMSIType) { - // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. - LOG(WARNING) << "MOC setup with no IMSI"; - // Cause 0x60 "Invalid mandatory information" - LCH->send(L3CMServiceReject(L3RejectCause(0x60))); - LCH->send(L3ChannelRelease()); - // The SIP side and transaction record don't exist yet. - // So we're done. - return; - } -} - - - - - -// vim: ts=4 sw=4 diff --git a/Control/ControlCommon.h b/Control/ControlCommon.h index 6f63f82..9157ecc 100644 --- a/Control/ControlCommon.h +++ b/Control/ControlCommon.h @@ -27,24 +27,43 @@ #include #include -#include +//#include #include -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include -#include "TMSITable.h" +//#include "TMSITable.h" +#include + +// These are macros for manufacturing states for state machines. +// Since we are using C++, they cannot be classes if we want to use switch(). +// The pd selects an address space for each group of messages, whose mti must <256. +// The pd might be: L3CallControlPD, L3MobilityManagementPD, L3RadioResourcePD, etc. +// Note we overload pd==0 (Group Call Control) for naked states in the state machines, which is ok because +// we dont support Group Call and even if we did, the Group Call MTIs (GSM 04.68 9.3) will probably not collide. +// We use higher unused pds for our own purposes: 16 is for SIP Dialog messages, 17 for L3Frame primitives. +#define L3CASE_RAW(pd,mti) (((pd)<<8)|(mti)) +#define L3CASE_CC(mti) L3CASE_RAW(L3CallControlPD,L3CCMessage::mti) // pd == 3 +#define L3CASE_MM(mti) L3CASE_RAW(L3MobilityManagementPD,L3MMMessage::mti) // pd == 5 +#define L3CASE_RR(mti) L3CASE_RAW(L3RadioResourcePD,L3RRMessage::mti) // pd == 6 +#define L3CASE_SS(mti) L3CASE_RAW(L3NonCallSSPD,L3SupServMessage::mti) // pd == 6 +#define L3CASE_SMS(mti) L3CASE_RAW(L3SMSPD,CPMessage::mti) // pd == 9 +#define L3CASE_PRIMITIVE(prim) L3CASE_RAW(17,prim) +// L3CASE_DIALOG_STATE is for a raw dialog state number, and L3CASE_SIP is for a dialog state name. +#define L3CASE_DIALOG_STATE(sipDialogState) L3CASE_RAW(16,sipDialogState) // SIP messages use pd == 16, which is unused by GSM. +#define L3CASE_SIP(dialogStateName) L3CASE_DIALOG_STATE(SIP::DialogState::dialogStateName) +//unused: #define L3CASE_ERROR L3CASE_RAW(18,0) -// Enough forward refs to prevent "kitchen sick" includes and circularity. +// Enough forward refs to prevent "kitchen sink" includes and circularity. namespace GSM { class L3Message; -class LogicalChannel; class SDCCHLogicalChannel; class SACCHLogicalChannel; class TCHFACCHLogicalChannel; @@ -55,20 +74,26 @@ class L3CMServiceRequest; /**@namespace Control This namepace is for use by the control layer. */ namespace Control { -class TransactionEntry; -class TransactionTable; +class TranEntry; +class NewTransactionTable; +class MMContext; +class MMUser; +class L3LogicalChannel; /**@name Call control time-out values (in ms) from ITU-T Q.931 Table 9-1 and GSM 04.08 Table 11.4. */ //@{ #ifndef RACETEST const unsigned T301ms=60000; ///< recv ALERT --> recv CONN const unsigned T302ms=12000; ///< send SETUP ACK --> any progress -const unsigned T303ms=10000; ///< send SETUP --> recv CALL CONF or REL COMP +const unsigned T303ms=30000; ///< send SETUP --> recv CALL CONF or REL COMP const unsigned T304ms=20000; ///< recv SETUP ACK --> any progress const unsigned T305ms=30000; ///< send DISC --> recv REL or DISC const unsigned T308ms=30000; ///< send REL --> rev REL or REL COMP const unsigned T310ms=30000; ///< recv CALL CONF --> recv ALERT, CONN, or DISC const unsigned T313ms=30000; ///< send CONNECT --> recv CONNECT ACK +const unsigned T3270ms=12000; ///< send IdentityRequest -> recv IdentityResponse +const unsigned TR1Mms=40000; ///< MO-SMS timer. 3GPP 4.11 10.0 says: 35s < TR1M < 45s +const unsigned TR2Mms=15000; ///< MT-SMS timer. 3GPP 4.11 10.0 says: 12s < TR2M < 20s #else // These are reduced values to force testing of poor network behavior. const unsigned T301ms=18000; ///< recv ALERT --> recv CONN @@ -79,59 +104,65 @@ const unsigned T305ms=3000; ///< send DISC --> recv REL or DISC const unsigned T308ms=3000; ///< send REL --> rev REL or REL COMP const unsigned T310ms=3000; ///< recv CALL CONF --> recv ALERT, CONN, or DISC const unsigned T313ms=3000; ///< send CONNECT --> recv CONNECT ACK +const unsigned T3270ms=12000; ///< send IdentityRequest -> recv IdentityResponse +const unsigned TR1Mms=40000; ///< MO-SMS timer. 3GPP 4.11 10.0 says: 35s < TR1M < 45s +const unsigned TR2Mms=15000; ///< MT-SMS timer. 3GPP 4.11 10.0 says: 12s < TR1M < 20s #endif //@} +// These enums are here because they need to be in a file included before others. +// Mobility Management Sublayer 4.08 4.1 +// The primary purpose is to set between CM Call Management requests and RR channel allocation. +// There can be multiple simultaneous requests and MMLayer mediates them. +// The MM sublayer is not an exposed entity in the sense that nobody ever sees it or this state. +// The official Mobility Management states defined on the Network Side are 4.08 4.1.2.3. + //1. IDLE The MM sublayer is not active except possibly when the RR sublayer is in Group Receive mode. + //2. WAIT FOR RR CONNECTION aka paging. + //3. MM CONNECTION ACTIVE The MM sublayer has a RR connection to a mobile station. One or more MM connections are active, or no + // MM connection is active but an RRLP procedure is ongoing. + //4. IDENTIFICATION INITIATED The identification procedure has been started by the network. The timer T3270 is running. + //5. AUTHENTICATION INITIATED The authentication procedure has been started by the network. The timer T3260 is running. + //6. TMSI REALLOCATION INITIATED The TMSI reallocation procedure has been started by the network. The timer T3250 is running. + //7. CIPHERING MODE INITIATED The cipher mode setting procedure has been requested to the RR sublayer. + //8a. WAIT FOR MOBILE ORIGINATED MM CONNECTION A CM SERVICE REQUEST message is received and processed, and the MM sublayer + // awaits the "opening message" of the MM connection. + // 9. WAIT FOR REESTABLISHMENT The RR connection to a mobile station with one or more active MM connection has been lost. + // The network awaits a possible re-establishment request from the mobile station. +// We use our own MM states that are only vaguely related. +// The spec assumes that the MM entity and CM entity are separate, but we run all procedures on top of a TransactionEntry object, +// so we can combine some MM states. +enum MMState { + MMStateUndefined, + MMStatePaging, // aka "2. WAIT FOR CONNECTION" + MMStateActive, // aka "3. MM CONNECTION ACTIVE" + // In our case this means that we are not planning to change the channel either. + MMStateChannelChange, // We are busy changing the channel, eg, SDCCH to TCH, which blocks everything. + MMBlocked, // blocked for LUR, Identification or Authentication in progress, either on this IMSI (blocked for sure) + // or on this TMSI (speculatively blocked.) The outcome of the procedure may determine whether + // we keep or discard this MMUser and any pending CM requests. +}; - -/**@name Common-use functions from the control layer. */ -//@{ - -/** - Get a message from a LogicalChannel. - Close the channel with abnormal release on timeout. - Caller must delete the returned pointer. - Throws ChannelReadTimeout, UnexpecedPrimitive or UnsupportedMessage on timeout. - @param LCH The channel to receive on. - @param SAPI The service access point. - @return Pointer to message. -*/ -// FIXME -- This needs an adjustable timeout. -GSM::L3Message* getMessage(GSM::LogicalChannel* LCH, unsigned SAPI=0); +// The MM reasons that may cause a call to end. +//enum MMCause { +// MMNormalEvent, // Means the MMLayer can try something else on this MS. +// MMCongestion, +// MMImsiDetach, // Means the MMLayer should terminate everything on this MS. +// MMFailure, // Authenticaion/id or other major failure. terminate everything +//}; -//@} /**@name Dispatch controllers for specific channel types. */ //@{ //void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH); //void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH); -void DCCHDispatcher(GSM::LogicalChannel *DCCH); +void DCCHDispatcher(L3LogicalChannel *DCCH); //@} -/** - Resolve a mobile ID to an IMSI. - Returns TMSI, if it is already in the TMSITable. - @param sameLAI True if the mobileID is known to have come from this LAI. - @param mobID A mobile ID, that may be modified by the function. - @param LCH The Dm channel to the mobile. - @return A TMSI value from the TMSITable or zero if not found. -*/ -unsigned resolveIMSI(bool sameLAI, GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH); - -/** - Resolve a mobile ID to an IMSI. - @param mobID A mobile ID, that may be modified by the function. - @param LCH The Dm channel to the mobile. -*/ -void resolveIMSI(GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH); - - - /** SMSCB sender function @@ -149,7 +180,7 @@ void *SMSCBSender(void*); A control layer excpection includes a pointer to a transaction. The transaction might require some clean-up action, depending on the exception. */ -class ControlLayerException { +class ControlLayerException : public std::exception { private: @@ -159,7 +190,9 @@ class ControlLayerException { ControlLayerException(unsigned wTransactionID=0) :mTransactionID(wTransactionID) - {} + { + LOG(INFO) << "ControlLayerException"; + } unsigned transactionID() { return mTransactionID; } }; @@ -169,7 +202,9 @@ class UnexpectedMessage : public ControlLayerException { public: UnexpectedMessage(unsigned wTransactionID=0) :ControlLayerException(wTransactionID) - {} + { + LOG(INFO) << "UnexpectedMessage Exception"; + } }; /** Thrown when recvL3 returns NULL */ @@ -185,7 +220,9 @@ class UnsupportedMessage : public ControlLayerException { public: UnsupportedMessage(unsigned wTransactionID=0) :ControlLayerException(wTransactionID) - {} + { + LOG(INFO) << "UnsupportedMessage Exception"; + } }; /** Thrown when the control layer gets the wrong primitive */ @@ -193,7 +230,9 @@ class UnexpectedPrimitive : public ControlLayerException { public: UnexpectedPrimitive(unsigned wTransactionID=0) :ControlLayerException(wTransactionID) - {} + { + LOG(INFO) << "UnexpectedPrimitive Exception"; + } }; /** Thrown when a T3xx expires */ @@ -209,13 +248,16 @@ class RemovedTransaction : public ControlLayerException { public: RemovedTransaction(unsigned wTransactionID=0) :ControlLayerException(wTransactionID) - {} + { + LOG(INFO) << "RemovedTransaction Exception"; + } }; //@} + } //Control @@ -223,7 +265,7 @@ class RemovedTransaction : public ControlLayerException { /**@addtogroup Globals */ //@{ /** A single global transaction table in the global namespace. */ -extern Control::TransactionTable gTransactionTable; +extern Control::NewTransactionTable gNewTransactionTable; //@} diff --git a/Control/ControlTransfer.cpp b/Control/ControlTransfer.cpp new file mode 100644 index 0000000..0ef3c1b --- /dev/null +++ b/Control/ControlTransfer.cpp @@ -0,0 +1,216 @@ +/**@file Declarations for common-use control-layer functions. */ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ +#define LOG_GROUP LogGroup::Control + +#include "ControlTransfer.h" +//#include "TransactionTable.h" +#include "L3TranEntry.h" +#include +#include + +namespace Control { + +#define CASENAME(x) case x: return #x; +const char *CodecType2Name(CodecType ct) +{ + switch (ct) { + CASENAME(GSM_FR) + CASENAME(GSM_HR) + CASENAME(GSM_EFR) + CASENAME(AMR_FR) + CASENAME(AMR_HR) + CASENAME(UMTS_AMR) + CASENAME(UMTS_AMR2) + CASENAME(TDMA_EFR) + CASENAME(PDC_EFR) + CASENAME(AMR_FR_WB) + CASENAME(UMTS_AMR_WB) + CASENAME(OHR_AMR) + CASENAME(OFR_AMR_WB) + CASENAME(OHR_AMR_WB) + CASENAME(PCMULAW) + CASENAME(PCMALAW) + default: return "CodecType undefined"; + } +} + +void CodecSet::text(std::ostream&os) const +{ + if (mCodecs & GSM_FR) os << " GSM_FR"; + if (mCodecs & GSM_HR) os << " GSM_HR"; + if (mCodecs & AMR_FR) os << " AMR_FR"; + if (mCodecs & AMR_HR) os << " AMR_HR"; + if (mCodecs & UMTS_AMR) os << " UMTS_AMR"; + if (mCodecs & UMTS_AMR2) os << " UMTS_AMR2"; + if (mCodecs & TDMA_EFR) os << " TDMA_EFR"; + if (mCodecs & PDC_EFR) os << " PDC_EFR"; + if (mCodecs & AMR_FR_WB) os << " AMR_FR_WB"; + if (mCodecs & UMTS_AMR_WB) os << " UMTS_AMR_WB"; + if (mCodecs & OHR_AMR) os << " OHR_AMR"; + if (mCodecs & OFR_AMR_WB) os << " OFR_AMR"; + if (mCodecs & OHR_AMR_WB) os << " OFR_AMR_WB"; + if (mCodecs & PCMULAW) os << " PCMULAW"; + if (mCodecs & PCMALAW) os << " PCMALAW"; +} + +std::ostream& operator<<(std::ostream& os, const CodecSet&cs) +{ + cs.text(os); + return os; +} + + +const char* CCState::callStateString(CallState state) +{ + switch (state) { + case NullState: return "null"; + case Paging: return "paging"; + //case AnsweredPaging: return "answered-paging"; + case MOCInitiated: return "MOC-initiated"; + case MOCProceeding: return "MOC-proceeding"; + case MOCDelivered: return "MOC-delivered"; + case MTCConfirmed: return "MTC-confirmed"; + case CallReceived: return "call-received"; + case CallPresent: return "call-present"; + case ConnectIndication: return "connect-indication"; + case Active: return "active"; + case DisconnectIndication: return "disconnect-indication"; + case ReleaseRequest: return "release-request"; + case SMSDelivering: return "SMS-delivery"; + case SMSSubmitting: return "SMS-submission"; + case HandoverInbound: return "HANDOVER Inbound"; + case HandoverProgress: return "HANDOVER Progress"; + case HandoverOutbound: return "HANDOVER Outbound"; + //case BusyReject: return "Busy Reject"; not used + // Do not add a default case. Let the compiler whine so we know if we covered all the cases. + // default: return NULL; + } + return "unrecognized CallState"; +} + +bool CCState::isInCall(CallState state) +{ + switch (state) { + case Paging: + //case AnsweredPaging: + case MOCInitiated: + case MOCProceeding: + case MTCConfirmed: + case CallReceived: + case CallPresent: + case ConnectIndication: + case HandoverInbound: + case HandoverProgress: + case HandoverOutbound: + case Active: + return true; + default: + return false; + } +} + +ostream& operator<<(ostream& os, CallState state) +{ + const char* str = CCState::callStateString(state); + if (str) os << str; + else os << "?" << ((int)state) << "?"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const TMSI_t&tmsi) +{ + if (tmsi.valid()) { + char buf[10]; sprintf(buf,"0x%x",tmsi.value()); os < +#include +#include +#include +#include + +namespace SIP { class DialogMessage; }; +namespace GSM { class L3Frame; class L2LogicalChannel; } + +extern int gCountTranEntry; + +namespace Control { +using namespace std; +class TranEntry; +class HandoverEntry; +class TransactionEntry; +class L3LogicalChannel; +typedef unsigned TranEntryId; +typedef vector TranEntryList; + +extern bool l3rewrite(); +extern void l3start(); +extern void controlInit(); +extern unsigned allocateRTPPorts(); + +// Meaning of these bits is hard to find: It is in 48.008 3.2.2.11: +enum CodecType { // Codec Bitmap defined in 26.103 6.2. It is one or two bytes + // low bit of first byte in bitmap + CodecTypeUndefined = 0, + GSM_FR = 0x1, // aka GSM610 + GSM_HR = 0x2, + GSM_EFR = 0x4, + AMR_FR = 0x8, + AMR_HR = 0x10, + UMTS_AMR = 0x20, + UMTS_AMR2 = 0x40, + TDMA_EFR = 0x80, // high bit of first byte in bitmap + // We can totally ignore the second byte: + PDC_EFR = 0x100, // low bit of second byte in bitmap + AMR_FR_WB = 0x200, + UMTS_AMR_WB = 0x400, + OHR_AMR = 0x800, + OFR_AMR_WB = 0x1000, + OHR_AMR_WB = 0x2000, + // then two reserved bits. + + // In addition the above codecs defined in the GSM spec and used on the air-interface, + // we will put other codecs we might want to use for RTP on the SIP interface in here too + // so we can use the same CodecSet in the SIP directory. + // This is not in the spec, but use this value to indicate none of the codecs above. + PCMULAW = 0x10000, // G.711 PCM, 64kbps. comes in two flavors: uLaw and aLaw. + PCMALAW = 0x20000 // We dont support it yet. + // There is also G711.1, which is slighly wider band, 96kbps. +}; + +const char *CodecType2Name(CodecType ct); + +// (pat) Added 10-22-2012. +// 3GPP 24.008 10.5.4.32 and 3GPP 26.103 +class CodecSet { + public: + CodecType mCodecs; // It is a set of CodecEnum + bool isSet(CodecType bit) { return mCodecs & bit; } + bool isEmpty() { return !mCodecs; } + CodecSet(): mCodecs(CodecTypeUndefined) {} + CodecSet(CodecType wtype) : mCodecs(wtype) {} + // Allow logical OR of two CodecSets together. + void orSet(CodecSet other) { mCodecs = (CodecType) (mCodecs | other.mCodecs); } + void orType(CodecType vals) { mCodecs = (CodecType) (mCodecs | vals); } + CodecSet operator|(CodecSet other) { return CodecSet((CodecType)(mCodecs | other.mCodecs)); } + void text(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& os, const CodecSet&); +}; + +class TMSI_t { + bool mValid; + uint32_t mVal; + public: + TMSI_t() : mValid(false) {} + TMSI_t(uint32_t wVal) : mValid(true), mVal(wVal) {} + TMSI_t &operator=(uint32_t wVal) { mValid=true; mVal = wVal; return *this; } + TMSI_t &operator=(const TMSI_t &other) { mVal=other.mVal; mValid=other.mValid; return *this; } + bool valid() const { return mValid; } + uint32_t value() const { assert(valid()); return mVal; } +}; +std::ostream& operator<<(std::ostream& os, const TMSI_t&tmsi); + +struct FullMobileId { + string mImsi; + TMSI_t mTmsi; + string mImei; + string fmidUsername() const; // "IMSI" or "TMSI" or "IMEI" + digits. + bool fmidMatch(const GSM::L3MobileIdentity &mobileId) const; + void fmidSet(string value); + FullMobileId() {} // Nothing needed. + FullMobileId(const string wAnything) { fmidSet(wAnything); } // Default is an imsi. +}; +std::ostream& operator<<(std::ostream& os, const FullMobileId&msid); + +/** Call states based on GSM 04.08 5 and ITU-T Q.931 */ +// (pat) These are the states in 4.08 defined in 5.1.2.2 used in procedures described in 5.2. +// 5.1.1 has a picture of the state machine including these states. +// The "Call State" numeric values are defined in 10.5.4.6 +// The network side states are N-numbers, and UE side are U-numbers, but with the same numeric values. +struct CCState { + enum CallState { + NullState = 0, + Paging = 2, // state N0.1 aka MTC MMConnectionPending + // AnsweredPaging is not a CallControl state. + // AnsweredPaging = 100, // Not a GSM Call Control state. Intermediate state used by OpenBTS between Paging and CallPresent + MOCInitiated = 1, // state N1 "Call initiated". + // 5.1.2.1.3 specifies state U1 in MS for MOC entered when MS requests "call establishment". + // 4.1.2.2.3 specifies state N1 in network received "call establishment request" but has not responded. + // Since these are CC states, we previously assumed that "call establishment" meant + // an L3Setup message, not CM Service Request. + // However, 24.008 11.3 implies that "CallInitiated" state starts when CM Service Request is sent. + // The optional authorization procedure intervenes between receipt of CM Service Request and sending the Accept. + MOCProceeding = 3, // state N3. network sent L3CallProceeding in response to Setup or EmergencySetup. + MOCDelivered = 4, // N4. MOC network sent L3Alerting. Not used in pre-l3rewrite code. + CallPresent = 6, // N6. MTC network sent L3Setup, started T303, waiting for L3CallConfirmed. + CallReceived = 7, // N7. MTC network recv L3CallAlerting. We use it in MOC to indicate SIP active. + // ConnectRequest = 8 // N8. We do not use. + MTCConfirmed = 9, // N9. network received L3CallConfirmed. + Active = 10, // N10. MOC: network received L3ConnectAcknowledge, MTC: network sent L3ConnectAcknowledge + DisconnectIndication = 12, // N12: Network sent a disconnect + // There is a DisconnectRequest state in the MS, but not in the network. + // MTCModify = 27, // N27 not used + ReleaseRequest = 19 , // N19: Network sent a Release message (per 24.008 5.4.2). + ConnectIndication = 28, // N28. MOC network sent L3Connect, start T313 + + // (pat) These are NOT call control states, but we use the CallState for all types of TransactionEntry. + SMSDelivering = 101, // MT-SMS set when paging answered; MT-SMS initial TransactionEntry state is NullState. + SMSSubmitting = 102, // MO-SMS TransactionEntry initial state. + + // (pat) These seem to be call control states to me, but they are not defined + // for the 10.5.4.6 "Call State" IE, so I am just making up values for them: + HandoverInbound = 103, // TransactionEntry initial state for inbound handover. + HandoverProgress = 104, + HandoverOutbound = 105, + //BusyReject, // pat removed, not used + }; + static const char* callStateString(CallState state); + static bool isInCall(CallState state); +}; +typedef CCState::CallState CallState; + + +// This is the reason a Transaction (TranEntry) was cancelled as desired to be known by the high side. +// It has nothing to do with the cancel causes on the low side, for example, CC Cause (for cloasing a single call) +// or RR Cause (for closing an entire channel.) +// An established SipDialog is ended by a SIP BYE, and an MO [Mobile Originated] SipDialog is canceled early using +// a SIP CANCEL, so this is used only for the case of an INVITE response where the ACK message has not been sent, +// or as a non-invite message (eg, SMS MESSAGE) error response. As such, there are only a few codes that +// the SIP side cares about. The vast majority of plain old errors, for example, loss of contact with the MS +// or reassignFailure will just map to the same SIP code so we use CancelCauseUnknown, however, all such cases +// are distinguished from CancelCauseNoAnswerToPage in that we know the MS is on the current system. +// we just . +enum CancelCause { + // Used for anything other than the specific causes below. It is not "unknown" so much as we just + // dont need to distinguish among various failure or hangup causes because we send the same SIP code for them all. + CancelCauseUnknown = 0, // Not completely unknown - we know that the MS was on this system. + CancelCauseNoAnswerToPage, // We dont have any clue if the MS is in this area or not. + CancelCauseBusy, // The MS is here. A future call may succeed. + CancelCauseCongestion, // The MS is here, but no resources. A future call may succeed. + CancelCauseHandoverOutbound, // A special case - the Dialog has been moved elsewhere. + CancelCauseSipInternalError, // Special case of the SipDialog itself being internally inconsistent. + CancelCauseOperatorIntervention, // Killed from console. +}; + + +/** Return a human-readable string for a GSM::CallState. */ +const char* CallStateString(CallState state); + +std::ostream& operator<<(std::ostream& os, CallState state); + +#if UNUSED_BUT_SAVE_FOR_UMTS +// A message to the CS L3 state machine. The message may come from a GSM LogicalChannel (FACCH, SDCCH, or SACCH), GPRS, or SIP. +// This is part of the L3 rewrite. +class GenericL3Msg { + public: + enum GenericL3MsgType { + MsgTypeLCH, + MsgTypeSIP + }; + enum GenericL3MsgType ml3Type; + const char *typeName(); + GSM::L3Frame *ml3frame; + GSM::L2LogicalChannel *ml3ch; + SIP::DialogMessage *mSipMsg; + const std::string mCallId; // TODO: Now unused, remove. + + //GenericL3Msg(GSM::L3Frame *wFrame, L3LogicalChannel *wChan) : ml3Type(MsgTypeLCH), ml3frame(wFrame),ml3ch(dynamic_cast(wChan)),mSipMsg(0) { assert(ml3frame); } + GenericL3Msg(GSM::L3Frame *wFrame, GSM::L2LogicalChannel *wChan) : ml3Type(MsgTypeLCH), ml3frame(wFrame),ml3ch(wChan),mSipMsg(0) { assert(ml3frame); } + GenericL3Msg(SIP::DialogMessage *wSipMsg, std::string wCallId) : ml3Type(MsgTypeSIP), ml3frame(0),ml3ch(0), mSipMsg(wSipMsg), mCallId(wCallId) { assert(mSipMsg); } + ~GenericL3Msg(); +}; +#endif + + +void NewTransactionTable_ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg); + +}; +#endif diff --git a/Control/DCCHDispatch.cpp b/Control/DCCHDispatch.cpp index faff8ca..156a401 100644 --- a/Control/DCCHDispatch.cpp +++ b/Control/DCCHDispatch.cpp @@ -18,17 +18,23 @@ */ +#define LOG_GROUP LogGroup::Control #include "ControlCommon.h" -#include "TransactionTable.h" +//#include "L3CallControl.h" +#include "L3MobilityManagement.h" +#include "L3StateMachine.h" +#include "L3LogicalChannel.h" +//#include "TransactionTable.h" #include "RadioResource.h" -#include "MobilityManagement.h" -#include -#include -#include -#include -#include +//#include "MobilityManagement.h" +//#include +//#include +//#include +//#include +//#include +//#include #include #undef WARNING @@ -42,111 +48,25 @@ using namespace Control; -/** - Dispatch the appropriate controller for a Mobility Management message. - @param req A pointer to the initial message. - @param DCCH A pointer to the logical channel for the transaction. -*/ -void DCCHDispatchMM(const L3MMMessage* req, LogicalChannel *DCCH) -{ - assert(req); - L3MMMessage::MessageType MTI = (L3MMMessage::MessageType)req->MTI(); - switch (MTI) { - case L3MMMessage::LocationUpdatingRequest: - LocationUpdatingController(dynamic_cast(req),DCCH); - break; - case L3MMMessage::IMSIDetachIndication: - IMSIDetachController(dynamic_cast(req),DCCH); - break; - case L3MMMessage::CMServiceRequest: - CMServiceResponder(dynamic_cast(req),DCCH); - break; - default: - LOG(NOTICE) << "unhandled MM message " << MTI << " on " << *DCCH; - throw UnsupportedMessage(); - } -} - - -/** - Dispatch the appropriate controller for a Radio Resource message. - @param req A pointer to the initial message. - @param DCCH A pointer to the logical channel for the transaction. -*/ -void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH) -{ - LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI(); - - assert(req); - L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI(); - switch (MTI) { - case L3RRMessage::PagingResponse: - PagingResponseHandler(dynamic_cast(req),DCCH); - break; - case L3RRMessage::AssignmentComplete: - AssignmentCompleteHandler( - dynamic_cast(req), - dynamic_cast(DCCH)); - break; - default: - LOG(NOTICE) << "unhandled RR message " << MTI << " on " << *DCCH; - throw UnsupportedMessage(); - } -} - - -void DCCHDispatchMessage(const L3Message* msg, LogicalChannel* DCCH) -{ - // Each protocol has it's own sub-dispatcher. - switch (msg->PD()) { - case L3MobilityManagementPD: - DCCHDispatchMM(dynamic_cast(msg),DCCH); - break; - case L3RadioResourcePD: - DCCHDispatchRR(dynamic_cast(msg),DCCH); - break; - default: - LOG(NOTICE) << "unhandled protocol " << msg->PD() << " on " << *DCCH; - throw UnsupportedMessage(); - } -} - - - - - /** Example of a closed-loop, persistent-thread control function for the DCCH. */ // (pat) DCCH is a TCHFACCHLogicalChannel or SDCCHLogicalChannel -void Control::DCCHDispatcher(LogicalChannel *DCCH) +void Control::DCCHDispatcher(L3LogicalChannel *DCCH) { while (1) { + // This 'try' is redundant, but we are ultra-cautious here since a mistake means a crash. try { // Wait for a transaction to start. LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS"; L3Frame *frame = DCCH->waitForEstablishOrHandover(); LOG(DEBUG) << *DCCH << " received " << *frame; gResetWatchdog(); - Primitive prim = frame->primitive(); - delete frame; - LOG(DEBUG) << "received primtive " << prim; - switch (prim) { - case ESTABLISH: { - // Pull the first message and dispatch a new transaction. - gReports.incr("OpenBTS.GSM.RR.ChannelSiezed"); - const L3Message *message = getMessage(DCCH); - LOG(INFO) << *DCCH << " received establishing messaage " << *message; - DCCHDispatchMessage(message,DCCH); - delete message; - break; - } - case HANDOVER_ACCESS: { - ProcessHandoverAccess(dynamic_cast(DCCH)); - break; - } - default: assert(0); - } + L3DCCHLoop(DCCH,frame); // This will not return until the channel is released. + } + catch (...) { + LOG(ERR) << "channel killed by unexpected exception "; } +#if 0 // Catch the various error cases. catch (RemovedTransaction except) { @@ -155,48 +75,49 @@ void Control::DCCHDispatcher(LogicalChannel *DCCH) catch (ChannelReadTimeout except) { LOG(NOTICE) << "ChannelReadTimeout"; // Cause 0x03 means "abnormal release, timer expired". - DCCH->send(L3ChannelRelease(0x03)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x03)); gTransactionTable.remove(except.transactionID()); } catch (UnexpectedPrimitive except) { LOG(NOTICE) << "UnexpectedPrimitive"; // Cause 0x62 means "message type not not compatible with protocol state". - DCCH->send(L3ChannelRelease(0x62)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x62)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } catch (UnexpectedMessage except) { LOG(NOTICE) << "UnexpectedMessage"; // Cause 0x62 means "message type not not compatible with protocol state". - DCCH->send(L3ChannelRelease(0x62)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x62)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } catch (UnsupportedMessage except) { LOG(NOTICE) << "UnsupportedMessage"; // Cause 0x61 means "message type not implemented". - DCCH->send(L3ChannelRelease(0x61)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x61)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } catch (Q931TimerExpired except) { LOG(NOTICE) << "Q.931 T3xx timer expired"; // Cause 0x03 means "abnormal release, timer expired". // TODO -- Send diagnostics. - DCCH->send(L3ChannelRelease(0x03)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x03)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } catch (SIP::SIPTimeout except) { // FIXME -- The transaction ID should be an argument here. LOG(WARNING) << "Uncaught SIPTimeout, will leave a stray transcation"; // Cause 0x03 means "abnormal release, timer expired". - DCCH->send(L3ChannelRelease(0x03)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x03)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } catch (SIP::SIPError except) { // FIXME -- The transaction ID should be an argument here. LOG(WARNING) << "Uncaught SIPError, will leave a stray transcation"; // Cause 0x01 means "abnormal release, unspecified". - DCCH->send(L3ChannelRelease(0x01)); + DCCH->l2sendm(L3ChannelRelease((RRCause)0x01)); if (except.transactionID()) gTransactionTable.remove(except.transactionID()); } +#endif } } diff --git a/Control/L3CallControl.cpp b/Control/L3CallControl.cpp new file mode 100644 index 0000000..352a54b --- /dev/null +++ b/Control/L3CallControl.cpp @@ -0,0 +1,1231 @@ +/* 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 distribuion. +* +* 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. +*/ + +#define LOG_GROUP LogGroup::Control + +#include "ControlCommon.h" +#include "L3CallControl.h" +#include "L3StateMachine.h" +#include "L3TranEntry.h" +#include "L3MMLayer.h" +#include "L3SupServ.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Control { + +using namespace GSM; +using namespace SIP; + + +// The base class for CC [Call Control] +// SS messages may be sent to CC transactions. +class CCBase : public SSDBase { + protected: + MachineStatus handleIncallCMServiceRequest(const GSM::L3Message *l3msg); + //MachineStatus stateRecvHold(const GSM::L3Hold*); + MachineStatus defaultMessages(int state, const GSM::L3Message*); + bool isVeryEarly(); + CCBase(TranEntry *wTran) : SSDBase(wTran) {} + MachineStatus closeCall(GSM::L3Cause cause); + MachineStatus sendReleaseComplete(GSM::L3Cause cause); + MachineStatus sendRelease(GSM::L3Cause cause); + void handleTerminationRequest(); +}; + +class MOCMachine : public CCBase { + enum State { + stateStartUnused, // Reserve 0 superstitiously. + stateCCIdentResult, + stateAssignTCHFSuccess, + }; + bool mIdentifyResult; + MachineStatus sendCMServiceReject(MMRejectCause rejectCause); + MachineStatus handleSetupMsg(const GSM::L3Setup *setup); + MachineStatus serviceAccept(); + + // The MOC is created by an initial CMServiceRequest: + // |-----------CMServiceRequest------------>| | old: CMServiceResponder calls MOCProcedure + // |<-------Authentication Procedure------->| | resolveIMSI + // |<-----------CMServiceAccept ------------| | MOCProcedure + // |-------------L3Setup(SDCH)------------->| | MOCProcedure + // |<----------CC-CALL PROCEEDING-----------|------------INVITE----------->| MOCProcedure,MOCSendINVITE + + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg); + MOCMachine(TranEntry *wTran) : CCBase(wTran) {} + const char *debugName() const { return "MOCMachine"; } +}; // mMOCProcedure; + +class AssignTCHMachine : public CCBase { + enum State { + stateStart, + stateAssignTimeout + }; + // We have to suspend processing of SIP messages while we are busy changing channels. + // We just let these messages go by, then after we get the new TCH (if we do), + // we will send any l3 messages required by the then-current sip state. + void sendReassignment(); + // The reassignment timer is how long we try to send reassignments; it should not abort the transaction + // immediately when it expires or it might abort a successful reassignment, so this timer must not be in the L3TimerId list. + // + Timeval TChReassignment; + protected: + + // |<----------ChannelModeModify------------| SIPState=Starting | MOCProcedure if veryEarly + // |----------ChannelModeModifyAck--------->| | MOCProcedure if veryEarly + // | call assignTCHF | | (used only for EA; for VEA we assigned TCH in AccessGrantResponder) + // |<---------L3AssignmentCommand-----------| | assignTCHF, repeated until answered + // |--------AssignmentComplete(FACCH)------>| | DCCHDispatchRR,AssignmentCompleteHandler, calls MOCController or MTCController + + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg); + AssignTCHMachine(TranEntry *wTran) : CCBase(wTran) {} + const char *debugName() const { return "AssignTCHMachine"; } +}; // mAssignTCHFProcedure; + + +class MTCMachine : public CCBase { + enum State { + stateStart, + statePostChannelChange, + }; + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg); + MTCMachine(TranEntry *wTran) : CCBase(wTran) {} + const char *debugName() const { return "MTCMachine"; } +}; + +class InboundHandoverMachine : public CCBase { + bool mReceivedHandoverComplete; + enum State { + stateStart, + }; + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg); + InboundHandoverMachine(TranEntry *wTran) : CCBase(wTran), mReceivedHandoverComplete(false) {} + const char *debugName() const { return "InboundHandoverMachine"; } +}; + +class InCallMachine : public CCBase { + enum State { + stateStart + }; + bool mDtmfSuccess; + char mDtmfKey; // Encoded as used inside L3KeypadFacility + void acknowledgeDtmf(); + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg); + //MachineStatus machineRunSipMsg(const SIP::DialogMessage *sipmsg); + InCallMachine(TranEntry *wTran) : CCBase(wTran) {} + const char *debugName() const { return "InCallMachine"; } +}; + +// MOCMachine: Mobile Originated Call State Machine +// GSM 4.08 5.2.1 Mobile originating call establishment. +// On entry phone has an RR connection but is trying to get the CM connection established. +// We run MM procedures (identify, authenticate) to associate the RR LogicalChannel with a MMUser. +// Process up through receiving the L3Setup message. +// This message indicates establishment of both an MM Layer (ie, LocationUpdating has been performed 4.08 4.5.1) and a CM Layer connection. +void startMOC(const GSM::L3MMMessage *l3msg, MMContext *dcch, CMServiceTypeCode serviceType) +{ + LOG(DEBUG) <lockAndStart(mocp,(GSM::L3Message*)l3msg); +} + +#if UNUSED +MachineStatus ProcedureDetach::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + getDialog()->dialogCancel(); // reudundant, chanLost would do it. Does nothing if dialog not yet started. + setGSMState(CCState::NullState); // redundant, we are deleting this transaction. + channel()->l3sendm(L3ChannelRelease()); + channel()->chanRelease(HARDRELEASE); + //channel()->l3sendp(HARDRELEASE); + //channel()->chanLost(); + return MachineStatusOK; +} +#endif + +// Identical to teCloseCallNow. +MachineStatus CCBase::sendReleaseComplete(L3Cause l3cause) +{ + tran()->teCloseCallNow(l3cause); + return MachineStatusQuitTran; +} + +MachineStatus CCBase::sendRelease(L3Cause l3cause) +{ + tran()->teCloseDialog(); // redundant, would happen soon anyway. + if (isL3TIValid()) { + unsigned l3ti = getL3TI(); + if (tran()->clearingGSM()) { + // Oops! Something went wrong. Clear immediately. + tran()->teCloseCallNow(l3cause); + return MachineStatusQuitTran; + } else { + // This tells the phone that the network intends to release the TI. + // The handset is supposed to respond with ReleaseComplete. + channel()->l3sendm(GSM::L3Release(l3ti,l3cause)); + setGSMState(CCState::ReleaseRequest); + timerStart(T308,T308ms,TimerAbortTran); + LOG(DEBUG) << gMMLayer.printMMInfo(); + return MachineStatusOK; // We are waiting for a ReleaseComplete + } + } else { + // The transaction is already dead. Kill the state machine and the next layer will send the RR Release. + LOG(DEBUG) << gMMLayer.printMMInfo(); + return MachineStatusQuitTran; + } +} + +// Perform a network initiated clearing 24.008 5.4. If things look ok send a disconnect and continue waiting for a Release message, +// or if things have gone wrong, send a ReleaseRequest and kill the transaction. We used to do that all the time +// but some handsets (BLU phone) report "Network Failure" if you dont go through the disconnect procedure. +// We dont send the RR releaes at this level - the MM layer does that after this transaction dies. +MachineStatus CCBase::closeCall(L3Cause l3cause) +{ + WATCHINFO("closeCall"<descriptiveString()); + tran()->teCloseDialog(); // Make sure; this is redundant because the call will be repeated when the transaction is killed, + // We could assert this if we dont call this until after an L3Setup. + if (isL3TIValid()) { + unsigned l3ti = getL3TI(); + // We dont have to send a disconnect at all, but if you dont, the phone may report that the call "failed". + CallState ccstate = tran()->getGSMState(); + if (ccstate == CCState::Active) { + if (1) { + channel()->l3sendm(GSM::L3Disconnect(l3ti,l3cause)); + setGSMState(CCState::DisconnectIndication); + } else { + // (pat 10-24-2013) As an option per 24.008 5.4.2: we could send a Release message and start T308 + channel()->l3sendm(GSM::L3Release(l3ti,l3cause)); + setGSMState(CCState::ReleaseRequest); + } + timerStart(T308,T308ms,TimerAbortTran); + return MachineStatusOK; // Wait for ReleaseComplete. + } else if (ccstate != CCState::NullState && ccstate != CCState::ReleaseRequest) { + channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,l3cause)); // This is a CC message that releases this Transaction. + } + } else { + // If no TI we cant send any CC release messages, just kill the transaction and if nothing is happening + // the MM layer will send an RR release on the channel. + } + WATCH("CLOSE CALL:"<teCloseCallNow(L3Cause::Preemption); +} + + +// See: callManagementDispatchGSM +// TODO (pat) 2-2014: The MS can send DTMF messages before the call is connected; currently we just ignore them, which works, +// but we should probably at least send a reject. +MachineStatus CCBase::defaultMessages(int state, const GSM::L3Message *l3msg) +{ + if (!l3msg) { return unexpectedState(state,l3msg); } // Maybe unhandled dialog message. + switch (state) { // L3CASE_RAW(l3msg->PD(),l3msg->MTI())) { + case L3CASE_CC(Hold): { + const L3Hold *hold = dynamic_cast(l3msg); + PROCLOG(NOTICE) << "rejecting hold request from " << tran()->subscriber(); + channel()->l3sendm(GSM::L3HoldReject(getL3TI(),L3Cause::ServiceOrOptionNotAvailable)); + return MachineStatusOK; // ignore bad message otherwise. + } + case L3CASE_MM(CMServiceAbort): { + const L3CMServiceAbort *cmabort = dynamic_cast(l3msg); + // 4.08 5.2.1 and 4.5.1.7: If the MS wants to cancel before we get farther it should send a CMServiceAbort. + PROCLOG(INFO) << "received CMServiceAbort, closing channel and clearing"; + timerStopAll(); + return closeCall(L3Cause::NormalCallClearing); // normal event. + } + case L3CASE_CC(Disconnect): { // MOD + // 4.08 5.4.3 says we must be prepared to receive a DISCONNECT any time. + //const L3Disconnect *dmsg = dynamic_cast(l3msg); Unused. + //changed 10-24-13: return closeCall(L3Cause::NormalCallClearing); // normal event. + timerStopAll(); + return sendRelease(L3Cause::NormalCallClearing); + } + case L3CASE_CC(Release): { + // 24.008 5.4.3.3: In any state except ReleaseRequest send a ReleaseComplete, then kill the transaction, + const L3Release *dmsg = dynamic_cast(l3msg); + if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG! + timerStopAll(); + return sendReleaseComplete(L3Cause::NormalCallClearing); + } + case L3CASE_CC(ReleaseComplete): { + // 24.008 5.4.3.3: Just kill the transaction immediately.. + const L3ReleaseComplete *dmsg = dynamic_cast(l3msg); + if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG! + timerStopAll(); + //changed 10-24-13: return closeCall(L3Cause::NormalCallClearing); // normal event. + // tran()->teCloseDialog(); // Redundant. + setGSMState(CCState::NullState); // redundant, we are deleting this transaction. + return MachineStatusQuitTran; + } + case L3CASE_MM(IMSIDetachIndication): { + const GSM::L3IMSIDetachIndication* detach = dynamic_cast(l3msg); + timerStopAll(); + // The IMSI detach procedure will release the LCH. + PROCLOG(INFO) << "GSM IMSI Detach " << *tran(); + + // FIXME: Must unregister. + // TODO: IMSIDetachController(detach,LCH); + + channel()->l3sendm(L3ChannelRelease()); + // Many handsets never complete the transaction. + // So force a shutdown of the channel. + channel()->chanRelease(HARDRELEASE); + return MachineStatusQuitChannel; + } + case L3CASE_RR(ApplicationInformation): { + const GSM::L3ApplicationInformation *aimsg = dynamic_cast(l3msg); + // handle RRLP answer. + // TODO + return MachineStatusOK; + } + case L3CASE_SS(Register): + case L3CASE_SS(ReleaseComplete): + case L3CASE_SS(Facility): { + return handleSSMessage(l3msg); + } + default: + return unexpectedState(state,l3msg); + } +} + +MachineStatus CCBase::handleIncallCMServiceRequest(const GSM::L3Message *l3msg) +{ + const GSM::L3CMServiceRequest *cmsrq = dynamic_cast(l3msg); + assert(cmsrq); + // SMS submission? The rest will happen on the SACCH. + if (cmsrq->serviceType().type() == GSM::L3CMServiceType::ShortMessage) { + PROCLOG(INFO) << "in call SMS submission on " << *channel(); + //FIXME: + //InCallMOSMSStarter(transaction); + //LCH->l3sendm(GSM::L3CMServiceAccept()); + return MachineStatusOK; + } + // For now, we are rejecting anything else. + PROCLOG(NOTICE) << "cannot accept additional CM Service Request from " << tran()->subscriber(); + // Can never be too verbose. + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported))); + return MachineStatusOK; +} + +// The reject cause is 4.08 10.5.3.6. It has values similar to L3Cause 10.5.4.11 +MachineStatus MOCMachine::sendCMServiceReject(MMRejectCause rejectCause) +{ + channel()->l3sendm(L3CMServiceReject(L3RejectCause(rejectCause))); + return closeChannel(L3RRCause::NormalEvent,RELEASE); +} + +bool CCBase::isVeryEarly() { return (channel()->chtype()==GSM::FACCHType); } + + +// GSM 04.08 5.2.1.2 +// This is where we set the TI [Transaction Identifier] in the TranEntry to what the MS sent us in the L3Setup message. +// We also start the SIP dialog now. +MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup) +{ + // pat fixed. See comments at MOCInitiated. setGSMState(CCState::MOCInitiated); + + PROCLOG(INFO) << *setup; + gReports.incr("OpenBTS.GSM.CC.MOC.Setup"); + if (setup->mFacility.mExtant) WATCH(setup); // USSD DEBUG! + + // See GSM 04.07 11.2.3.1.3. + // Set the high bit, since this TI came from the MS. + // Set l3ti before calling any aborts so we will handle the response to the MS properly. + // (pat) The MS will continue to use the original TI (without the high bit set) when it communicates with us, + // and We need to set the high bit only when we send an L3TI to the MS. + tran()->setL3TI(setup->TI() | 0x08); + tran()->setCodecs(setup->getCodecSet()); + string calledNumber; + { + if (!setup->haveCalledPartyBCDNumber()) { + // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. + // (pat) I disagree: this exactly follows GSM 4.08 5.4.2 + PROCLOG(WARNING) << "MOC setup with no number"; + return closeCall(L3Cause::InvalidMandatoryInformation); + } + const L3CalledPartyBCDNumber& calledPartyIE = setup->calledPartyBCDNumber(); + tran()->setCalled(calledPartyIE); + calledNumber = calledPartyIE.digits(); + } + + /* early RLLP request */ + /* this seems to need to be sent after initial call setup + -kurtis */ + if (gConfig.getBool("Control.Call.QueryRRLP.Early")) { + // Query for RRLP +#if ORIGINAL_CODE + if (!sendRRLP(mobileID, LCH)) { + PROCLOG(INFO) << "RRLP request failed"; + } +#else + // TODO: RRLPServer.start(mobileID); +#endif + } + + + // Start a new SIP Dialog, which sends an INVITE. + PROCLOG(DEBUG) << "SIP start engine"; + //getDialog()->dialogOpen(tran()->subscriberIMSI()); + //const char * imsi = tran()->subscriberIMSI(); // someday these will be a strings already + // The sipDialogMOC creates the SIP Dialog and sends the INVITE. + // The setDialog associates the new dialog with this transaction. + SipDialog *dialog = SipDialog::newSipDialogMOC(tran()->tranID(),tran()->subscriber(),calledNumber,tran()->getCodecs(), channel()); + if (dialog == NULL) { + // We failed to create the SIP session for some reason. I dont think this can happen, but dont crash here. + LOG(ERR) << "Failed to create SIP Dialog, dropping connection"; + return closeChannel(L3RRCause::Unspecified,RELEASE); + } + //setDialog(dialog); Moved into newSipDialogMOC to eliminate a race. + + + // Once we can start SIP call setup, send Call Proceeding. + // (pat) 4.08 5.2.1.2 says we are supposed to verify the number before sending call proceeding. + // (pat) TODO: I dont think this is right - supposed to wait for SIP proceeding before sending this. + PROCLOG(INFO) << "Sending Call Proceeding, transaction:" <l3sendm(GSM::L3CallProceeding(getL3TI())); + setGSMState(CCState::MOCProceeding); + return MachineStatusOK; +} + + +// FIXME -- At this point, verify that the subscriber has access to this service. +// If the subscriber isn't authorized, send a CM Service Reject with +// cause code, 0x41, "requested service option not subscribed", +// followed by a Channel Release with cause code 0x6f, "unspecified". +// Otherwise, proceed to the next section of code. +// For now, we are assuming that the phone won't make a call if it didn't +// get registered. +MachineStatus MOCMachine::serviceAccept() +{ + GPRS::GPRSNotifyGsmActivity(tran()->subscriber().mImsi.c_str()); + + // Allocate a TCH for the call, if we don't have it already. + // TODO: This should be a function in MMContext. + if (!isVeryEarly()) { + if (! channel()->reassignAllocNextTCH()) { + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); + tran()->teCloseDialog(CancelCauseCongestion); + // (pat) TODO: Now what? We are supposed to go back to using SDCCH in case of an ongoing SMS, + // so lets just close the Transaction. + return closeChannel(L3RRCause::NormalEvent,RELEASE); + } + } + + // Let the phone know we're going ahead with the transaction. + PROCLOG(INFO) << "sending CMServiceAccept"; + channel()->l3sendm(GSM::L3CMServiceAccept()); + + // We are now waiting for a L3Setup message. + // We could attach the MMContext to the MMUser at any time but it might start receiving calls or SMS immediately, + // so we are going to wait until this call kicks off, which may be safer. + + return MachineStatusOK; +} + + +// This is used both for MOC and emergency calls, which are differentiated by the service type in the CMServiceRequest message. +// (pat) The Blackberry will attempt an MOC even if periodic LUR returned unauthorized! +MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + switch (state) { + // This is the start state: + case L3CASE_MM(CMServiceRequest): { + timerStart(T303,T303ms,TimerAbortTran); // MS side: start CMServiceRequest sent; stop CallProceeding received. + // This is both the start state and a request to start a new MO SMS when one is already in progress, as per GSM 4.11 5.4 + setGSMState(CCState::MOCInitiated); + const L3CMServiceRequest *req = dynamic_cast(l3msg); + const GSM::L3MobileIdentity &mobileID = req->mobileID(); // Reference ok - the SM is going to copy it. + + return machPush(new L3IdentifyMachine(tran(),mobileID, &mIdentifyResult), stateCCIdentResult); + } + + case stateCCIdentResult: { + // TODO: We may want an option to return an immediate CM service reject if this BTS is not configured + // to handle calls, for example, if it is an SMS-only server or such like. + // The L3IdentifyMachine checks for emergency calls, but we will check again here to be sure. + if (mIdentifyResult) { + return serviceAccept(); + } else { + // If handset is not in TMSI table We do not return any programmable failure codes here, + // we must return cause CM Service Reject Cause 4, + // which will cause the MS to do a new Location Update, and the Location Update code + // will either pass it or determine an appropriate reject code. + return sendCMServiceReject(L3RejectCause::IMSIUnknownInVLR); + } + } + +#if 0 // (pat) 9-15-2013: replaced with code to call the common L3IdentifyMachine. + case L3CASE_MM(CMServiceRequest): { + const L3CMServiceRequest *req = dynamic_cast(l3msg); + // We dont want to leave our GSMState indicator in NullState once we start + // doing things here, so we want to change the state to something. + // On receipt of CMServiceRequest we are doing MM procedures so you would think there is + // no CC state yet, but apparently that is not the case; see comments at CCState::MOCInitiated, + // indicating that this state is correct. + setGSMState(CCState::MOCInitiated); + // There is no specific timer in the documentation on the network side for this case. + // T303 is defined on the MS side, and we use that. It is a generic 30 second timer. + timerStart(T303,T303ms,TimerAbortTran); // MS side: start CMServiceRequest sent; stop CallProceeding received. + + // If we got a TMSI, find the IMSI. + // Note that this is a copy, not a reference. + GSM::L3MobileIdentity mobileID = req->mobileID(); + + // ORIGINAL CODE: resolveIMSI(mobileID,LCH); + + // I think other messages are errors during this part of the state diagram, but the old + // code ignored them (rather, it set the state improperly which error was later corrected) + // while waiting for the RR AssignmentComplete message so I will too. + + // Pat says: Take care that RRLP does not use up the 30 second T303 timer running in the MS now. + if (gConfig.getBool("Control.Call.QueryRRLP.Early")) { + // TODO... + } + + // Have an imsi already? + if (mobileID.type()==IMSIType) { + string imsi(mobileID.digits()); + tran()->setSubscriberImsi(string(mobileID.digits()),true); + if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) { + return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + } + return serviceAccept(); + } + + // If we got a TMSI, find the IMSI. + if (mobileID.type()==TMSIType) { + unsigned authorized; + string imsi = gTMSITable.tmsiTabGetIMSI(mobileID.TMSI(),&authorized); + if (imsi.size()) { + // TODO: We need to authenticate this. + // But for now, just accept it. + tran()->setSubscriberImsi(imsi,true); + if (!authorized) { + return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + } + return serviceAccept(); + } + } + + + // Still no IMSI? Ask for one. + // TODO: We should ask the SIP Registrar. + // (pat) This is not possible if the MS is compliant (unless the TMSI table has been lost) - + // the MS should have done a LocationUpdate first, which provides us with the IMSI. + PROCLOG(NOTICE) << "MOC with no IMSI or valid TMSI. Reqesting IMSI."; + timerStart(T3270,T3270ms,TimerAbortChan); // start IdentityRequest sent; stop IdentityResponse received. + channel()->l3sendm(L3IdentityRequest(IMSIType)); + return MachineStatusOK; + } + + // TODO: This should be moved to an MM Identify procedure run before starting the MOC. + case L3CASE_MM(IdentityResponse): { + timerStop(T3270); + const L3IdentityResponse *resp = dynamic_cast(l3msg); + L3MobileIdentity mobileID = resp->mobileID(); + if (mobileID.type()==IMSIType) { + string imsi(mobileID.digits()); + tran()->setSubscriberImsi(imsi,true); + if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) { + return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + } + return serviceAccept(); + } else { + // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. + PROCLOG(WARNING) << "MOC setup with no IMSI"; // (pat) It is used for MO-SMS, not MOC. + // Reject cause in 10.5.3.6. + // Cause 0x62 means "message type not not compatible with protocol state". + return sendCMServiceReject(L3RejectCause::MessageTypeNotCompatibleWithProtocolState); + } + return something + } +#endif + + + case L3CASE_CC(Setup): { + timerStop(T303); + if (getGSMState() == CCState::MOCProceeding) { + LOG(DEBUG) << "ignoring duplicate L3EmergencySetup"; + return MachineStatusOK; + } + const L3Setup *msg = dynamic_cast(l3msg); + + MachineStatus stat = handleSetupMsg(msg); + if (stat != MachineStatusOK) { return stat; } + return machPush(new AssignTCHMachine(tran()), stateAssignTCHFSuccess); + } + + case stateAssignTCHFSuccess: { + // We have just received our shiny new TCH. See if the SIP state changed while we were waiting; + // if so, the sip messages themselves were discarded, but invite response, if any, was saved. + // Take care: we are invoking a dialog state without passing the DialogMessage, which is gone. + return machineRunState(L3CASE_DIALOG_STATE(getDialog()->getDialogState()),NULL,NULL); + } + + case L3CASE_SIP(dialogStarted): { + return MachineStatusOK; // It just means the dialog has not received an answer to the initial INVITE yet. + } + case L3CASE_SIP(dialogProceeding): { + // pat 2-2014: Tried out the L3Progress message to fix the ZTE lack of ring-back. + // I notice we are sending an invalid Progress value so it was worth a try, but did not help. + channel()->l3sendm(L3Progress(getL3TI())); + if (getGSMState() != CCState::MOCProceeding) { // No CCState change on receiving this message. + PROCLOG(ERR) << "MOC received SIP Progress message in unexpected GSM state:"<< getGSMState(); + } + return MachineStatusOK; + } + case L3CASE_SIP(dialogRinging): { +#if ATTEMPT_TO_FIX_ZTE_PHONE + // pat 2-2014: The ZTE phone does not play in audio ringing during the Alerting. + // Looks like a bug in the phone. To try work around it add a Progress Indicator IE. + // If you set in-band audio it will play whatever you send it, but it will just not generate its own ring tone in any case. + //L3ProgressIndicator progressIE(L3ProgressIndicator::ReturnedToISDN); This one tells it to not use in-band audio, but did not help. + L3ProgressIndicator progressIE(L3ProgressIndicator::InBandAvailable); + channel()->l3sendm(L3Alerting(getL3TI(),progressIE)); +#else + channel()->l3sendm(L3Alerting(getL3TI())); +#endif + setGSMState(CCState::MOCDelivered); + return MachineStatusOK; + } + case L3CASE_SIP(dialogActive): { + // Success! The call is connected. + + if (gConfig.getBool("GSM.Cipher.Encrypt")) { + int encryptionAlgorithm = gTMSITable.tmsiTabGetPreferredA5Algorithm(tran()->subscriberIMSI().c_str()); + if (!encryptionAlgorithm) { + LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for " << tran()->subscriberIMSI(); + } else if (channel()->getL2Channel()->decryptUplink_maybe(tran()->subscriberIMSI(), encryptionAlgorithm)) { + LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI(); + channel()->l3sendm(GSM::L3CipheringModeCommand( + GSM::L3CipheringModeSetting(true, encryptionAlgorithm), + GSM::L3CipheringModeResponse(false))); + } else { + LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI(); + } + } + + channel()->l3sendm(L3Connect(getL3TI())); + setGSMState(CCState::ConnectIndication); + getDialog()->MOCInitRTP(); + getDialog()->MOCSendACK(); + return MachineStatusOK; // We are waiting for the ConnectAcknowledge. + } + + case L3CASE_CC(ConnectAcknowledge): { + if (getDialog()->isActive()) { + // We're rolling. Fire up the in-call procedure. + setGSMState(CCState::Active); + return callMachStart(new InCallMachine(tran())); + } else if (getDialog()->isFinished()) { + // The SIP side hung up on us! + return closeCall(L3Cause::NormalCallClearing); + } else { + // Not possible. + PROCLOG(ERR) << "Connect Acknowledge received in incorrect SIP Dialog state:"<< getDialog()->getDialogState(); + } + return callMachStart(new InCallMachine(tran())); + } + + case L3CASE_RR(AssignmentComplete): { // Ignore duplicate message subsequent to AssignTCHF. + PROCLOG(INFO) << "Ignoring duplicate GSM AssignmentComplete " << *tran(); + return MachineStatusOK; + } + case L3CASE_RR(ChannelModeModifyAcknowledge): { // Ignore duplicate message subsequent to AssignTCHF. + PROCLOG(INFO) << "Ignoring duplicate GSM ChannelModeModifyAcknowledge " << *tran(); + return MachineStatusOK; + } + + case L3CASE_SIP(dialogBye): { + // The other user hung up before we could finish. + return closeCall(L3Cause::NormalCallClearing); + } + case L3CASE_SIP(dialogFail): { + // 0x11: "User Busy"; 0x7f "Interworking unspecified" + int sipcode = getDialog()->getLastResponseCode(); + // This is where we should translate SIP codes into more meaningful L3Cause returns. + switch (sipcode) { + case 486: case 600: case 603: + return closeCall(L3Cause::UserBusy); + default: + return closeCall(L3Cause::InterworkingUnspecified); + } + } + +#if TODO // TODO: What to do about this? + MachineStatus MOCMachine::stateExpiredT303() + { + PROCLOG(INFO) << "T303 expired, closing channel and clearing"; + return closeChannel(?); // no sip yet, just exit + } +#endif + default: + return defaultMessages(state,l3msg); + } +} + + +//=== AssignTCHMachine === +// Replaces assignTCHF() + + +// TODO: This should move to MMContext. +void AssignTCHMachine::sendReassignment() +{ + static const GSM::L3ChannelMode speechMode(GSM::L3ChannelMode::SpeechV1); + + if (isVeryEarly()) { return; } + GSM::TCHFACCHLogicalChannel *tch = dynamic_cast(channel()->mNextChan); + // FIXME - We should probably be setting the initial power here. + channel()->l3sendm(GSM::L3AssignmentCommand(tch->channelDescription(),speechMode)); +} + +MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + static const GSM::L3ChannelMode speechMode(GSM::L3ChannelMode::SpeechV1); + + //beginning: + switch (state) { + + case stateStart: { + // If this timer goes off the assignment was (possibly) successful, at least it was not unsuccessful. + //onTimeout1(GSM::T3101ms-1000,stateAssignTimeout); + + // We postpone processing any dialog messages until after this procedure. + //tran()->mSipDialogMessagesBlocked = true; + + // (pat) The original assignTCHF code sent the L3Assignment multiple times. + // That shouldnt be necessary because it is using LAPDm, but I am going to duplicate the behavior. + + if (isVeryEarly()) { + // For very early assignment, we gave the MS a TCH in the initial ImmediateAssignment but we need a mode change. + static const GSM::L3ChannelMode modemsg(GSM::L3ChannelMode::SpeechV1); + GSM::TCHFACCHLogicalChannel *tch = dynamic_cast(channel()); + channel()->l3sendm(L3ChannelModeModify(tch->channelDescription(),modemsg)); + } else { + // For early (not "very early") assignment, we gave the MS an SDDCH in the initial ImmediateAssignment. + // Send the TCH assignment now. + // (pat) We do not support "late assignment" as defined in 4.08 7.3.2. + channel()->reassignStart(); + // And I quote 4.08 11.1.2: "This timer [T3101] is started when a channel is allocated with an IMMEDIATE ASSIGNMENT message. + // It is stopped when the MS has correctly seized the channels." + // If we receive a reassignment failure we will resend the assignment, which often works the second time. + // The TChReassignment timer controls how long we will keep re-trying. + // We used to use 3 (T3101-1) seconds, but I dont think this time is related to T3101 and + // I dont know what the ultimate limit is, maybe nothing. I am going to up it just use T3101. + TChReassignment.future(T3101ms); + timerStart(T3101,T3101ms,stateAssignTimeout); // This timer will truly abort. + sendReassignment(); // Sets T3101 as a side effect, way down in L1Decoder. + } + return MachineStatusOK; + } + + case L3CASE_RR(ChannelModeModifyAcknowledge): { + const GSM::L3ChannelModeModifyAcknowledge*ack = dynamic_cast(l3msg); + bool modeOK = (ack->mode()==speechMode); + + // if (!modeOK) return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x06)); + // (pat) TODO: Why is this todo here? network send 'ChannelUnacceptable'? + // Since we already started sip, if the channel is unacceptable the only recovery to close the call. + //tran()->mSipDialogMessagesBlocked = false; + if (!modeOK) return closeCall(L3Cause::ChannelUnacceptable); + return MachineStatusPopMachine; + } + + // We retry this loop in case there are stray messages in the channel. + // On some phones, we see repeated Call Confirmed messages on MTC. + + //case stateAssignRetry: // We are sending the TCH assignment on the old SDCCH. + // DCCH->l3sendm(GSM::L3AssignmentCommand(TCH->channelDescription(),GSM::L3ChannelMode(GSM::L3ChannelMode::SpeechV1))); + // return MachineStatusOK; + + // This arrives on the new FACCH, however, the channel() comes from the Context which is still mapped to the old channel, + // but reassignmentComplete knows this. + case L3CASE_RR(AssignmentComplete): { + timerStop(T3101); + channel()->reassignComplete(); + PROCLOG(INFO) << "successful assignment"; + PROCLOG(DEBUG) << gMMLayer.printMMInfo(); + if (IS_LOG_LEVEL(DEBUG)) { + cout << "AssignmentComplete:\n"; + CommandLine::printChansV4(cout,false); + } + //tran()->mSipDialogMessagesBlocked = false; // Next process will handle the postponed dialog messages. + return MachineStatusPopMachine; + } + + case L3CASE_RR(AssignmentFailure): { + // We tried to reassign the MS from SDCCH to TCH and failed. + // This arrives on the old SDCCH after "The mobile station has failed to seize the new channel." + // The old code continually retried in this case. So we will too, because + // conceivably this could be working around some bug in OpenBTS. + // Old code retried until T3101ms-1000, which just cant be right. + if (! TChReassignment.passed()) { + sendReassignment(); + return MachineStatusOK; + } else { + goto caseAssignTimeout; + } + } + + // This would be a new CMServiceRequest, eg, for SMS message. + // TODO: Can the MS send this so early in the MOC process? + case L3CASE_MM(CMServiceRequest): { + handleIncallCMServiceRequest(l3msg); + // Resend the channel assignment. + sendReassignment(); // duplicates old code, but is this really necessary? + } + + case stateAssignTimeout: + caseAssignTimeout: + channel()->reassignFailure(); + // TODO: This is not optimal - we should drop back to the MMLayer to see if it wants to do something else. + if (getDialog()) { getDialog()->dialogCancel(); } // Should never be NULL, but dont crash. + // We dont call closeCall because we already sent the specific RR message required for this situation. + return closeChannel(L3RRCause::NoActivityOnTheRadio,RELEASE); + + case L3CASE_CC(Setup): + + default: + if (sipmsg) { + LOG(DEBUG) << "Dialog message received in AssignTCHF procedure."; + // We just ignore sip messages. The caller will handle the final SIP state when we return. + return MachineStatusOK; + } + return defaultMessages(state,l3msg); + } +} + + +// Timer values in 24.008 table 11.4 +MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + switch(state) { + case stateStart: { + //MachineStatus stat = checkForSipFailure(getDialog()->getDialogState()); + //if (stat != MachineStatusOK) { return stat; } + if (getDialog()->isFinished()) { + // SIP side closed already. + return closeCall(L3Cause::InterworkingUnspecified); + } + + // Allocate channel now, to be sure there is one. + if (!isVeryEarly()) { + if (! channel()->reassignAllocNextTCH()) { + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); + tran()->teCloseDialog(CancelCauseCongestion); + // (pat) TODO: We are supposed to go back to using SDCCH in case of an ongoing SMS. + return closeChannel(L3RRCause::NormalEvent,RELEASE); + } + } + + // Allocate Transaction Identifier + unsigned l3ti = channel()->chanGetContext(true)->mmGetNextTI(); + tran()->setL3TI(l3ti); + + // Send Setup message to MS. + L3Setup setupmsg(l3ti,tran()->calling()); + // pat 2-2014: Attempt to make the buggy ZTE phone by sending an explicit L3Signal IE in the L3Setup message. + // Update: did not help. + //L3Signal tone(L3Signal::SignalBusyToneOn); // Tryed both Ringing and Busy tone, no joy. + //setupmsg.setSignal(tone); + PROCLOG(INFO) << "sending L3Setup to call " << LOGVAR2("calling",tran()->calling()) << tran() <l3sendm(setupmsg); + setGSMState(CCState::CallPresent); + timerStart(T303,T303ms,TimerAbortTran); // Time state "Call Present"; start CMServiceRequest recv; stop CallProceeding recv. + + // And send trying message to SIP + getDialog()->MTCSendTrying(); + + return MachineStatusOK; // Wait for L3CallConfirmed message. + } + + case L3CASE_CC(CallConfirmed): { + timerStop(T303); + // Some handsets send a CallConfirmed both before and after the channel change. + if (getGSMState() == CCState::MTCConfirmed) { + LOG(DEBUG) << "ignoring duplicate L3CallConfirmed"; + return MachineStatusOK; + } + setGSMState(CCState::MTCConfirmed); + timerStart(T310,T310ms,TimerAbortTran); // Time state "Call Confirmed"; start CallConfirmed recv; stop Alert,Connect,Disconnect recv. + // Change channels. + return machPush(new AssignTCHMachine(tran()), statePostChannelChange); + } + + case statePostChannelChange: { + // We just wait for something else to happen. + if (IS_LOG_LEVEL(DEBUG)) { CommandLine::printChansV4(cout,false); } + switch (getDialog()->getDialogState()) { + case DialogState::dialogUndefined: + case DialogState::dialogStarted: + case DialogState::dialogProceeding: + case DialogState::dialogRinging: + case DialogState::dialogDtmf: + // We dont care about these. + return MachineStatusOK; // Waiting for L3Alerting or L3Connect. + case DialogState::dialogActive: + case DialogState::dialogBye: + case DialogState::dialogFail: + return machineRunState(L3CASE_DIALOG_STATE(getDialog()->getDialogState()),NULL,NULL); + } + } + + + // TODO: Should we resend the Ringing message on some timer? + case L3CASE_CC(Alerting): { + // We send a Ringing indication to SIP every time we receive an L3Alerting message. + const GSM::L3Alerting*msg = dynamic_cast(l3msg); + if (msg->mFacility.mExtant) WATCH(msg); // USSD DEBUG! + timerStart(T301,T301ms,TimerAbortTran); // Time state "Call Received"; start Alert recv; stop Connect recv. + setGSMState(CCState::CallReceived); + getDialog()->MTCSendRinging(); + return MachineStatusOK; // Waiting for L3Connect. + } + + case L3CASE_CC(Connect): { + timerStop(T301); + timerStop(T303); + timerStop(T310); + timerStopAll(); // a little redundancy here. + if (getGSMState() == CCState::ConnectIndication) { + // I think the code below would work ok, but this is neater. + LOG(DEBUG) << "ignoring duplicate L3Connect"; + return MachineStatusOK; + } + //timerStop(TRing); + // We used to set GSMstate Active when we received the Connect, + // but now we wait until we send the ConnectAcknowledge, which is after we receive confirmation + // from the SIP side. This is necessary because it is not until then that rtp is inited, + // and we use the CCState flag to indicate when the RTP traffic can start. + // Setting state Active later is probably more technically correct too. + //old: setGSMState(CCState::Active); + setGSMState(CCState::ConnectIndication); // Note: This may technically be an MOC only defined state. + getDialog()->MTCSendOK(tran()->chooseCodec(),channel()); + return MachineStatusOK; // Wait for SIP OK-ACK + } + + case L3CASE_SIP(dialogActive): { // SIP Dialog received SIP ACK to 200 OK. + // Success! The call is connected. + + // (pat) To doug: The place to move cipher starting is probably InCallMachine::machineRunState case stateStart. + if (gConfig.getBool("GSM.Cipher.Encrypt")) { + int encryptionAlgorithm = gTMSITable.tmsiTabGetPreferredA5Algorithm(tran()->subscriberIMSI().c_str()); + if (!encryptionAlgorithm) { + LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI(); + } else if (channel()->getL2Channel()->decryptUplink_maybe(tran()->subscriberIMSI(), encryptionAlgorithm)) { + LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI(); + channel()->l3sendm(GSM::L3CipheringModeCommand( + GSM::L3CipheringModeSetting(true, encryptionAlgorithm), + GSM::L3CipheringModeResponse(false))); + } else { + LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI(); + } + } + + setGSMState(CCState::Active); + getDialog()->MTCInitRTP(); + channel()->l3sendm(GSM::L3ConnectAcknowledge(tran()->getL3TI())); + return callMachStart(new InCallMachine(tran())); + } + + case L3CASE_SIP(dialogStarted): + case L3CASE_SIP(dialogProceeding): + case L3CASE_SIP(dialogRinging): { + PROCLOG(ERR) << "MTC received unexpected SIP Dialog message: << sipmsg;SIP Progress message"; + return MachineStatusOK; + } + + // SIP Dialog failure cases. + case L3CASE_SIP(dialogBye): { + // The other user hung up before we could finish. + return closeCall(L3Cause::NormalCallClearing); + } + case L3CASE_SIP(dialogFail): { + // It cannot be busy because it is a MTC. + return closeCall(L3Cause::InterworkingUnspecified); + } + + default: + return defaultMessages(state,l3msg); + } +} + +MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + + switch (state) { + case stateStart: + // The MS has already established the channel. The HandoverComplete message should arrive + // immediately unless the signal goes bad. + // We need a timer to make sure we eventually receive HandoverComplete. + // There is no specific timer for this. + // Instead of using the generic channel-loss timeout, use a shorter timer here. + timerStart(THandoverComplete,5000,TimerAbortChan); + return MachineStatusOK; + + case L3CASE_RR(HandoverComplete): { + timerStop(THandoverComplete); + mReceivedHandoverComplete = true; + // MS has successfully arrived on BS2. Open the SIPDialog and attempt to transfer the SIP session. + + // Send re-INVITE to the remote party. + HandoverEntry *hop = tran()->getHandoverEntry(true); + SIP::SipDialog *dialog = SIP::SipDialog::newSipDialogHandover(tran(),hop->mSipReferStr); + if (dialog == NULL) { + // We cannot abort the handover - it is too late. All we can do is drop the call. + LOG(ERR) << "handover failure due to failure to create dialog for " << tran(); // Will probably never happen. + closeCall(L3Cause::InterworkingUnspecified); + return closeChannel(L3RRCause::NormalEvent,RELEASE); + } + setDialog(dialog); + setGSMState(CCState::HandoverProgress); + timerStart(TSipHandover,4000,TimerAbortChan); + return MachineStatusOK; // Wait for SIP response from peer. + } + + case L3CASE_SIP(dialogFail): + // TODO: We should send a CC message to the phone based on the SIP fail code. + return closeChannel(L3RRCause::NormalEvent,RELEASE); + + case L3CASE_SIP(dialogBye): + // SIP end hung up. Just hang up the MS. + closeCall(L3Cause::NormalCallClearing); + return closeChannel(L3RRCause::NormalEvent,RELEASE); + + case L3CASE_SIP(dialogActive): { + // Success! SIP side is active. + timerStop(TSipHandover); + getDialog()->MOCSendACK(); + + // Send completion to peer BTS. TODO: This should be in a separate thread. + gPeerInterface.sendHandoverComplete(tran()->getHandoverEntry(true)); + + // Convert to a normal call. The Active status will (hopefully) cause RTP data to start + // being transferred by the service loop as soon as we return... + setGSMState(CCState::Active); + getDialog()->MOCInitRTP(); + + // We can connect to the MMUser now. + // TODO: I moved this to InCallMachine but I dont want to test handover right now so leave this here too; + // doesnt hurt to call mmAttachByImsi twice. + string imsi = tran()->subscriberIMSI(); + if (imsi.empty()) { + LOG(ALERT) "handover with empty imsi?"; // Should be an assert. + } + gMMLayer.mmAttachByImsi(channel(),imsi); + + // Update subscriber registry to reflect new registration. + /*** Pat thinks these are not used. + if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) { + gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str()); + } + ***/ + LOG(INFO) << "succesful inbound handover " << tran(); + return callMachStart(new InCallMachine(tran())); + } + + default: + // If we get any other message before receiving the HandoverComplete, it is unrecoverable. + if (!mReceivedHandoverComplete) { + machineErrorMessage(LOG_NOTICE,state,l3msg,sipmsg,"waiting for Handover Complete"); + return closeChannel(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,RELEASE); + } else { + // This state machine may need to be modified to handle this message, whatever it is: + machineErrorMessage(LOG_NOTICE,state,l3msg,sipmsg,"waiting for SIP Handover Complete"); + return MachineStatusOK; // Just keep going... + } + } +} + +void startInboundHandoverMachine(TranEntry *tran) +{ + InboundHandoverMachine *ihm = new InboundHandoverMachine(tran); + tran->lockAndStart(ihm); +} + + +void InCallMachine::acknowledgeDtmf() +{ + if (mDtmfSuccess) { + L3KeypadFacility thekey(mDtmfKey); + channel()->l3sendm(GSM::L3StartDTMFAcknowledge(tran()->getL3TI(),thekey)); + } else { + LOG (CRIT) << "DTMF sending attempt failed; is any DTMF method defined?"; + channel()->l3sendm(GSM::L3StartDTMFReject(tran()->getL3TI(),L3Cause::ServiceOrOptionNotAvailable)); + } +} + +static bool supportRFC2833() { + return gConfig.getBool("SIP.DTMF.RFC2833"); +} +static bool supportRFC2976() { + // Unfortunately the config option was originall misnamed, so test for both. + return gConfig.getBool("SIP.DTMF.RFC2976") || (gConfig.defines("SIP.DTMF.RFC2967") && gConfig.getBool("SIP.DTMF.RFC2967")); +} + +MachineStatus InCallMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + + switch (state) { + case stateStart: + // In the MOC case, this attachByImsi lets the MS start receiving MTSMS now. + // In the MTC case, this attachByImsi was done previously. + gMMLayer.mmAttachByImsi(channel(),tran()->subscriberIMSI()); + //channel()->setVoiceTran(tran()); + PROCLOG(DEBUG) << "setting voice tran="<(l3msg); + // Transalate to RFC-2976 or RFC-2833. + mDtmfSuccess = false; + mDtmfKey = startDtmfMsg->key().IA5(); + PROCLOG(INFO) << "DMTF key=" << mDtmfKey << ' ' << *tran(); + if (supportRFC2833()) { + // TODO: Who do we need to lock here? + bool s = getDialog()->startDTMF(mDtmfKey); + if (!s) PROCLOG(ERR) << "DTMF RFC-28333 failed."; + mDtmfSuccess |= s; + } + if (supportRFC2976()) { + unsigned bcd = GSM::encodeBCDChar(mDtmfKey); + getDialog()->sendInfoDtmf(bcd); + // In this case we need to return and wait for the reply to the INFO message; + // when it arrives we will go to the dialogDtmf state. + } else { + // If RFC2697 used we will send acknowledgement to MS when SIP OK arrives, otherwise send it now. + acknowledgeDtmf(); + } + return MachineStatusOK; + } + case L3CASE_SIP(dialogDtmf): { // This is returned if RFC2697 is used, ie, SIP instead of RTP. + if (sipmsg->sipStatusCode() == 200) { + mDtmfSuccess = true; + } else { + PROCLOG(ERR) << "DTMF RFC-2967 failed with code="<sipStatusCode(); + } + acknowledgeDtmf(); + return MachineStatusOK; + } + + // Stop DTMF RFC-2967 or RFC-2833 + case L3CASE_CC(StopDTMF): { + if (supportRFC2833()) { + getDialog()->stopDTMF(); + } + // For RFC2976 there is nothing more to do - we sent one SIP INFO message and that is it. + channel()->l3sendm(GSM::L3StopDTMFAcknowledge(tran()->getL3TI())); + return MachineStatusOK; + } + + case L3CASE_SIP(dialogProceeding): { + PROCLOG(INFO) << "Ignoring duplicate SIP Proceeding " << *tran(); + return MachineStatusOK; + } + case L3CASE_SIP(dialogRinging): { + PROCLOG(INFO) << "Ignoring duplicate SIP Ringing " << *tran(); + return MachineStatusOK; + } + case L3CASE_SIP(dialogActive): { + PROCLOG(ERR) << "Ignoring duplicate SIP Active " << *tran(); + return MachineStatusOK; + } + case L3CASE_SIP(dialogBye): { + return closeCall(L3Cause::NormalCallClearing); + } + case L3CASE_SIP(dialogFail): { + // This is MTD - Mobile Terminated Disconnect. SIP sends a CANCEL which translates to this Fail. + // It cant be busy at this point because we already connected. + //devassert(! sipmsg->isBusy()); + return closeCall(L3Cause::InterworkingUnspecified); + } + case L3CASE_SIP(dialogStarted): + devassert(0); + return MachineStatusQuitTran; // Shouldnt happen, but dont crash. + + default: + // Note: CMServiceRequest is handled at a higher layer, see handleCommonMessages. + return defaultMessages(state,l3msg); + + } + return MachineStatusOK; +} + +void initMTC(TranEntry *tran) +{ + tran->teSetProcedure(new MTCMachine(tran)); +} + + +}; // namespace diff --git a/Control/L3CallControl.h b/Control/L3CallControl.h new file mode 100644 index 0000000..808990c --- /dev/null +++ b/Control/L3CallControl.h @@ -0,0 +1,29 @@ + +/* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +*/ +#ifndef _L3CALLCONTROL_H_ +#define _L3CALLCONTROL_H_ 1 + +#include "L3StateMachine.h" +#include +#include + +namespace Control { +void startMOC(const GSM::L3MMMessage *l3msg, MMContext *dcch, L3CMServiceType::TypeCode serviceType); +void initMTC(TranEntry *tran); +void startInboundHandoverMachine(TranEntry *tran); + +}; // namespace + +#endif diff --git a/Control/L3Handover.cpp b/Control/L3Handover.cpp new file mode 100644 index 0000000..3b38c43 --- /dev/null +++ b/Control/L3Handover.cpp @@ -0,0 +1,630 @@ +/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */ + +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2011, 2012, 2013 Range Networks, Inc. +* + + 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. + +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. +*/ + +#include +#include +#include + +#include +#include "ControlCommon.h" +#include "RadioResource.h" +#include "L3CallControl.h" +#include "L3MMLayer.h" + +#include +#include +#include "../GPRS/GPRSExport.h" + +#include +#include +#include + +#include +#include +#undef WARNING + + + + +using namespace std; +using namespace GSM; +namespace Control { + +static void abortInboundHandover(RefCntPointer transaction, RRCause cause, L3LogicalChannel *LCH=NULL) +{ + LOG(DEBUG) << "aborting inbound handover " << *transaction; + unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff"); + gPeerInterface.sendHandoverFailure(transaction->getHandoverEntry(true),cause,holdoff); + //gTransactionTable.remove(transaction); +} + + + +#if UNUSED +bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp) +{ + assert(! l3rewrite()); // Not used in l3rewrite. See TCHFACCHL1Decoder::writeLowSideRx + // In this function, we are "BS2" in the ladder diagram. + // This is called from L1 when a handover burst arrives. + + // We will need to use the transaction record to carry the parameters. + // We put this here to avoid dealing with the transaction table in L1. + TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverRef(handoverReference); + if (!transaction) { + LOG(ERR) << "no inbound handover with reference " << handoverReference; + return false; + } + + if (timingError > gConfig.getNum("GSM.MS.TA.Max")) { + // Handover failure. + LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction; + // RR cause 8: Handover impossible, timing advance out of range + OldAbortInboundHandover(transaction,L3RRCause::HandoverImpossible,dynamic_cast(transaction->channel())); + return false; + } + + LOG(INFO) << "saving handover access for " << *transaction; + transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp)); + return true; +} +#endif + + + +//void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH) +//{ +// // In this function, we are "BS2" in the ladder diagram. +// // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive. +// // The information it needs was saved in the transaction table by SaveHandoverAccess. +// +// +// assert(TCH); +// LOG(DEBUG) << *TCH; +// +// TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverChan(TCH); +// if (!transaction) { +// LOG(WARNING) << "handover access with no inbound transaction on " << *TCH; +// TCH->l2sendp(HARDRELEASE); +// return; +// } +// +// // clear handover in transceiver +// LOG(DEBUG) << *transaction; +// transaction->getL2Channel()->handoverPending(false); +// +// // Respond to handset with physical information until we get Handover Complete. +// int TA = (int)(transaction->inboundTimingError() + 0.5F); +// if (TA<0) TA=0; +// if (TA>62) TA=62; +// unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105"); +// unsigned sendCount = gConfig.getNum("GSM.Ny1"); +// L3Frame* frame = NULL; +// while (!frame && sendCount) { +// TCH->l2sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA); +// sendCount--; +// frame = TCH->l2recv(repeatTimeout); +// if (frame && frame->primitive() == HANDOVER_ACCESS) { +// LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; +// delete frame; +// frame = NULL; +// } +// } +// +// // Timed out? +// if (!frame) { +// LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction; +// // RR cause 4: Abnormal release, no activity on the radio path +// OldAbortInboundHandover(transaction,4,TCH); +// return; +// } +// +// // Screwed up channel? +// if (frame->primitive()!=ESTABLISH) { +// LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on " +// << *TCH << ": " << *frame << " for " << *transaction; +// delete frame; +// // RR cause 0x62: Message not compatible with protocol state +// OldAbortInboundHandover(transaction,0x62,TCH); +// return; +// } +// +// // Get the next frame, should be HandoverComplete. +// delete frame; +// frame = TCH->l2recv(); +// L3Message* msg = parseL3(*frame); +// if (!msg) { +// LOG(NOTICE) << "unparsable message waiting for Handover Complete on " +// << *TCH << ": " << *frame << " for " << *transaction; +// delete frame; +// // RR cause 0x62: Message not compatible with protocol state +// TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState)); +// OldAbortInboundHandover(transaction,0x62,TCH); +// return; +// } +// delete frame; +// +// L3HandoverComplete* complete = dynamic_cast(msg); +// if (!complete) { +// LOG(NOTICE) << "expecting for Handover Complete on " +// << *TCH << "but got: " << *msg << " for " << *transaction; +// delete frame; +// // RR cause 0x62: Message not compatible with protocol state +// TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState)); +// OldAbortInboundHandover(transaction,0x62,TCH); +// } +// delete msg; +// +// // Send re-INVITE to the remote party. +// unsigned RTPPort = allocateRTPPorts(); +// SIP::SIPState st = transaction->inboundHandoverSendINVITE(RTPPort); +// if (st == SIP::Fail) { +// OldAbortInboundHandover(transaction,4,TCH); +// return; +// } +// +// transaction->GSMState(CCState::HandoverProgress); +// +// while (1) { +// // FIXME - the sip engine should be doing this +// // FIXME - and checking for timeout +// // FIXME - and checking for proceeding (stop sending the resends) +// st = transaction->inboundHandoverCheckForOK(); +// if (st == SIP::Active) break; +// if (st == SIP::Fail) { +// LOG(NOTICE) << "received Fail while waiting for OK"; +// OldAbortInboundHandover(transaction,4,TCH); +// return; +// } +// } +// st = transaction->inboundHandoverSendACK(); +// LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *transaction; +// +// // Send completion to peer BTS. +// char ind[100]; +// sprintf(ind,"IND HANDOVER_COMPLETE %u", transaction->tranID()); +// gPeerInterface.sendUntilAck(transaction,ind); +// +// // Update subscriber registry to reflect new registration. +// if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) { +// gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str()); +// } +// +// // The call is running. +// LOG(INFO) << "succesful inbound handover " << *transaction; +// transaction->GSMState(CCState::Active); +// callManagementLoop(transaction,TCH); +//} + +// How did we get here you ask? Peering received a handover request on BTS2 (us), allocated a channel and set the handoverPending flag, +// created a transaction with the specified IMSI, returned an L3 handover command which BTS1 sent to the MS, which then +// sent a handover access to BTS2, and here we are! +void ProcessHandoverAccess(L3LogicalChannel *chan) +{ + using namespace SIP; + // In this function, we are "BS2" in the ladder diagram. + // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive. + // The information it needs was saved in the transaction table by SaveHandoverAccess. + LOG(DEBUG) << *chan; + + RefCntPointer tran = chan->chanGetVoiceTran(); + if (tran == NULL) { + LOG(WARNING) << "handover access with no inbound transaction on " << chan; + chan->chanRelease(HARDRELEASE); + return; + } + LOG(DEBUG) << *tran; + + if (!tran->getHandoverEntry(false)) { + LOG(WARNING) << "handover access with no inbound handover on " << *chan; + chan->chanRelease(HARDRELEASE); + return; + } + + // clear handover in transceiver and get the RSSI and TE. + // This instructs L2 to stop looking for and stop sending HANDOVER_ACCESS. + // However, we cant just flush them out of the queue here because that is running in another + // thread and it may keep pushing HANDOVER_ACCESS at, so we keep flushing them (below) + // However, we should NEVER see HANDOVER_ACCESS after the ESTABLISH, yet I did. + GSM::HandoverRecord hr = chan->getL2Channel()->handoverPending(false,0); + + // TODO: Move this into L1? + if (hr.mhrTimingError > gConfig.getNum("GSM.MS.TA.Max")) { + // Handover failure. + LOG(NOTICE) << "handover failure on due to TA=" << hr.mhrTimingError << " for " << *tran; + // RR cause 8: Handover impossible, timing advance out of range + abortInboundHandover(tran,L3RRCause::HandoverImpossible,dynamic_cast(tran->channel())); + chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? + return; + } + + chan->getL2Channel()->setPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp); + + // Respond to handset with physical information until we get Handover Complete. + int TA = (int)(hr.mhrTimingError + 0.5F); + if (TA<0) TA=0; + if (TA>62) TA=62; + + // We want to do this loop carefully so we exit as soon as we get a frame that is not HANDOVER_ACCESS. + Z100Timer T3105(gConfig.getNum("GSM.Timer.T3105")); // It defaults to only 50ms. + + // 4.08 11.1.3 "Ny1: The maximum number of repetitions for the PHYSICAL INFORMATION message during a handover." + for (unsigned sendCount = gConfig.getNum("GSM.Ny1"); sendCount > 0; sendCount--) { + T3105.set(); + // (pat) It is UNIT_DATA because the channel is not established yet. + // (pat) WARNING: This l3sendm call is not blocking because it is sent on FACCH which has a queue. + // Rather than modifying the whole LogicalChannel stack to have a blocking mode, + // we are just going to wait afterwards. The message should take about 20ms to transmit, + // and GSM uses roughly 4 out of every 5 frames, so 20-25ms would transmit the message continuously. + chan->l3sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA); + + // (pat) Throw away all the HANDOVER_ACCESS that arrive while we were waiting. + // They are not messages that take 4 bursts; they can arrive on every burst, so there + // can be a bunch of them queued up (I would expect 5) for each message we send. + while (L3Frame *frame = chan->l2recv(T3105.remaining())) { + switch (frame->primitive()) { + case HANDOVER_ACCESS: + // See comments above. L2 is no longer generating these, but we need + // to flush any extras from before we started, and there also might be have been + // some in progress when we turned them off, so just keep flushing. + LOG(INFO) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; + delete frame; + continue; + case ESTABLISH: + delete frame; + // Channel is established, so the MS is there. Finish up with a state machine. + startInboundHandoverMachine(tran.self()); + return; + default: + // Something else? + LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on " + << *chan << ": " << *frame << " for " << *tran; + delete frame; + abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); + chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? + return; + } + } + } + + // Failure. + LOG(NOTICE) << "timed out waiting for Handover Complete on " << *chan << " for " << *tran; + // RR cause 4: Abnormal release, no activity on the radio path + abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan); + chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? + return; + + +#if 0 + // // Get the next frame, should be HandoverComplete. + // delete frame; + // frame = chan->l2recv(); + // L3Message* msg = parseL3(*frame); + // if (!msg) { + // LOG(NOTICE) << "unparsable message waiting for Handover Complete on " + // << *chan << ": " << *frame << " for " << *tran; + // delete frame; + // // The MS is listening to us now, so we have to send it something if we abort. + // // TODO: Should be a state machine from here on. + // // RR cause 0x62: Message not compatible with protocol state + // chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE); + // abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); + // return; + // } + // delete frame; + // + // L3HandoverComplete* complete = dynamic_cast(msg); + // if (!complete) { + // LOG(NOTICE) << "expecting for Handover Complete on " + // << *chan << "but got: " << *msg << " for " << *tran; + // delete frame; + // // RR cause 0x62: Message not compatible with protocol state + // chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE); + // abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); + // } + // delete msg; + // + // // MS has successfully arrived on BS2. Open the SIPDialog and attempt to transfer the SIP session. + // + // // Send re-INVITE to the remote party. + // //unsigned RTPPort = allocateRTPPorts(); + // SIP::SIPDialog *dialog = SIP::SIPDialog::newSIPDialogHandover(tran); + // if (dialog == NULL) { + // // TODO: Can we abort at this point? It is too late. + // // But this only fails if the address is wrong. + // //abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan); + // LOG(NOTICE) << "handover failure due to failure to create dialog for " << *tran; // Will probably never happen. + // tran->teCloseCall(L3Cause::InterworkingUnspecified); + // chan->chanClose(L3RRCause::Unspecified,RELEASE); + // return; + // } + // tran->setDialog(dialog); + // tran->setGSMState(CCState::HandoverProgress); + // + // while (DialogMessage*dmsg = dialog->dialogRead()) { + // switch (dmsg->dialogState()) { + // case SIPDialog::dialogActive: + // // We're good to go. + // tran->setGSMState(CCState::Active); + // break; + // case SIPDialog::dialogBye: + // // Other end hung up. Just hang up. + // tran->teCloseCall(L3Cause::NormalCallClearing); + // chan->chanClose(L3RRCause::NormalEvent,RELEASE); + // return; + // default: + // LOG(ERR) << "unrecognized SIP Dialog state while waiting for handover re-invite OK"<teCloseCall(L3Cause::InterworkingUnspecified); + // chan->chanClose(L3RRCause::Unspecified,RELEASE); + // return; + // } + // delete dmsg; + // if (tran->getGSMState() == CCState::Active) { break; } + // } + // SIP::SIPState st = dialog->inboundHandoverSendACK(); + // LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *tran; + // + // // Send completion to peer BTS. + // char ind[100]; + // sprintf(ind,"IND HANDOVER_COMPLETE %u", tran->tranID()); + // gPeerInterface.sendUntilAck(tran->getHandoverEntry(true),ind); + // + // // Update subscriber registry to reflect new registration. + // /*** Pat thinks these are not used. + // if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) { + // gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str()); + // } + // ***/ + // + // // The call is running. + // LOG(INFO) << "succesful inbound handover " << *tran; + // //callManagementLoop(transaction,TCH); +#endif +} + + +// Warning: This runs in a separate thread. +void HandoverDetermination(const L3MeasurementResults& measurements, float myRxLevel, SACCHLogicalChannel* SACCH) +{ + // This is called from the SACCH service loop. + + // Valid measurements? + if (measurements.MEAS_VALID()) return; + + // Got neighbors? + // (pat) I am deliberately not aging the neighbor list if the measurement report is empty because + // I am afraid it may be empty because the MS did not have time to make measurements during this time + // period, rather than really indicating that there are no neighbors. + unsigned N = measurements.NO_NCELL(); + if (N==0) { return; } + + if (N == 7) { + LOG(DEBUG) << "neighbor cell information not available"; + return; + } + + // (pat) TODO: If you add your own IP address to the sql neighbor list, the MS will return info on yourself, + // which will attempt a handover to yourself unless you throw those measurement reports away here. + // We should detect this and throw them out. + // Currently processNeighborParams() detects this condition when it gets a Peer report (but not at startup!) + // but we dont save the BSIC in memory so we dont have that information here where we need it. + + // Look at neighbor cell rx levels + SACCH->neighborStartMeasurements(); + int best = 0; + int bestRxLevel = -1000; + for (unsigned int i=0; ineighborAddMeasurement(thisFreq,thisBSCI,thisRxLevel); + if (thisRxLevel>bestRxLevel) { + best = i; + bestRxLevel = thisRxLevel; + } + } + int bestBCCH_FREQ_NCELL = measurements.BCCH_FREQ_NCELL(best); // (pat) This is an index into the neighborlist, not a frequency. + int bestBSIC = measurements.BSIC_NCELL(best); + + // Is our current signal OK? + //int myRxLevel = measurements.RXLEV_SUB_SERVING_CELL_dBm(); + int localRSSIMin = gConfig.getNum("GSM.Handover.LocalRSSIMin"); + int threshold = gConfig.getNum("GSM.Handover.ThresholdDelta"); + int gprsRSSI = gConfig.getNum("GPRS.ChannelCodingControl.RSSI"); + // LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm localRSSIMin=" << localRSSIMin << " dBm"; + LOG(DEBUG) <neighborText(); + // Does the best exceed the current by more than the threshold? If not dont handover. + if (bestRxLevel < (myRxLevel + threshold)) { return; } + + const char *what; + if (myRxLevel > localRSSIMin) { + // The current signal is ok; see if we want to do a discretionery handover. + if (!( + (gBTS.TCHTotal() == gBTS.TCHActive()) || // Is the current BTS full? + (myRxLevel < gprsRSSI && bestRxLevel > gprsRSSI) || // Would a handover let GPRS use a better codec? + (bestRxLevel > myRxLevel + 3 * threshold) // Is the other BTS *much* better? + )) { return; } // If not, dont handover. + what = "discretionary"; + } else { + // Mandatory handover because the signal is poor and the neighbor BTS is threshold better. + //LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm, best neighbor=" << bestRxLevel << " dBm, threshold=" << threshold << " dB"; + what = "mandatory"; + } + + // OK. So we will initiate a handover. Woo hoo! + LOG(DEBUG) <(SACCH->hostChan()); + L3LogicalChannel *mainChan = const_cast(mainChanConst); // idiotic language + // The RefCntPointer prevents the tran from being deleted while we are working here, as unlikely as that would be. + const RefCntPointer tran = mainChan->chanGetVoiceTran(); + if (tran == NULL) { + LOG(ERR) << "active SACCH with no transaction record: " << *SACCH; + return; + } + if (tran->getGSMState() != CCState::Active) { + LOG(DEBUG) << "skipping handover for transaction " << tran->tranID() + << " due to state " << tran->getGSMState(); + return; + } + // Don't hand over an emergency call based on an IMEI. It WILL fail. + if (tran->servicetype() == GSM::L3CMServiceType::EmergencyCall && + //Unconst(tran)->subscriber().mImsi.length() == 0) + tran->subscriber().mImsi.length() == 0) { + LOG(ALERT) << "cannot handover emergency call with non-IMSI subscriber ID: " << *tran; + return; + } + + // (pat) Dont handover a brand new transaction. This also prevents an MS from bouncing + // back and forth between two BTS. We dont need a separate timer for this handover holdoff, + // we can just use the age of the Transaction. + // I dont see any such timer in the spec; I am reusing T3101ms, which is not correct but vaguely related. + // Update - this is now unnecessary because the averaging method of myRxLevel prevents a handover for the first 5-10 secs. + unsigned age = tran->stateAge(); // in msecs. + unsigned holdoff = 1000 * gConfig.getNum("GSM.Timer.Handover.Holdoff"); // default 10 seconds. + if (age < holdoff) { + WATCH("skipping handover for transaction " << tran->tranID() << " due to young"<tranID() << " because age "<tranID() + << " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm"; + + // The handover reference will be generated by the other BTS. + // We don't set the handover reference or state until we get RSP HANDOVER. + + // TODO: Check for handover request to our own BTS and avoid it. Dont forget to check the port too. +#if 0 // This did not work for some reason. + struct sockaddr_in peerAddr; + if (resolveAddress(&peerAddr,peer.c_str())) { + LOG(ALERT) "handover"<getHandoverEntry(true); + L3Frame HandoverCommand(hop->mHexEncodedL3HandoverCommand.c_str()); + LOG(INFO) <l3sendf(HandoverCommand); + //TCH->l3sendm(GSM::L3HandoverCommand( + // hep->mOutboundCell, + // hep->mOutboundChannel, + // hep->mOutboundReference, + // hep->mOutboundPowerCmd, + // hep->mOutboundSynch + // )); + + // Start a timer for T3103, the handover failure timer. + // This T3103 timer is for the outbound leg of the handover on BS1. + // There is another T3103 timer in GSML1FEC for the inbound handover on BS2. + GSM::Z100Timer outboundT3103(gConfig.getNum("GSM.Timer.T3103") + 1000); + outboundT3103.set(); + + // The next step for the MS is to send Handover Access to BS2. + // The next step for us is to wait for the Handover Complete message + // and see that the phone doesn't come back to us. + // BS2 is doing most of the work now. + // We will get a handover complete once it's over, but we don't really need it. + + // Q: What about transferring audio packets? + // A: There should not be any after we send the Handover Command. + // A2: (pat 7-25-2013) Wrong, the MS may take up to a second to get around to handover, so we should keep sending + // audio packets as long as we can. + + // Get the response. + // This is supposed to time out on successful handover, similar to the early assignment channel transfer.. + GSM::L3Frame *result = TCH->l2recv(outboundT3103.remaining()); + if (result) { + // If we got here, the handover failed and we just keep running the call. + L3Message *msg = parseL3(*result); + LOG(NOTICE) << "failed handover, received " << *result << msg; + if (msg) { delete msg; } + delete result; + // Restore the call state. + transaction->setGSMState(CCState::Active); + return false; + } + + // If the phone doesn't come back, either the handover succeeded or + // the phone dropped the connection. Either way, we are clearing the call. + + // Invalidate local cache entry for this IMSI in the subscriber registry. + // (pat) TODO: I dont understand how this works - it looks like it is over-writing what BS2 added. + string imsi = string("IMSI").append(transaction->subscriber().mImsi); + // (mike) TODO: disabled as there no longer local vs upstream caches + //gSubscriberRegistry.removeUser(imsi.c_str()); + + transaction->teCancel(); // We need to do this immediately in case a reverse handover comes back soon. + + // We need to immediately destroy the dialog. + + LOG(INFO) "timeout following outbound handover; exiting normally"; + //TCH->l2sendp(GSM::HARDRELEASE); now done by caller. + return true; +} + +}; // namespace Control diff --git a/Control/L3LogicalChannel.cpp b/Control/L3LogicalChannel.cpp new file mode 100644 index 0000000..1f0affc --- /dev/null +++ b/Control/L3LogicalChannel.cpp @@ -0,0 +1,444 @@ +/* +* 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 distribuion. +* +* 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. + +*/ + +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging + +#include "L3LogicalChannel.h" +#include "L3MMLayer.h" +#include // Needed for getL2Channel() +#include // For gBTS + +namespace Control { +using namespace GSM; + +void L3LogicalChannel::L3LogicalChannelReset() +{ + LOG(DEBUG) << this; + ScopedLock lock(gMMLock,__FILE__,__LINE__); // FIXMENOW Added 10-23-2013 + // We could reset mNextChan too, but it is unused unless needed so dont bother. + chanFreeContext(); // If we do cancel any dialogs, it is in error. + LOG(DEBUG); + if (mNextChan && mNextChan->mChState == chReassignTarget) { + // This rare case may occur for channel loss or if the MS sends, for example, an IMSI Detach + // during the channel reassignment procedure. + LOG(INFO) << "lost contact with MS during reassignment procedure "<chanFreeContext(); + mNextChan = NULL; + } + LOG(DEBUG); +} + +void L3LogicalChannel::L3LogicalChannelInit() +{ + // We must NOT reset mNextChan,mPrevChan as part of the LogicalChannelReset because these must + // survive the establishment and release of channels during the reassignment procedure. + // We dont use them for anything else, so it is ok to end with them set. + mNextChan = NULL; + //mPrevChan = NULL; + mChState = chIdle; + mChContext = NULL; + L3LogicalChannelReset(); +} + +L3LogicalChannel::~L3LogicalChannel() +{ + chanFreeContext(); +} + +const char *L3LogicalChannel::descriptiveString() const { + return "undefined"; +} + +L2LogicalChannel * L3LogicalChannel::getL2Channel() { + // We dont need a dynamic cast since L2 and L3 LogicalChannel are always allocated together. + // But UMTS may change that. + return dynamic_cast(this); +} + +// TODO: This should probably be removed and the few uses replaced by specific functions, +// like getChannelDescription and getSACCH. +const L2LogicalChannel * L3LogicalChannel::getL2Channel() const { + return dynamic_cast(this); +} + +L3LogicalChannel *L3LogicalChannel::getSACCHL3() { + return dynamic_cast(this->getL2Channel()->SACCH()); +} + +void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/) +{ + WATCHINFO("l3sendm"<primitive()==ESTABLISH) return req; + if (req->primitive()==HANDOVER_ACCESS) return req; + LOG(INFO) << "L3LogicalChannel: Ignored primitive:"<primitive(); + delete req; + } + return NULL; // to keep the compiler happy +} + +MMContext *L3LogicalChannel::chanGetContext(bool create) +{ + LOG(DEBUG); + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG); + if (mChContext == NULL) { + if (create) { mChContext = new MMContext(this); } + } + return mChContext; +} + +// WARNING: This is called from the CLI thread. +string L3LogicalChannel::chanGetImsi(bool verbose) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + return mChContext ? mChContext->mmGetImsi(verbose) : string(verbose ? "no-MMContext" : ""); +} + +//void L3LogicalChannel::chanSetContext(MMContext* wContext) +//{ +// chanFreeContext(); +// mChContext = wContext; +// mChContext->mmSetChannel(this); +//} + +// The sipcode would be used if a SipDialog on this channel is still active, which indicates a channel loss failure +// or server error. +void L3LogicalChannel::chanFreeContext() +{ + LOG(DEBUG); + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG); + MMContext *save = mChContext; + mChContext = NULL; + if (save) { + LOG(DEBUG) <(tch); + // (pat) TODO: If not VEA, we should try doing the tch->open() here to see if it reduces the number + // of channel reassignment failures. + return true; +} + +// This is run on the old channel. +void L3LogicalChannel::reassignStart() +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG) << this << LOGVAR(mNextChan); + // The current channel is the SDCCH, and mNextChan is the allocated TCH the MS will use next. + assert(mNextChan); // reassignmentAllocNextTCH was called first. + + // We set this state so when the LAPDm RELEASE arrives on this channel we dont kill everything off, + // which is the normal reaction to a RELEASE. + // Update: now we use the MMContext mmcChannelUseCnt. + //chanSetState(L3LogicalChannel::chReassignPending); + + //mNextChan->chanSetContext(); Dont call this yet. It changes the channel back pointer. + if (mNextChan->mChState != chIdle) { + LOG(ERR) <<"At start of channel reassignment target channel is not idle:" + <chanFreeContext(); // This is supposed to be a no-op. + mNextChan->mChContext = mChContext->tsDup(); // Must set directly. Does not change the channel back pointer. + // We set this state on nextChan so that if + mNextChan->chanSetState(L3LogicalChannel::chReassignTarget); + + GSM::L2LogicalChannel *tch = mNextChan->getL2Channel(); + GSM::L2LogicalChannel *sdcch = this->getL2Channel(); + // Note we do not want to do a HARDRELEASE if this fails, because that bypasses the very timer we are supposed to be using. + LOG(INFO) << "sending AssignmentCommand for " << tch << " on " << this; + tch->open(); // This sets T3101 as a side effect. + tch->setPhy(*sdcch); +} + +// This occurs on the channel being assigned from. +// We need to release this channel and nextChannel. +// Release of nextChan also occurs if a channel drops out of the main service loop and the nextChan state is still ReassignTarget +void L3LogicalChannel::reassignFailure() +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG) << this; + // Clean up the next chan we were trying to reassign to. + if (mNextChan) { + devassert(mNextChan->isTCHF()); + mNextChan->chanSetState(L3LogicalChannel::chRequestRelease); // This will probably block the chan for 30 seconds. + // If we never received the ESTABLISH primitive on nextChan, then the service loop is not runnning, + // so it will never detect the change of state on nextChan, so we must free the channel here. + // The service loops check dcch->chanRunning(), so they will not do anything that might try to use the Context we are freeing.. + mNextChan->chanFreeContext(); + mNextChan = NULL; + } else { + LOG(ERR) << "reassignment failure but no nextChan? "<l3sendm(L3ReleaseComplete(l3ti,l3cause)); // Release the transaction identifier. + // We might as well drop the whole channel. + // Old code had cause 4, but that does not seem right: + // RR Cause 0x04 -- "abnormal release, no activity on the radio path" + // Caller does this now. + //l3sendm(GSM::L3ChannelRelease(L3RRCause::NoActivityOnTheRadio)); + // The MS is supposed to release the channel, which will send a RELEASE primitive up to Layer3 to close the channel. +} + +// Both old and new L3LogicalChannel point to the same MMContext. +// The message arrives on the new channel, but we run this function on the old channel +// because we have not changed the LogicalChannel that the Context points to yet. +// mNextChan still points to the new channel. +// Beware that the two channels are serviced by different threads. +void L3LogicalChannel::reassignComplete() +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + //timerStop(TChReassignment); // Handled by assignTCHFProcedure, which is notified after us. + + if (!mChContext) { + // Logic error. + LOG(ERR) << "received channel reassignment complete on dead channel:"<mChState != chEstablished) { + // The nextChan was supposed to get an ESTABLISH primitive then the L3AssignComplete command in order to get here. + // There could be a logic error or the MS may have dropped the channel at this inopportune moment, + // so it is not necessarily an error. + LOG(NOTICE)<< "Next channel in unexpected state, dropping channel"<mmSetChannel(mNextChan); + LOG(INFO) <<"successful channel reassignment" <chanSetState(L3LogicalChannel::chReassignFailure); + //mNextChan->mPrevChan = NULL; + mNextChan = NULL; + } + chanFreeContext(); +} +#endif + +// Set the flag, which will perform the channel release from the channel serviceloop. +void L3LogicalChannel::chanRelease(Primitive prim) +{ + OBJLOG(DEBUG) << prim; + switch (prim) { + case HARDRELEASE: + chanSetState(L3LogicalChannel::chRequestHardRelease); + return; + case RELEASE: + chanSetState(L3LogicalChannel::chRequestRelease); + return; + default: + assert(0); + } +} + +// This completely releases the channel and all transactions on it. +// FIXME no it doesnt, and L2 can hang when we send the primitive, so these transactions and dialogs +// are not cleaned up until the next time the channel is used. Very bad. +void L3LogicalChannel::chanClose(L3RRCause cause,Primitive prim) +{ + // Note: timer expiry may indicate unresponsive MS so this may block for 30 seconds. + l3sendm(L3ChannelRelease(cause)); + chanRelease(prim); +} + + +//void L3LogicalChannel::chanSetVoiceTran(TranEntry *tran) +//{ +// MMContext *set = chanGetContext(true); +// set->tsSetVoiceTran(tran); +//} + +RefCntPointer L3LogicalChannel::chanGetVoiceTran() +{ + MMContext *set = chanGetContext(true); + return set->tsGetVoiceTran(); +} + +void L3LogicalChannel::chanEnqueueFrame(L3Frame *frame) +{ + ml3UplinkQ.write(frame); +} + +// When L3 wants to drop a channel, it must set a flag in the L3LogicalChannel, which will be queried here. +// Return false to drop the channel. +bool L3LogicalChannel::chanRunning() +{ + // Check for channel release. + switch (this->mChState) { + case L3LogicalChannel::chEstablished: + case L3LogicalChannel::chReassignTarget: + //case L3LogicalChannel::chReassignPending: + return true; // Still running. + case L3LogicalChannel::chIdle: // seeing this would be a bug. + case L3LogicalChannel::chRequestRelease: + case L3LogicalChannel::chRequestHardRelease: + //case L3LogicalChannel::chReassignFailure: + //case L3LogicalChannel::chReassignComplete: + //case L3LogicalChannel::chReleased: + return false; + } + return false; +} + +const char *L3LogicalChannel::ChannelState2Text(ChannelState chstate) +{ + switch (chstate) { + case L3LogicalChannel::chIdle: return "Idle"; + case L3LogicalChannel::chEstablished: return "Established"; + //case L3LogicalChannel::chReleased: return "Released"; + case L3LogicalChannel::chRequestRelease: return "RequestRelease"; + case L3LogicalChannel::chRequestHardRelease: return "RequestHardRelease"; + case L3LogicalChannel::chReassignTarget: return "ReassignTarget"; + //case L3LogicalChannel::chReassignPending: return "ReassignPending"; + //case L3LogicalChannel::chReassignComplete: return "ReassignmentComplete"; + //case L3LogicalChannel::chReassignFailure: return "ReassignmentFailure"; + } + return "(chstate undefined)"; +} + +// Info about just this L3LogicalChannel +std::ostream& L3LogicalChannel::chanText(ostream& os) const +{ + os << descriptiveString() << LOGVAR2("state",ChannelState2Text(mChState)); + if (mNextChan) { os <descriptiveString()); } + return os; +} + +// Info about just the underlying MMContext for this L3LogicalChannel. +// Warning: This is called from the CLI thread. +std::ostream& L3LogicalChannel::chanContextText(ostream& os) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + if (MMContext *mmc = Unconst(this)->chanGetContext(false)) { + mmc->mmcText(os); + } + return os; +} + +ostream& operator<<(ostream& os, const L3LogicalChannel& chan) { chan.chanText(os); return os; } + +std::ostream& operator<<(std::ostream&os, const L3LogicalChannel*ch) { + if (ch) { ch->chanText(os); } else { os << "(null channel)"; } + return os; +} + +// pat FIXME - Called from CLI so must lock channel. +// Warning: This is called from the CLI thread. +void L3LogicalChannel::getTranIds(TranEntryList &tids) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + tids.clear(); + if (const MMContext *set = Unconst(this)->chanGetContext(false)) { + set->getTranIds(tids); + } +} + + +bool L3LogicalChannel::isTCHF() const +{ + return chtype()==GSM::FACCHType; +} + +bool L3LogicalChannel::isSDCCH() const +{ + return chtype()==GSM::SDCCHType; +} + +// For use by the CLI. +void printChansInfo(std::ostream&os) +{ + L2ChanList chans; + gBTS.getChanVector(chans); + for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) { + L3LogicalChannel *chan = dynamic_cast(*it); + os << chan; + chan->chanContextText(os); + os << endl; + } +} + +}; // namespace diff --git a/Control/L3LogicalChannel.h b/Control/L3LogicalChannel.h new file mode 100644 index 0000000..ce42dcd --- /dev/null +++ b/Control/L3LogicalChannel.h @@ -0,0 +1,162 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ +#ifndef _L3LOGICALCHANNEL_H_ +#define _L3LOGICALCHANNEL_H_ 1 +#include "ControlTransfer.h" +#include +#include + +namespace Control { +using namespace GSM; +class TranEntry; +class MMContext; + +class L3LogicalChannel { + friend class AssignTCHMachine; + friend void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame*frame); + + // Normally only one thread accesses each LogicalChannel, however, during reassignment the LogicalChannel + // is accessed from the thread of the previous channel. I dont think conflicts are possible, but to be + // safe we will use a Mutex to protect those methods. + // Update: There are so many penetrations through L3LogicalChannel into MMContext, which must be locked + // with gMMLock, that I gave up on having a separate lock here and just use gMMLock. + // mutable Mutex mChanLock; +#if UNUSED + //L3FrameFIFO ml3DownlinkQ; + + // New way. This is only used for RR and CC messages which are always on SAPI 0. + //void l3sendm(const L3Message& msg); + // (pat) Stick a primitive in the uplink queue. + //void l2uplinkEneuquep(const GSM::Primitive& prim, unsigned SAPI=0) + // { assert(mL2[SAPI]); mL2[SAPI]->l2WriteHighSide(GSM::L3Frame(prim)); } + + // If there is an ongoing voice call, we need the SIP pointer. + // For convenience I am using the TransactionEntry to find the voice data, + // but do NOT use this TransactionEntry for anything else; there may be multiple TransactionEntries associated + // with each logical channel. For example, L3 messages should be sent to the + // L3 state machines in case the TI [Transaction Identifier] in the message does not match this TransactionEntry. + //TranEntry *mVoiceTrans; +#endif + // DCCH Channels go through two regimes in both GSM and UMTS. + // When the channel is first granted to an MS the MS is usually unidentified, + // meaning it sent us a TMSI but we either do not know or do not trust the TMSI->IMSI mapping. + // This is Regime 1, there is only an MMContext, and only MO operations are permitted. + // After the MS is identified, we enter the Regime 2 where the channel is also associated with an MMUser, + // and can process (possibly multiple simultaneous) MT transactions. + // We may never enter Regime 2, meaning we may never associate an IMSI-identified MMUser with this channel, + // for example, for SOS calls. + + // The MMContext holds the active Transactions on a channel. + // It can be moved to a different channel by RR Procedures. + // (In contrast, an MMUser is associated with an IMSI.) + MMContext *mChContext; // TODO: RefCntPointer? + protected: + L3LogicalChannel *mNextChan; // Used in GSM during channel reassignment. + //L3LogicalChannel *mPrevChan; + + public: + bool chanRunning(); + InterthreadQueue ml3UplinkQ; // uplink SACCH message are enqueued here. + + private: + // This can be thought of as the RR state, as known from an L3 perspective. + // It informs the service loop that we want to release the channel, since we dont want + // to send things on the channel from some other channel's thread. + // It is GSM-specific, not used in UMTS. + enum ChannelState { + chIdle, // Not assigned to any MS. + chEstablished, // Assigned to a MS + chRequestRelease, // controlling thread requests RELEASE. + chRequestHardRelease, // controlling thread requests HARDRELEASE. + chReassignTarget, // State of the channel we are assigning to, until it is established. + //chReleased, // Channel released by Layer3 by sending RELEASE or HARDRELEASE to layer 2. + // These two states could be combined since they are functionally the same, but nice for debugging to tell what happened. + // No longer used: + //chReassignPending, // This is an SDCCH with pending reassignment to TCH. + //chReassignComplete, // This is an SDCCH after successful reassignment to TCH, ie, we are going to release it momentarily. + //chReassignFailure // This channel is the reassignment target; needs to be released after a reassignment failure. + } volatile mChState; + static const char *ChannelState2Text(ChannelState chstate); + void chanSetState(ChannelState wChState) { mChState = wChState; } + + public: + // Pass-throughs from Layer2. These will be different for GSM or UMTS. + virtual GSM::L3Frame * l2recv(unsigned timeout_ms = 15000, unsigned SAPI=0) = 0; + // In GSM the l2send methods may block in LAPDm. + virtual void l2sendf(const GSM::L3Frame& frame, SAPI_t SAPI=SAPI0) = 0; + virtual void l2sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0) = 0; + virtual void l2sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0) = 0; + void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0); + void l3sendf(const GSM::L3Frame& frame); + void l3sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0); + virtual unsigned N200() const = 0; + virtual bool multiframeMode(unsigned SAPI) const = 0; // Used by SMS code. + virtual const char* descriptiveString() const; + virtual bool radioFailure() const = 0; + virtual GSM::ChannelType chtype() const =0; + bool isSDCCH() const; + bool isTCHF() const; + bool isReleased() const { return mChState == chRequestRelease || mChState == chRequestHardRelease; } + + + // Return the L2 info given the L3LogicalChannel. + GSM::L2LogicalChannel *getL2Channel(); // This method will not work under UMTS. + const GSM::L2LogicalChannel *getL2Channel() const; // Stupid language. + L3LogicalChannel* getSACCHL3(); + + MMContext *chanGetContext(bool create); + std::string chanGetImsi(bool verbose) const; // If the IMSI is known, return it, else string("") or if verbose, something to display in error messages and CLI. + //void chanSetContext(MMContext* wTranSet); + void chanFreeContext(); + void reassignComplete(); + void reassignFailure(); + void reassignStart(); + bool reassignAllocNextTCH(); + + //MMUser *getMMC() { return mMMC; } + //void setMMC(MMUser *mmc) { mMMC = mmc; } + //void chanLost(); + // Send a channel release message, then release it. + void chanClose(GSM::L3RRCause cause,GSM::Primitive prim); // prim is RELEASE or HARDRELEASE + // Request an immediate channel release. + // Dont use HARDRELEASE if you can avoid it - only used when the channel is already completely cleared. + void chanRelease(Primitive prim); + + RefCntPointer chanGetVoiceTran(); + //void chanSetVoiceTran(TranEntry *trans); + void chanEnqueueFrame(L3Frame *frame); + + /** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */ + GSM::L3Frame* waitForEstablishOrHandover(); + + void L3LogicalChannelReset(); + void L3LogicalChannelInit(); + L3LogicalChannel() { L3LogicalChannelInit(); } + // In GSM the L3LogicalChannel is attached to the various DCCH at startup and is immortal. + // In UMTS there is one LogicalChannel per UE, destroyed when we lose contact with the UE. + void getTranIds(TranEntryList &tids) const; + ~L3LogicalChannel(); + std::ostream& chanText(std::ostream&os) const; + std::ostream& chanContextText(std::ostream&os) const; +}; + +extern void printChansInfo(std::ostream&os); + + +std::ostream& operator<<(std::ostream&, const L3LogicalChannel&); +std::ostream& operator<<(std::ostream&os, const L3LogicalChannel*ch); +}; +#endif diff --git a/Control/L3MMLayer.cpp b/Control/L3MMLayer.cpp new file mode 100644 index 0000000..440fd43 --- /dev/null +++ b/Control/L3MMLayer.cpp @@ -0,0 +1,1195 @@ +/* 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 distribuion. +* +* 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. +*/ + +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging + +#include "L3MMLayer.h" +#include "L3MobilityManagement.h" +#include +#include "L3CallControl.h" +#include // Needed for L3LogicalChannel +#include // Needed for L3LogicalChannel +#include +#include +#include +#include +#include "ControlCommon.h" +#include "L3TranEntry.h" +#include "L3SMSControl.h" +#include + +namespace Control { +using namespace GSM; +using namespace SIP; + +MMLayer gMMLayer; +Mutex gMMLock; // This is a global lock for adding/removing MMContext and MMUser and hooking them together. + // This global lock does not prevent internal modification of an MMContext/MMUser. + // The global lock must not be held when executing any state machines. + // See more comments at MMContext. + +// What is to prevent an MS from allocating multiple channels, eg, a TCH and SDCCH simultaneously? I think nothing. + +// Procedure State Machines: +// assignTCHF +// on success (have TCH), if MTCq, start it, else (they hung up) if SMSq run switchToSDDCH, else run closeChannel. +// switchToSDCCH +// on success (have SDCCH), if MTCq, run assignTCHF, else if SMSq start it, else run closeChannel. +// Add new transaction: +// If existing MM procedure on TMSI or IMSI, block. +// If SMS and existing SMS, queue. + +// On end of any transaction (MM or CS or SMS): +// On end of CS transaction: +// If MTCq, run that, else if MTSMSq run switchToSDCCH, else run closeChannel. +// On end of SMS transaction: +// If MTSMSq, run that. +// On end of MM transaction: +// If MTCq, start assignTCH. +// On assignTCH success: +// if MTCq, run it, else, goto ? + + +//void MMUser::mmuCleanupDialogs() { } + +void MMContext::startSMSTran(TranEntry *tran) +{ + { + ScopedLock lock(gMMLock,__FILE__,__LINE__); // Make sure. + LOG(INFO) << "new MTSMS"<mmGetTran(MMContext::TE_MTSMS).isNULL()); + this->mmConnectTran(MMContext::TE_MTSMS,tran); + + initMTSMS(tran); + } + tran->lockAndStart(); +} + +// (pat) WARNING: If this routine returns true it has performed the gMMLock.unlock() corresponding to a lock() in the caller. +bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel. +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + //ScopedLock lock(mmuLock,__FILE__,__LINE__); + // TODO: check for blocks on our IMSI or TMSI? + + // TODO: Move this to the logical channel main thread. + // Service the MMC queues. + if (mmuContext->mmGetTran(MMContext::TE_CS1).isNULL()) { + if (mmuMTCq.size()) { + TranEntry *tran = mmuMTCq.pop_frontr(); + LOG(INFO) << "new MTC"<mmConnectTran(MMContext::TE_CS1,tran); + + // Did the SIP session give up while we were waiting? + // That will be handled in the MTCMachine. + initMTC(tran); + gMMLock.unlock(); + tran->lockAndStart(); + return true; + } + } + if (mmuContext->mmGetTran(MMContext::TE_MTSMS).isNULL()) { + if (mmuMTSMSq.size()) { + TranEntry *tran = mmuMTSMSq.pop_frontr(); + gMMLock.unlock(); + mmuContext->startSMSTran(tran); + return true; + } + } + return false; +} + +bool MMUser::mmuIsEmpty() +{ + ScopedLock lock(mmuLock,__FILE__,__LINE__); + return mmuMTCq.size() + mmuMTSMSq.size() == 0; +} + +bool MMContext::mmIsEmpty() +{ + //devassert(gMMLock.lockcnt()); // Caller locked it. + ScopedLock lock(gMMLock,__FILE__,__LINE__); + for (unsigned i = 0; i < TE_num; i++) { + if (mmcTE[i] != NULL) { return false; } + } + return mmcMMU ? mmcMMU->mmuIsEmpty() : true; +} + +// Return the Mobility Management state. Defined in 24.008 section 4. +// Except the only thing we really care about is whether any MM procedure is currently running, which is boolean. +bool MMContext::mmInMobilityManagement() +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // (pat) I dont think this lock is necessary because we use RefCntPointer now. + return ! mmGetTran(MMContext::TE_MM).isNULL(); +} + +// See if there are any new transactions to start. +// If all transactions are gone, initiate a channel release. +// Return true if anything happened. +bool MMContext::mmCheckNewActivity() +{ + // If there is a mobility management procedure in progress then we wont start anything else until it is finished. + // We shouldnt need to lock gMMLock yet because the MMContext cannot be deleted while in the thread that called us, + // and mmcServiceRequests is a thread safe queue. + if (! mmInMobilityManagement()) { + if (const L3Message *l3msg = mmcServiceRequests.readNoBlock()) { + const L3CMServiceRequest *cmmsg = dynamic_cast(l3msg); + NewCMServiceResponder(cmmsg,this); + delete cmmsg; + return true; + } + // We are refererencing the MMUser so we cannot let that change and the only + // completely safe way to do that is to lock the entire MMLayer. + // The unlock() corresponding to this lock() may be in mmuServiceMTQueues. + gMMLock.lock(__FILE__,__LINE__); + if (mmcMMU) { + if (mmcMMU->mmuServiceMTQueues()) { return true; } + } + gMMLock.unlock(); + } + // If there are no transactions, kill the channel. + // TODO: A new SIP transaction could creep in here between the time + // we check isEmpty and when the channel actually closes. + // What to do about that? + // When we detach the MMUser, if it has anything on it, just leave it there, + // and paging will restart. + if (mmIsEmpty()) { + mmcChan->chanClose(L3RRCause::NormalEvent,RELEASE); + return true; // This is new activity - the calling loop should skip back to the top + } + return false; +} + +// What a stupid language. +MMUser::MMUser(string& wImsi) +{ + MMUserInit(); + mmuImsi = wImsi; + LOG(DEBUG) << "MMUser ALLOC "<<(void*)this; +} + +//MMUser::MMUser(string& wImsi, TMSI_t wTmsi) +//{ +// MMUserInit(); +// mmuImsi = wImsi; +// mmuTmsi = wTmsi; +// LOG(DEBUG) << "MMUser ALLOC "<<(void*)this; +//} + +// Caller enters with the whole MMLayer locked so no one will try to add new contexts while we are doing this. +void MMUser::mmuFree(MMUserMap::iterator *piter, CancelCause cause) // Some callers deleted it from the MMUsers more efficiently than looking it up again. +{ + devassert(mmuContext == NULL); // Caller already unlinked or verified that it was unattached. + devassert(gMMLock.lockcnt()); // Caller locked it. + + // mmuCleanupDialogs(); + + { + ScopedLock lock(mmuLock,__FILE__,__LINE__); // Now redundant. + LOG(DEBUG) << "MMUser DELETE "<<(void*)this <teCancel(cause); delete tran; } + while (TranEntry *tran = mmuMTSMSq.pop_frontr()) { tran->teCancel(cause); delete tran; } + + if (piter) { // It is just an efficiency issue to use the iterator if we already have one. + gMMLayer.MMUsers.erase(*piter); + } else { + LOG(DEBUG) << "MMUser erase begin " << this->mmuImsi; + bool exists = gMMLayer.MMUsers.find(this->mmuImsi) != gMMLayer.MMUsers.end(); + LOG(DEBUG) << "MMUser erase "<mmuImsi<mmuImsi); + } + assert(gMMLayer.MMUsers.find(this->mmuImsi) == gMMLayer.MMUsers.end()); + } + // The ScopedLock points into MMUser so we must release it before deleting this. + delete this; +} + + +bool MMContext::mmCheckSipMsgs() +{ + // Update: We cannot hold the global lock while invoking state machines because they can block. + // As an interim measure, just dont lock this and hope for the best. + //ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + bool result = false; + for (unsigned i = TE_first; i < TE_num; i++) { + RefCntPointer tranp = mmGetTran(i); + if (! tranp.isNULL()) { result |= tranp->lockAndInvokeSipMsgs(); } + } + return result; +} + +bool MMContext::mmCheckTimers() +{ + // Update: We cannot hold the global lock while invoking state machines because they can block. + // As an interim measure, just dont lock this and hope for the best. + //ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary; the channel cannot change when this is called, but be safe. + // Check MM timers. + + // checkTimers locks the transaction if any timer needs servicing. + bool result = false; + for (unsigned i = TE_first; i < TE_num; i++) { + RefCntPointer tranp = mmGetTran(i); + if (! tranp.isNULL()) { result |= tranp->checkTimers(); } + } + return result; +} + + +// This is the first L3 message on the new channel. +// The Context for this channel is empty. +// All the TranEntrys are still in the Context on the old channel. +//void MMContext::reassignComplete() +//{ +// +// //case L3RRCASE(AssignmentComplete): +// // TODO: what timer? timeout1Cancel(); +// //timerStop(TChReassignment); // Handled by assignTCHFProcedure, which is notified after us. +// LOG(INFO) << "successful assignment"; +// +// // The two channels are serviced by different threads. +// // TODO: We need to lock the other channels thread. +// +// // Move all the transactions to the new channel: +//#if UNUSED +// MMContext *prevSet = mPrevChan->getContext(); +// for (unsigned i = 0; i < TE_num; i++) { +// devassert(mmcTE[i] == NULL); +// mmcTE[i] = prevSet->mmcTE[i]; +// prevSet->mmcTE[i] = NULL; +// if (mmcTE[i]) { mmcTE[i]->mContext = this; } +// } +//#endif +// +// // release the old channel. +// // The old SDCCH channel will be released when the l2recv finishes and the channel notices that its state has changed. +// // mPrevChan->l3sendp(GSM::HARDRELEASE); Dont do this. It can block. Set the chReassignComplete flag and let that thread do it. +// mPrevChan->chanSetState(L3LogicalChannel::chReassignComplete); +// +// // We are going to delete this. So get everything we want out of it first. +// +// // Just move the MMContext prevChan to this one. +// //mChan->freeContext(); // It is not being used, but we are running in it! +// //mChan->mContext = mPrevChan->mTranSet; +// //mPrevChan->mContext = NULL; +// +// mPrevChan->chanMoveTo(this->mChan); // Careful! Deletes this as a side effect. +// +// // Clear everything. This is overkill because some of these are already 0. +// prevSet->mNextChan = prevSet->mPrevChan = 0; +// this->mNextChan = this->mPrevChan = 0; +// +// //tran()->setChannel(tran()->mNextChannel); +// //tran()->mNextChan = NULL; +// //return callProcStart(new MOCConnect(tran())); Now it could be for MTC too. +//} + +// The significant bits of the L3TI. The fourth bit is a direction indicator and we ignore it. +//static int l3TISigBits(int val) { return val & 7; } + +#if UNUSED +// // Find the transaction that wants this frame/message. +// TranEntry *MMContext::findTran(L3PD pd, unsigned ti, int mti) +// { +// devassert(gMMLock.lockcnt()); // Caller locked it. +// TranEntry *tran = NULL; +// switch (pd) { +// case L3CallControlPD: { +// // Setup message is special because it is the message that establishes the TI correspondence. +// bool isSetup = (mti == L3CCMessage::Setup); +// TranEntry *cs = mmcTE[TE_CS1]; +// if (cs && (isSetup || l3TISigBits(cs->getL3TI()) == l3TISigBits(ti))) { +// return cs; +// } +// break; +// } +// case L3SMSPD: { +// for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) { +// TranEntry *sms = mmcTE[tx]; +// if (sms && l3TISigBits(sms->getL3TI()) == l3TISigBits(ti)) { +// tran = sms; +// break; +// } +// } +// // For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives. +// // So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best. +// if (tran == NULL) { tran = mmcTE[TE_MOSMS1]; } +// break; +// } +// case L3RadioResourcePD: +// #if 0 // Now both channels share the MMContext so we just send it normally. +// if (l3msg->MTI() == L3RRMessage::AssignmentComplete) { +// // We have to notify the Procedure when complete, however when rmsimsieassignComplete returns +// // we have replaced the MMContext on this channel with the one from the old channel, +// // and 'this' has been deleted. So we pass the tran that needs to be notified to reassignComplete +// // to actually do it, and we have to return from here without touching the data again. +// if (! postReassignment) { +// L3LogicalChannel *chan = this->tsChannel(); +// chan->reassignComplete(); +// // Be careful! 'this' is now invalid! That is why we cached channel(). +// // Redispatch the message on the now-reassigned channel. +// return chan->chanGetContext()->mmDispatchL3Msg(l3msg,true); +// } +// // else fall through +// } +// #endif +// // Fall through for all other RR messages. +// case L3MobilityManagementPD: +// // TODO: This is a hack. We should split the Procedures into MM and CS parts, and +// // run the MM procedure first to identify the channel, then send a message to the CS procedure to start it. +// tran = mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS]; +// break; +// default: +// LOG(ERR) << "unrecognized L3 frame:"< MMContext::findTran(const L3Frame *frame, const L3Message *l3msg) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); //FIXMENOW + GSM::L3PD pd; int ti; + // Handle naked primitives. + if (frame && !frame->isData()) { + // Theoretically primitives should go to all transactions, but in reality the only state + // machine that wants to receive primitives is MT-SMS, so we will just triage the primitives here + // by returning that transaction, if any. + return mmcTE[TE_MTSMS].self(); // We dont need to keep this locked because the tran is used only internally, so can use self and return TranEntry* + } + if (frame && frame->isData()) { + pd = frame->PD(); + ti = frame->TI(); // Only call control, SMS and SS frames have a useful ti. + } else if (l3msg) { + pd = l3msg->PD(); + ti = l3msg->TI(); // Only call control, SMS and SS frames have a useful ti; returns nonsense for other PDs. + } else { + return (TranEntry*)NULL; // Shouldnt happen, but be safe. + } + switch (pd) { + case L3CallControlPD: { + // Setup message is special because it is the message that establishes the TI correspondence. + // Dont need to bother checking l3msg because we dont send naked setup messages. + int mti = frame ? frame->MTI() : l3msg->MTI(); + bool isSetup = (mti == L3CCMessage::Setup); + TranEntry *cs = mmcTE[TE_CS1].self(); + if (cs && (isSetup || cs->matchL3TI(ti,true))) { + return cs; + } + break; + } + case L3SMSPD: { + for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) { + TranEntry *sms = mmcTE[tx].self(); + if (sms && sms->matchL3TI(ti,true)) { + return sms; + } + } + // For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives. + // So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best. + if (TranEntry *te = mmcTE[TE_MOSMS1].self()) { return te; } + break; + } + case L3RadioResourcePD: + // Fall through for all other RR messages. + case L3MobilityManagementPD: + // TODO: This is a hack. We should split the Procedures into MM and CS parts, and + // run the MM procedure first to identify the channel, then send a message to the CS procedure to start it. + //return mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS]; + for (unsigned txi = TE_MM; txi < TE_num; txi++) { + if (TranEntry *te = mmcTE[txi].self()) { return te; } + } + break; + case L3NonCallSSPD: { + // The transaction identifier is used to identify whether the SS message applies to a specific + // call or is outside any call, ie, was started by a CM service request. + // I am not sure what to do about in-call SS: we could pass the USSD SIP INFO message in + // the dialog of the call, but asterisk is going to just dump it. + for (ActiveTranIndex ati = TE_CS1; ati <= TE_CSHold; ati = (ActiveTranIndex) (ati + 1)) { + TranEntry *cs = mmcTE[ati].self(); + if (cs && cs->matchL3TI(ti,true)) { + WATCHINFO("Found SS message matching CC transaction" <getL3TI())); + return cs; + } + } + TranEntry *te = mmcTE[TE_SS].self(); + if (te) { + // Dont even bother to check the tran id. + // If it is a SSRegister message, it would be defining the tran id. + WATCHINFO("Sending SS message to SS machine"<getL3TI())); + } else { + WATCH("Ignoring SS message with no transaction "<MTI()); + return (TranEntry*)NULL; +} + + +#if UNUSED +// Find the transaction that wants to handle this message and invoke it. +// Return true if the message was handled. +//bool MMContext::mmDispatchL3Msg(const L3Message *l3msg, bool postReassignment) +bool MMContext::mmDispatchL3Msg(const L3Message *l3msg, L3LogicalChannel *chan) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + GSM::L3PD pd = l3msg->PD(); + LOG(DEBUG) << LOGVAR(pd) << this; + int ti = 0, mti = 0; + if (pd == L3CallControlPD || pd == L3SMSPD) { + ti = l3msg->TI(); + mti = l3msg->MTI(); + } + RefCntPointer tran = findTran(pd,ti,mti); + + //TranEntry *tran = NULL; + ////GSM::L3CMServiceType service = tran->service(); + //switch (pd) { + // case L3CallControlPD: { + // //bool isSetup = (pd == L3CallControlPD) && l3msg->MTI() == L3CCMessage::Setup; + // //msgti = dynamic_cast(l3msg)->TI(); + // // Setup message is special because it is the messages that establishes the TI correspondence. + // TranEntry *cs = mmcTE[TE_CS1]; + // if (cs) LOG(DEBUG) << cs <TI()); + // if (cs && (l3msg->MTI() == L3CCMessage::Setup || l3TISigBits(cs->getL3TI()) == l3TISigBits(l3msg->TI()))) { + // tran = cs; + // } + // break; + // } + // case L3SMSPD: { + // //msgti = dynamic_cast(l3msg)->TI(); + // for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) { + // TranEntry *sms = mmcTE[tx]; + // if (sms) { + // LOG(DEBUG) <getL3TI())<TI()); + // } + // if (sms && l3TISigBits(sms->getL3TI()) == l3TISigBits(l3msg->TI())) { + // tran = sms; + // break; + // } + // // For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives. + // // So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best. + // if (tran == NULL) { tran = mmcTE[TE_MOSMS1]; } + // } + // break; + // } + // case L3RadioResourcePD: + //#if 0 // Now both channels share the MMContext so we just send it normally. + // if (l3msg->MTI() == L3RRMessage::AssignmentComplete) { + // // We have to notify the Procedure when complete, however when rmsimsieassignComplete returns + // // we have replaced the MMContext on this channel with the one from the old channel, + // // and 'this' has been deleted. So we pass the tran that needs to be notified to reassignComplete + // // to actually do it, and we have to return from here without touching the data again. + // if (! postReassignment) { + // L3LogicalChannel *chan = this->tsChannel(); + // chan->reassignComplete(); + // // Be careful! 'this' is now invalid! That is why we cached channel(). + // // Redispatch the message on the now-reassigned channel. + // return chan->chanGetContext()->mmDispatchL3Msg(l3msg,true); + // } + // // else fall through + // } + //#endif + // // Fall through for all other RR messages. + // case L3MobilityManagementPD: + // // TODO: This is a hack. We should split the Procedures into MM and CS parts, and + // // run the MM procedure first to identify the channel, then send a message to the CS procedure to start it. + // tran = mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS]; + // break; + // default: + // LOG(ERR) << "unrecognized L3"<deadOrRemoved()) { + LOG(DEBUG) << tran; + // We pass chan, not mmcChan. They are != only during channel reassignment. + return tran->lockAndInvokeL3Msg(l3msg); + } + return false; +} +#endif + +// Arguments are the L3 frame and if the frame is non-primitive the result of parsel3(frame). +// l3msg may be NULL for primitives or unparseable messages. +// Frame may be NULL when we send a naked message. +bool MMContext::mmDispatchL3Frame(const L3Frame *frame, const L3Message *msg) +{ + // Update: We cannot hold the global lock while invoking state machines because they can block. + // As an interim measure, just dont lock this and hope for the best. + //ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + // Does any transaction want this frame/message? + RefCntPointer tran = findTran(frame,msg); + LOG(DEBUG) << *frame << tran.self(); + if (tran == (TranEntry*)NULL) { return false; } + + if (tran->deadOrRemoved()) { + if (msg) { + LOG(INFO) <<"Received message for expired transaction. "<<*msg; + } else { + LOG(INFO) <<"Received unparseable frame for expired transaction. "<<*frame; + } + return false; + } + return tran->lockAndInvokeFrame(frame,msg); +} + + +void MMContext::mmcPageReceived() const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + // TODO: We should do a single authentication, if necessary, before starting both MTC and MTSMS + + RefCntPointer tran1 = mmGetTran(MMContext::TE_CS1); + if (! tran1.isNULL()) { + // The MS sent a page response when there is an active voice call? Either that or we are totally goofed up. + LOG(ERR) < tran2 = mmGetTran(MMContext::TE_MTSMS); + if (! tran2.isNULL()) { + LOG(ERR) <= 7) { mNextTI = 0; } + return mNextTI; +} + +#if UNUSED +bool MMLayer::mmStartMTDialog(SipDialog *dialog, SipMessage *invite) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + +#if UNUSED + // Find any active transaction for this IMSI with an assigned TCH or SDCCH. + L3LogicalChannel *chan = gTransactionTable.findChannel(mobileID); + if (chan) { + // If the type is TCH and the service is SMS, get the SACCH. + // Otherwise, for now, just say chan=NULL. + if (serviceType==L3CMServiceType::MobileTerminatedShortMessage && chan->chtype()==FACCHType) { + chan = chan->getL2Channel()->SACCH(); // GSM Specific. + } else { + // FIXME -- This will change to support multiple transactions. + // (pat) Yes. For voice calls we need to initiate a call-waiting notification. + // For SMS we need to add to the SMS queue. + chan = NULL; + } + } +#endif + + TODO: Add this isBusy check + + // So we will need a new channel. + // Check gBTS for channel availability. + if (!chan && !channelAvailable) { + LOG(CRIT) << "MTC CONGESTION, no channel availble"; + // FIXME -- We need the retry-after header. + //newSendEarlyError(msg,proxy.c_str(),503,"Service Unvailable"); + dialog->sendError(503,"Service Unavailable"); + return; + } + if (chan) { LOG(INFO) << "using existing channel " << chan->descriptiveString(); } + else { LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; } + + // Check for new user busy condition. + if (!chan && gTransactionTable.isBusy(mobileID)) { + LOG(NOTICE) << "user busy: " << mobileID; + //newSendEarlyError(msg,proxy.c_str(),486,"Busy Here"); + dialog->sendError(503,"Service Unavailable"); + dialog->detach(); + return; + } + + return true; +} +#endif + +void MMUser::mmuAddMT(TranEntry *tran) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill. + //ScopedLock lock(mmuLock,__FILE__,__LINE__); + mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113")); + switch (tran->servicetype()) { + case L3CMServiceType::TestCall: + case L3CMServiceType::FuzzCallTch: + case L3CMServiceType::MobileTerminatedCall: + mmuMTCq.push_back(tran); + break; + case L3CMServiceType::FuzzCallSdcch: + case L3CMServiceType::MobileTerminatedShortMessage: + mmuMTSMSq.push_back(tran); + break; + default: + assert(0); + } +} + +void MMUser::mmuText(std::ostream&os) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // way overkill + //ScopedLock lock(mmuLock,__FILE__,__LINE__); + os << " MMUser("; + os <tsChannel(); + } else { + os << " channel:(unattached)"; + } + os << " queued transactions:"; + //os <tranID(); + } + for (MMUQueue_t::const_iterator it = mmuMTSMSq.begin(); it != mmuMTSMSq.end(); ++it) { + const TranEntry *tran = *it; + os << " SMS:" << tran->tranID(); + } + os << ")"; +} +string MMUser::mmuText() const { std::ostringstream ss; mmuText(ss); return ss.str(); } +std::ostream& operator<<(std::ostream& os, const MMUser&mmu) { mmu.mmuText(os); return os; } +std::ostream& operator<<(std::ostream& os, const MMUser*mmu) { if (mmu) mmu->mmuText(os); else os << "(null MMUser)"; return os; } + +void MMContext::MMContextInit() +{ + // The BLU phone seems to have a bug that a new MTC beginning too soon after a previous MTC with the same TI + // seems to hang the phone, even though we definitely went through the CC release procedure whose specific + // purpose is to release the TI for recycling. Making the initial TI random seems to help. + mNextTI = rand() & 0x7; // Not supposed to matter what we pick here. + mmcMMU = NULL; + mmcChan = NULL; + mmcChannelUseCnt = 1; + mmcFuzzPort = NULL; + //mVoiceTrans = NULL; + memset(mmcTE,0,sizeof(mmcTE)); + LOG(DEBUG)<<"MMContext ALLOC "<<(void*)this; +} + +// Called only from L3LogicalChannel::chanGetContext() +MMContext::MMContext(L3LogicalChannel *wChan) +{ + MMContextInit(); + mmcChan = wChan; +} + +MMContext *MMContext::tsDup() +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + mmcChannelUseCnt++; // There are now two channels referring to the same MMContext. + LOG(DEBUG) << *this; + return this; +} + +string MMContext::mmGetImsi(bool verbose) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + return mmcMMU ? mmcMMU->mmuGetImsi(verbose) : (verbose ? string("no-MMUser") : string("")); +} + +void MMContext::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/) +{ + WATCHINFO("sendm "<l2sendm(msg,prim,SAPI); +} + +void MMContext::mmcText(std::ostream&os) const +{ + // Called from CLI so we need to lock the MMContext, and the only way we can do that is the global lock. + ScopedLock lock(gMMLock,__FILE__,__LINE__); // way overkill. + os << " MMContext("; + os <mmcText(os); else os << "(null Context)"; return os; } + + +void MMContext::getTranIds(TranEntryList &tranlist) const +{ + // This is called from the CLI via L3LogicalChannel, which locks the L3LogicalChannel, guaranteeing + // that the MMContext is not freed while we are here. We need to lock this particular MMContext + // to avoid changes to mmcTE, but we dont have a private lock in each MMContext, only the global lock, + // so we lock that, even though it is way overkill. + ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill, but necessary to lock MMContext. + tranlist.clear(); + for (unsigned ati = TE_first; ati < TE_num; ati++) { + if (mmcTE[ati] != NULL) { tranlist.push_back(mmcTE[ati]->tranID()); } + } + if (mmcMMU) { + // TODO + } +} + +RefCntPointer MMContext::mmGetTran(unsigned ati) const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // overkill to lock the world but its the lock we have. + assert(ati < TE_num); + return mmcTE[ati]; +} + +// Connect the Transaction to this channel. Sets pointers in both directions. +// After this, the RefCntPointer in mmcTE takes over the job of deleting the transaction when the last pointer to it disappears. +void MMContext::mmConnectTran(ActiveTranIndex ati, TranEntry *tran) +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + // When a primary transaction is deleted we may promote the secondary transaction, so make sure we delete them all: + for (unsigned tries = 0; tries < 3; tries++) { + if (mmcTE[ati] != NULL) { + LOG(ERR) << "Transaction over-writing existing transaction" + <teCancel(); + } + } + mmcTE[ati] = tran; + tran->teSetContext(this); + +} + +// Connect the Transaction to this channel. Sets pointers in both directions. +void MMContext::mmConnectTran(TranEntry *tran) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + ActiveTranIndex txi; + switch (tran->servicetype()) { + case L3CMServiceType::MobileTerminatedCall: + case L3CMServiceType::MobileOriginatedCall: + case L3CMServiceType::EmergencyCall: + case L3CMServiceType::HandoverCall: + case L3CMServiceType::TestCall: + case L3CMServiceType::FuzzCallTch: + txi = TE_CS1; + break; + case L3CMServiceType::ShortMessage: // specifically, MO-SMS + txi = mmcTE[TE_MOSMS1]!=NULL ? TE_MOSMS2 : TE_MOSMS1; + break; + case L3CMServiceType::FuzzCallSdcch: + case L3CMServiceType::MobileTerminatedShortMessage: + txi = TE_MTSMS; + break; + case L3CMServiceType::LocationUpdateRequest: + txi = TE_MM; + break; + + case L3CMServiceType::SupplementaryService: + WATCHINFO("connect tran for SS"); + txi = TE_SS; + break; + //VoiceCallGroup=9, + //VoiceBroadcast=10, + //LocationService=11, // (pat) See GSM 04.71. Has nothing to do with MM Location Update. + default: + assert(0); + } + mmConnectTran(txi,tran); +} + + +// This is called only from TranEntry which is already in the process of deleting the transaction. +void MMContext::mmDisconnectTran(TranEntry *tran) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe. + for (unsigned tx = 0; tx < TE_num; tx++) { + if (mmcTE[tx] == tran) { + LOG(DEBUG) << "found "<tranID()<<" not found in MMContext"; +} + +// Does nothing if already unlinked. +void MMContext::mmcUnlink() +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + MMContext *mmc = this; + MMUser *mmu = mmc->mmcMMU; + // Detach MMUser from MMContext: + if (mmu) { + assert(mmu->mmuContext == mmc); + assert(mmc->mmcMMU == mmu); + mmc->mmcMMU = NULL; // old comment: Deletes its RefCntPointer and may delete it. + mmu->mmuContext = NULL; + } +} + +void MMContext::mmcLink(MMUser *mmu) +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + // Detach the mmu from its existing channel, if any. That happens when the MS disappeared temporarily + // and then came back on another channel, for example, handover to another BTS and back. + //RefCntPointer saveme(mmu); // Dont delete mmu during this procedure. + MMContext *mmc = this; + if (mmc->mmcMMU == mmu) { + // Already connected. + devassert(mmu->mmuContext == mmc); // We always maintain pointers both ways. + return; + } + if (mmu->mmuContext) { mmu->mmuContext->mmcUnlink(); } + mmc->mmcUnlink(); + mmc->mmcMMU = mmu; + mmu->mmuContext = mmc; +} + + +void MMContext::mmcFree() +{ + assert(this->mmcMMU == NULL); + devassert(gMMLock.lockcnt()); // Caller locked it. + if (mmcFuzzPort) { + mmcFuzzPort->close(); + mmcFuzzPort = NULL; + } + + // Cancel all the enclosed transactions and their dialogs. + for (unsigned i = 0; i < TE_num; i++) { + // When a primary transaction is deleted we may promote the secondary transaction, so delete them all: + for (unsigned tries = 0; tries < 3; tries++) { + if (mmcTE[i] != NULL) { mmcTE[i]->teCancel(); } // Removes the transaction from mmcTE via mmDisconnectTran + } + assert(mmcTE[i] == NULL); // teCancel removed it. + } + LOG(DEBUG)<<"MMContext DELETE "<<(void*)this; + delete this; +} + +// The logical channel no longer points to this Context, so release it. +void MMLayer::mmFreeContext(MMContext *mmc) +{ + // There can be multiple logical channels pointing to the same Context, so decrement + // the channel use count and delete only when 0. + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG) << mmc; + if (--mmc->mmcChannelUseCnt > 0) return; + + MMUser *mmu = mmc->mmcMMU; + mmc->mmcUnlink(); // resets mmcMMU + + // This channel was closed normally, or because of channel loss (for example, reassign failure) or an internal error. + // In the latter cases there could be running SipDialogs on the channel and we have to tell them something. + // The SipCode 408 allows the peer to retry immediately. + + // Paul at Null Team says: + // 408 is reserved for SIP protocol timeouts (no answer to SIP message) + // 504 indicates some other timeout beyond SIP (interworking) + // 480 indicates some temporary form of resource unavailability or congestion but resource is accessible and can be checked + // 503 indicates the service is unavailable but does not imply for how long + SipCode sipcode(480,"Temporarily Unavailable"); + + if (mmu) { + // It is possible for new SIP dialogs to have started between the time we decided + // to close this channel and now. It is also possible that we closed the channel + // because of loss of contact with the MS. In either case, if the MMU has dialogs, + // dont delete it - just leave it alone and we will start repaging this MS again. + // TODO: But first, walk though dialogs and cancel any that need it. + if (mmu->mmuIsEmpty()) { + mmu->mmuFree(NULL,CancelCauseUnknown); // CancelCause is not used because it is empty. + } + } + mmc->mmcFree(); +} + +void MMLayer::mmMTRepage(const string imsi) +{ + // Renew the page timer. + LOG(DEBUG) <mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113")); + } else { + LOG(DEBUG) << "repeated INVITE/MESSAGE with no MMUser record"; + } +} + + +// Called when we have positively identified the MS associated with chan, so now we +// want to connect the channel to its MMUser. +void MMLayer::mmAttachByImsi(L3LogicalChannel *chan, string imsi) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + // Find or create the MMUser. + WATCHINFO("attachMMC" <chanGetContext(true); + // They are linked together from now on. + mmc->mmcLink(mmu); + LOG(DEBUG); + + // TODO: The MM procedure may have blocked tmsis and imsis. + // So now we want to unblock any previously blocked imsi/tmsi +} + +// This is the way MMUsers are created from the SIP side. +void MMLayer::mmAddMT(TranEntry *tran) +{ + LOG(DEBUG) <subscriberIMSI()); + MMUser *mmu = mmFindByImsi(imsi,true); + // Is there a guaranteed tmsi? + // We will delay this until we page in case an LUR is occurring right now. + //if (uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(imsi,true)) { mmu->mmuTmsi = /*tran->subscriber().mTmsi =*/ tmsi; } + assert(mmu); + mmu->mmuAddMT(tran); + } + mmPageSignal.signal(); +} + +MMUser *MMLayer::mmFindByImsi(string imsi, // Do not change this to a reference. We need a copy of the string + // to insert into the map. If pass by reference here the map points to the string from the caller, + // which may have long since gone out of scope. What a great language. + bool create) +{ + LOG(DEBUG) < but we only want the iterator. + //pair result = MMUsers.insert(pair(imsi,(MMUser*)NULL)); + bool exists = MMUsers.find(imsi) != MMUsers.end(); + LOG(DEBUG) << LOGVAR(imsi)<(imsi,(MMUser*)NULL)).first; + result = it->second; + if (result == NULL) { + result = it->second = new MMUser(imsi); + LOG(DEBUG) << "inserting new MMUser "<<(void*)result; + what = "new "; + } + LOG(DEBUG) << "MMUsers["<second; + } + LOG(DEBUG) <second; + if (mmu->mmuTmsi.valid() && mmu->mmuTmsi.value() == tmsi) { result = mmu; break; } + } + LOG(DEBUG) << LOGVAR(result); + return result; +} + +MMUser *MMLayer::mmFindByMobileId(L3MobileIdentity&mid) +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + if (mid.isIMSI()) { + string imsi = mid.digits(); + return mmFindByImsi(imsi,false); + } else { + assert(mid.isTMSI()); + return mmFindByTmsi(mid.TMSI()); + } +} + +// If wait flag, block until there are some, called forever from the paging thread. +// When called from the paging thread loop this function is responsible for noticing expired pages and deleting them. +void MMLayer::mmGetPages(NewPagingList_t &pages, bool wait) +{ + LOG(DEBUG); + ScopedLock lock(gMMLock,__FILE__,__LINE__); + + assert(pages.size() == 0); // Caller passes us a new list each time. + + LOG(DEBUG) <second; + MMUserMap::iterator thisone = it++; + LOG(DEBUG)<mmuIsAttached()) { // Is it already attached to a radio channel? + LOG(DEBUG) << "MMUser already attached:"<mmuImsi; + continue; + } + if (mmu->mmuPageTimer.passed()) { + // Expired. Get rid of it. + LOG(INFO) << "Page expired for imsi="<mmuImsi; + // Erasing from a map invalidates the iterator, but not the iteration. + //MMUsers.erase(thisone); + // (pat) The SIP error for no page should probably not be 480 Temporarily Unavailable, + // because that implies we know that the user is at the BTS, but if it did not answer the page, we do not. + // Paul at Null Team recommended 504 + mmu->mmuFree(&thisone,CancelCauseNoAnswerToPage); + continue; + } + // TODO: We could add a check for a "provisional IMSI" + + bool wantCS = mmu->mmuMTCq.size() > 0; // Is it a CS transaction, as opposed to SMS? + if (! mmu->mmuTmsi.valid()) { + uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mmu->mmuImsi,true); + LOG(DEBUG)<<"tmsiTabGetTMSI imsi="<mmuImsi<< "returns"<mmuTmsi = tmsi; } + } + NewPagingEntry tmp(wantCS,mmu->mmuImsi,mmu->mmuTmsi); + WATCH("page "<mmuTmsi)<mmuImsi)); + pages.push_back(tmp); + } + if (pages.size()) { return; } + + // Wait for someone to add a new MMUser. + if (!wait) { return; } + // too many of these... LOG(DEBUG) <<"waiting for new MMUser signal"; + // We need to provide a timeout so that we free expired pages in a timely manner; if we dont + // then that MMUser is essentially locked because incoming SIP invites get a busy return, and it + // wont get released until some other unrelated page intervenes. + mmPageSignal.wait(gMMLock,500); + // Need a while loop here because the wait does not guarantee it was signalled. + } +} + +// For use by the CLI: create a copy of the paging list and print it. +void MMLayer::printPages(ostream &os) +{ + // This does not need to lock anything. The mmGetPages provides locked access to the MMUser list. + NewPagingList_t pages; + gMMLayer.mmGetPages(pages,false); + for (NewPagingList_t::iterator it = pages.begin(); it != pages.end(); ++it) { + NewPagingEntry &pe = *it; + os <chanGetContext(true); + mmchan->mmcLink(mmu); + // At this point the MMC cannot be deleted until the L3LogicalChannel is released, + // so we no longer need the MM lock, and we should release it before locking + // the MMUser to avoid deadlock. + } + + // TODO: If there is a channel lock, this might want to use that. + LOG(INFO) << "paging reponse for " << mmu; + mmchan->mmcPageReceived(); // Just prints errors. + return true; +} + +// If unattached flag, print only unattached Contexts, used from CLI. +void MMLayer::printMMUsers(std::ostream&os, bool onlyUnattached) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ++it) { + MMUser *mmu = it->second; + if (onlyUnattached && mmu->mmuIsAttached()) { continue; } + mmu->mmuText(os); + os << endl; + } +} + +void MMLayer::printMMInfo(std::ostream&os) +{ + L2ChanList chans; + gBTS.getChanVector(chans); + ScopedLock lock(gMMLock,__FILE__,__LINE__); + for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) { + L3LogicalChannel *chan = dynamic_cast(*it); + // (pat) When we used a separate mChanLock in the L3LogicalChannel, then deadlock was possible here. + // chanGetContext calls mChanLock, but there could be some + // other thread waiting in a L3LogicalChannel method with mChanLock already locked + // and waiting for the gMMLock, which is locked above. + // I saw this deadlock when two channel assignments happened simultaneously, and printChansV4 + // and printMMInfo tried to run simultaneously. + MMContext *mmc = chan->chanGetContext(false); + if (mmc) { + mmc->mmcText(os); + os << endl; + } + } + printMMUsers(os,false); +} + +string MMLayer::printMMInfo() +{ + ostringstream ss; + printMMInfo(ss); + return ss.str(); +} + + +}; diff --git a/Control/L3MMLayer.h b/Control/L3MMLayer.h new file mode 100644 index 0000000..b7b91d0 --- /dev/null +++ b/Control/L3MMLayer.h @@ -0,0 +1,245 @@ +/* +* 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 distribuion. +* +* 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. + +*/ + +#ifndef _L3MMLAYER_H +#define _L3MMLAYER_H 1 + +#include +#include +#include + + +#include +#include "ControlCommon.h" +#include "L3TranEntry.h" // Needed because InterthreadQueue deletes its elements on destruction. +#include "RadioResource.h" // For Paging +#include "L3Utils.h" +#include + +namespace Control { +class MMLayer; +class MMContext; +typedef std::map MMUserMap; +using namespace GSM; + + +#if 0 +// A list of pointers with methods designed for pointers that return NULL on error. +template +class PtrList : public std::list { + //typedef typename std::list::iterator itr_t; + //typedef typename std::list type_t; + public: + T* pop_front_ptr() { + if (this->empty()) { return NULL; } + T* result = this->front(); + this->pop_front(); + return result; + } +}; +#endif + + + +// This is the per-IMSI data for a subscriber, and a data-cache for data whose primary storage is persistent. +// It is now persistent beyond the life of a single transaction call to save RRLP status. +// Anything that needs to be persistent across reboots or shared via handover needs to be backed up +// to the external TMSI table or to the external subscriber registry. +// TODO: We should check the TMSI table when we create the MMUser +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(MMUser,MemCheckMMUser) +class MMUser : public MemCheckMMUser /*: public RefCntBase*/ { + // This is lock is to add/remove from the Transaction queues, since they are written from a thread in the SIP directory + // and read from the thread running the LogicalChannel. + friend class MMContext; + mutable Mutex mmuLock; + Timeval mmuPageTimer; + protected: + typedef PtrList MMUQueue_t; + MMUQueue_t mmuMTCq; + MMUQueue_t mmuMTSMSq; + friend class MMLayer; + MMState mmuState; + MMContext* mmuContext; + string mmuImsi; // Just the imsi, without "IMSI" + TMSI_t mmuTmsi; + void mmuFree(MMUserMap::iterator *it,CancelCause cause /*= CancelCauseUnknown*/); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the universe first. + + void MMUserInit() { mmuState = MMStateUndefined; mmuContext = NULL; } + public: + MMUser(string& wImsi); + //MMUser(string& wImsi, TMSI_t wTmsi); + + void mmuAddMT(TranEntry *tran); + //void mmuPageReceived(L3LogicalChannel *chan); + string mmuGetImsi(bool verbose) { return mmuImsi.empty() ? (verbose ? "no-imsi" : "") : mmuImsi; } + //void mmuClose(); // TODO + bool mmuIsAttached() { return mmuContext != NULL; } // Are we attached to a radio channel? + bool mmuIsEmpty(); + void mmuCleanupDialogs(); // Let go of any dead dialogs + //void mmuCallFinished(L3LogicalChannel *chan,MMCause cause); + bool mmuServiceMTQueues(); + void mmuText(std::ostream&os) const; + string mmuText() const; +}; +std::ostream& operator<<(std::ostream& os, const MMUser&mmu); +std::ostream& operator<<(std::ostream& os, const MMUser*mmu); + +// This is the set of actively runnning TranEntrys on an L3LogicalChannel. +// TODO: The MM operations should run directly on the MMContext, not in a TranEntry. +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(MMContext,MemCheckMMContext) +class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { + friend class MMLayer; + friend class MMUser; + private: + //mutable Mutex mmcLock; // mostly unused + int mmcChannelUseCnt; + L3LogicalChannel *mmcChan; + //RefCntPointer mmcMMU; + MMUser* mmcMMU; + //L3Timer TChReassignment; + + protected: + void mmcUnlink(); + void mmcLink(MMUser *mmu); + + // These are the Transactions/Procedures that may be active simultaneously: + public: + UDPSocket *mmcFuzzPort; + + enum ActiveTranIndex { + TE_first = 0, // Start of table. + TE_MM = 0, // One MM Procedure. + TE_CS1, // Primary CS Transaction. + TE_CSHold, // CS transaction on hold. + // Dont reorder these without checking for 'for' loops in L3MMLayer.cpp + TE_MOSMS1, // The primary MO-SMS. + TE_MOSMS2, // The follow-on MO-SMS. + TE_MTSMS, // Only one MT-SMS allowed at a time. + TE_SS, // Dedicated supplementary services transaction. + TE_num // Not a Transaction; The max number of entries in this table. + }; + RefCntPointer mmcTE[TE_num]; + unsigned mNextTI; + InterthreadQueue mmcServiceRequests; + void startSMSTran(TranEntry *tran); + + void MMContextInit(); + void mmcFree(); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the MMUserMap first. + + public: + MMContext(L3LogicalChannel *chan); + bool mmInMobilityManagement(); // Is a mobility management procedure running? + //void mmClose(); + L3LogicalChannel *tsChannel() { return mmcChan; } + MMContext *tsDup(); + void mmcPageReceived() const; + + RefCntPointer mmGetTran(unsigned ati) const; + void mmConnectTran(ActiveTranIndex ati, TranEntry *tran); + void mmConnectTran(TranEntry *tran); + void mmDisconnectTran(TranEntry *tran); + + unsigned mmGetNextTI(); + void getTranIds(TranEntryList &tranlist) const; + // By returning a RefCntPointer we prevent destruction of the transaction during use by caller. + RefCntPointer tsGetVoiceTran() const { return mmcTE[TE_CS1]; } + void tsSetVoiceTran(TranEntry*tran) { mmcTE[TE_CS1] = tran; } + void mmSetChannel(L3LogicalChannel *wChan) { mmcChan = wChan; } + string mmGetImsi(bool verbose); // If the IMSI is known, return it, else "" + + bool mmIsEmpty(); + bool mmCheckNewActivity(); // Check for new activity. Return true if any found. Also checks for normal channel release. + bool mmCheckSipMsgs(); // Return true if anything happened. + bool mmCheckTimers(); // Return true if anything happened. + RefCntPointer findTran(const L3Frame *frame, const L3Message *l3msg) const; + bool mmDispatchL3Frame(const L3Frame *frame, const L3Message *msg); + void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0); + void mmcText(std::ostream&os) const; +}; +std::ostream& operator<<(std::ostream& os, const MMContext&mmc); +std::ostream& operator<<(std::ostream& os, const MMContext*mmc); + +// Maps imsi to MMUser. No imsi, no MMUser. + +extern Mutex gMMLock; +class MMLayer { + friend class MMUser; + // Locking rules: + // The thread running the L3LogicalChannel service loop "owns" the MMContext on its channel, + // so it is allowed to manipulate it without locking this global Mutex. + // Once the MMUser is 'attached' it cannot be deleted except by the the L3LogicalChannel thread, + // so the MMUser can also be used by that thread without fear of destruction during use. + // But MMUsers are created by an external thread (specifically, from SIPInterface) and may + // be destroyed as a result of expired pages, so this global Mutex is used to + // to add/remove MMUsers, to search for MMUsers, paging, + // to connect/disconnect an MMUser with/from a MMContext. + // It is also used to create/delete MMContexts which is probably unnecessary. + // The MMUser::Mutex is used only to protect the queues inside MMUser, + // used to add/remove transactions to those queues. + //Mutex gMMLock; + Signal mmPageSignal; ///< signal to wake the paging loop + MMUserMap MMUsers; + public: + void mmGetPages(NewPagingList_t &pages, bool wait); + void printPages(std::ostream &os); + bool mmPageReceived(MMContext *mmchan, L3MobileIdentity &mobileId); + // Add a new MT transaction, and signal the pager to come notice it. + void mmAddMT(TranEntry *tran); + void mmFreeContext(MMContext *mmc); + // This is called when the MT SIP engine on the other side has sent us another message. + // (pat) We could be blocked for several reasons, including paging, waiting for LUR to complete, waiting for channel to change, etc. + // But if we are paging, reset the paging timer so we keep paging. + void mmMTRepage(const string imsi); // Reset the paging timer so we continue paging. + void mmAttachByImsi(L3LogicalChannel *chan, string imsi); + //bool mmStartMTDialog(SIP::SipDialog*dialog, SIP::SipMessage*invite); + MMUser *mmFindByImsi(string imsi, bool create=false); + MMUser *mmFindByTmsi(uint32_t tmsi); + MMUser *mmFindByMobileId(L3MobileIdentity&mid); + void printMMUsers(std::ostream&os, bool onlyUnattached); + void printMMInfo(std::ostream&os); + string printMMInfo(); + + // Is the single MTC slot busy? + bool mmIsBusy(string &imsi) { + ScopedLock lock(gMMLock,__FILE__,__LINE__); + MMUser *mmu = mmFindByImsi(imsi,false); + LOG(DEBUG) <mmuMTCq.size()) return true; // Someone already waiting in the MTC queue. + if (!mmu->mmuContext) return false; + LOG(DEBUG) <<"mmc="<mmuContext; + return mmu->mmuContext->tsGetVoiceTran() != NULL; + } + + RefCntPointer mmFindVoiceTranByImsi(string &imsi) { + ScopedLock lock(gMMLock,__FILE__,__LINE__); + LOG(DEBUG); + MMUser *mmu = mmFindByImsi(imsi,false); + LOG(DEBUG) <mmuContext; + LOG(DEBUG) <tsGetVoiceTran().self(); + return mmc->tsGetVoiceTran(); + } +}; + +extern MMLayer gMMLayer; + +}; // namespace Control + +#endif diff --git a/Control/L3MobilityManagement.cpp b/Control/L3MobilityManagement.cpp new file mode 100644 index 0000000..688498a --- /dev/null +++ b/Control/L3MobilityManagement.cpp @@ -0,0 +1,1264 @@ +/* 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 distribuion. +* +* 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. +*/ + +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging + +#include +#include "L3TranEntry.h" +#include +#include "ControlCommon.h" +#include "L3MobilityManagement.h" +#include "L3CallControl.h" +#include "L3SMSControl.h" +#include "L3MMLayer.h" +#include "L3SupServ.h" +#include +#include +#include + + +#include +#include +#include "ControlCommon.h" +//#include +//#include +//#include +#include // Doesnt this poor L3Message get lonely? When apparently there are multiple L3MMMessages and L3CCMessages? +#include +#include +//#include +#include +#include +#include "RRLPServer.h" +using namespace GSM; + + + +// Note: GSM 4.08 4.1.2.3 has MM States on Network Side. + +namespace Control { +using namespace SIP; +using namespace GSM; + +static const int testWelcomeMessage = 1; + + +void NewCMServiceResponder(const L3CMServiceRequest* cmsrq, MMContext* mmchan) +{ + assert(cmsrq); + assert(mmchan); + LOG(INFO) << *cmsrq; + //TranEntry *tran; + // The transaction may or may not be cleared, + // depending on the assignment type. + CMServiceTypeCode serviceType = cmsrq->serviceType().type(); + switch (serviceType) { + case L3CMServiceType::MobileOriginatedCall: + gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOC"); + startMOC(cmsrq,mmchan,serviceType); + break; + case L3CMServiceType::ShortMessage: + gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOSMS"); + startMOSMS(cmsrq,mmchan); + break; + case L3CMServiceType::SupplementaryService: + startMOSSD(cmsrq,mmchan); + break; + default: + gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.Unhandled"); + LOG(NOTICE) << "service not supported for " << *cmsrq; + mmchan->l3sendm(L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported))); + //mmchan->l3sendm(L3ChannelRelease(L3RRCause::Unspecified)); + return; + } +} + +// For MTC we paged the MS, it RACHed in, was given a channel with an ImmediateAssignment, +// without knowing the MS identity. The MS then sends us this message. +// The purpose of this function is to identify the MS so we can associate the +// radio channel with the MMUser. +void NewPagingResponseHandler(const L3PagingResponse* resp, MMContext* mmchan) +{ + assert(resp); + assert(mmchan); + LOG(INFO) << *resp; + + // Nowadays, we dont page unless we know both the tmsi and the imsi of the MS, so just look it up. + L3MobileIdentity mobileId = resp->mobileID(); + if (! gMMLayer.mmPageReceived(mmchan,mobileId)) { + + LOG(WARNING) << "Paging Reponse with no Mobility Management record for " << mobileId; + mmchan->l3sendm(L3ChannelRelease(L3RRCause::CallAlreadyCleared)); + return; // There is nothing more we can do about this because we dont know who it is. + } +} + +// The BLU Deco Mini handset rejects the first SMS with a "protocol error unspecified" after an LUR procedure +// regardless if it is delivered immediately on the same channel, paged later, or even if you wait 10 seconds. +// Must wait 30 seconds before it will accept a new SMS. +// Evidently we need to watch the return state of the welcome message so we can resend it if necessary. +// But this problem should be hoisted into sipauthserve. +static void sendWelcomeMessage(MMSharedData *mmsd, const char* messageName, const char* shortCodeName, const FullMobileId &msid, + L3LogicalChannel* DCCH) +{ + LOG(DEBUG); + if (mmsd->store.getWelcomeSent()) { return; } + + // (pat) TODO: We should store the authorization state of the welcome message that was sent so that when there is an + // authorization state change (ie, from unauthorized to authorized) we can send a new welcome message. + // But this should all be moved into sipauthserve anyway. + string stmp = gConfig.getStr("Control.LUR.RegistrationMessageFrequency"); + if (stmp == "PLMN") { + // We only send the registration message if it is an imsi attach. + // If it is a normal updating we assume a welcome message was sent by a different BTS, or possibly + // earlier by us and the TMSI_TABLE database was lost. + // If it is a periodic updating we assume a welcome message was sent by us but we lost the tmsi database somehow. + if (! mmsd->isImsiAttach()) { + mmsd->store.setWelcomeSent(2); // welcome message sent by someone else + LOG(DEBUG); + return; + } + } else if (stmp == "NORMAL") { + // We send the registration message the first time this BTS sees this MS. + // If it is periodic updating, then we assume that we have seen the MS previously but our TMSI_TABLE database was lost. + if (! mmsd->isInitialAttach()) { + mmsd->store.setWelcomeSent(2); // welcome message sent by this BTS previously. + LOG(DEBUG); + return; + } + } else { + // This is the stmp == 'FIRST' option. + // We send the message if the WELCOME_SENT field is 0, regardless of the status reported by the MS. + } + LOG(DEBUG); + + if (!gConfig.defines(messageName) || !gConfig.defines(shortCodeName)) return; + string message = gConfig.getStr(messageName); + string shortCode = gConfig.getStr(shortCodeName); + if (!message.length() || !shortCode.length()) return; + LOG(INFO) << "sending " << messageName << " message to handset"; + message += string(" IMSI:") + msid.mImsi; + // (pat) We use the short code as the originator calling number so the user can hit reply. + Control::TranEntry *tran = Control::TranEntry::newMTSMS( + NULL, // No SipDialog + msid, // MS we are sending SMS to. + GSM::L3CallingPartyBCDNumber(shortCode.c_str()), + message, // message body + string("text/plain")); // message content type + if (1) { + // This line starts the SMS immediately after the Mobility Management procedure finishes on the same channel; + // it works on the Blackberry but does not work on the BLU mini, but nothing works on that. + DCCH->chanGetContext(false)->startSMSTran(tran); + } else { + // This causes the BTS to page the MS for the SMS message after the MM procedure releases the channel. + // Some handsets will not respond to a page if sent certain LU reject codes, so this does not work + // to send the reject message. + Control::gMMLayer.mmAddMT(tran); + } + mmsd->store.setWelcomeSent(1); // welcome message sent by us. TODO: We should not set this until we have confirmation of delivery. + return; +} + +// The L3IdentifyMachine is invoked for SMS and USSD. It is not used during the Location Update procedure. +MachineStatus L3IdentifyMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + PROCLOG2(DEBUG,state)<subscriber()); + switch (state) { + // This is the start state. It may return immediately if the MS is already identified. + case stateStart: { + // Have an imsi already? + if (mMobileID.type()==IMSIType) { + string imsi(mMobileID.digits()); + tran()->setSubscriberImsi(imsi,false); + *mResultPtr = gTMSITable.tmsiTabCheckAuthorization(imsi); + return MachineStatusPopMachine; + } + + // If we got a TMSI, find the IMSI. + if (mMobileID.type()==TMSIType) { + unsigned authorized; + string imsi = gTMSITable.tmsiTabGetIMSI(mMobileID.TMSI(),&authorized); + LOG(DEBUG) <<"lookup"<setSubscriberImsi(imsi,false); + *mResultPtr = authorized; + return MachineStatusPopMachine; + } + } + + + // Still no IMSI? Ask for one. + // TODO: We should ask the SIP Registrar. + // (pat) This is not possible if the MS is compliant (unless the TMSI table has been lost) - + // the MS should have done a LocationUpdate first, which provides us with the IMSI. + PROCLOG(NOTICE) << "No IMSI or valid TMSI. Reqesting IMSI."; + timerStart(T3270,T3270ms,TimerAbortChan); + channel()->l3sendm(L3IdentityRequest(IMSIType)); + return MachineStatusOK; + } + + + // TODO: This should be moved to an MM Identify procedure run before starting the MOC. + case L3CASE_MM(IdentityResponse): { + timerStop(T3270); + const L3IdentityResponse *resp = dynamic_cast(l3msg); + const L3MobileIdentity &mobileID = resp->mobileID(); // Do not need a copy operation. + if (mobileID.type()==IMSIType) { + string imsi = string(mobileID.digits()); + tran()->setSubscriberImsi(imsi,false); + *mResultPtr = gTMSITable.tmsiTabCheckAuthorization(imsi); + + } else { + // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. + PROCLOG(WARNING) << "Requested IMSI but got:"<subscriberIMSI(); } +const char * LUBase::getImsiCh() const { return getImsi().c_str(); } +const string LUBase::getImsiName() const { return string("IMSI") + getImsi(); } +FullMobileId &LUBase::subscriber() const { return tran()->subscriber(); } +MMSharedData *LUBase::ludata() const +{ + if (!tran()->mMMData) { tran()->mMMData = new MMSharedData; } + return tran()->mMMData; +} + +#if 0 +// (pat) This queryForRejectCause was part of the ancient release 3 fuzzing interface, but please please do not +// put this back in; I dont want to support this methodology any more; this wget is going to hang the state machine and that is bad. +// We support a new wonderful and non-dorky way for the customer to return a reject cause, +// which is simply to include it in the SIP Registrar response message using the "P-GSM-Reject-Cause" SIP header. + +#endif + + +// (pat) This was formerly a member of LUBase so it could get at mNewImei for the S_RELEASE ifdefed code, +// but I dont think that should be re-enabled in any case, and I am making this a simple global function. +// TODO: Reject cause should be determined in a more central location, probably sipauthserve. +// We may want different reject codes based on the IMSI or MSISDN of the MS, or on the CM service being requested (CC, SMS, USSD, GPRS), +// although this code here is used only by LUR. +static MMRejectCause getRejectCause(unsigned sipCode) +{ + MMRejectCause rejectCause; + unsigned utmp; + switch (sipCode) { + case 400: // This value is used in the SIP code for unrecoverable errors in a SIP message from the Registrar. + rejectCause = L3RejectCause::NetworkFailure; + break; + case 401: { // SIP 401 "Unauthorized" + // The sip nomenclature for 401 and 404 are exactly reversed: + // This sip code is "Unauthorized" but what it really means is the Registrar + // failed the IMSI without a challenge, ie, the MS was not found in the database. + utmp = gConfig.getNum("Control.LUR.UnprovisionedRejectCause"); + rejectCause = (MMRejectCause) utmp; + break; + } + case 403: { // SIP 403 "Forbidden" + rejectCause = L3RejectCause::LocationAreaNotAllowed; + break; + } + case 404: { // SIP 404 "Not Found" + // The sip nomenclature for this code is "Not Found" but it really means failed authorization. + // (pat) TODO: The reject cause may want to be different for home and roaming subscribers, + // and may want to depend on the IMSI or MSISDN, and we may want allow or disallow on the same criteria + // in which case it is already too late here; but the appropriate code should + // be determined at the Registrar level, not in the BTS, so this is not the place to fix it. + utmp = gConfig.getNum("Control.LUR.404RejectCause"); + rejectCause = (MMRejectCause) utmp; + break; + } + case 424: { // SIP 424 "Bad Location Information" + rejectCause = L3RejectCause::RoamingNotAllowedInLA; + break; + } + case 504: { // SIP 504 "Servier Time-out" + rejectCause = L3RejectCause::Congestion; + break; + } + case 603: { // SIP 603 "Decline" + rejectCause = L3RejectCause::IMSIUnknownInVLR; + break; + } + case 604: { // SIP 604 "Does Not Exist Anywhere" + rejectCause = L3RejectCause::IMSIUnknownInHLR; + break; + } + default: + LOG(NOTICE) << "REGISTER unexpected response from Registrar" <mLUMobileId = lur->mobileID(); // This is a copy operation. + ludata()->mLULAI = lur->LAI(); // This is a copy operation too. + // (pat) Documentation for IMSI Attach is in 24.008 4.4.3. + // The documentation is confusing, but I observe that when the MS is first powered on, it sends a + // "Normal Location Updating" if it was previously IMSI attached (ie, successful LUR) in the same PLMN. + ludata()->mLUType = lur->getLocationUpdatingType(); + + // The location updating request gets mapped to a SIP + // registration with the SIP registrar. + + // If the handset is allowed to register it may receive a TMSI reassignment. + gReports.incr("OpenBTS.GSM.MM.LUR.Start"); + + switch (ludata()->mLUMobileId.type()) { + case GSM::IMSIType: { + ludata()->mFullQuery = true; + string imsi = string(ludata()->mLUMobileId.digits()); + // TODO: We should notify the MM layer. + //gMMLayer.mmBlockImsi(imsi); + tran()->setSubscriberImsi(imsi,false); // We will need this for authorization below. + // If the MS sent an IMSI but the TMSI is already in the database, most likely we just did not send the TMSI assignment. + // but it could also be a tmsi collision. + uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(imsi,false); // returns 0 if IMSI not in database. + if (tmsi) { + ludata()->setTmsi(tmsi,tmsiNotAssigned); + //gMMLayer.mmBlockTmsi(tmsi); + } else { + assert(ludata()->getTmsiStatus() == tmsiNone); + } + return machineRunState(stateHaveImsi); + } + case GSM::TMSIType: { + uint32_t tmsi = ludata()->mLUMobileId.TMSI(); + ludata()->mOldTmsi = tmsi; + // Look in the TMSI table to see if it's one we assigned. + bool sameLAI = ludata()->mLULAI == gBTS.LAI(); + string imsi; + if (sameLAI) imsi = gTMSITable.tmsiTabGetIMSI(tmsi,NULL); + if (imsi.size()) { + // There is an TMSI/IMSI pair already in the TMSI table corresponding to this TMSI, + // but we dont know if this is the same MS yet. We will try to authenticate using the stored IMSI, + // and if that fails, we will query for the IMSI and try again. + tran()->setSubscriberImsi(imsi,false); // We will need this for authorization below. + ludata()->setTmsi(tmsi,tmsiProvisional); // We (may have) assigned this tmsi sometime in the past. + //gMMLayer.mmBlockTmsi(tmsi); + //gMMLayer.mmBlockImsi(imsi); + LOG(DEBUG) << "resolving mobile ID (table): " << ludata()->mLUMobileId; + return machineRunState(stateHaveImsi); + } else { + // Unrecognized TMSI; Query for IMSI + // We leave the TMSI state at tmsiNone and save the unrecognized tmsi only in mOldTmsi. + ludata()->mFullQuery = true; + return sendQuery(IMSIType); + } + } + case GSM::IMEIType: + // (pat) The phone was not supposed to send an IMEI in the LUR message, + // but lets go ahead and accept it. So we need to query for the imsi: + ludata()->store.setImei(ludata()->mLUMobileId.digits()); + return sendQuery(IMSIType); + default: + LOG(ERR) << "Unexpected MobileIdentity type in LocationUpdateRequest:"<mFullQuery = true; + return sendQuery(IMSIType); + } +} + +// Send a query. Only send each query once. +MachineStatus LUStart::sendQuery(MobileIDType qtype) +{ + assert(qtype == IMSIType || qtype == IMEIType); + ludata()->mQueryType = qtype; + timerStart(T3270,12000,TimerAbortChan); + channel()->l3sendm(GSM::L3IdentityRequest(qtype)); + return MachineStatusOK; +} + +// Receive the identity response which may be IMSI or IMEI +MachineStatus LUStart::stateRecvIdentityResponse(const GSM::L3IdentityResponse *resp) +{ + //const GSM::L3IdentityResponse *resp = dynamic_cast(l3msg); + LOG(INFO) << *resp; + MobileIDType idtype = resp->mobileID().type(); + // Meaningful IdentityResponse? + + // Store the result, even if it was not what we asked for. + if (idtype == IMSIType) { + string imsi = string(resp->mobileID().digits()); + + { + // Check for perfidy on the part of the MS. We're checking that it did not send two different IMSIs in the same attempt, + // something that will probably never happen. + string prevImsi = getImsi(); + if (prevImsi.size() && prevImsi != imsi) { + LOG(ERR) << "MS returned two different IMSIs"; + MMRejectCause failCause = L3RejectCause::InvalidMandatoryInformation; + // There is no ludata()->store yet so just set it directly: + gTMSITable.tmsiTabSetRejected(imsi,(int)failCause); + // I dont know what the cause should be here, but if this ever happens, we dont care. + channel()->l3sendm(L3LocationUpdatingReject(failCause)); + return closeChannel(L3RRCause::NormalEvent,RELEASE); + } + } + + tran()->setSubscriberImsi(imsi,false); + //gMMLayer.mmBlockImsi(imsi); + + // If this is the second attempt, we could check if this IMSI matches what we had on record, + // and if so just reject immediately, but this doesn't happen often and I'm just going to let it proceed again + // with the whole registration process. + LOG(DEBUG) <mSecondAttempt) <mPrevRegisterAttemptImsi) <getTmsiStatus()); + if (ludata()->mSecondAttempt == 0) { + // The MS sent a TMSI that was unrecognized, so we queried for the IMSI. + assert(ludata()->getTmsiStatus() == tmsiNone); + } else { + // The second attempt means that registration by TMSI failed, so we queried for the imsi. + // If the IMSI matches what is in the TMSI table, it is a genuine failure. + devassert(ludata()->mPrevRegisterAttemptImsi.size()); + if (ludata()->mPrevRegisterAttemptImsi == imsi) { + // It was not a TMSI collision; this subscriber is really unauthorized. + // The register procedure already called regSetFail. + devassert(ludata()->mRegistrationResult.isValid()); + return callMachStart(new LUFinish(tran())); + } + // We already assigned the new imsi above. We just fall through to try another registration. + assert(ludata()->getTmsiStatus() == tmsiFailed); + } + } else if (idtype == IMEIType) { + // We do not check whether the IMEI matches what we may have stored already because + // we dont care if the user has switched their SIM card to a new handset. + ludata()->store.setImei(string(resp->mobileID().digits())); + } else { + LOG(WARNING) << "MS Identity Response unexpected type: " << idtype; + return MachineStatusOK; // Just ignore it. T3270 is still running. Maybe it will return what we asked for later. + } + + if (ludata()->mQueryType == NoIDType) { + // This was an unsoliticed or duplicate IdentityResponse. + return MachineStatusOK; + } + + if (ludata()->mQueryType == idtype) { // success + timerStop(T3270); + ludata()->mQueryType = NoIDType; + // Go to the next state. + timerStart(TMMCancel,12000,TimerAbortChan); + if (idtype == IMSIType) { + return machineRunState(stateHaveImsi); + } else { + return machineRunState(stateHaveIds); + } + } else { + // The MS goofed. + LOG(WARNING) << "MS Identity Response for "<mQueryType<<" returned "<(l3msg); + return stateRecvLocationUpdatingRequest(lur); + } + + // This is the start state for the second attempt. + case stateSecondAttempt: { + // The second attempt is initiated from LUAuthentication if registration by tmsi fails. + ludata()->mFullQuery = true; + return sendQuery(IMSIType); + } + + case L3CASE_MM(IdentityResponse): { + const GSM::L3IdentityResponse *resp = dynamic_cast(l3msg); + return stateRecvIdentityResponse(resp); + } + + case stateHaveImsi: + { + if (ludata()->mFullQuery && gConfig.defines("Control.LUR.QueryIMEI") && ludata()->store.getImei().size() == 0) { return sendQuery(IMEIType); } + return machineRunState(stateHaveIds); + } + + case stateHaveIds: + { + // We have the IMSI and IMEI if needed. Proceed with authorization. + GPRS::GPRSNotifyGsmActivity(this->getImsiCh()); + + // OpenBTS version 3 generated a TMSI here for every new phone we see, even if we don't actually assign it. + + // (pat) Start of Authorization Procedure. + // TODO: This needs to pass a message to SR and wait for a message back. + + + // What if a TMSI comes in and then the Registrar does not challenge it? + // We could get the phones mixed up. + // If the previous authorization has not expired: + // If it was previously unauthorized, just reject it without contacting the Registrar. + // If there was a challenge, we could accept immediately, or re-run the previous challenge, + // or preferably, the Registrar would return a string of challenge/response pairs so we can keep using them. + TmsiTableStore *store = &ludata()->store; + // On the second attempt we need to do a real authentication via registration, not just re-run cached authentication. + if (! ludata()->mSecondAttempt && gTMSITable.tmsiTabGetStore(getImsi(),store)) { + // Is the cached authorization still valid? + int authExpiry = store->getAuthExpiry(); + if (authExpiry && time(NULL) <= authExpiry) { + if (! store->isAuthorized()) { + // Not authorized. + ludata()->mRegistrationResult.regSetFail(0,(MMRejectCause)store->getRejectCode()); + return callMachStart(new LUFinish(tran())); + } else { +#if CACHE_AUTH + // Handset was authorized. + // We do not use the authorization cache if the handset is authorized because we + // need to inform sipauthserve of the whereabouts of the handset so it can update the + // database used by asterisk for MTC. + ludata()->mUsingCachedAuthentication = true; + //if (store->rand.size()) { + // ludata()->mRegistrationResult.regSetChallenge(0,store->rand); + // return callMachStart(new LUAuthentication(tran())); + //} else { + // ludata()->mRegistrationResult.regSetSuccess(); + // return callMachStart(new LUFinish(tran())); + //} +#endif + } + } + } + + // The TranEntry already has the correct SipEngine. + // DCCH is available in tran() + string emptySRES; + ludata()->mPrevRegisterAttemptImsi = getImsi(); + return machPush(new L3RegisterMachine(tran(),SIPDTRegister, + emptySRES, &ludata()->mRegistrationResult), + stateRegister1Response); + } + + case stateRegister1Response: + { + timerStart(TMMCancel,12000,TimerAbortChan); + // Did we get a RAND for challenge-response? + LOG(DEBUG)<text(); + if (ludata()->mRegistrationResult.mRegistrationStatus == RegistrationChallenge) { + return callMachStart(new LUAuthentication(tran())); + } else { + return callMachStart(new LUFinish(tran())); + } +#if 0 + if (ludata()->mRegistrationResult.isFailure()) { + } + //if (ludata()->mRAND.length() == 0) + if (ludata()->mRegistrationResult.mRegistrationStatus != RegistrationChallenge) { + // If the RAND is not provided, no challenge needed. + // The phone may be authorized or not, but LUFinish handles both cases. + return callMachStart(new LUFinish(tran())); + } +#endif + } + default: + return unexpectedState(state,l3msg); + } // switch +} + +// ====== State Machine LUAuthentication ===== + +MachineStatus LUAuthentication::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) +{ + switch (state) { + case stateStart: { + gReports.incr("OpenBTS.GSM.MM.Authenticate.Request"); + // Get the mobile's SRES. + LOG(INFO) << "sending " << ludata()->mRegistrationResult.text() << " to mobile"; + uint64_t uRAND; + uint64_t lRAND; + string rand = ludata()->mRegistrationResult.mRand; // mRAND; + rand = rand.substr(0,rand.find('.')); + if (rand.size() != 32) { + LOG(ALERT) << "Invalid RAND challenge returned by Registrar (RAND length=" <mRegistrationResult.regSetError(); + //channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::ServiceOptionTemporarilyOutOfOrder)); + //return closeChannel(L3RRCause::NormalEvent,RELEASE); + return callMachStart(new LUFinish(tran())); + } + + // TODO: This needs to be message based. + Utils::stringToUint(rand, &uRAND, &lRAND); + // Sending authenticaion request moved to LUAuthentication::stateStart + timerStart(T3260,12000,TimerAbortChan); + channel()->l3sendm(GSM::L3AuthenticationRequest(0,GSM::L3RAND(uRAND,lRAND))); + return MachineStatusOK; + } + + // The MS returns SRES in response to an authentication request with a RAND, and here we send a second + // registration request back to the Registrar to see if the SRES is correct. + case L3CASE_MM(AuthenticationResponse): { + timerStop(T3260); + timerStart(TMMCancel,12000,TimerAbortChan); // TODO: How long should we wait for authentication? + const GSM::L3AuthenticationResponse*resp = dynamic_cast(l3msg); + LOG(INFO) << *resp; + uint32_t mobileSRES = resp->SRES().value(); + // verify the SRES that was sent to use by the MS. + //ostringstream os; + //os << hex << mobileSRES; + //string SRESstr = os.str(); +#if CACHE_AUTH + if (ludata()->mUsingCachedAuthentication) { + if (mobileSRES == ludata()->store.SRES) { + ludata()->mRegistrationResult.regSetSuccess(); + } else { + ludata()->mRegistrationResult.regSetFail(0,store->rejectCode); + } + } else +#endif + { + string SRESstr = format("%x",mobileSRES); + return machPush(new L3RegisterMachine(tran(),SIPDTRegister, + SRESstr, &ludata()->mRegistrationResult), + stateRegister2Response); + } + } + + case stateRegister2Response: { + // The TMSI table is updated as follows: + // on success, only in this case; + // on failure, by LUFinish::stateSendLUResponse(), which is called in other places too. + LOG(DEBUG) <getTmsi()) <getTmsiStatus()) <subscriberIMSI()); + timerStop(TMMCancel); + switch (ludata()->mRegistrationResult.mRegistrationStatus) { + case RegistrationUninitialized: + default: + devassert(0); + // Fall Through + case RegistrationError: + //return callMachStart(new LUNetworkFailure(tran())); + return callMachStart(new LUFinish(tran())); + case RegistrationFail: // In which case the mSipCode tells why. + if (ludata()->mSecondAttempt == 0 && ludata()->getTmsiStatus() == tmsiProvisional) { + //mmUnblockImsi(getImsi()); + // Registration by TMSI failed. Try again using an IMSI. To do that we will start authentication over from scratch. + // Delete both the stored tmsi and the imsi stored in the transaction. + ludata()->setTmsi(0,tmsiFailed); + tran()->setSubscriberImsi(string(""),false); // This IMSI was not authorized and may not be the IMSI for this TMSI. + ludata()->mSecondAttempt = true; // Start second attempt. + // Start over and this time query for the IMSI and try again. + return callMachStart(new LUStart(tran()),LUStart::stateSecondAttempt); + } else { + // We dont need to update the TmsiStatus because we are finished. + // LUFinish will check open-registration, send a reject message if we really failed. + return callMachStart(new LUFinish(tran())); + } + case RegistrationChallenge: + // This should not happen. + LOG(ERR) << "Registrar error: second registration includes challenge."; + // What to do? + ludata()->mRegistrationResult.regSetError(); + return callMachStart(new LUFinish(tran())); + case RegistrationSuccess: + // Authorization success: Move on. + break; + } +#if 0 + if (ludata()->mRegistrationResult.isNetworkFailure()) { + return callMachStart(new LUNetworkFailure(tran())); + } else if (ludata()->mRegistrationResult == RegistrationFail) { // really failed. + if (ludata()->mSecondAttempt == 0 && ludata()->getTmsiStatus() == tmsiProvisional) { + // Registration by TMSI failed. Try again using an IMSI. To do that we will start authentication over from scratch. + // Delete both the stored tmsi and the imsi stored in the transaction. + ludata()->setTmsi(0,tmsiFailed); + tran()->setSubscriberImsi(string(""),false); // This IMSI was not authorized and may not be the IMSI for this TMSI. + ludata()->mSecondAttempt = true; // Start second attempt. + // Start over and this time query for the IMSI and try again. + return callMachStart(new LUStart(tran())); + } else { + // We dont need to update the TmsiStatus because we are finished. + // LUFinish will check open-registration, send a reject message if we really failed. + return callMachStart(new LUFinish(tran())); + } + } else +#endif + { + // Query for classmark? + // (pat) We need to do this if the IMEI changed also, because a new handset may have different capabilities. + // Instead of checking IMEI, just always query the classmark and dont worry about checking + // whether we already have a valid classmark or not. + if (gConfig.getBool("GSM.Cipher.Encrypt") || gConfig.getBool("Control.LUR.QueryClassmark")) { + timerStart(TMMCancel,12000,TimerAbortChan); + channel()->l3sendm(L3ClassmarkEnquiry()); + return MachineStatusOK; + } else { + return callMachStart(new LUFinish(tran())); + } + } + } + + case L3CASE_RR(ClassmarkChange): { + timerStart(TMMCancel,12000,TimerAbortChan); + const GSM::L3ClassmarkChange *resp = dynamic_cast(l3msg); + const L3MobileStationClassmark2& classmark = resp->classmark(); + // We are storing the A5Bits for later use by CC, which is probably unnecessary because + // it is included in the CC message. + int A5Bits = (classmark.A5_1()<<2) + (classmark.A5_2()<<1) + classmark.A5_3(); + ludata()->store.setClassmark(A5Bits,classmark.powerClass()); + //gTMSITable.classmark(getImsiCh(),classmark); // This one is going away; we'll update once later. + + if (gConfig.getBool("GSM.Cipher.Encrypt")) { + //int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(getImsi().c_str()); + int encryptionAlgorithm = getPreferredA5Algorithm(A5Bits); + if (!encryptionAlgorithm) { + LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for " << getImsiName(); + } else if (channel()->getL2Channel()->decryptUplink_maybe(getImsi(), encryptionAlgorithm)) { + LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for " << getImsiName(); + channel()->l3sendm(GSM::L3CipheringModeCommand( + GSM::L3CipheringModeSetting(true, encryptionAlgorithm), + GSM::L3CipheringModeResponse(false))); + } else { + LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for " << getImsiName(); + } + } + return callMachStart(new LUFinish(tran())); + } + + default: + return unexpectedState(state,l3msg); + } +} + + +// ====== State Machine LUFinish ===== + + +MachineStatus LUFinish::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) +{ + LOG(DEBUG) <<"LUFinish" <mExpectingTmsiReallocationComplete) { + LOG(ERR) << "unexpected TMSIReallocationComplete"; + } else { + uint32_t newTmsi = ludata()->getTmsi(); + if (! newTmsi) { + LOG(ERR) << "TMSI logic inconsistency"; + } else { + LOG(DEBUG) <store.setAssigned(1); + // Putting the TMSI in the subscriber info is irrelevant. This tran is going away momentarily, + // but it should be in the MMContext, but even that doesnt matter because it wont be used again after initial authentication. + tran()->subscriber().mTmsi = newTmsi; + } + } + return statePostAccept(); + } + + default: + return unexpectedState(state,l3msg); + } +} + +// N200 is number of LAPDm retransmissions, and is 34 on FACCH or 5 on SACCH. +// getMsg timeout is N200=34*T200ms=900 = 6.8s on FACCH, +// or N200-5*T200ms=900 = 4.5s on SACCH. +// That is not very useful because the MS times out if it does not receive a MM command in 10s in downlink, +// but maybe in uplink we can wait longer. + +MachineStatus LUFinish::stateSendLUResponse() +{ + LOG(DEBUG); + timerStart(TMMCancel,12000,TimerAbortChan); + string imsi = this->getImsi(); + + // We fail closed unless we're configured otherwise. + // mRegistrationResult.regGetSuccess() is whether we are granting service. + // rather than being allowed service due to network failure or open registration. + Authorization authorization = AuthUnauthorized; + MMRejectCause failCause = L3RejectCause::Zero; + + // (pat) TODO: We should store the authorization state of the welcome message that was sent so that when there is an + // authorization state change (ie, from unauthorized to authorized) we can send a new welcome message. + // But this should all be moved into sipauthserve anyway. + if (ludata()->store.getWelcomeSent() == 0) { + string stmp = gConfig.getStr("Control.LUR.RegistrationMessageFrequency"); + if (stmp == "PLMN") { + // We only send the registration message if it is an imsi attach. + // If it is a normal updating we assume a welcome message was sent by a different BTS, or possibly + // earlier by us and the TMSI_TABLE database was lost. + // If it is a periodic updating we assume a welcome message was sent by us but we lost the tmsi database somehow. + if (! ludata()->isImsiAttach()) { + ludata()->store.setWelcomeSent(2); // welcome message sent by someone else + } + } else if (stmp == "NORMAL") { + // We send the registration message the first time this BTS sees this MS. + // If it is periodic updating, then we assume that we have seen the MS previously but our TMSI_TABLE database was lost. + if (! ludata()->isInitialAttach()) { + ludata()->store.setWelcomeSent(2); // welcome message sent by us previously. + } + } else { + // This is the stmp == 'FIRST' option. + // We send the message if the WELCOME_SENT field is 0, regardless of the status reported by the MS. + } + } + + switch (ludata()->mRegistrationResult.mRegistrationStatus) { + case RegistrationSuccess: + authorization = AuthAuthorized; + break; + case RegistrationError: + if (failOpen()) { + authorization = AuthFailOpen; + //ludata()->mRegistrationResult.regSetSuccess(); + } else { + failCause = L3RejectCause::NetworkFailure; + } + break; + case RegistrationFail: + // The OpenRegistration option does not distinguish between unrecognized and unauthorized imsis, + // which is unfortunate. + if (openRegistration()) { + //ludata()->mRegistrationResult.regSetSuccess(); + authorization = AuthOpenRegistration; + } else { + failCause = ludata()->mRegistrationResult.mRejectCause; + } + break; + default: devassert(0); + } + + if (authorization != AuthUnauthorized) { + if (authorization == AuthAuthorized) { + LOG(INFO) << "registration SUCCESS: " << ludata()->mLUMobileId; + } else { + LOG(INFO) << "registration ALLOWED: " << ludata()->mLUMobileId; + } + + ludata()->store.setAuthorized(authorization); + + // This switch calls either tmsiTabUpdate or tmsiTabAssign to udpate the TMSI_TABLE. + // We update the TMSI_ASSIGNED status in the TMSI_TABLE to reflect the phone's opinion, which could differ from the BTS. + switch (ludata()->getTmsiStatus()) { + case tmsiFailed: + // Getting here means we succeeded on the second attempt: TMSI failed but IMSI passed, ie, it was a TMSI collision. + // Fall through... + case tmsiNone: { + // This is done only on the first registration in this BTS: + // Allocate a new tmsi to go with the updated imsi. + // Someday the tmsi may come from the registration server. + //uint32_t newTmsi = gTMSITable.tmsiTabAssign(imsi,&ludata()->mLULAI,ludata()->mOldTmsi,&ludata()->store); + uint32_t newTmsi = gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi); + ludata()->setTmsi(newTmsi,tmsiNew); + break; + } + + case tmsiNotAssigned: { + // The MS authenticated based on the IMSI even though it is already in the tmsi table. + // If we are SendTMSIs is on, then either someone changed the option after this MS registered, + // or maybe the MS just never received the TMSI assignment. + // We may need to assign a new TMSI if the MS has just become registered and formerly had a fake tmsi, + // or if the SendTMSIs option is on, so we call tmsiTabCreateOrUpdate instead of tmsiTabUpdate. + //ludata()->setTmsiStatus(tmsiNew); no need for this + ludata()->store.setAssigned(0); // Make sure TMSI database matches what the MS thinks. + //gTMSITable.tmsiTabUpdate(imsi,&ludata()->store); + uint32_t newTmsi1 = gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi); + ludata()->setTmsi(newTmsi1,tmsiNotAssigned); // Update to reflect possible new tmsi. + break; + } + case tmsiProvisional: // The TMSI from the tmsi table authenticated. + ludata()->setTmsiStatus(tmsiAuthenticated); + ludata()->store.setAssigned(1); + gTMSITable.tmsiTabUpdate(imsi,&ludata()->store); + break; + + case tmsiAuthenticated: + case tmsiNew: + // These cases should not occur here. + devassert(0); + LOG(ERR) <<"Unexpected TMSI state:"<< ludata()->getTmsiStatus(); + break; + } + + // We update these values on every registration: + // update: This is done by the registration engine now. + //gTMSITable.putKc(tran()->subscriberIMSI().c_str(),ludata()->mKc, ludata()->mAssociatedUri, ludata()->mAssertedIdentity); + + LOG(DEBUG) <getTmsi()) <getTmsiStatus()) <subscriberIMSI()); + + if (IS_LOG_LEVEL(DEBUG)) { + TmsiStatus stat = ludata()->getTmsiStatus(); + assert(stat == tmsiNew || stat == tmsiNotAssigned || stat == tmsiAuthenticated); + uint32_t ourTmsi = ludata()->getTmsi(); + string checkImsi = gTMSITable.tmsiTabGetIMSI(ourTmsi,NULL); + string myimsi(tran()->subscriberIMSI()); + if (checkImsi != myimsi) { + WATCH("TMSI Table insertion created TMSI collision for"<getTmsi()) <mLUMobileId << LOGVAR(failCause); + devassert(imsi.size()); + //gTMSITable.tmsiTabSetRejected(imsi,failCause); + ludata()->store.setRejectCode(failCause); + gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi); + channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause(failCause))); + + sendWelcomeMessage(ludata(), "Control.LUR.FailedRegistration.Message", // Does nothing if the SQL var is not set. + "Control.LUR.FailedRegistration.ShortCode",subscriber(),channel()); + + // tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field. + gTMSITable.tmsiTabUpdate(imsi,&ludata()->store); + + //return closeChannel(L3RRCause::NormalEvent,RELEASE); + return MachineStatusQuitTran; + } + + // (pat) We must NOT attach the MMContext to the MMUser during the Location Updating procedure; + // Some MS (BLU phone) do not seem to be happy about starting an SMS on the same channel immediately after the LUR, + // so we have to hang up the channel and re-page the MS to do the next procedure, which sucks. + // gMMLayer.mmAttachByImsi(channel(),imsi); + + // Send the "short name" and time-of-day. + string shortName = gConfig.getStr("GSM.Identity.ShortName"); + if (ludata()->isInitialAttach() && shortName.size()) { + channel()->l3sendm(L3MMInformation(shortName.c_str())); + } + + // Send LU Accept. Include a TMSI assignment, if needed. + bool sendTMSIs = configSendTmsis(); + uint32_t newTmsi = ludata()->getTmsi(); + LOG(DEBUG) <getTmsiStatus()) <needsTmsiAssignment()) <needsTmsiAssignment() && sendTMSIs && newTmsi) { + ludata()->mExpectingTmsiReallocationComplete = true; + // Send the TMSI assignment in the LU Accept. + // (pat) This used to be 1 second but the BLU phone, for one, does not send the TMSI Reallocation complete fast enough. + timerStart(TMisc1,5000,stateLUAcceptTimeout); + L3MobileIdentity mid(newTmsi); + // (pat 10-2013) I tried sending the welcome message on the same channel after sending the location updating + // accept but the Blackberry just timed out and the BLU Deco Mini sent a SMS CP-ERROR. + // Update: blackberry works sometimes. + // I tried setting the follow-in proceed flag in the LocationUpdatingAccept and it did not help. + channel()->l3sendm(L3LocationUpdatingAccept(gBTS.LAI(),mid,true)); + // (pat) This 1 second delay was in the original code, so I am duplicating it. + // If we dont get the TMSIReallocationComplete within 1 second, go on to the next step anyway. + // In the old code if it came later disaster could ensue, but now it would be ok. + // Wait for MM TMSIReallocationComplete (0x055b). + return MachineStatusOK; + } else { + // Do not send a TMSI assignment, just an LU Accept. + channel()->l3sendm(L3LocationUpdatingAccept(gBTS.LAI(),true)); + return statePostAccept(); + } +} + +MachineStatus LUFinish::statePostAccept() +{ + LOG(DEBUG); + timerStop(TMisc1); // The mystery timer. + timerStop(TMMCancel); // all finished. + bool dorrlp; + if ((dorrlp = gConfig.getBool("Control.LUR.QueryRRLP"))) { + // Query for RRLP + // TODO: RRLP should be another procedure. + if (!sendRRLP(ludata()->mLUMobileId, channel())) { + LOG(INFO) << "RRLP request failed"; + } + } + + // If this is an IMSI attach, send a welcome message. + // (pat) This should be in the sipauthserve, not the BTS. + // (pat) We dont want to send the message on every IMSI attach, which happens whenever the phone is powered up. + // We also dont really want to send the message if the message wanders into our cell from other Range cell. + // So we only send the message if it is the first IMSI attach seen in this cell, which means we + // need a special flag for this in the TMSI table. + // For testing we can reset that flag. + LOG(DEBUG) << LOGVAR(ludata()->isImsiAttach()) << LOGVAR(ludata()->getTmsiStatus()) << LOGVAR(ludata()->store.getWelcomeSent()); + if (ludata()->store.getAuth() == AuthAuthorized) { + sendWelcomeMessage(ludata(), "Control.LUR.NormalRegistration.Message", + "Control.LUR.NormalRegistration.ShortCode", subscriber(), channel()); + } else { + sendWelcomeMessage(ludata(), "Control.LUR.OpenRegistration.Message", + "Control.LUR.OpenRegistration.ShortCode", subscriber(), channel()); + } + + // tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field. + gTMSITable.tmsiTabUpdate(getImsi(),&ludata()->store); + + + if (dorrlp) { + // DEBUG: Wait for pirates. + //L3Frame* resp = channel()->l2recv(130000); + //LOG(ALERT) << "RRLP returned " << *resp; + // (pat) The RRLP message takes 2.5 secs download best case. The response from the Nokia comes after 3.5 secs. + // So wait here for an RRLP response. + LOG(DEBUG) <<"Waiting for RRLP"; + timerStart(TMMCancel,10000,TimerAbortChan); + return MachineStatusOK; + } + + // Release the channel and return. + LOG(DEBUG) <<"MM procedure complete"; + return MachineStatusQuitTran; +} + +// The l3msg is LocationUpdatingRequest +void LURInit(const GSM::L3Message *l3msg, MMContext *mmchan) +{ + LOG(DEBUG) << mmchan; + TranEntry *tran = TranEntry::newMOMM(mmchan); + + LOG(DEBUG) <<"lockAndStart" <tranID()); + tran->lockAndStart(new LUStart(tran),(GSM::L3Message*)l3msg); +} + +// ====== State Machine LUNetworkFailure ===== + +MachineStatus LUNetworkFailure::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) +{ + switch (state) { + case stateStart: + PROCLOG(ALERT)<< "SIP authentication timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration"); + // Reject with a "network failure" cause code, 0x11. + gReports.incr("OpenBTS.GSM.MM.LUR.Timeout"); + // (pat) FIXME: I am faithfully duplicating the 4 second delay, but we should find out what + // message we are expecting so we can finish if we see it. + // Is this T3213 - location updating failure in the MS? + // (pat) I believe this 4 delay is supposed to be T3111, but is it inapplicable to Location Updating; + // even though it is defined in the RR timers, I think it is only applicable to a CC "L3Disconnect" because + // the reason cited for the delay is to allow time for additional messages, but there would not be any for a low level delay. + // There is a T3111 timer on channel release code in GSML1FEC, and this is redundant. + // Another thought: maybe the channel close prejudicially closes the LAP2Dm communication, and this is to give LAP2Dm a chance to + // get the message through. + //onTimeout1(4000,stateAuthFail); + timerStart(TMMCancel,4000,TimerAbortChan); // Mystery timer. + // We dont unauthorize because it is not the MS fault. + channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::NetworkFailure)); + return MachineStatusOK; + default: + return unexpectedState(state,l3msg); + } +} + +// ====== State Machine L3RegisterMachine ===== + +L3RegisterMachine::L3RegisterMachine(TranEntry *wTran, + SIP::DialogType wMethod, + string &wSRES, // may be NULL for the initial registration query to elicit a + RegistrationResult *wRResult // Result returned here: true (1), false(0), timeout (-1). +) : + LUBase(wTran), + mSRES(wSRES), + mRResult(wRResult) +{ + PROCLOG(DEBUG)<<"ProcedureRegister"<text())<subscriber(),mRResult->mRand,mSRES,channel()); + return MachineStatusOK; + + case L3CASE_SIP(dialogActive): { + int status = sipmsg->sipStatusCode(); + const DialogAuthMessage *amsg = dynamic_cast(sipmsg); + if (amsg == NULL) { + LOG(ERR) << "L3RegisterMachine could not convert DialogAuthMessage " << sipmsg; + mRResult->regSetError(); + return MachineStatusPopMachine; + } + // This should be an assert, but we dont want to crash: + if (status != 200) { PROCLOG(ERR) << "unexpected"<subscriberIMSI().c_str(),amsg->dmKc, amsg->dmPAssociatedUri, amsg->dmPAssertedIdentity); + ludata()->store.setKc(amsg->dmKc); + ludata()->store.setAssociatedUri(amsg->dmPAssociatedUri); + ludata()->store.setAssertedIdentity(amsg->dmPAssertedIdentity); + PROCLOG(INFO) << "REGISTER success"; + mRResult->regSetSuccess(); + return MachineStatusPopMachine; + } + case L3CASE_SIP(dialogFail): { + int sipCode = sipmsg->sipStatusCode(); + switch (sipCode) { + case 401: { // SIP 401 "Unauthorized" + //string wRANDresponse = SIP::randy401(sipmsg); + const DialogChallengeMessage *challenge = dynamic_cast(sipmsg); + if (challenge == NULL) { + LOG(ERR) << "L3RegisterMachine could not convert DialogChallengeMessage " << sipmsg; + mRResult->regSetError(); + return MachineStatusPopMachine; + } + string wRANDresponse = challenge->dmRand; + // if rand is included on 401 unauthorized, then the challenge-response game is afoot + if (wRANDresponse.length() != 0) { + PROCLOG(INFO) << "REGISTER challenge RAND=" << wRANDresponse; + mRResult->regSetChallenge(wRANDresponse); + break; + } else { + // The Registrar disallowed this IMSI without a challenge. + goto defaultcase; + } + devassert(0); // We do not arrive here. + break; + } + default: + defaultcase: + // If the Registrar specified the reject code in our SIP private header, use it, otherwise + // translate the SIP result code into a reject cause using getRejectCause(). + const DialogChallengeMessage *challenge = dynamic_cast(sipmsg); + MMRejectCause rejectCause = L3RejectCause::Zero; // unused init to shut up gcc. + if (challenge && challenge->dmRejectCause) { + rejectCause = (MMRejectCause)(int)challenge->dmRejectCause; + +#if 0 // (pat) Please dont enable this. See comments at queryForRejectCause +#endif + } else { + rejectCause = getRejectCause(sipCode); + } + mRResult->regSetFail(sipCode,rejectCause); + PROCLOG(INFO) << "REGISTER fail -- unauthorized" < +//#include // for auto_ptr, shared_ptr +#include "L3StateMachine.h" +#include "TMSITable.h" +#include +#include + + +namespace SIP { class DialogMessage; }; + +namespace Control { +using namespace GSM; + +void NewPagingResponseHandler(const L3PagingResponse* resp, MMContext* mmchan); +void NewCMServiceResponder(const L3CMServiceRequest* cmsrq, MMContext* mmchan); + + + +// (pat) LISTEN UP! Before you touch this class hiearchy: +// There is a major fauxpax in C++ that all virtual classes bypass the class hierarchy and are finalized only in the most derived class. +// I dont see any implementation reason for this, so it is just speced this way arbitrarily. +// Therefore if a virtual method in a virtual base class is over-ridden in an intermediate class in a hierarchy involving multiple paths +// to a final derived class, there must also be a unique method defined in the final derived class. +// This is true if any class in the path is virtual, so the the virtual class basically contaminates the hierarchy +// and makes virtual methods nearly worthless in that hierarchy. It is a pretty big oops. +// The consequence for us in our attempt to create state machines using protected methods to implement the states is that +// it creates a problem with shared data. The shared data wants to be in a virtual class common to all the classes implementing state machines. +// However, that same virtual class cannot be in the path of the virtual methods implementing the states. +// Therefore the shared data must be in a separate class so that the main path to virtual methods is not contaminated by 'virtual' anywhere. +// The alternatives are either to put the data in a separately allocated class and use pointers, or put virtual accessor methods in +// the LUBase class and the data itself in the most derived class. +// Another problem to beware is if the final derived class defines a pure virtual method, that seems to over-ride the definition of the same method +// in the intermediate classes, which may be a bug. + +class L3IdentifyMachine : public MachineBase +{ + const GSM::L3MobileIdentity mMobileID; // We make a copy because the original disappears. + bool *mResultPtr; + + protected: + enum States { + stateStart, + }; + MachineStatus machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage*sipmsg); + + public: + // On success the resultant imsi is placed in wTran->subscriber. + L3IdentifyMachine(TranEntry *wTran, + const GSM::L3MobileIdentity &wMobileID, + bool *wResultPtr) // Returns true on success, false on failure. + : MachineBase(wTran), mMobileID(wMobileID), mResultPtr(wResultPtr) + {} + + const char *debugName() const { return "L3IdentifyMachine"; } +}; + +enum RegistrationStatus { + RegistrationUninitialized, + // We distinguish the "Error" case from "Fail" case, the latter meaning rejection by Registrar. + RegistrationError, // Something went wrong: network failure, invalid SIP message, etc. + RegistrationChallenge, + RegistrationSuccess, + RegistrationFail, // Rejected by Registrar, in which case the mSipCode tells why. +}; + +struct RegistrationResult { + RegistrationStatus mRegistrationStatus; // Over-all result from Registrar. + MMRejectCause mRejectCause; // Only if mRegistrationStatus == RegistrationFail. + unsigned mSipCode; // Registration result SIP code, used only for error messages. + string mRand; // The authentication challenge rand. + RegistrationResult() : mRegistrationStatus(RegistrationUninitialized), mRejectCause(L3RejectCause::Zero) , mSipCode(0) + {} + void regSetSuccess() { mRegistrationStatus = RegistrationSuccess; } + void regSetError() { mRegistrationStatus = RegistrationError; } + void regSetFail(unsigned sipCode, MMRejectCause cause) { mRegistrationStatus = RegistrationFail; mSipCode = sipCode; mRejectCause = cause; } + void regSetChallenge(string wRand) { mRegistrationStatus = RegistrationChallenge; mRand = wRand; } + bool isValid() { return mRegistrationStatus != RegistrationUninitialized; } + bool isSuccess() { return mRegistrationStatus == RegistrationSuccess; } + string text(); +}; + +// Follow on Proceed flag in LocationUpdateAccept, dont release RR connection until T3255, value not specified in spec. + +// Making the LUStart be base classes did not work because +// we need a final override for each of the states in each virtual base class. +// The MMSharedData must not contain MachineBase as a base class because virtual methods +// and virtual classes do not mix well. + +// MS sends IMSI +// A. authorize +// if authorization failure: +// TMSITable set AUTH_STATUS = 0. Do we want to delete the record? +// exit +// if authorization success: +// if existing record: +// TMSITable update lai, classmark, AUTH_STATUS +// send assignedTmsi to handset. +// else: +// assignedTmsi = allocate new TMSI. +// TMSITable update everything. +// send newTmsi to handset. +// MS sends TMSI +// oldTmsi = tmsi. +// TMSITable get IMSI for oldTmsi. +// if no record, get IMSI. +// authorize +// if authorization success +// TMSITable update accessed, and everything else in case it changed. +// auth failure: +// We do not delete the record because it may belong to someone else. +// get IMSI +// if IMSI matches record, exit. +// goto A. +// + + // The LUR may have been using a TMSI or IMSI. + // If we get a TMSI we try authentication, but if we fail, it may be a TMSI collision, + // so at that point we have to query for IMSI and retry authorization. + enum TmsiStatus { + tmsiNone, // We dont have a TMSI. + tmsiProvisional, // MS sent a TMSI that was found in the TMSI table but has not been authenticated yet. + tmsiAuthenticated, // MS sent a TMSI that was found in the TMSI table (started as tmsiProvisional) and authenticated ok. + tmsiNotAssigned, // MS sent an IMSI that was found in the TMSI table. + tmsiFailed, // TMSI failed authentication. + tmsiNew, // We allocated a new TMSI for this MS. + }; + + // This class holds the common data for all LocationUpdating sub-states. + // We save up everything until we have authenticated the MS and then put it in the TMSI table only if MS authenticates ok. + // Previously it was included as virtual by every Procedure, but now it lives in the TransactionEntry. + class MMSharedData { + uint32_t mAssignedTmsi; // a TMSI that has been assigned to the MS either now or in the past. + TmsiStatus mTmsiStatus; + public: + GSM::L3MobileIdentity mLUMobileId; // Copy saved from original request. + GSM::L3LocationAreaIdentity mLULAI; // Copy saved from original request. + LocationUpdateType mLUType; + uint32_t mOldTmsi; // The tmsi sent in LUR, which is irrelevant if the LAI is not ours, but saved only for reporting purposes. + //string mRAND; + TmsiTableStore store; // In-memory storage for stuff in the TMSI_TABLE. + GSM::MobileIDType mQueryType; // What mobileId did we last request: IMSIType or IMEIType? + RegistrationResult mRegistrationResult; + // If we received or queried for IMSI (as opposed to registration by TMSI) we set mFullQuery so that + // in this case we will also optionally query for IMEI. + Bool_z mFullQuery; + // The second attempt occurs if registration by TMSI fails, so we have to register by IMSI. + // This has nothing to do with keeping track of the 2 register messages for each over-all registration attempt. + Bool_z mSecondAttempt; + string mPrevRegisterAttemptImsi; + Bool_z mExpectingTmsiReallocationComplete; +#if CACHE_AUTH + Bool_z mUsingCachedAuthentication; +#endif + + // Only prints the subset that is interesting for debugging. + string text() { + return format("AssignedTmsi=0x%x status=%d RegistrationResult=%s",mAssignedTmsi,mTmsiStatus, + mRegistrationResult.text().c_str()); + //return format("AssignedTmsi=0x%x status=%d RegistrationResult=%s rand=%s",mAssignedTmsi,mTmsiStatus, + // mRegistrationResult.text().c_str(),mRAND.c_str()); + } + + // You must set the tmsistatus of any tmsi you assign, so we wrap that in a method. + void setTmsi(uint32_t tmsi,TmsiStatus status) { mAssignedTmsi = tmsi; mTmsiStatus = status; } + // But you can update the tmsi status without changing the tmsi. + void setTmsiStatus(TmsiStatus status) { mTmsiStatus = status; } + TmsiStatus getTmsiStatus() { return mTmsiStatus; } + + //bool haveTmsi() { return mTmsiStatus != tmsiNone && mTmsiStatus ; } + // Is this a brand new registration? + // FIXME THIS needs to check that authorization has changed + // Need two variables: one for new authorization, one for welcome message. + // bool isFirstTime() { return mTmsiStatus == tmsiNone || mTmsiStatus == tmsiNew; } + bool isImsiAttach() { return this->mLUType == LUTImsiAttach; } + // Is this the initial attach on this BTS? Any attach type except periodic updating. + bool isInitialAttach() { return this->mLUType == LUTImsiAttach || this->mLUType == LUTNormalLocationUpdating; } + bool needsTmsiAssignment() { return mTmsiStatus == tmsiNew || mTmsiStatus == tmsiNotAssigned; } + uint32_t getTmsi() { return mAssignedTmsi; } + + MMSharedData() : + mAssignedTmsi(0), mTmsiStatus(tmsiNone), mOldTmsi(0), mQueryType(GSM::NoIDType) + {} + }; + + class LUBase: public MachineBase { + protected: + MMSharedData* ludata() const; + public: + LUBase(TranEntry *tran) : MachineBase(tran) {} + //L3RejectCause getRejectCause(); + bool openRegistration() const; + bool failOpen() const; + // Return a persistent IMSI string that will not go away + string getImsi() const; + const char * getImsiCh() const; + uint32_t getTmsi() const { return ludata()->getTmsi(); } + const string getImsiName() const; + FullMobileId &subscriber() const; + }; + + // Initial identification phase of LU - Location Updating. + class LUStart : public LUBase /*, public virtual LUSharedData*/ { + MachineStatus sendQuery(GSM::MobileIDType); + public: + enum State { // There is no integral start state because the state machine start state receives the LUR message. + stateSecondAttempt, + stateHaveImsi, + stateHaveIds, + stateRegister1Response, + }; + MachineStatus stateRecvLocationUpdatingRequest(const GSM::L3LocationUpdatingRequest*); + MachineStatus stateRecvIdentityResponse(const GSM::L3IdentityResponse *); + //MachineStatus stateExpiredT3260(); + public: + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg=0, const SIP::DialogMessage *sipmsg=0); + public: + LUStart(TranEntry *wTran) : LUBase(wTran) {} + friend class L3ProcedureLocationUpdate; + const char *debugName() const { return "LUStart"; } + } /*mLUStart*/; + + class LUAuthentication: public LUBase /*, public virtual LUSharedData*/ { + enum States { + stateStart, + stateRegister2Response + }; + + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg=0, const SIP::DialogMessage *sipmsg=0); + public: + LUAuthentication(TranEntry *wTran) : LUBase(wTran) {} + const char *debugName() const { return "LUAuthentication"; } + } /*mLUAuthentication*/; + + class LUFinish: public LUBase /*, public virtual LUSharedData*/ { + enum States { + stateStart, + stateRegister2Response, + stateLUAcceptTimeout, + }; + MachineStatus stateExpiredT3270(); + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg=0); + MachineStatus statePostAccept(); + MachineStatus stateQueryClassmark(); + MachineStatus stateSendLUResponse(); + //MachineStatus stateLUAcceptTimeout(); + public: + LUFinish(TranEntry *wTran) : LUBase(wTran) {} + const char *debugName() const { return "LUFinish"; } + } /*mLUFinish*/; + + class LUNetworkFailure: public LUBase /*, public virtual LUSharedData*/ { + enum States { + stateStart + }; + MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg=0); + public: + const char *debugName() const { return "LUNetworkFailure"; } + LUNetworkFailure(TranEntry *wTran) : LUBase(wTran) {} + } /*mLUNetworkFailure*/; + +void LURInit(const GSM::L3Message *l3msg, MMContext *dcch); + + +class L3RegisterMachine : public LUBase //MachineBase +{ + // I started using the engine in the transaction, but we have to create + // a new callid for each transaction, and the easiest way was to create a new SIPEngine. + // Update: Now just changing the call_id of the SIPEngine + string mSRES; + RegistrationResult *mRResult; + + protected: + enum States { // Only state 0 is used, so dont bother with an enum. + stateStart, + }; + MachineStatus machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage*sipmsg); + + public: + L3RegisterMachine(TranEntry *wTran, + SIP::DialogType wMethod, + string &wSRES, // may be NULL for the initial registration query to elicit a + RegistrationResult *wResult // Result returned here: true (1), false(0), timeout (-1). + ); + const char *debugName() const { return "L3RegisterMachine"; } + SIP::SipMessage *makeRegisterMsg1(); +}; + +}; // namespace Control +#endif diff --git a/Control/L3SMSControl.cpp b/Control/L3SMSControl.cpp new file mode 100644 index 0000000..ead1184 --- /dev/null +++ b/Control/L3SMSControl.cpp @@ -0,0 +1,674 @@ +/* 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 distribuion. +* +* 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. +*/ + +#define LOG_GROUP LogGroup::Control + +#include "ControlCommon.h" +#include "L3StateMachine.h" +#include "L3TranEntry.h" +#include "L3MMLayer.h" +#include "L3SMSControl.h" +#include +//#include +#include +#include +#include +#include + + +namespace Control { +using namespace GSM; +using namespace SMS; +using namespace SIP; + +struct SMSCommon : public MachineBase { + unsigned mRpduRef; + SMSCommon(TranEntry *tran) : MachineBase(tran) {} + void l3sendSms(const L3Message &msg, SAPI_t sapi); // Send an SMS message to the correct place. + L3LogicalChannel *getSmsChannel() const; +}; + + +class MOSMSMachine : public SMSCommon +{ + enum State { // These are the machineRunState states for our State Machine. + stateStartUnused, // unused. + stateIdentResult + }; + SmsState mSmsState; + bool mIdentifyResult; + MachineStatus machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg); + bool handleRPDU(const RLFrame& RPDU); + public: + MOSMSMachine(TranEntry *tran) : SMSCommon(tran), mSmsState(MoSmsIdle) {} + const char *debugName() const { return "MOSMSMachine"; } + friend void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan); +}; + +class MTSMSMachine : public SMSCommon +{ + enum State { // These are the machineRunState states for our State Machine. + stateStart, + }; + // Use machineRunState1 so we can get the ESTABLISH primitive: + MachineStatus machineRunState1(int state, const L3Frame *frame, const L3Message *l3msg, const SIP::DialogMessage *sipmsg); + bool handleRPDU(const RLFrame& RPDU); + bool createRPData(RPData &rp_data); + public: + MTSMSMachine(TranEntry *tran) : SMSCommon(tran) {} + const char *debugName() const { return "MTSMSMachine"; } +}; + + +L3LogicalChannel *SMSCommon::getSmsChannel() const +{ + if (channel()->isSDCCH()) { + return channel(); // Use main SDCCH. + } else { + assert(channel()->isTCHF()); + return channel()->getSACCHL3(); // Use SACCH associated with TCH. + } +} + +void SMSCommon::l3sendSms(const L3Message &msg, SAPI_t sapi) +{ + getSmsChannel()->l3sendm(msg,GSM::DATA,sapi); +} + + +/** + Process the RPDU. + @param mobileID The sender's IMSI. + @param RPDU The RPDU to process. + @return true if successful. +*/ +bool MOSMSMachine::handleRPDU(const RLFrame& RPDU) +{ + LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI(); + switch ((RPMessage::MessageType)RPDU.MTI()) { + case RPMessage::Data: { + string contentType = gConfig.getStr("SMS.MIMEType"); + ostringstream body; + + if (contentType == "text/plain") { + // TODO: Clean this mess up! + RPData data; + data.parse(RPDU); + TLSubmit submit; + submit.parse(data.TPDU()); + + body << submit.UD().decode(); // (pat) TODO: efficiencize this. + } else if (contentType == "application/vnd.3gpp.sms") { + RPDU.hex(body); + } else { + LOG(ERR) << "\"" << contentType << "\" is not a valid SMS payload type"; + } + // (pat) In this case the recipient address is buried in the message somewhere. + string address = gConfig.getStr("SIP.SMSC"); + + /* The SMSC is not defined, we are using an older version */ + if (address.length() == 0) { + RPData data; + data.parse(RPDU); + TLSubmit submit; + submit.parse(data.TPDU()); + + address = string(submit.DA().digits()); + } + + // Steps: + // 1 -- Complete transaction record. + // 2 -- Send TL-SUBMIT to the server. + // 3 -- Wait for response or timeout. + // 4 -- Return true for OK or ACCEPTED, false otherwise. + + // Step 1 and 2 -- Complete the transaction record and send TL-SUMBIT to server. + // Form the TLAddress into a CalledPartyNumber for the transaction. + // Attach calledParty and message body to the transaction. + SipDialog::newSipDialogMOSMS(tran()->tranID(), tran()->subscriber(), address, body.str(), contentType); + return true; + } + case RPMessage::Ack: + case RPMessage::SMMA: + return true; + case RPMessage::Error: + default: + return false; + } +} + +// see: Control::MOSMSController +MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + // See GSM 04.11 Arrow Diagram A5 for the transaction (pat) NO, A5 is for GPRS. Closest diagram is F1. + // SIP->Network message. + // Step 1 MS->Network CP-DATA containing RP-DATA with message + // Step 2 Network->MS CP-ACK + // 4.11 6.2.2 State wait-for-RP-ACK, timer TR1M + // Step 3 Network->MS CP-DATA containing RP-ACK or RP-Error + // Step 4 MS->Network CP-ACK + + // LAPDm operation, from GSM 04.11, Annex F: + // """ + // Case A: Mobile originating short message transfer, no parallel call: + // The mobile station side will initiate SAPI 3 establishment by a SABM command + // on the DCCH after the cipher mode has been set. If no hand over occurs, the + // SAPI 3 link will stay up until the last CP-ACK is received by the MSC, and + // the clearing procedure is invoked. + // """ + + WATCHF("MOSMS state=%x\n",state); + PROCLOG2(DEBUG,state)<subscriber()); + switch (state) { + case L3CASE_MM(CMServiceRequest): { + timerStart(TCancel,30*1000,TimerAbortTran); // Just in case. + // This is both the start state and a request to start a new MO SMS when one is already in progress, as per GSM 4.11 5.4 + const L3CMServiceRequest *req = dynamic_cast(l3msg); + const GSM::L3MobileIdentity &mobileID = req->mobileID(); // Reference ok - the SM is going to copy it. + + // FIXME: We only identify this the FIRST time. + // The L3IdentifySM can check the MM state and just return. + // FIXME: check provisioning + return machPush(new L3IdentifyMachine(tran(),mobileID, &mIdentifyResult), stateIdentResult); + } + case stateIdentResult: { + if (! mIdentifyResult) { + //const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation); + l3sendSms(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation),SAPI0); + return MachineStatusQuitTran; + } + + // Let the phone know we're going ahead with the transaction. + // The CMServiceReject is on SAPI 0, not SAPI 3. + PROCLOG(DEBUG) << "sending CMServiceAccept"; + // Update 8-6-2013: The nokia does not accept this message on SACCH SAPI 0 for in-call SMS; + // so I am trying moving it to the main channel. + //l3sendSms(GSM::L3CMServiceAccept(),SAPI0); + channel()->l3sendm(GSM::L3CMServiceAccept(),GSM::DATA,SAPI0); + + gReports.incr("OpenBTS.GSM.SMS.MOSMS.Start"); + return MachineStatusOK; + } + +#if FIXME + case L3CASE_ERROR: { + // (pat) TODO: Call this on parsel3 error... + // TODO: Also send an error code to the sip side, if any. + + l3sendSms(CPError(getL3TI()),SAPI3); + return MachineStatusQuitTran; + } +#endif + + case L3CASE_SMS(DATA): { + timerStop(TCancel); + timerStart(TR1M,TR1Mms,TimerAbortTran); + // Step 0: Wait for SAP3 to connect. + // The first read on SAP3 is the ESTABLISH primitive. + // That was done by our caller. + //delete getFrameSMS(LCH,GSM::ESTABLISH); + + // Step 1: This is the first message: CP-DATA, containing RP-DATA. + unsigned L3TI = l3msg->TI() | 0x08; + tran()->setL3TI(L3TI); + + const CPData *cpdata = dynamic_cast(l3msg); + if (cpdata == NULL) { // Currently this is impossible, but maybe someone will change the code later. + l3sendSms(CPError(L3TI),SAPI3); + return MachineStatusQuitTran; + } + + // Step 2: Respond with CP-ACK. + // This just means that we got the message and could parse it. + PROCLOG(DEBUG) << "sending CPAck"; + l3sendSms(CPAck(L3TI),SAPI3); + + // (pat) The SMS message has already been through L3Message:parseL3, which called SMS::parseSMS(source), which manufactured + // a CPMessage::CPData and called L3Message::parse() which called CPData::parseBody which called L3Message::parseLV, + // which called CPUserData::parseV to leave the result in L3Message::CPMessage::CPData::mData. + // As the mathemetician said after filling 3 blackboards with formulas: It is obvious! + + // FIXME -- We need to set the message ref correctly, even if the parsing fails. + // The compiler gives a warning here. Let it. It will remind someone to fix it. + // (pat) Update: If we cant parse far enough to get the ref we send a CPError that does not need the ref. +#if 0 + unsigned ref; + bool success = false; + try { + // (pat) hierarchy is L3Message::CPMessage::CPData; L3Message::parse calls CPData::parseBody. + CPData data; + data.parse(*CM); + LOG(INFO) << "CPData " << data; + // Transfer out the RPDU -> TPDU -> delivery. + ref = data.RPDU().reference(); + // This handler invokes higher-layer parsers, too. + success = handleRPDU(transaction,data.RPDU()); + } + catch (SMSReadError) { + LOG(WARNING) << "SMS parsing failed (above L3)"; + // Cause 95, "semantically incorrect message". + LCH->l3sendf(CPData(L3TI,RPError(95,ref)),3); + delete CM; + throw UnexpectedMessage(); + } + catch (GSM::L3ReadError) { + LOG(WARNING) << "SMS parsing failed (in L3)"; + delete CM; + throw UnsupportedMessage(); + } + delete CM; +#endif + + // Step 3 + // Send CP-DATA containing message ref and either RP-ACK or RP-Error. + // If we cant parse the message, we send RP-Error immeidately, otherwise we wait for the dialog to finish one way or the other. + const RLFrame &rpdu = cpdata->data().RPDU(); + this->mRpduRef = rpdu.reference(); + bool success = false; + try { + // This creates the outgoing SipDialog to send the message. + success = handleRPDU(rpdu); + } catch (...) { + LOG(WARNING) << "SMS parsing failed (above L3)"; + } + + if (! success) { + PROCLOG(INFO) << "sending RPError in CPData"; + // Cause 95 is "semantically incorrect message" + l3sendSms(CPData(L3TI,RPError(95,mRpduRef)),SAPI3); + } + mSmsState = MoSmsWaitForAck; + LOG(DEBUG) << "case DATA returning"; + return MachineStatusOK; + } + + case L3CASE_SIP(dialogBye): { // SIPDialog sends this when the MESSAGE clears. + PROCLOG(INFO) << "sending RPAck in CPData"; + l3sendSms(CPData(getL3TI(),RPAck(mRpduRef)),SAPI3); + LOG(DEBUG) << "case dialogBye returning"; + } + case L3CASE_SIP(dialogFail): { + PROCLOG(INFO) << "sending RPError in CPData"; + // TODO: Map the dialog failure state to an RPError state. + // Cause 127 is "internetworking error, unspecified". + // See GSM 04.11 8.2.5.4 Table 8.4. + l3sendSms(CPData(getL3TI(),RPError(127,mRpduRef)),SAPI3); + LOG(DEBUG) << "case dialogFail returning"; + } + + case L3CASE_SMS(ACK): { + timerStop(TR1M); + // Step 4: Get CP-ACK from the MS. + const CPAck *cpack = dynamic_cast(l3msg); + PROCLOG(INFO) << "CPAck " << cpack; + + gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete"); + + /* MOSMS RLLP request */ + //if (gConfig.getBool("Control.SMS.QueryRRLP")) { + // Query for RRLP + //if (!sendRRLP(mobileID, LCH)) { + // LOG(INFO) << "RRLP request failed"; + //} + //} + + // Done. + mSmsState = MoSmsMMConnection; + // TODO: if (set) set->mmCallFinished(); + LOG(DEBUG) << "case ACK returning"; + // This attach causes any pending MT transactions to start now. + gMMLayer.mmAttachByImsi(channel(),tran()->subscriberIMSI()); + return MachineStatusQuitTran; + } + + default: + LOG(DEBUG) << "unexpected state"; + return unexpectedState(state,l3msg); + } +} + +#if UNUSED +// Return the state of the current SMS procedure, if any. +SmsState getCurrentSMSState() +{ + MMContext *set = channel()->chanGetContext(true); + TranEntry *sms = set->getTran(MMContext::TE_MOSMS1); + if (! sms) { return SmsNonexistent; } + + MachineBase *base = sms->currentProcedure(); + if (base) { // This should be an assert. + MOSMSMachine *smssm = dynamic_cast(base); + if (smssm) { // This too. + return smssm->mSmsState; + } + } + return SmsNonexistent; +} +#endif + +// There can be a max of two simultaneous MO-SMS. +// The CM Service Request to start a new MO-SMS during an existing one may arrive before the final ACK of the previous MO-SMS, as per GSM 4.11 5.4 +// Therefore there are two MO-SMS possible: MMContext::TE_MOSMS1 is the old one and TE_MOSMS2 is the new one. +void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan) +{ + LOG(DEBUG) <chanGetContext(true); + RefCntPointer prevMOSMS = mmchan->mmGetTran(MMContext::TE_MOSMS1); + if (! prevMOSMS.self()) { + // happiness. + //set->setTran(MMContext::TE_MOSMS1,tran); + //tran->teConnectChannel(dcch,TE_MOSMS1); + } else { + // Check for perfidy on the part of the MS: it cannot start a new MO-SMS unless the previous is nearly finished. + //SmsState smsState = getCurrentSMSState(); + MachineBase *base = prevMOSMS->currentProcedure(); + bool badbunny = false; + if (base) { // This may happen if the MS is a bad bunny and sends two CM Sevice Requests effectively simultaneously. + MOSMSMachine *smssm = dynamic_cast(base); + if (! smssm || smssm->mSmsState != MoSmsWaitForAck) { + badbunny = true; + } + } else { + badbunny = true; + } + + if (badbunny) { + LOG(ERR) << "Received new MO-SMS before previous MO-SMS completed"< prevMOSMS2 = mmchan->mmGetTran(MMContext::TE_MOSMS2); + if (prevMOSMS2.self()) { + LOG(ERR) <<"Received third simultaneous MO-SMS, which is illegal:"<teCancel(); // Promotes TE_MOSMS2 to TE_MOSMS1 + devassert(mmchan->mmGetTran(MMContext::TE_MOSMS2) == NULL); + } + //mmchan->setTran(MMContext::TE_MOSMS2,tran); + //tran->teConnectChannel(mmchan,MMContext::TE_MOSMS2); + } + TranEntry *tran = TranEntry::newMOSMS(mmchan); + + // Fire up an SMS state machine for this transaction. + MOSMSMachine *mocp = new MOSMSMachine(tran); + // The message is CMServiceRequest. + tran->lockAndStart(mocp,(GSM::L3Message*)l3msg); +} + +// Return true on success. +bool MTSMSMachine::createRPData(RPData &rp_data) +{ +#if 0 + // HACK -- Check for "Easter Eggs" + // TL-PID + unsigned TLPID=0; + if (strncmp(message,"#!TLPID",7)==0) sscanf(message,"#!TLPID%d",&TLPID); + + unsigned reference = random() % 255; + //CPData deliver(L3TI, + rp_data = RPData(reference, + RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()), + TLDeliver(callingPartyDigits,message,TLPID)); +#else + // TODO: Read MIME Type from smqueue!! + + const char *contentType = tran()->mContentType.c_str(); + PROCLOG(DEBUG)<mMessage); + if (strncmp(contentType,"text/plain",10)==0) { + TLAddress tlcalling = TLAddress(tran()->calling().digits()); + TLUserData tlmessage = TLUserData(tran()->mMessage.c_str()); + PROCLOG(DEBUG)<mRpduRef, + RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()), + TLDeliver(tlcalling,tlmessage,0)); + } else if (strncmp(contentType,"application/vnd.3gpp.sms",24)==0) { + BitVector2 RPDUbits(strlen(tran()->mMessage.c_str())*4); + if (!RPDUbits.unhex(tran()->mMessage.c_str())) { + LOG(WARNING) << "Hex string parsing failed (in incoming SIP MESSAGE)"; + //throw UnexpectedMessage(); + return false; + } + + try { + RLFrame RPDU(RPDUbits); + LOG(DEBUG) << "SMS RPDU: " << RPDU; + + rp_data.parse(RPDU); + LOG(DEBUG) << "SMS RP-DATA " << rp_data; + } + catch (SMSReadError) { + LOG(WARNING) << "SMS parsing failed (above L3)"; + // Cause 95, "semantically incorrect message". + //LCH->l2sendf(CPData(L3TI,RPError(95,this->mRpduRef)),3); + //throw UnexpectedMessage(); + return false; + } + catch (GSM::L3ReadError) { + LOG(WARNING) << "SMS parsing failed (in L3)"; + // TODO:: send error back to the phone + //throw UnsupportedMessage(); + return false; + } + catch (...) { + LOG(ERR) << "Unexpected throw"; // cryptic, but should never happen. + return false; + } + } else { + LOG(WARNING) << "Unsupported content type (in incoming SIP MESSAGE) -- type: " << contentType; + //throw UnexpectedMessage(); + return false; + } + return true; +#endif +} + +MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const L3Message*l3msg, const SIP::DialogMessage*sipmsg) +{ + // Step 1 Network->MS CP-DATA containing RP-DATA with message + // Step 2 MS->Network CP-ACK + // 4.11 6.2.2 State wait-to-send-RP-ACK, timer TR2M + // Step 3 MS->Network CP-DATA containing RP-ACK or RP-Error + // Step 4 Network->MS CP-ACK + // Network->SIP response. + + PROCLOG2(DEBUG,state)<subscriber()); + switch(state) { + case stateStart: { + // There is no dialog for a SMS initiated on this BTS. + if (getDialog() && getDialog()->isFinished()) { + // SIP side closed already. + // We can no longer inform the SIP side whether we succeed or not. + // Should we continue and deliver the message to the MS or not? + return MachineStatusQuitTran; + } + timerStart(TR2M,TR2Mms,TimerAbortTran); + + // Allocate Transaction Identifier + unsigned l3ti = channel()->chanGetContext(true)->mmGetNextTI(); + tran()->setL3TI(l3ti); + setGSMState(CCState::SMSDelivering); + + this->mRpduRef = random() % 255; + + gReports.incr("OpenBTS.GSM.SMS.MTSMS.Start"); + + L3LogicalChannel *smschan = getSmsChannel(); + + if (smschan->multiframeMode(3)) { goto step1; } // If already established. + + // Start ABM in SAP3. + smschan->l3sendp(GSM::ESTABLISH,SAPI3); + // Wait for SAP3 ABM to connect. + // The next read on SAP3 should be the ESTABLISH primitive. + // This won't return NULL. It will throw an exception if it fails. + // (pat) WARNING: Previous code waited for a return ESTABLISH, + // but I think the l3sendp(ESTABLISH) will hang until this happens so it is now a no-op. + // delete getFrameSMS(LCH,GSM::ESTABLISH); + LOG(DEBUG) << "case start returning, after sending ESTABLISH"; + return MachineStatusOK; // Wait for the ESTABLISH on the uplink. + } + + case L3CASE_PRIMITIVE(ESTABLISH): { + step1: + + // Step 1 + // Send the first message. + // CP-DATA, containing RP-DATA. + + RPData rp_data; + int l3ti = getL3TI(); + if (! createRPData(rp_data)) { + l3sendSms(CPData(l3ti,RPError(95,this->mRpduRef)),SAPI3); + // TODO: Is this correct? + // TODO: Make sure MachineStatusQuitTran sends a failure code to SIP. + if (getDialog()) getDialog()->MTSMSReply(400, "Bad Request"); + return MachineStatusQuitTran; + } + + CPData deliver(l3ti,rp_data); + PROCLOG(INFO) << "sending " << deliver; + // WORKING: MS Does not respond to this message. + // Probably the messages are not hooked properly + l3sendSms(deliver,SAPI3); + LOG(DEBUG) << "case ESTABLISH returning, after receiving ESTABLISH"; + return MachineStatusOK; // Wait for CP-ACK message. + } + + // Step 2 + // Get the CP-ACK. + case L3CASE_SMS(ACK): { + // FIXME -- Check reference and check for RPError. + return MachineStatusOK; // Now we are waiting for CP-DATA. + } + + // Step 3 + // Get CP-DATA containing RP-ACK and message reference. + case L3CASE_SMS(DATA): { + timerStop(TR2M); + PROCLOG(DEBUG) << "MTSMS: data from MS " << *l3msg; + // FIXME -- Check L3 TI. + + // Parse to check for RP-ACK. + // We already called parsel3 on the message. + //CPData data; + //try { + // data.parse(*CM); + // LOG(DEBUG) << "CPData " << data; + //} + //catch (SMSReadError) { + // LOG(WARNING) << "SMS parsing failed (above L3)"; + // // Cause 95, "semantically incorrect message". + // LCH->l2sendf(CPError(L3TI,95),3); + // throw UnexpectedMessage(); + //} + //catch (GSM::L3ReadError) { + // LOG(WARNING) << "SMS parsing failed (in L3)"; + // throw UnsupportedMessage(); + //} + //delete CM; + + const CPData *cpdata = dynamic_cast(l3msg); + + // FIXME -- Check SMS reference. + + bool success = true; + if (cpdata->RPDU().MTI()!=RPMessage::Ack) { + PROCLOG(WARNING) << "unexpected RPDU " << cpdata->RPDU(); + success = false; + } + + gReports.incr("OpenBTS.GSM.SMS.MTSMS.Complete"); + + // Step 4 + // Send CP-ACK to the MS. + PROCLOG(INFO) << "MTSMS: sending CPAck"; + l3sendSms(CPAck(getL3TI()),SAPI3); + + // Ack in SIP domain. + if (!getDialog()) { + LOG(DEBUG) << "No dialog found for MTSMS; could be welcome message, CLI SMS, or Dialog pre-destroyed error"; + } else if (success) { + getDialog()->MTSMSReply(200,"OK"); + } else { + getDialog()->MTSMSReply(400, "Bad Request"); + } + + LOG(DEBUG) << "case DATA returning"; + return MachineStatusQuitTran; // Finished. + } + default: + return unexpectedState(state,l3msg); + } +} + +void initMTSMS(TranEntry *tran) +{ + tran->teSetProcedure(new MTSMSMachine(tran)); + //tran->lockAndStart(new MTSMSMachine(tran)); +} + +#if UNUSED // (pat) what was I thinking here? +// Parse an incoming SMS message into RPData, save everything else we need. +// We do this immediately upon reception of a SIP message to error check it before queueing it for delivery to an MS. +// Return result or NULL on failure. SIP should return error 400 "Bad Request" in this case. +RPData *parseSMS(const char *callingPartyDigits, const char* message, const char* contentType) +{ + // TODO: Read MIME Type from smqueue!! + unsigned reference = random() % 255; + RPData *rp_data = NULL; + + if (strncmp(contentType,"text/plain",10)==0) { + rp_data = new RPData(reference, + RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()), + TLDeliver(callingPartyDigits,message,0)); + } else if (strncmp(contentType,"application/vnd.3gpp.sms",24)==0) { + BitVector2 RPDUbits(strlen(message)*4); + if (!RPDUbits.unhex(message)) { + LOG(WARNING) << "Hex string parsing failed (in incoming SIP MESSAGE)"; + return NULL; + } + + try { + RLFrame RPDU(RPDUbits); + LOG(DEBUG) << "SMS RPDU: " << RPDU; + + rp->data = new RPData(); + rp_data->parse(RPDU); + LOG(DEBUG) << "SMS RP-DATA " << rp_data; + } + catch (SMSReadError) { + LOG(WARNING) << "SMS parsing failed (above L3)"; + delete rp_data; rp_data = NULL; + } + catch (GSM::L3ReadError) { + LOG(WARNING) << "SMS parsing failed (in L3)"; + delete rp_data; rp_data = NULL; + } + catch (...) { // Should not happen, but be safe. + LOG(WARNING) << "SMS parsing failed (unexpected error)"; + delete rp_data; rp_data = NULL; + } + return rp_data; + } else { + LOG(WARNING) << "Unsupported content type (in incoming SIP MESSAGE) -- type: " << contentType; + return NULL; + } +} +#endif + +}; diff --git a/Control/L3SMSControl.h b/Control/L3SMSControl.h new file mode 100644 index 0000000..e46a602 --- /dev/null +++ b/Control/L3SMSControl.h @@ -0,0 +1,37 @@ +/* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +*/ + +#ifndef _L3SMSCONTROL_H_ +#define _L3SMSCONTROL_H_ + +#include "L3LogicalChannel.h" +#include "ControlCommon.h" + +namespace Control { + +// 3GPP 4.11 5.2. These are the SMS states. It is nearly empty. The only one we care about is awaiting the final ack. +enum SmsState { + SmsNonexistent, // Not an SMS state; we use to mean there is no SMS transaction. + MoSmsIdle, + MoSmsWaitForAck, + // In the spec this is a catch-all for any other state. For us this is a transitory state we dont care about because + // the TranEntry will be removed momentarily. + MoSmsMMConnection, +}; + +void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *dcch); +void initMTSMS(TranEntry *tran); + +}; +#endif diff --git a/Control/L3StateMachine.cpp b/Control/L3StateMachine.cpp new file mode 100644 index 0000000..615468e --- /dev/null +++ b/Control/L3StateMachine.cpp @@ -0,0 +1,1092 @@ +/**@file Declarations for Circuit Switched State Machine and related classes. */ +/* +* 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 distribuion. +* +* 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. + +*/ +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging +#include "L3StateMachine.h" +#include "L3CallControl.h" +#include "L3TranEntry.h" +#include "L3MobilityManagement.h" +#include "L3MMLayer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace GSM; +namespace Control { + +// See documentation as class MachineStatus. +MachineStatus MachineStatusOK = MachineStatus(MachineStatus::MachineCodeOK); +MachineStatus MachineStatusPopMachine = MachineStatus(MachineStatus::MachineCodePopMachine); +MachineStatus MachineStatusQuitTran = MachineStatus(MachineStatus::MachineCodeQuitTran); +MachineStatus MachineStatusQuitChannel = MachineStatus(MachineStatus::MachineCodeQuitChannel); +MachineStatus MachineStatusAuthorizationFail = MachineStatus(MachineStatus::MachineCodeQuitChannel); +MachineStatus MachineStatusUnexpectedState = MachineStatus(MachineStatus::MachineCodeUnexpectedState); + +std::ostream& operator<<(std::ostream& os, MachineStatus::MachineStatusCode status) +{ +#define CASE1(x) case MachineStatus::x: os<<#x; break; + switch (status) { + CASE1(MachineCodeOK) + CASE1(MachineCodePopMachine) + //CASE1(MachineCodeUnexpectedMessage) + //CASE1(MachineCodeUnexpectedPrimitive) + CASE1(MachineCodeUnexpectedState) + //CASE1(MachineCodeAuthorizationFail) + CASE1(MachineCodeQuitTran) + CASE1(MachineCodeQuitChannel) + } + return os; +} + + +//void MachineBase::timerStartAbort(L3TimerId tid, unsigned val) { tran()->timerStartAbort(tid,val); } + +void MachineBase::timerStart(L3TimerId tid, unsigned val, int nextState) +{ + tran()->timerStart(tid,val,nextState); +} + +void MachineBase::timerStop(L3TimerId tid) +{ + tran()->timerStop(tid); +} + +void MachineBase::timerStopAll() +{ + tran()->timerStopAll(); +} + +void MachineBase::machineErrorMessage(int level, int state, const L3Message *l3msg, const SIP::DialogMessage *sipmsg, const char *format) +{ + ostringstream os; + os << pthread_self() << Utils::timestr() << " " <text(); + } else { + LOG(INFO)<<"Unexpected state:"<L3TimerList::timerExpired(tid); } + +MMContext *MachineBase::getContext() const { return mTran->teGetContext(); } + +void MachineBase::machText(std::ostream&os) const +{ + os <<" Machine=("; + os <tranID()) <<" "; + if (channel()) { os <descriptiveString(); } else { os <<"(no chan)"; } + os <getGSMState()); + os <= 0) os<machText(); + // TODO: Call handleMachineStatus(MachineStatus status) + return tran()->handleRecursion(wProc->machineRunState1(startState)); // Unless the StateMachine starts with a message, the start state must be state 0 in every Machine. +} + +L3LogicalChannel* MachineBase::channel() const { + return tran()->channel(); +} + + +CallState MachineBase::getGSMState() const { return tran()->getGSMState(); } +void MachineBase::setGSMState(CallState state) { tran()->setGSMState(state); } + +SIP::SipDialog * MachineBase::getDialog() const { return tran()->getDialog(); } +void MachineBase::setDialog(SIP::SipDialog*dialog) { return tran()->setDialog(dialog); } + +unsigned MachineBase::getL3TI() const { return mTran->getL3TI(); } + +bool MachineBase::isL3TIValid() const { + return getL3TI() != TranEntry::cL3TIInvalid; +} + + +// TODO: Current this twiddles the RRLP status flags; +// the whole RRLP server needs to turn into some kind of state machine so we can tell what is going on, +// and whether a status message might be for RRLP or something else. +static void handleStatus(const L3Message *l3msg, MMContext *mmchan) +{ + assert(l3msg->MTI() == L3RRMessage::RRStatus); + + const L3RRStatus *statusMsg = dynamic_cast(l3msg); + if (!statusMsg) { + LOG(ERR) << "Could not cast Layer 3 RR Status message?"; + return; + } + int cause = statusMsg->cause().causeValue(); + + switch (cause) { + case 97: + LOG(INFO) << "Received RR status message with cause: message not implemented"; + // (pat) TODO: Figure out if this is in response to RRLP or not... + //Rejection code only useful if we're gathering IMEIs + if (gConfig.getBool("Control.LUR.QueryIMEI")){ + // flag unsupported in SR so we don't waste time on it again + string imsi = mmchan->mmGetImsi(false); // with false flag, empty if no imsi + if (imsi.size() >= 14 && isdigit(imsi[0])) { // Cheap partial validation + imsi = string("IMSI") + imsi; + // TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access + //if (gSubscriberRegistry.imsiSet(imsi, "RRLPSupported", "0")) { + // LOG(INFO) << "SR update problem"; + //} + } + } + return; + case 98: + LOG(INFO) << "Received RR status message with cause: message type not compatible with protocol state"; + return; + default: + LOG(INFO) << "Received RR Status with"<PD(),l3msg->MTI())) { + case L3CASE_RR(L3RRMessage::PagingResponse): + NewPagingResponseHandler(dynamic_cast(l3msg),mmchan); + return true; + case L3CASE_RR(L3RRMessage::ApplicationInformation): + recvRRLP(mmchan, l3msg); + return true; + case L3CASE_RR(L3RRMessage::RRStatus): + handleStatus(l3msg,mmchan); + return true; + case L3CASE_MM(L3MMMessage::LocationUpdatingRequest): + //LocationUpdatingController(dynamic_cast(req),mmchan); + //tran->lockAndStart(new L3ProcedureLocationUpdate(tran), (L3Message*)req); + // TODO: Should we check that this an appropriate time to start it? + LURInit(l3msg,mmchan); + return true; + case L3CASE_MM(L3MMMessage::IMSIDetachIndication): + // (pat) TODO, but it is not very important. + //IMSIDetachController(dynamic_cast(l3msg),mmchan); + return true; + case L3CASE_MM(L3MMMessage::CMServiceRequest): + mmchan->mmcServiceRequests.write(l3msg); + //NewCMServiceResponder(dynamic_cast(l3msg),dcch); + *deletemsg = false; + return true; + default: + break; + } + *deletemsg = false; + return false; +} + +MachineStatus MachineBase::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + // The state machine must implement one of: machineRunState, machineRunL3Msg or machineRunFrame. + assert(0); +} + +#if UNUSED +MachineStatus MachineBase::machineRunL3Msg(int state, const GSM::L3Message *l3msg) +{ + bool deleteit; + if (handleCommonMessages(l3msg, channel(),&deleteit)) { + LOG(DEBUG) << "message handled by handleCommonMessages"<PD(),l3msg->MTI()); + LOG(DEBUG) <<"calling machineRunState "<primitive()) { + case GSM::ESTABLISH: + // Indicates SABM mode establishment. The state machines that use machineRunState can ignore these. + // The transaction is started by an L3 message like CMServiceRequest. + return MachineStatusOK; + default: + // We took all the other primitives out of the message stream in csl3HandleFrame + assert(0); + //case GSM::HANDOVER_ACCESS: + // // TODO: test that this is on TCHFACH not SDCCH. + // assert(0); // Someone higher handled this. + // //ProcessHandoverAccess(lch); + // return MachineStatusQuitChannel; + // + //case DATA: + //case UNIT_DATA: + // assert(0); // Caller checked this. + // + //case ERROR: ///< channel error + // // The LAPDM controller was aborted. + // lch->chanRelease(RELEASE); // Kill off all the transactions associated with this channel. + // return MachineStatusQuitChannel; + // + //case HARDRELEASE: + // if (frame->getSAPI() == 0) { lch->chanRelease(HARDRELEASE); } // Release the channel. + // return MachineStatusQuitChannel; + //default: + // LOG(ERR) <primitive(); // This is horrible. But lets warn instead of crashing. + // // Fall through + //case RELEASE: ///< normal channel release + // if (frame->getSAPI() == 0) { lch->chanRelease(RELEASE); } + // return MachineStatusQuitChannel; + } +} + +// The state machines may receive messages via machineRunState or machineRunState1. +// This is the extended version that lets the state machine handle the primitives, unparseable messages, +// and includes the frame, too, if it is available. +// Generally either frame or l3msg or sipmsg will be non-NULL. +// The l3msg will be provided instead of the frame if we were invoked by lockAndStart with just an l3msg, +// but the state machine should know that. +// l3msg may be NULL for primitives or unparseable messages. +MachineStatus MachineBase::machineRunState1(int state, const L3Frame *frame, const L3Message *l3msg, const SIP::DialogMessage *sipmsg) +{ + LOG(DEBUG)<isData()) { return handlePrimitive(frame,channel()); } + } + if (l3msg || sipmsg || state < 256) { + LOG(DEBUG) <<"calling machineRunState "<isData()) { + // Note: the frame MTI must be ANDed with 0xbf. See 4.08 10.4. I moved that into class L3Frame. + state = L3CASE_RAW(frame->PD(),frame->MTI()); + } else { + state = L3CASE_PRIMITIVE(frame->primitive()); + } + } else if (l3msg) { + state = L3CASE_RAW(l3msg->PD(),l3msg->MTI()); + } else { + LOG(ERR) << "called with no arguments?"; + return MachineStatusOK; + } + return machineRunState1(state,frame,l3msg); +#if 0 + if (frame->isData()) { + if (L3Message *msg = parseL3(*frame)) { + LOG(DEBUG) <PD(),frame->MTI()); + LOG(DEBUG) <<"calling machineRunState1 "<chanGetContext(true)->mmDispatchError(PD,MTI,lch); + return MachineStatusOK; // Was not handled, but the caller cant do anything more with this frame. + } + } else { + Primitive primitive = frame->primitive(); + int state = L3CASE_PRIMITIVE(primitive); + LOG(DEBUG) <<"calling machineRunState1 "<tNextState(); + LOG(DEBUG) <= 0) { + return machineRunState1(nextState); + } else if (nextState == TimerAbortTran) { + // TODO: How do we kill it? + // Answer: It depends on the procedure. The caller should have done any specific + // closing, for example CC message, before exiting the state machine. + LOG(INFO) << "Timer "<tName()<<" timeout"; + + // If it is an SMS transaction, just drop it. + // If it is a CC transaction, lets send a message to try to kill it, which may block for 30 seconds. + switch (tran()->servicetype()) { + case L3CMServiceType::MobileOriginatedCall: + case L3CMServiceType::MobileTerminatedCall: + tran()->teCloseCallNow(L3Cause::InterworkingUnspecified); + return MachineStatusQuitTran; + // Changed 10-24-2013 + // if (! tran()->clearingGSM()) { + // // We are bypassing the ReleaseRequest state, which is allowed by GSM 04.08 5.4.2. + // // setGSMState(CCState::ReleaseRequest); + // // (pat) The old forceGSMClearing did not put the l3cause in this message - why not? + // // Note: timer expiry may indicate unresponsive MS so this this may block for 30 seconds. + // return tran()->teCloseCall(L3Cause::InterworkingUnspecified,true); + // } + // break; + default: + break; + } + + // Code causes caller to kill the transaction, which will also cancel the dialog if any, which is + // partly redundant with the teCloseCall above.. + return MachineStatusQuitTran; + } else if (nextState == TimerAbortChan) { + LOG(INFO) << "Timer "<tName()<<" timeout"; + // This indirectly causes immediate destruction of all transactions on this channel. + return closeChannel(L3RRCause::TimerExpired,RELEASE); + } else { + assert(0); + } +} + + +MachineStatus MachineBase::dispatchSipDialogMsg(const SIP::DialogMessage *dialogmsg) +{ + return machineRunState1(L3CASE_DIALOG_STATE(dialogmsg->dialogState()),(L3Frame*)NULL,(L3Message*)NULL,dialogmsg); +} + + +MachineStatus MachineBase::dispatchL3Msg(const L3Message *l3msg) +{ + int state = L3CASE_RAW(l3msg->PD(),l3msg->MTI()); + LOG(DEBUG) <<"calling machineRunState1 "<typeName()<<" message for l3rewrite"; + mCSL3Fifo.write(msg); + return true; // Our code is going to deal with this message. + } else { + LOG(DEBUG) << "Received "<typeName()<<" message, handling with version 1 code"; + // Caller must deal with this message with pre-existing version 1 code. + return false; + } +} +#endif + + +#if UNUSED // now it is +// TODO: This might as well be in MMContext. +static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch) +{ + // Do we have one or more transactions already running on this logical channel? + // There can be multiple transactions on the same channel. + // What is to prevent an MS from allocating multiple channels, eg, a TCH and SDCCH simultaneously? + // I think previously nothing. But we are not going to try to aggregate messages from multiple channels in the same MS together. + // We assume that all the messages for a single Machine arrive on a single channel+SACCH pair. + if (handleCommonMessages(l3msg, lch)) { + LOG(DEBUG) << "message handled by handleCommonMessagse"<chanGetContext(true)->mmDispatchL3Msg(l3msg,lch); + + //TranEntry *tran = gNewTransactionTable.ttFindByL3Msg(l3msg,lch); + //bool handled = false; + //if (tran) { + // OBJLOG(DEBUG) <<"received l3msg for dcch:"<<*lch<<" l3msg="<<*l3msg<text()); + // // TODO: Do we ever need a way for the Procedure to tell us that a message was completely handled + // // and that we should not examine it further? + // if (tran->lockAndInvokeL3Msg(l3msg,lch)) handled++; + //} + + if (!handled) { + LOG(DEBUG) <<"unhandled l3msg" <chanGetContext(false); + } + delete l3msg; +} +#endif + +// Return true if it was an l3 message or or a primitive that we pass on to state machines, false otherwise. +// After calling us the caller should test chanRunning to see if the channel is still up. +static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi) +{ + LOG(DEBUG)<chanLost(); // Kill off all the transactions associated with this channel. + LOG(ERR) << "Layer3 received ERROR from layer2 on channel "<chanRelease(RELEASE); // Kill off all the transactions associated with this channel. + return false; + + case HARDRELEASE: ///< forced release after an assignment + if (sapi == 0) lch->chanRelease(HARDRELEASE); // Release the channel. + return false; + case RELEASE: ///< normal channel release + //if (lch->mChState == L3LogicalChannel::chReassignPending || lch->mChState == L3LogicalChannel::chReassignComplete) { + // // This is what we wanted. + // // We will exit and the service loop will change the L3LogicahChannel mChState. + //} else { + // // This is a bad thing when happening on an active channel. + // // Link either closed by peer or lost due to timeout in a lower layer. + // // The LAPDm is already released so we cannot send any more messages. + // // Just drop the channel. + // lch->chanLost(); // Kill off all the transactions associated with this channel. + //} + if (sapi == 0) lch->chanRelease(RELEASE); + return false; + default: + LOG(ERR) <chanRelease(RELEASE); // Kill off all the transactions associated with this channel. + //lch->freeContext(); + return false; + } +} + +static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch) +{ + L3Message *l3msg = NULL; + // The primitives apply directly to the L3LogicalChannel and a specific SAPI, rather than to the MMContext. + if (! checkPrimitive(frame->primitive(), lch, frame->getSAPI())) { return; } + // Everything else runs on the MMContext. + MMContext *mmchan = lch->chanGetContext(true); + if (frame->isData()) { + l3msg = parseL3(*frame); + if (l3msg) { + WATCHINFO(lch <<" received L3 message "<<*l3msg); + } else { + LOG(ERR) <mmDispatchL3Frame(frame,l3msg); + if (l3msg) delete l3msg; +#if UNUSED + L3Message *msg = NULL; + try { + // Even through parseL3 catches L3ReadError, looks to me like this can still throw SMS_READ_ERROR, so catch it: + msg = parseL3(*frame); + } catch (...) { + msg = NULL; + } + if (msg) { + LOG(DEBUG) <PD(); + int MTI = frame->MTI(); + LOG(ERR) << "unparseable Layer3 message with"<chanGetContext(true)->mmDispatchError(PD,MTI,lch); + } +#endif +} + +#if UNUSED +// Handle timeouts. Return the next timeout or -1 if no timeout is pending. +int CSL3StateMachine::csl3HandleTimers() +{ + ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__); + int nextTimeout = -1; + for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) { + TranEntry *tran = itr->second; + if (tran->deadOrRemoved()) continue; + tran->checkTimers(); + } + for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) { + TranEntry *tran = itr->second; + int remaining = tran->remainingTime(); + if (remaining >= 0) { + nextTimeout = (nextTimeout == -1) ? remaining : min(nextTimeout,remaining); + } + LOG(DEBUG)<<"transaction "<tranID()<mTranId); + if (tran && ! tran->deadOrRemoved()) { + found = true; + LOG(DEBUG) << "sip message code="<mSIPStatusCode <<" handled by trans "<tranID(); + // Call deletes it + tran->lockAndInvokeSipMsg(sipmsg); + } +#if 0 + ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__); + int code = sipmsg->sipStatusCode(); + LOG(DEBUG) << "Received Dialog message "<second; + if (tran->deadOrRemoved()) continue; + if (tran->SIPCallID() == callid) { + found = true; + LOG(DEBUG) << "sip message code="<tranID(); + tran->lockAndInvokeSipMsg(sipmsg); + } + } +#endif + if (!found) { LOG(DEBUG) << "sip message code="<mSIPStatusCode <<" no matching transaction found."; } +} +#endif + +#if UNUSED +void CSL3StateMachine::csl3HandleMsg(GenericL3Msg *gmsg) +{ + switch (gmsg->ml3Type) { + case GenericL3Msg::MsgTypeLCH: + csl3HandleFrame(gmsg->ml3frame,gmsg->ml3ch); + break; + case GenericL3Msg::MsgTypeSIP: + csl3HandleSipMsg(gmsg->mSipMsg /*, gmsg->mCallId*/); + break; + default: assert(0); + } + delete gmsg; // Deletes the L3Frame in the GenericL3Msg as well. +} +#endif + +/** + Update vocoder data transfers in both directions. + @param transaction The transaction object for this call. + @param TCH The traffic channel for this call. + @return bytes transferred. +*/ +static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH) +{ + // We dont set the state Active until both SIP dialog and MS side have acked. + LOG(DEBUG); + if (transaction->getGSMState() != CCState::Active) { return false; } + unsigned activity = 0; + + // Both the rx and tx directions block if rtp_session_set_blocking_mode() is true. + + // Transfer in the uplink direction (GSM->RTP). + // Flush FIFO to limit latency. + unsigned numFlushed = 0; + { + unsigned maxQ = gConfig.getNum("GSM.MaxSpeechLatency"); + static Timeval testTimeStart; + while (TCH->queueSize()>maxQ) { + if (numFlushed == 0) testTimeStart.now(); + numFlushed++; + // (pat) LOG is too slow to call in here. With 1000 backed up the delay is 1/2 sec. + //LOG(DEBUG) <queueSize()); + delete TCH->recvTCH(); + } + if (numFlushed) { LOG(DEBUG) <recvTCH()) { + activity += ulFrame->sizeBytes(); + // Send on RTP. + LOG(DEBUG) <txFrame(ulFrame,numFlushed); + delete ulFrame; + } + + // Transfer in the downlink direction (RTP->GSM). + // Blocking call. On average returns 1 time per 20 ms. + // Returns non-zero if anything really happened. + // Make the rxFrame buffer big enough for G.711. + if (GSM::AudioFrame *dlFrame = transaction->rxFrame()) { + activity += dlFrame->sizeBytes(); + if (activity == 0) { activity++; } // Make sure we signal activity. + LOG(DEBUG) <sendTCH(dlFrame); + } + + // Return a flag so the caller will know if anything transferred. + LOG(DEBUG) <chanGetContext(true); + + if (GSM::L3Frame *l3frame = dcch->l2recv(delay)) { + LOG(DEBUG) <l2recv(0,3)) { + LOG(DEBUG) <ml3UplinkQ.readNoBlock()) { + //if (IS_LOG_LEVEL(DEBUG)) { + //std::ostringstream os; os << *aframe; + //WATCHF("Frame on SACCH %s: %s\n",dcch->descriptiveString(),os.str().c_str()); + //} + WATCH("Recv frame on SACCH "<descriptiveString()<<" "<<*aframe); + csl3HandleFrame(aframe, dcch); + delete aframe; + return true; // Go see if it terminated the TranEntry while we were potentially blocked. + } + + // Any Dialog messages from the SIP side? + // Sadly we cannot process the sip messages in a separate global L3 thread because the TranEntry/procedure may be + // locked for up to 30 seconds when sending to LAPDm, which would then stall that L3 thread. + if (set->mmCheckSipMsgs()) { + LOG(DEBUG) <mmCheckTimers()) { + LOG(DEBUG) <mmCheckNewActivity()) { + LOG(DEBUG) <chtype() == FACCHType); + GSM::TCHFACCHLogicalChannel *tch = dynamic_cast(dcch); + // The gReports call invokes sqlite3 which takes tenths of seconds, which is too long + // during a voice call, and especially a handover, so only call gReports if nothing else is pending. + bool needReports = true; + int nextDelay = 0; + + //LOG(INFO) << "call connected "<<*tran; + size_t fCount = 0; // A rough count of frames. + // chanRunning checks for loss in L3. radioFailure checks for loss in L2. + // Original code used throw...catch but during channel reassignment the channel state is terminated by changing + // the state by a different thread, so we just the same method for all cases and terminate by changing the channel state. + unsigned alternate = 0; + while (dcch->chanRunning()) { + if (tch->radioFailure()) { + LOG(NOTICE) << "radio link failure, dropped call"<chanRelease(HARDRELEASE); // Kill off all the transactions associated with this channel. + //tran->getDialog()->dialogCancel(); //was forceSIPClearing + //tran->teRemove(); + return; + } + // The voiceTrans will not yet be set when this loop starts. + // The MS establishes SABM over LAPDm which sends up an ESTABLISH primitive, + // then we get the AssignmentComplete L3 message over this FACCH channel, + // which calls the controling state machine to set voiceTrans. + RefCntPointer rtran = dcch->chanGetVoiceTran(); + TranEntry *tran = rtran.self(); + LOG(DEBUG) <deadOrRemoved()) { + // This is ok. If there is something else ongoing (eg SMS) when the voice call ends + // we should keep the channel open until that ends. + LOG(NOTICE) << "attempting to use a defunct Transaction"<chanClose(L3RRCause::PreemptiveRelease,RELEASE); + return; + } + // TODO: This needs to check all the transactions in the MMContext. + // The termination request comes from the CLI or from RadioResource to make room for an SOS call. + if (tran->terminationRequested()) { + LOG(DEBUG)<terminateHook(); // Gives the L3Procedure state machine a chance to do something first. + dcch->chanClose(L3RRCause::PreemptiveRelease,RELEASE); + return; // We wont be back. + } + if (tran->getGSMState() == CCState::HandoverOutbound) { + if (outboundHandoverTransfer(tran,dcch)) { + LOG(DEBUG)<chanRelease(HARDRELEASE); + return; // We wont be back. + } + } + + // Any Q.931 timer expired? + // TODO: Remove this, redundant with the below. + if (tran->checkTimers()) { // This calls a handler function and resets the timer. + LOG(DEBUG) <anyTimerExpired()) + // Cause 0x66, "recover on timer expiry" + //abortCall(transaction,dcch,GSM::L3Cause(0x66)); + //return true; + continue; // The transaction is probably defunct. + } + } + + // Look for messages or timers. + // (pat 8-6-2013) We have an architectural problem that we do not have a separate thread handling SAPI3 + // so the in-call SMS handler hangs for several seconds (in the best case.) The rxFrame below also hangs. + // In order not to wait too long to service of these two things, I am adding a super-hack to alternate servicing them. + // I think we need to add another thread to run SAPI3. + if (++alternate % 2) { + if (checkemMessages(dcch,nextDelay)) { gResetWatchdog(); nextDelay = 0; continue; } + } + nextDelay = 0; + LOG(DEBUG) <chtype() == SDCCHType); + while (dcch->chanRunning()) { + if (dcch->radioFailure()) { // Checks expiry of T3109, set at 30s. + LOG(NOTICE) << "radio link failure, dropped call"; + //gNewTransactionTable.ttLostChannel(dcch); + dcch->chanRelease(HARDRELEASE); // Kill off all the transactions associated with this channel. + return; + } + + // Any L3 Messages from the MS side on this channel? + // Wait 100ms. I made this number up out of thin air. + // Since an incoming L3 message will be returned instantly, this delay is the effective delay + // in handling SIP messages or timers, and could be much longer since none of those are precision timers. + if (checkemMessages(dcch,100)) { gResetWatchdog(); continue; } + } +} + +// dcch may be SDCCH or FACCH. +// This does not return until the channel is released. +void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame) +{ + LOG(INFO) <<"DCCH LOOP OPEN "<primitive(); + delete frame; + + dcch->chanSetState(L3LogicalChannel::chEstablished); + switch (prim) { + case ESTABLISH: + break; + case HANDOVER_ACCESS: + ProcessHandoverAccess(dcch); + // If the handover fails, it sets the chState such that the loop below will return immediately, + // so we can just break here. + break; + default: + assert(0); // Caller prevented anything else. + } + + switch (dcch->chtype()) { + case SDCCHType: + L3SDCCHLoop(dcch); + break; + case FACCHType: + l3CallTrafficLoop(dcch); + break; + default: + assert(0); + } + devassert(dcch->mChState != L3LogicalChannel::chIdle); // This would be a bug. + } catch (exception &e) { + LOG(ERR) << "exception "<L3LogicalChannelReset(); + switch (dcch->mChState) { + case L3LogicalChannel::chRequestRelease: + // The RELEASE primitive will block up to 30 seconds, so we NEVER EVER send it from anywhere but right here. + // To release the channel, set the channel state to chReleaseRequest and let it come here to release the channel. + // FIXME: Actually, LAPDm blocks in this forever until it gets the next ESTABLISH, so this is where the serviceloop really waits. + dcch->l3sendp(RELEASE); // WARNING! This must be the only place in L3 that sends this primitive. + break; + case L3LogicalChannel::chRequestHardRelease: + dcch->l3sendp(HARDRELEASE); + break; + default: break; + } + LOG(DEBUG) <<"CLOSE "<chanSetState(L3LogicalChannel::chIdle); + LOG(DEBUG) << "DCCHLoop exiting "<= 0) { + msg = mCSL3Fifo.read(timeout); // wait only until the next timer expires. + LOG(DEBUG) "read(timeout="<start((void*(*)(void*))Control::DCCHDispatcher,chan); + mCSL3Thread->start(csl3ThreadLoop,NULL); +} + +CSL3StateMachine gCSL3StateMachine; +CSL3StateMachine::CSL3StateMachine() : mCSL3Thread(NULL) {} +#endif + +void l3start() +{ + // We are not doing it this way in GSM. Each channel has its own service loop. + // if (l3rewrite()) { gCSL3StateMachine.csl3Start(); } +} + + +}; // namespace diff --git a/Control/L3StateMachine.h b/Control/L3StateMachine.h new file mode 100644 index 0000000..a2f72a8 --- /dev/null +++ b/Control/L3StateMachine.h @@ -0,0 +1,198 @@ +/**@file Declarations for Circuit Switched State Machine and related classes. */ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ + +// TODO: To avoid bugs where the state machines get stuck, +// send a HARDRELEASE from L3 when mT3109 expires, which is the uplink activity counter in XCCHL1Decoder, +// which is used for TCHFACCHL1Decoder, SDDCHL1Decoder and SACCHL1Decoder. +// Technique is similar to: if (mUpstream!=NULL) mUpstream->writeLowSide(L2Frame(ESTABLISH)); +// Currently, the TCH handler in Control dir polls radioFailure() which eventually checks mT3109. +// For SDCCH, the ch just gets reused after mT3109 expiry, and L3 is not notified - we just hope it is no longer in use after 30s. + + +#ifndef CSL3STATEMACHINE_H +#define CSL3STATEMACHINE_H + +#include + +#include +#include +#include + + +#include +#include "ControlCommon.h" +#include "L3Utils.h" +//#include +//#include +//#include +//#include // Doesnt this poor L3Message get lonely? When apparently there are multiple L3MMMessages and L3CCMessages? +#include +#include +#include +//#include +namespace SIP { class SipDialog; }; + +// These are only for use inside state machines: +#define PROCLOG(level) LOG(level)< CSL3StateMachineFifo; +#endif + +// This is a return state from a state machine. + +struct MachineStatus { + // There are only 4 things a state machine procedure can do on return. + // Any error returns have to map to one of these four. + enum MachineStatusCode { + MachineCodeOK, // continue the procedure, meaning return to L3 message handler and wait for the next message. + MachineCodePopMachine, // return to previous procedure on stack + MachineCodeQuitTran, // Pop all machines from stack and remove the transaction. This is the normal exit from a completed procedure. + // NOTE: MachineCodeQuitChannel does not close the channel - The user must call closeChannel or equivalent first. + MachineCodeQuitChannel, // Drop the channel, which kills all transactions on this channel. + // All these others are error conditions. They all terminate like MachineCodeQuit, but they are different + // so we can print a more informative diagnostic message. + //MachineCodeUnexpectedMessage, // The MS sent a message that was not anticipated by the state machine running the Transaction. + //MachineCodeUnexpectedPrimitive, /** Thrown when the control layer gets the wrong primitive */ // Not sure this happens. + MachineCodeUnexpectedState, // Unexpected message or state was not handled by the current state machine. + //MachineCodeAuthorizationFail, // Self explanatory. + }; + MachineStatusCode mCode; + //GSM::RRCause mRRCause; + //string mSomeMessage; + bool operator==(MachineStatus &other) { return mCode == other.mCode; } + bool operator!=(MachineStatus &other) { return mCode != other.mCode; } + MachineStatus(MachineStatusCode code) { mCode = code; /*mRRCause = GSM::L3RRCause::NormalEvent;*/ } + +}; +std::ostream& operator<<(std::ostream& os, MachineStatus::MachineStatusCode state); + +// These ones have no arguments so they might as well be constants. +extern MachineStatus MachineStatusOK, MachineStatusPopMachine, MachineStatusQuitTran, MachineStatusQuitChannel, MachineStatusAuthorizationFail; +extern MachineStatus MachineStatusAuthorizationFail, MachineStatusUnexpectedState; + + +struct MachineStatusQuitTran : MachineStatus { + //MachineStatusQuitTran(GSM::CCCause wcause) : MachineStatus(MachineCodeQuitTran) { mCCCause = wCCcause; } +}; + + + +// A base class for the individual Procedure state machines. +// Formerly this contained state so had to be unique for each state machine. +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(MachineBase,MemCheckMachineBase) +class MachineBase : public MemCheckMachineBase +{ + friend class TranEntry; + friend class ProcCommon; + protected: + int mPopState; // If we push into a procedure, save the return location here. + public: + // Args are the L3Procedure we are calling and the state in the current procedure to which we will return when nextProcedure is popped. + MachineStatus machPush( MachineBase*wCalledProcedure, int wNextState); + + // The one and only TranEntry that is running this procedure. + // The CallHold and CallWaiting will have multiple TransactionEntries and multiple procedures. + private: TranEntry *mTran; + + // No TranEntry yet? Then no L3Procedure can be started. + protected: + MachineBase(TranEntry *wTran): mTran(wTran) { + mPopState = 0; // unnecessary but neat. + } + void setTran(TranEntry *wTran) { mTran = wTran; } + TranEntry *tran() const { return mTran; } + MMContext *getContext() const; + MachineBase *currentProcedure() { return this; } + + MachineStatus callMachStart(MachineBase* wProc, unsigned startState=0); + + void timerStart(L3TimerId tid, unsigned val, int nextState); // Executes nextState on expiry. + void timerStop(L3TimerId tid); + void timerStopAll(); + bool timerExpired(L3TimerId tid); // Is it expired? + + public: + virtual ~MachineBase() {} // Must be virtual to allow derived L3Procedures to delete themselves properly. + void machText(std::ostream&) const; + string machText() const; + + // accessors + L3LogicalChannel *channel() const; // may be SDCCH or FACCH. + CallState getGSMState() const; + void setGSMState(CallState); + SIP::SipDialog *getDialog() const; + void setDialog(SIP::SipDialog*dialog); + unsigned getL3TI() const; + bool isL3TIValid() const; + virtual const char *debugName() const = 0; + MachineStatus unexpectedState(int state, const L3Message*l3msg); + MachineStatus closeChannel(L3RRCause cause,Primitive prim); + void machineErrorMessage(int level, int state, const L3Message *l3msg, const SIP::DialogMessage *sipmsg, const char *format); + + virtual void handleTerminationRequest() {} // Procedure can over-ride this to do nicer cleanup. + + // Methods for Procedure States. + // The primary entry point for the state machine at the specified state. + // State 0 is always reserved as the initial start state. + // If this invocation is due to a message arrival, it is included as an argument. + // The other possibilities are: timeout, popProcedure, or initial invocation in state 0. + + // The state machine must implement one of the following methods, depending on how much control it wants over its input. + virtual MachineStatus machineRunState(int state, const GSM::L3Message *l3msg=NULL, const SIP::DialogMessage *sipmsg=NULL); + virtual MachineStatus machineRunState1(int state, const GSM::L3Frame *frame=NULL, const GSM::L3Message *l3msg=NULL, const SIP::DialogMessage *sipmsg=NULL); + + //virtual MachineStatus procRunSipMsg(const SIP::DialogMessage *sipmsg) = 0; + + MachineStatus dispatchSipDialogMsg(const SIP::DialogMessage *msg); + MachineStatus dispatchL3Msg(const GSM::L3Message *msg); + MachineStatus dispatchTimeout(L3Timer *timer); + MachineStatus dispatchFrame(const L3Frame *frame, const L3Message *l3msg); +}; + + +#if UNUSED_BUT_SAVE_FOR_UMTS +// TODO: This class could go away. I am keeping it around to see if it is useful for UMTS. +// This is the outer layer state machine to process CS L3 Messages. +// CS [Circuit Switched] L3 messages are specified in 3GPP 4.08, as opposed to PS [Packet Switched] L3 messages handled by the SGSN. +// This is part of the L3 rewrite. +class CSL3StateMachine +{ + CSL3StateMachineFifo mCSL3Fifo; + Thread* mCSL3Thread; + + public: + CSL3StateMachine(); + //void csl3ServiceLoop(); + //void csl3Start(); + // Put a message on the queue of messages to process. + //bool csl3Write(GenericL3Msg *msg); // return TRUE if the message was handled. +}; +extern CSL3StateMachine gCSL3StateMachine; +#endif + +}; // namespace Control + +#endif + diff --git a/Control/L3SupServ.cpp b/Control/L3SupServ.cpp new file mode 100644 index 0000000..a8828a8 --- /dev/null +++ b/Control/L3SupServ.cpp @@ -0,0 +1,726 @@ +/* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +*/ +#define LOG_GROUP LogGroup::Control + +#include +#include +#include "L3SupServ.h" +#include "L3TranEntry.h" +#include "L3MMLayer.h" +#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 wairing? + // 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(L3RejectCause::ServiceOptionNotSupported))); + 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::InvalidMandatoryInformation); + channel()->l3sendm(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation)); + return MachineStatusQuitTran; + } + + 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 MachineStatusQuitTran; + } + + case L3CASE_SIP(dialogBye): { + if (sipmsg == NULL) { + LOG(ERR) << "USSD client error: missing BYE message"; + return MachineStatusQuitTran; + } + 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 MachineStatusQuitTran; + } + + default: + LOG(DEBUG) << "unexpected state"; + return unexpectedState(state,l3msg); + } +} + +}; // namespace diff --git a/Control/L3SupServ.h b/Control/L3SupServ.h new file mode 100644 index 0000000..5422b03 --- /dev/null +++ b/Control/L3SupServ.h @@ -0,0 +1,35 @@ +/* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +*/ +#ifndef _L3SUPSERV_H_ +#define _L3SUPSERV_H_ 1 + + +#include "ControlCommon.h" +#include "L3StateMachine.h" +#include + +namespace Control { + +// The base class for SS [Supplementary Services] +class SSDBase : public MachineBase { + protected: + MachineStatus handleSSMessage(const GSM::L3Message*ssmsg); + SSDBase(TranEntry *wTran) : MachineBase(wTran) {} +}; + +void startMOSSD(const GSM::L3CMServiceRequest*cmsrq, MMContext *mmchan); +string ssMap2Ussd(const unsigned char *mapcmd,unsigned maplen); + +}; +#endif diff --git a/Control/L3TranEntry.cpp b/Control/L3TranEntry.cpp new file mode 100644 index 0000000..6c94b40 --- /dev/null +++ b/Control/L3TranEntry.cpp @@ -0,0 +1,1976 @@ +/**@file TransactionTable and related classes. */ + +/* +* Copyright 2008, 2010 Free Software Foundation, Inc. +* Copyright 2010 Kestrel Signal Process, Inc. +* Copyright 2011, 2012, 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 distribuion. +* +* 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. + +*/ + +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging + +#include "ControlCommon.h" +#include "L3TranEntry.h" +#include "L3MMLayer.h" + +#include +#include +#include +#include +#include +#include + +#include + +//#include +//#include +#include + +//#include + +#include +#include +#undef WARNING + +// This is in the global namespace. +Control::NewTransactionTable gNewTransactionTable; +Control::StaleTransactionTable gStaleTransactionTable; + +int gCountTranEntry = 0; + +namespace Control { +using namespace std; +using namespace GSM; +using namespace SIP; + + +#if EXTERNAL_TRANSACTION_TABLE +// (pat) This external transaction table is obsolete and we will not support it any more. +// The code implementing it has eroded and would not work if enabled. +// It is retained here until we release version 4 in the remote off-chance that some important customer +// has built legacy applications that use this, so we can help migrate that customer to something different. +// This is extremely unlikely, since we have no customers. +static const char* createNewTransactionTable = { + "CREATE TABLE IF NOT EXISTS TRANSACTION_TABLE (" + "ID INTEGER PRIMARY KEY, " // internal transaction ID + "CHANNEL TEXT DEFAULT NULL," // channel description string (cross-refs CHANNEL_TABLE) + "CREATED INTEGER NOT NULL, " // Unix time of record creation + "CHANGED INTEGER NOT NULL, " // time of last state change + "TYPE TEXT, " // transaction type + "SUBSCRIBER TEXT, " // IMSI, if known + "L3TI INTEGER, " // GSM L3 transaction ID, +0x08 if generated by MS + "SIP_CALLID TEXT, " // SIP-side call id tag + "SIP_PROXY TEXT, " // SIP proxy IP + "CALLED TEXT, " // called party number + "CALLING TEXT, " // calling party number + "GSMSTATE TEXT, " // GSM/Q.931 state + "SIPSTATE TEXT " // SIP state + ")" +}; +#endif + + + + + +HandoverEntry::HandoverEntry(const TranEntry *tran) : + mMyTranID(tran->tranID()), + mHandoverOtherBSTransactionID(0) +{ +}; + +HandoverEntry *TranEntry::getHandoverEntry(bool create) const // It is not const, but we want C++ to be a happy compiler. +{ + if (!mHandover && create) { mHandover = new HandoverEntry(this); } + return mHandover; +} + +// class base initialization goes here. +void TranEntry::TranEntryInit() +{ + mID = gNewTransactionTable.ttNewID(); + mL3TI = cL3TIInvalid; // Until we know better. + mDialog = 0; + mHandover = NULL; + //mGSMState = CCState::NullState; moved to TranEntryProtected + mNumSQLTries = gConfig.getNum("Control.NumSQLTries"); // will be increased later by the SOS constructor. + mContext = NULL; + //mChannel = NULL; + //mNextChannel = NULL; + mMMData = NULL; + //mRemoved = false; moved to TranEntryProtected + //initTimers(); +} + + +//DIG: Debug Start +#include +//DIG: Debug End +TranEntry::TranEntry( + SipDialog *wDialog, + //const L3MobileIdentity& wSubscriber, + const L3CMServiceType& wService) +{ + gCountTranEntry++; + TranEntryInit(); + if (wDialog) setDialog(wDialog); + //mSubscriber = wSubscriber; + mService = wService; + + //DIG: Debug Start + if (0) { + printf("*********************************************************\n"); + printf("*********************************************************\n"); + printf(" CONSTRUCTOR\n"); + printf("TranEntry::TranEntry() called, call stack follows:\n"); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("*********************************************************\n"); + printf("*********************************************************\n"); + } + //DIG: Debug End + startTime = time(NULL); + endTime = 0; + //gNewTransactionTable.ttAdd(this); +} + +// For MO the channel is always known. +TranEntry *TranEntry::newMO(MMContext *wChan, const GSM::L3CMServiceType& wService) +{ + //L3MobileIdentity unknownId; + //TranEntry *result = new TranEntry(proxy,unknownId,wChannel,wService,CCState::NullState); + TranEntry *result = new TranEntry(NULL,wService); // No SipDialog yet for MO transactions. + LOG(DEBUG); + //wChan->chanGetContext(true)->mmConnectTran(result); + wChan->mmConnectTran(result); + gNewTransactionTable.ttAdd(result); + return result; +} + +void TranEntry::setDialog(SIP::SipDialog *dialog) { mDialog = dialog; dialog->setTranId(mID); } +void TranEntry::txFrame(GSM::AudioFrame* frame, unsigned numFlushed) { getDialog()->txFrame(frame,numFlushed); } +GSM::AudioFrame *TranEntry::rxFrame() { return getDialog()->rxFrame(); } // Crashes if rtp not established. + +short TranEntry::getRTPPort() const +{ + if (SipDialog *dialog = getDialog()) { return dialog->RTPPort(); } + return 0; +} + + +TranEntry *TranEntry::newMOSSD(MMContext* wChannel) +{ + return newMO(wChannel,L3CMServiceType::SupplementaryService); +} + +TranEntry *TranEntry::newMOC(MMContext* wChannel, CMServiceTypeCode serviceType) +{ + assert(serviceType == L3CMServiceType::MobileOriginatedCall); + return newMO(wChannel,serviceType); +} + +TranEntry *TranEntry::newMOSMS(MMContext* wChannel) +{ + return newMO(wChannel,L3CMServiceType::ShortMessage); +} + +TranEntry *TranEntry::newMOMM(MMContext* wChannel) +{ + return newMO(wChannel,L3CMServiceType::LocationUpdateRequest); +} + +// The transaction is created without an assigned channel. +TranEntry *TranEntry::newMTC( + SipDialog *dialog, + const FullMobileId& msid, + const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI. + const string wCallerId) + //const L3CallingPartyBCDNumber& wCalling) +{ + //L3MobileIdentity subscriber(toImsiDigits.c_str()); + //TranEntry *result = new TranEntry(dialog,subscriber, wService); + TranEntry *result = new TranEntry(dialog, wService); + result->mSubscriber = msid; + result->mCalling = GSM::L3CallingPartyBCDNumber(wCallerId.c_str()); + LOG(DEBUG) <mCalling.digits()); + gNewTransactionTable.ttAdd(result); + return result; +} + + +// post-l3-rewrite +TranEntry *TranEntry::newMTSMS( + SipDialog *dialog, + const FullMobileId& msid, + const L3CallingPartyBCDNumber& wCalling, + string smsBody, // (pat) The recommendation for C++11 is to pass-by-value parameters that will be copied. + string smsContentType) +{ + //TranEntry *result = new TranEntry(dialog,subscriber,GSM::L3CMServiceType::MobileTerminatedShortMessage); + TranEntry *result = new TranEntry(dialog,GSM::L3CMServiceType::MobileTerminatedShortMessage); + result->mSubscriber = msid; + // The the L3TI is assigned when the transaction starts running. If ever. + result->mCalling = wCalling; + result->mMessage = smsBody; + result->mContentType = smsContentType; + gNewTransactionTable.ttAdd(result); + return result; +} + + + +// Form for inbound handovers. +TranEntry *TranEntry::newHandover( + const struct sockaddr_in* peer, + unsigned wInboundHandoverReference, + SimpleKeyValue ¶ms, + L3LogicalChannel *wChannel, + unsigned wHandoverOtherBSTransactionID) +{ + MMContext *mmchan = wChannel->chanGetContext(true); // This is where we create the MMContext for a handover. + //TranEntry *result = new TranEntry(proxy,imsi,wChannel,GSM::L3CMServiceType::HandoverCall,CCState::HandoverInbound); + // We dont want to open the dialog before receiving the handover. + // The proxy is not used until the dialog is created so it is no longer a parameter. + TranEntry *result = newMO(mmchan, GSM::L3CMServiceType::HandoverCall); + + //TranEntry *result = new TranEntry(NULL, GSM::L3CMServiceType::HandoverCall); + //wChannel->getContext(true)->mmConnectTran(this); + //wChannel->chanSetVoiceTran(result); // TODO: This should error check no tran there yet. + + result->setGSMState(CCState::HandoverInbound); + const char* IMSI = params.get("IMSI"); + if (IMSI) result->mSubscriber = FullMobileId(IMSI); + + const char* called = params.get("called"); + if (called) { + // TODO: Do we need to call setCalled() which will update sql? + result->mCalled = GSM::L3CalledPartyBCDNumber(called); + result->mService = GSM::L3CMServiceType::MobileOriginatedCall; + } + + const char* calling = params.get("calling"); + if (calling) { + result->mCalling = GSM::L3CallingPartyBCDNumber(calling); + result->mService = GSM::L3CMServiceType::MobileTerminatedCall; + } + + const char* L3TI = params.get("L3TI"); + if (L3TI) { + result->mL3TI = strtol(L3TI,NULL,10); + } else { + // TODO: And what should l3ti be otherwise? + result->mL3TI = 7; // (pat) Not sure what this should be if not in inbound handover parameters. + } + + const char* codec = params.get("codec"); + // TODO: Is this an RTP codec number or a CodecSet number? + // Assuming this information came from a peer OpenBTS unit it is our internal CodecSet number. + if (codec) result->mCodecs = CodecSet((CodecType)atoi(codec)); + + // Set the SIP state. + //result->mSIP->setSipState(SIP::HandoverInbound); + + //const char * callId = params.get("CallID"); + //result->mSIP->setCallId(callId); + + + // This is used for inbound handovers. + // We are "BS2" in the handover ladder diagram. + // The message string was formed by the handoverString method. + result->getHandoverEntry(true)->initHandoverEntry(peer,wInboundHandoverReference,wHandoverOtherBSTransactionID,params); + + return result; +} + +void HandoverEntry::initHandoverEntry( + const struct sockaddr_in* peer, + unsigned wInboundHandoverReference, + unsigned wHandoverOtherBSTransactionID, + SimpleKeyValue ¶ms) +{ + // FIXME: This is also in the params. Which do we want to use? (pat) This one. + mInboundReference = wInboundHandoverReference; + mHandoverOtherBSTransactionID = wHandoverOtherBSTransactionID; + + // Save the peer address. + bcopy(peer,&mInboundPeer,sizeof(mInboundPeer)); + + const char* refer = params.get("REFER"); + if (refer) { + // We changed spaces to tabs to get the REFER message through the peering interface. + // Since we are sending it through the SIP parser, it does not matter very much, + // however the tabs are preserved in a few places, especially the SDP strings, + // so change all the tabs back to spaces to be safe. + const char *inp; char *outp, *outbuf = (char*)alloca(strlen(refer)+1); + for (inp = refer, outp = outbuf; *inp; inp++, outp++) { + *outp = (*inp == '\t') ? ' ' : *inp; + } + *outp = 0; + mSipReferStr = string(outbuf); + } +} + + +TranEntry::~TranEntry() +{ +//DIG: Debug Start + if (0) { + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf(" destructor\n"); + printf("TranEntry::~TranEntry() called, call stack follows:\n"); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + } +//DIG: Debug End + gCountTranEntry--; + // This lock should go out of scope before the object is actually destroyed. + //ScopedLock lock(mLock,__FILE__,__LINE__); + + // This is the l3-rewrite stack of procedures running for this transaction. + while (mProcStack.size()) { + MachineBase *pb = mProcStack.back(); + mProcStack.pop_back(); + delete pb; + } + + // Remove any FIFO from the gPeerInterface. + gPeerInterface.removeFIFO(tranID()); + + if (mMMData) { delete mMMData; } + if (mHandover) { delete mHandover; } + +#if EXTERNAL_TRANSACTION_TABLE + // Delete the SQL table entry. (pat) There wont be any for LocationUpdating procedure, or transactions that did not run until they got an IMSI. + char query[100]; + sprintf(query,"DELETE FROM TRANSACTION_TABLE WHERE ID=%u",tranID()); + runQuery(query); +#endif +} + +//bool TranEntryProtected::isRemoved() const +//{ +// if (mRemoved) { +// assert(0); +// return true; +// } +// return false; +//} + + +bool TranEntryProtected::clearingGSM() const +{ + //if (isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); + return (mGSMState==CCState::ReleaseRequest) || (mGSMState==CCState::DisconnectIndication); +} + + +bool TranEntryProtected::isStuckOrRemoved() const +{ + //if (mRemoved) return true; + unsigned age = mStateTimer.elapsed(); + + // 180-second tests + if (age < 180*1000) return false; + // Dead if someone requested removal >3 min ago. + // (pat) Post-l3-rewrite we dont need to wait to delete TranEntrys, + // because nothing points back to them permanently, only currently running functions, for example, + // Peering gets a TranEntry pointer and immediately modifies it. One second would be over-kill. + // But having TranEntrys stick around a while may still be useful for debugging to see them in the CLI, + // so I did not change this. + // Any GSM state other than Active for >3 min? + if (getGSMState() !=CCState::Active) { return true; } + // Any SIP stte other than active for >3 min? + //if (lSIPState !=SIP::Active) return true; + return false; +} + +bool TranEntry::deadOrRemoved() const +{ + //ScopedLock lock(mLock,__FILE__,__LINE__); + if (isStuckOrRemoved()) { + LOG(NOTICE)<<"Transaction in state "<3 minutes; "<<*this; + return true; + } + SipDialog *dialog = getDialog(); + if (dialog && dialog->sipIsStuck()) return true; + return false; // still going +} + +//SIP::SipState TranEntry::getSipState() const +//{ +// if (mDialog) { return mDialog->getSipState(); } // post-l3-rewrite +// return SIP::NullState; +//} + + +void TranEntryProtected::stateText(ostream &os) const +{ + //if (mRemoved) os << " [removed]"; + os << " GSMState=" << mGSMState; // Dont call getGSMState(), it asserts 0 if the transaction has been removed; + if (isStuckOrRemoved()) os << " [defunct]"; +} + +void TranEntryProtected::stateText(unsigned &state, std::string &deleted) const +{ + state = (unsigned)mGSMState; // Dont call getGSMState(), it asserts 0 if the transaction has been removed; + if (isStuckOrRemoved()) deleted = " [defunct]"; + else deleted = ""; +} + + +// Use this for the column headers for the "calls" output +void TranEntry::header(ostream& os) +{ + std::string fmtBuf(""); + char buf[BUFSIZ]; + + fmtBuf += TranFmt::lblfmt_Active; + fmtBuf += TranFmt::lblfmt_TranId; + fmtBuf += TranFmt::lblfmt_L3TI; + fmtBuf += TranFmt::lblfmt_Service; + fmtBuf += TranFmt::lblfmt_To; + fmtBuf += TranFmt::lblfmt_From; + fmtBuf += TranFmt::lblfmt_AgeSec; + fmtBuf += TranFmt::lblfmt_StartTime; + fmtBuf += TranFmt::lblfmt_EndTime; + fmtBuf += TranFmt::lblfmt_Message; + fmtBuf += "\n"; + + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + "Active", + "TranId", + "L3TI", + "Service", + "To", + "From", + "AgeSec", + "Start Time", + "End Time", + "Message"); + os << buf; + + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + "======", + "==========", + "=========", + "=========", + "================", + "================", + "=========", + "=====================================", + "=====================================", + "========================================================"); + os << buf; +} + +// Use this for the column data for the "calls" output +void TranEntry::textTable(ostream& os) const +{ + std::string fmtBuf(""); + char buf[BUFSIZ]; + + fmtBuf += TranFmt::fmt_Active; + fmtBuf += TranFmt::fmt_TranId; + fmtBuf += TranFmt::fmt_L3TI; + fmtBuf += TranFmt::fmt_Service; + fmtBuf += TranFmt::fmt_To; + fmtBuf += TranFmt::fmt_From; + fmtBuf += TranFmt::fmt_AgeSec; + fmtBuf += TranFmt::fmt_StartTime; + if (endTime) + fmtBuf += TranFmt::fmt_EndTime; + else + fmtBuf += TranFmt::fmt_EndTime2; + fmtBuf += TranFmt::fmt_Message; + fmtBuf += "\n"; + + struct tm startTm, endTm; + localtime_r(&startTime, &startTm); + std::ostringstream svc; + mService.text(svc); + const char *psSvc = svc.str().c_str(); + if (endTime) + { + localtime_r(&endTime, &endTm); + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + endTime == 0 ? "yes" : "no", + tranID(), + mL3TI, + psSvc, + mCalled.digits()[0] ? mCalled.digits() : "", + mCalling.digits()[0] ? mCalling.digits() : "", + (stateAge()+500)/1000, + startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday, + startTm.tm_hour, startTm.tm_min, startTm.tm_sec, + startTime, + endTm.tm_year + 1900, endTm.tm_mon + 1, endTm.tm_mday, + endTm.tm_hour, endTm.tm_min, endTm.tm_sec, + endTime, + mMessage.c_str()); + } else + { + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + endTime == 0 ? "yes" : "no", + tranID(), + mL3TI, + psSvc, + mCalled.digits()[0] ? mCalled.digits() : "", + mCalling.digits()[0] ? mCalling.digits() : "", + (stateAge()+500)/1000, + startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday, + startTm.tm_hour, startTm.tm_min, startTm.tm_sec, + startTime, + "", // no end time + mMessage.c_str()); + } + os << buf; +} + +// Use this for the column data for the "calls" output +void TranEntry::text(ostream& os) const +{ + //ScopedLock lock(mLock,__FILE__,__LINE__); + if (endTime != 0) return; // don't bother printing one that's complete through this interface + os << " TranEntry("; + os <proxyPort(); + // os << " SIPState=" << mSIP->sipState(); + //} + os << LOGVARM(mService); + if (mCalled.digits()[0]) os << " to=" << mCalled.digits(); + if (mCalling.digits()[0]) os << " from=" << mCalling.digits(); + os << " stateAge=(" << (stateAge()+500)/1000 << " sec)"; + if (currentProcedure()) { + os << " stack=("; + for (list::const_iterator it = mProcStack.begin(); it != mProcStack.end(); it++) { + (*it)->machText(os); + } + os << ")"; + } + L3TimerList::text(os); + if (mMessage.size()) os << " message=\"" << mMessage << "\""; + } + os << ")"; +} + +string TranEntry::text() const +{ + ostringstream os; + text(os); + return os.str(); +} + +ostream& operator<<(ostream& os, const TranEntry& entry) +{ + entry.text(os); + return os; +} + +ostream& operator<<(ostream& os, const TranEntry* entry) +{ + if (entry == NULL) { os << "(null TranEntry)"; return os; } + entry->text(os); + return os; +} + +//void TranEntry::message(const char *wMessage, size_t length) +//{ +// /*if (length>520) { +// LOG(NOTICE) << "truncating long message: " << wMessage; +// length=520; +// }*/ +// if (isRemoved()) throw RemovedTransaction(tranID()); +// //ScopedLock lock(mLock,__FILE__,__LINE__); +// //memcpy(mMessage,wMessage,length); +// //mMessage[length]='\0'; +// mMessage.assign(wMessage, length); +//} +// +//void TranEntry::messageType(const char *wContentType) +//{ +// if (isRemoved()) throw RemovedTransaction(tranID()); +// //ScopedLock lock(mLock,__FILE__,__LINE__); +// mContentType.assign(wContentType); +//} + + + +#if EXTERNAL_TRANSACTION_TABLE +void TranEntry::runQuery(const char* query) const +{ + // Caller should hold mLock and should have already checked isRemoved().. + for (unsigned i=0; igetSipState(); +// sipStateSS << mPrevSipState; +// +// string subscriber = mSubscriber.fmidUsername(); +// +// const char* stateString = CCState::callStateString(getGSMState()); +// assert(stateString); +// +// // FIXME -- This should be done in a single SQL transaction. +// +// char query[500]; +// unsigned now = (unsigned)time(NULL); +// sprintf(query,"INSERT INTO TRANSACTION_TABLE " +// "(ID,CREATED,CHANGED,TYPE,SUBSCRIBER,L3TI,CALLED,CALLING,GSMSTATE,SIPSTATE,SIP_CALLID,SIP_PROXY) " +// "VALUES (%u,%u, %u, '%s','%s', %u,'%s', '%s', '%s', '%s', '%s', '%s')", +// tranID(),now,now, +// serviceTypeSS.str().c_str(), +// subscriber.c_str(), +// mL3TI, +// mCalled.digits(), +// mCalling.digits(), +// stateString, +// sipStateSS.str().c_str(), +// mDialog->callId().c_str(), +// mDialog->proxyIP().c_str() +// ); +// +// runQuery(query); +// +// if (!channel()) return; +// sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANNEL='%s' WHERE ID=%u", +// channel()->descriptiveString(), tranID()); +// runQuery(query); +//} + + + +void TranEntry::setSubscriberImsi(string imsi, bool andAttach) +{ + mSubscriber.mImsi = imsi; + // Now that we have an imsi we can hook up the MMUser. + if (andAttach) { + gMMLayer.mmAttachByImsi(channel(),imsi); + } +} + +L3LogicalChannel* TranEntry::channel() +{ + // Dont do this isRemoved test. We use the channel just for LOG messages. The ts will be NULL if the tran is removed. + //if (isRemoved()) throw RemovedTransaction(tranID()); + MMContext *ts = teGetContext(); + return ts ? ts->tsChannel() : NULL; +} + +const L3LogicalChannel* TranEntry::channel() const +{ + //if (isRemoved()) throw RemovedTransaction(tranID()); + //MMContext *ts = const_cast(this)->teGetContext(); // gotta love it. + MMContext *ts = Unconst(this)->teGetContext(); // gotta love it. + return ts ? ts->tsChannel() : NULL; +} + +//bool TranEntry::isChannelMatch(const L3LogicalChannel *lch) +//{ +// // The void* compares pointers even if someone defines operator== on L3LogicalChannel. +// return ((void*)this->channel() == (void*)lch || (void*)this->getL2Channel()->SACCH() == (void*)lch || +// (this->mNextChannel && ((void*)this->mNextChannel == (void*)lch || (void*)this->mNextChannel->getL2Channel()->SACCH() == (void*)lch))); +//} + +L2LogicalChannel* TranEntry::getL2Channel() const +{ + L3LogicalChannel *chan = Unconst(channel()); // what a pathetic language + return chan ? dynamic_cast(chan) : NULL; +} + + +// This is used after the channel() is changed from SDCCH to to TCHFACCH just to be safe. +L3LogicalChannel* TranEntry::getTCHFACCH() { + devassert(channel()->chtype()==FACCHType); // This is the type returned by the TCHFACCHLogicalChannel, even though it is TCH too. + return channel(); +} + + + +unsigned TranEntry::getL3TI() const +{ + //if (isRemoved()) throw RemovedTransaction(tranID()); + return mL3TI; +} + +CallState TranEntryProtected::getGSMState() const +{ + // Dont throw this for just asking about the GSMState; we do that even while the transaction is being removed, + // to print it, and etc. + // if (isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); // redundant + return mGSMState; +} + + +void TranEntryProtected::setGSMState(CallState wState) +{ + //if (wState != CCState::NullState && isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); + mStateTimer.now(); + + mGSMState = wState; +} + +SIP::SipState TranEntry::echoSipState(SIP::SipState state) const +{ + // Caller should hold mLock. + if (mPrevSipState==state) return state; + mPrevSipState = state; + + const char* stateString = SIP::SipStateString(state); + assert(stateString); + +#if EXTERNAL_TRANSACTION_TABLE + unsigned now = time(NULL); + char query[150]; + sprintf(query, + "UPDATE TRANSACTION_TABLE SET SIPSTATE='%s',CHANGED=%u WHERE ID=%u", + stateString,now,tranID()); + runQuery(query); +#endif + + return state; +} + + + +void TranEntry::setCalled(const L3CalledPartyBCDNumber& wCalled) +{ + //if (isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); + mCalled = wCalled; + +#if EXTERNAL_TRANSACTION_TABLE + char query[151]; + snprintf(query,150, + "UPDATE TRANSACTION_TABLE SET CALLED='%s' WHERE ID=%u", + mCalled.digits(), tranID()); + runQuery(query); +#endif +} + + +// Does this ti reported by the MS match this transaction? +bool TranEntry::matchL3TI(unsigned ti, bool fromMS) +{ + // Old incorrect way: + //return l3TISigBits(mL3TI) == l3TISigBits(ti); + if (fromMS) { + // If the ti argument came from the MS flip the TI flag. + if (ti & 0x8) { ti &= ~0x8; } else { ti |= 0x8; } + } + return mL3TI == ti; +} + + +void TranEntry::setL3TI(unsigned wL3TI) +{ + //if (isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); + mL3TI = wL3TI; + +#if EXTERNAL_TRANSACTION_TABLE + char query[151]; + snprintf(query,150, + "UPDATE TRANSACTION_TABLE SET L3TI=%u WHERE ID=%u", + mL3TI, tranID()); + runQuery(query); +#endif +} + + +bool TranEntry::terminationRequested() +{ + ScopedLock lock(mAnotherLock,__FILE__,__LINE__); + //if (isRemoved()) throw RemovedTransaction(tranID()); + bool retVal = mTerminationRequested; + mTerminationRequested = false; + return retVal; +} + + +// The handover is from BS1 to BS2. +// This is run in BS1 to create the handover string to send to BS2. +// The string must contain everything about the SIP side of the session. +// Everything needed to be known about the radio side of the session was transferred as an L3 HandoverCommand. +string TranEntry::handoverString(string peer) const +{ + // This string is a set of key-value pairs. + // It needs to carry all of the information of the GSM Abis Handover Request message, + // as well as all of the information of the SIP REFER message. + // We call this as "BS1" in the handover ladder diagram. + // It is decoded at the other end by a TransactionEnty constructor. + + //if (isRemoved()) throw RemovedTransaction(tranID()); + //ScopedLock lock(mLock,__FILE__,__LINE__); + ostringstream os; + os << tranID(); + os << " IMSI=" << mSubscriber.mImsi; + // We dont need these. + //HandoverEntry *handover = getHandoverEntry(true); + //if (getGSMState()==CCState::HandoverInbound) os << " inbound-ref=" << handover->mInboundReference; + //if (getGSMState()==CCState::HandoverOutbound) os << " outbound-ref=" << handover->mOutboundReference.value(); + os << " L3TI=" << mL3TI; + if (mCalled.digits()[0]) os << " called=" << mCalled.digits(); + if (mCalling.digits()[0]) os << " calling=" << mCalling.digits(); + + const SipBase *sip = Unconst(this)->getDialog(); + os << " REFER=" << sip->dsHandoverMessage(peer); + + // remote ip and port (pat) This is where we send the re-INVITE, but subsequent messages + // are sent to our proxy IP. This is wrong, but our SIP response routing for other messages is wrong too. + // RFC3261 section 4 page 16 describes routing as follows: + // 1. The INVITE is necessarily sent via proxies, which add their own "via" headers. + // 2. The reply to the INVITE must include the "via" headers so it can get back. + // 3. Subsequently, if there is a Contact field, all messages bypass the proxies and are sent directly to the Contact. + // 4. But the proxies might want to see the messages too, so they can add a "required-route" parameter which trumps + // the "contact" header and specifies that messages are sent there instead. This is called "Loose Routing." What a mess. + // And I quote: "These procedures separate the destination of the request (present in the Request-URI) from + // the set of proxies that need to be visited along the way (present in the Route header field)." + // In contrast, A Strict Router "follows the Route processing rules of RFC 2543 and many prior work in + // progress versions of this RFC. That rule caused proxies to destroy the contents of the Request-URI + // when a Route header field was present." + // 8.1.1.1: Normally the request-URI is equal to the To: field. But if there is a configured proxy (our case) + // this is called a "pre-existing route set" and we must follow 12.2.1.1 using the request-URI as the + // remote target URI(???) + // 12.2: The route-set is immutably defined by the initial INVITE. You can change the remote-URI in a re-INVITE + // (aka target-refresh-request) but not the route-set. + // Remote-URI: Intial request remote-URI must == To: field. + + + // Functional but unused. See comments in SIP::inboundHandoverSendINVITE() + //os << " RTPState=" << + // sip->RTPSession()->rtp.snd_time_offset << "," << + // sip->RTPSession()->rtp.snd_ts_offset << "," << + // sip->RTPSession()->rtp.snd_rand_offset << "," << + // sip->RTPSession()->rtp.snd_last_ts << "," << + // sip->RTPSession()->rtp.rcv_time_offset << "," << + // sip->RTPSession()->rtp.rcv_ts_offset << "," << + // sip->RTPSession()->rtp.rcv_query_ts_offset << "," << + // sip->RTPSession()->rtp.rcv_last_ts << "," << + // sip->RTPSession()->rtp.rcv_last_app_ts << "," << + // sip->RTPSession()->rtp.rcv_last_ret_ts << "," << + // sip->RTPSession()->rtp.hwrcv_extseq << "," << + // sip->RTPSession()->rtp.hwrcv_seq_at_last_SR << "," << + // sip->RTPSession()->rtp.hwrcv_since_last_SR << "," << + // sip->RTPSession()->rtp.last_rcv_SR_ts << "," << + // sip->RTPSession()->rtp.last_rcv_SR_time.tv_sec << "," << sip->RTPSession()->rtp.last_rcv_SR_time.tv_usec << "," << + // sip->RTPSession()->rtp.snd_seq << "," << + // sip->RTPSession()->rtp.last_rtcp_report_snt_r << "," << + // sip->RTPSession()->rtp.last_rtcp_report_snt_s << "," << + // sip->RTPSession()->rtp.rtcp_report_snt_interval << "," << + // sip->RTPSession()->rtp.last_rtcp_packet_count << "," << + // sip->RTPSession()->rtp.sent_payload_bytes; + + return os.str(); +} + +void NewTransactionTable::ttInit() +{ + //if (! l3rewrite()) return; // Only one of TransactionTable::init or NewTransactionTable::ttInit + LOG(DEBUG); + // This assumes the main application uses sdevrandom. + //mIDCounter = random(); + mIDCounter = 100; // pat changed. 0 is reserved. Start it high enough so it cannot possibly be confused with an L3TI. + +} + + + +unsigned NewTransactionTable::ttNewID() +{ + unsigned iCntr; + rwLock.wlock(); + iCntr = mIDCounter++; + rwLock.unlock(); + return iCntr; +} + + +void NewTransactionTable::ttAdd(TranEntry* value) +{ + LOG(DEBUG); + LOG(INFO) << "new transaction " << *value; + rwLock.wlock(); + //value->vGetRef(); + value->incRefCnt(); + mTable[value->tranID()]=value; + rwLock.unlock(); +//DIG: Debug Start + if (0) { + printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printf(" transaction ttAdd\n"); + printf("NewTransactionTable::ttAdd(%p) called, call stack follows:\n", value); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + } +//DIG: Debug End +} + + + +TranEntry* NewTransactionTable::ttFindById(TranEntryId key) +{ + // Since this is a log-time operation, we don't screw that up by calling clearDeadEntries. + + // ID==0 is a non-valid special case. + LOG(DEBUG) << "by key: " << key; + assert(key); + + TranEntry* poEntry = NULL; + rwLock.rlock(); + NewTransactionMap::iterator itr = mTable.find(key); + if (itr!=mTable.end()) + if (!itr->second->deadOrRemoved()) + poEntry = itr->second; + rwLock.unlock(); + return poEntry; +} + + +// In l3-rewrite this is called ONLY from teRemove. +// mark the element as done +bool NewTransactionTable::ttRemove(TranEntryId key) +{ + LOG(DEBUG) <second)); // copy constructor + poEntry->setEndTime(time(NULL)); + gStaleTransactionTable.ttAdd(poEntry); + mTable.erase(itr); // erase the original + } + rwLock.unlock(); + return bRet; +} + +void NewTransactionTable::ttErase(NewTransactionMap::iterator itr) +{ +//DIG: Debug Start + if (0) { + printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); + printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); + printf(" transaction ttErase\n"); + printf("NewTransactionTable::ttErase(%p) called, call stack follows:\n", itr->second); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); + printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n"); + } +//DIG: Debug End + + // we are already locked + mTable.erase(itr); // erase the original +} + +// Return true if we found it, or false if not found. +// This is called from a separate thread, so we set the flag and wait for the service loop to handle it. +bool NewTransactionTable::ttTerminate(TranEntryId tid) +{ + bool bRet = true; + rwLock.rlock(); + NewTransactionMap::iterator itr = mTable.find(tid); + if (itr==mTable.end()) bRet = false; + else + { + TranEntry *tran = itr->second; + ScopedLock lock2(tran->mAnotherLock,__FILE__,__LINE__); + tran->mTerminationRequested = true; + } + rwLock.unlock(); + return bRet; +} + +// Does the TranEntry referenced by this id still pointer to its SipDialog? +// We use the TranEntryId so we can delete the TranEntry completely separately from the SipDialog. +// However, the TranEntry has a pointer to the SipDialog, so we dont delete that until its gone. +bool NewTransactionTable::ttIsDialogReleased(TranEntryId tid) +{ + bool bRet = true; + rwLock.rlock(); + NewTransactionMap::iterator itr = mTable.find(tid); + if (itr==mTable.end()) bRet = false; + else bRet = (itr->second->mDialog == 0); + rwLock.unlock(); + return bRet; +} + +// This is only used as a bug work around for the buggy smqueue. +bool NewTransactionTable::ttSetDialog(TranEntryId tid, SipDialog *dialog) +{ + bool bRet = true; + rwLock.rlock(); + NewTransactionMap::iterator itr = mTable.find(tid); + if (itr==mTable.end()) bRet = false; + else itr->second->setDialog(dialog); + rwLock.unlock(); + return bRet; +} + +//void NewTransactionTable::clearDeadEntries() +//{ + // We just cant do this any more because there are pointers to TranEntry in the MMContext or MMUser + // If we want this functionality it has to be in the MMContext and MMUser. +//} + + +//TranEntry* NewTransactionTable::ttFindByLCH(const L3LogicalChannel *chan) +//{ +// //LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")"; +// +// ScopedLock lock(mttLock,__FILE__,__LINE__); +// +// // Yes, it's linear time. +// // Since clearDeadEntries is also linear, do that here, too. +// clearDeadEntries(); +// +// // Brute force search. +// // This search assumes in order by transaction ID. +// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { +// TranEntry *tran = itr->second; +// if (tran->deadOrRemoved()) continue; +// if ((void*)tran->channel() == (void*)chan || (void*)tran->mNextChannel == (void*)chan) return tran; +// } +// LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; +// return NULL; // not found +//} + +// Release anything associated with this channel. +//void NewTransactionTable::ttLostChannel(const L3LogicalChannel *chan) +//{ +// ScopedLock lock(mttLock,__FILE__,__LINE__); +// +// // Yes, it's linear time. +// // Since clearDeadEntries is also linear, do that here, too. +// clearDeadEntries(); +// +// // Brute force search. +// // This search assumes in order by transaction ID. +// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { +// TranEntry *tran = itr->second; +// if (tran->deadOrRemoved()) continue; +// if (tran->isChannelMatch(chan)) { +// //tran->terminate(); +// tran->teRemove(); +// } +// } +// //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; +//} + + +//TranEntry* NewTransactionTable::ttFindBySACCH(const GSM::SACCHLogicalChannel *chan) +//{ +// LOG(DEBUG) << "by SACCH: " << *chan << " (" << chan << ")"; +// +// ScopedLock lock(mttLock,__FILE__,__LINE__); +// +// // Yes, it's linear time. +// // Since clearDeadEntries is also linear, do that here, too. +// clearDeadEntries(); +// +// // Brute force search. +// TranEntry *retVal = NULL; +// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { +// if (itr->second->deadOrRemoved()) continue; +// const GSM::L2LogicalChannel* thisChan = itr->second->getL2Channel(); +// if (thisChan->SACCH() != chan) continue; +// retVal = itr->second; +// } +// return retVal; +//} + + +#if UNUSED +TranEntry* NewTransactionTable::ttFindByTypeAndOffset(GSM::TypeAndOffset desc) +{ + LOG(DEBUG) << "by type and offset: " << desc; + + ScopedLock lock(mttLock,__FILE__,__LINE__); + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + const L3LogicalChannel* thisChan = itr->second->channel(); + if (thisChan->typeAndOffset()!=desc) continue; + return itr->second; + } + //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; + return NULL; +} +#endif + + +#if UNUSED +TranEntry* NewTransactionTable::ttFindByMobileIDState(const L3MobileIdentity& mobileID, CallState state) +{ + LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state; + + ScopedLock lock(mttLock,__FILE__,__LINE__); + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->getGSMState() != state) continue; + if (itr->second->subscriber() != mobileID) continue; + return itr->second; + } + return NULL; +} +#endif + +#if UNUSED +bool NewTransactionTable::isBusy(const L3MobileIdentity& mobileID) +{ + LOG(DEBUG) << "id: " << mobileID << "?"; + + ScopedLock lock(mttLock,__FILE__,__LINE__); + + // Yes, it's linear time. + // Since clearDeadEntries is also linear, do that here, too. + clearDeadEntries(); + + // Brute force search. + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->subscriber() != mobileID) continue; + GSM::L3CMServiceType::TypeCode service = itr->second->servicetype(); + bool speech = + service==GSM::L3CMServiceType::MobileOriginatedCall || + service==GSM::L3CMServiceType::MobileTerminatedCall; + if (!speech) continue; + // OK, so we found a transaction for this call. + bool inCall = CCState::isInCall(itr->second->getGSMState()); + if (inCall) return true; + } + return false; +} +#endif + +#if UNUSED +// Find the TranEntry that wants to receive this l3msg, if any. +// Look at the PD and the TI. +// TODO: Fix this. When we start a MOC there is no TI yet so the Setup message TI will not match the TranEntry. +// If no TranEntry matches the TI, we should call, um, we cant just call the default TranEntry +// because the message may be for an old dead TranEntry. Maybe should just special-case Setup. +// Maybe the TranEntry should expectCC(Setup). +// Another way to fix might be to add a default TranEntry for each L3PD, but again that would get dead TIs. +// What we really want is a separate MM manager to route the messages. +TranEntry *NewTransactionTable::ttFindByL3Msg(GSM::L3Message *l3msg, L3LogicalChannel *lch) +{ + GSM::L3PD pd = l3msg->PD(); + ScopedLock lock(gNewTransactionTable.mttLock,__FILE__,__LINE__); + for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) { + TranEntry *tran = itr->second; + if (tran->deadOrRemoved()) continue; + if (! tran->isChannelMatch(lch)) continue; + GSM::L3CMServiceType service = tran->service(); + switch (pd) { + case L3CallControlPD: + return tran; // Only one for now. + //if (service.isCC() && tran->getL3TI() == dynamic_cast(l3msg)->TI()) { return tran; } + continue; + case L3SMSPD: + return tran; // Only one for now. + //if (service.isSMS() && tran->getL3TI() == dynamic_cast(l3msg)->TI()) { return tran; } + continue; + case L3MobilityManagementPD: + case L3RadioResourcePD: + // We dont yet have a separate MobilityManagement layer, so MM and RR messages are handled by the primary TranEntry, + // which is either the LocationUpdateRequest or the in-progress CC TranEntry, which needs RR messages + // to modify the channel for the voice call. + if (service.isMM()) { return tran; } + if (service.isSMS()) continue; + // For now, just assume there is only one transaction, so this must be it. + return tran; + default: + LOG(ERR) << "unrecognized L3"<second; + if (tran->deadOrRemoved()) continue; + if (tran->getDialog() == pDialog) return tran; + } + return NULL; +} +#endif + +// (pat added) Add a message to the TranEntry inbox. +void NewTransactionTable::ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg) +{ + rwLock.rlock(); + TranEntry* tran = ttFindById(tranid); + if (tran) { + tran->mTranInbox.write(dmsg); + rwLock.unlock(); + } else { + rwLock.unlock(); // don't do the log or delete while locked - reduce the timing window + + // This is ok - the SIP dialog and L3 transaction side are completely decoupled so it is quite + // possible that the transaction was deleted (for example, MS signal failure) while + // a SIP dialog is still running. + LOG(DEBUG) << "info: SIP Dialog message to non-existent"<second; +// if (tran->deadOrRemoved()) continue; +// if (tran->mDialog->callID() != callIDString) continue; +// if (tran->subscriber() != mobileID) continue; +// return itr->second; +// } +// return NULL; +//} + + +TranEntry* NewTransactionTable::ttFindHandoverOther(const L3MobileIdentity& mobileID, unsigned otherBS1TranId) +{ + LOG(DEBUG) <second; + + // PERFORMANCE NOTE: Should not do logging in a lock + LOG(DEBUG) << "comparing "<mHandover?tran->mHandover->mHandoverOtherBSTransactionID:-1)); + if (tran->deadOrRemoved()) continue; + if (!tran->mHandover) { + LOG(DEBUG) "no match, no handover"<mHandover->mHandoverOtherBSTransactionID != otherBS1TranId) { + LOG(DEBUG) "no match "<mHandover->mHandoverOtherBSTransactionID<<"!="<subscriber().fmidMatch(mobileID)) { + LOG(DEBUG) "no match"<subscriber()) <second->deadOrRemoved()) continue; + sz++; + os << *(itr->second) << endl; + } + rwLock.unlock(); + return sz; +} + +size_t NewTransactionTable::dumpTable(ostream& os) const +{ + rwLock.rlock(); + size_t sz = 0; + for (NewTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + sz++; + itr->second->textTable(os); + } + rwLock.unlock(); + return sz; +} + + +TranEntryId NewTransactionTable::findLongestCall() +{ + rwLock.rlock(); + //clearDeadEntries(); + long longTime = 0; + TranEntryId iRet = 0; + NewTransactionMap::iterator longCall = mTable.end(); + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (!(itr->second->channel())) continue; + if (itr->second->getGSMState() != CCState::Active) continue; + long runTime = itr->second->stateAge(); + if (runTime > longTime) { + runTime = longTime; + longCall = itr; + } + } + if (longCall == mTable.end()) iRet = 0; + else iRet = longCall->second->tranID(); + rwLock.unlock(); + return iRet; +} + +/** + Return an even UDP port number for the RTP even/odd pair. +*/ +unsigned allocateRTPPorts() +{ + const unsigned base = gConfig.getNum("RTP.Start"); + const unsigned range = gConfig.getNum("RTP.Range"); + const unsigned top = base+range; + static Mutex lock; + // Pick a random starting point. (pat) Why? Because there is a bug and we are trying to avoid it? + static unsigned port = base + 2*(random()%(range/2)); + unsigned retVal; + lock.lock(); + //This is a little hacky as RTPAvail is O(n) + do { + retVal = port; + port += 2; + if (port>=top) port=base; + } while (!gNewTransactionTable.RTPAvailable(retVal)); + lock.unlock(); + return retVal; +} + +/* linear, we should move the actual search into this structure */ +// (pat) Speed entirely irrelevant; this is done once per call. +bool NewTransactionTable::RTPAvailable(short rtpPort) +{ + rwLock.rlock(); + //clearDeadEntries(); + bool avail = true; + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->getRTPPort() == rtpPort){ + avail = false; + break; + } + } + rwLock.unlock(); + return avail; +} + +void StaleTransactionTable::ttAdd(StaleTranEntry* value) +{ + rwLock.wlock(); + if ((int)mTable.size() >= gConfig.getNum("Control.Reporting.TransactionMaxCompletedRecords")) { + mTable.erase(mTable.begin()); + } + mTable[value->tranID()]=value; + rwLock.unlock(); +} + +void StaleTransactionTable::ttErase(StaleTransactionMap::iterator itr) +{ + // we are already locked + mTable.erase(itr); // erase the original +} + +size_t StaleTransactionTable::dumpTable(ostream& os) const +{ + rwLock.rlock(); + size_t sz = 0; + for (StaleTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + sz++; + itr->second->textTable(os); + } + rwLock.unlock(); + return sz; +} + +void StaleTransactionTable::clearTable() +{ + rwLock.wlock(); + mTable.clear(); + rwLock.unlock(); +} + +MachineBase *TranEntry::tePopMachine() +{ + if (mProcStack.size() == 0) { return NULL; } + MachineBase *top = mProcStack.back(); + mProcStack.pop_back(); + return top; +} + +void TranEntry::tePushProcedure(MachineBase *it) +{ + mProcStack.push_back(it); +} + + +// Replace the current procedure with that specified. +// We dont delete the current procedure when switching between sub-procedures of an over-all procedure, for example, +// when switching from LUIdentication to LUAuthentication with L3ProcedureLocationUpdate. +// Update: the above case does not exist any more. +void TranEntry::teSetProcedure(MachineBase *wProc, bool wDeleteCurrent) +{ + if (currentProcedure() == wProc) { return; } + MachineBase *old = tePopMachine(); + wDeleteCurrent = true; // 9-24-2013: We always delete except when pusing into a procedure and that is handled by tePushProcedure. + if (wDeleteCurrent && old) { delete old; } + tePushProcedure(wProc); +} + +// Note: handleRecursion returns a MachineStatus and is meant to be used when within a state machine. +// handleMachineStatus returns a bool and is the final status-handler called when all state machines have processed the current state to completion. +MachineStatus TranEntry::handleRecursion(MachineStatus status) +{ + while (status == MachineStatusPopMachine) { // return to previous procedure on stack + // Special case: we do not return, we immediately invoke the popped-to method. + delete tran()->tePopMachine(); + LOG(DEBUG) "popped to "<debugName()<<" at state "<mPopState; + status = currentProcedure()->machineRunState(currentProcedure()->mPopState); + } + return status; +} + +// Return TRUE if the status indicates the message or whatever had a message handler, regardless of the success/fail result. +bool TranEntry::handleMachineStatus(MachineStatus status) +{ + //MMContext *set = teGetContext(); + OBJLOG(DEBUG) <isReleased()) { + // If the caller did not already call this, we dont know what the heck happened, so do a RELEASE instead of HARDRELEASE. + channel()->chanRelease(RELEASE); + } + return true; + case MachineStatus::MachineCodeQuitTran: // aka MachineStatusQuitTran + // Pop all procedures from stack and remove the transaction. Procedure already sent messages. + // This is the normal exit from a completed procedure. + teRemove(CancelCauseUnknown); // Danger will robinson!!!! Deletes the Transaction we are running. + return true; + case MachineStatus::MachineCodeUnexpectedState: // aka MachineStatusUnexpectedState + return false; // The message or state was unrecognized by this state machine. + //default: + //return true; // All others; Message was handled by the current Procedure. + } + +#if 0 + switch (status) { + case MachineStatusUnexpectedState: // Invalid procRun argument; very unlikely internal error. + LOG(ERR) << "unexpected state"; + return false; // unhandled. Should we keep going anyway? probably not. + case MachineStatusUnexpectedMessage: // error message printed by caller. + LOG(ERR) << "unsupported message"; + return false; // unhandled but keep going. + case MachineStatusQuit: + while (currentProcedure()) { + delete tran()->tePopMachine(); + } + teClose(); // Danger will robinson!!!! + return true; + case MachineStatusUnexpectedPrimitive: + LOG(ERR) << "unexpected primitive"; // Dont think this MachineStatus is used anywhere. + return false; + default: + return true; // All others; Message was handled by the current Procedure. + } +#endif + return true; // unnecessary but makes gcc happy. +} + + +// The 'lockAnd...' methods are used to initially start or restart a Procedure. +// Update: This locking is no longer needed or relevant. +// When jumping between procedures we dont use these, although it would not matter since the locks can be recursive. +// Start a procedure by calling stateStart: +// If no proc is specified here, assume that teSetProcedure was called previously and start the currentProcedure. +bool TranEntry::lockAndStart(MachineBase *wProc) +{ + ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); + if (wProc) { + teSetProcedure(wProc,false); + assert(wProc == currentProcedure()); + } else { + wProc = currentProcedure(); + assert(wProc); // Someone set the currentProcedure before calling this method. + } + return handleMachineStatus(wProc->callMachStart(wProc)); +} + + +// Start a procedure by passing it this L3 message: +bool TranEntry::lockAndStart(MachineBase *wProc, GSM::L3Message *l3msg) +{ + ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); + teSetProcedure(wProc,false); + assert(wProc == currentProcedure()); + return handleMachineStatus(wProc->dispatchL3Msg(l3msg)); +} + + +// l3msg may be NULL for primitives or unparseable messages. +bool TranEntry::lockAndInvokeFrame(const L3Frame *frame, const L3Message *l3msg) +{ + LOG(DEBUG) << l3msg; + ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); + if (MachineBase *proc = currentProcedure()) { + LOG(DEBUG) <<"sending frame to"<dispatchFrame(frame,l3msg)); + } + LOG(ERR) <<"Received message for transaction with no state machine. "<mSipHandlerState; + if (state >= 0) { return teProcInvoke(state,NULL,sipmsg); } + return MachineStatusUnexpectedMessage; +#endif + if (MachineBase *proc = currentProcedure()) { + return handleMachineStatus(proc->dispatchSipDialogMsg(sipmsg)); + } + return false; +} + +bool TranEntry::lockAndInvokeSipMsgs() +{ + // SIP Message processing is blocked during the AssignTCHF procedure. + // Now that is handled by checking for sip state changes when the AssignTCHF procedure is finished. + //if (mSipDialogMessagesBlocked) { return false; } + if (DialogMessage*dmsg = this->mTranInbox.readNoBlock()) { + lockAndInvokeSipMsg(dmsg); + delete dmsg; + // Since the message can result in the transaction being killed, only process one message + // then we return to let the caller invoke us again if the transaction is still active. + return true; + } + return false; +} + +bool TranEntry::lockAndInvokeTimeout(L3Timer *timer) +{ + LOG(DEBUG) << LOGVAR2("timer",timer->tName()) << this; + ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); + if (MachineBase *proc = currentProcedure()) { + return handleMachineStatus(proc->dispatchTimeout(timer)); + } + return false; +} + +void TranEntry::terminateHook() +{ + if (MachineBase *proc = currentProcedure()) { + proc->handleTerminationRequest(); + } +} + +void TranEntry::teCloseDialog(CancelCause cause) +{ + CallState state = getGSMState(); + + // An MO transaction may not have a dialog yet. + // The dialog can also be NULL because the phone will send a DISCONNECT first thing if a previous call did not close correctly. + SipDialog *dialog = getDialog(); + + if (dialog) { + // For the special case of outbound handover we must destroy the dialog immediately + // in case a new handover comes back to us in the reverse direction. + // just drop the dialog, dont send a BYE. + //bool terminate = (state == CCState::HandoverOutbound); + if (state == CCState::HandoverOutbound) { + cause = CancelCauseHandoverOutbound; + } + dialog->dialogCancel(cause); // Does nothing if dialog not yet started. + } +} + + +StaleTranEntry::StaleTranEntry(TranEntry &old) +{ + mStateTimer = old.mStateTimer; + mID = old.tranID(); + mService = old.mService; + mL3TI = old.mL3TI; + mCalled = old.mCalled; + mCalling = old.mCalling; + mMessage = old.mMessage; + startTime = old.startTime; + endTime = old.endTime; + + //DIG: Debug Start + if (0) { + printf("*********************************************************\n"); + printf("*********************************************************\n"); + printf(" STALE TRANSACTION ENTRY CONSTRUCTOR\n"); + printf("StaleTranEntry::StaleTranEntry() called, call stack follows:\n"); + printf("This %p, tranid %d\n", this, mID); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("*********************************************************\n"); + printf("*********************************************************\n"); + } + //DIG: Debug End +} + +StaleTranEntry::~StaleTranEntry() +{ +//DIG: Debug Start + if (0) { + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf(" destructor\n"); + printf("StalTranEntry::~StalTranEntry() called, call stack follows:\n"); + printf("This %p, tranid %d\n", this, mID); + const int elements = 100; + void *buffer[elements]; + int nptrs = backtrace(buffer, elements); + char **strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { perror("backtrace_symbols"); } + else + { + for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]); + free(strings); + } + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + } +//DIG: Debug End +} + +// Use this for the column headers for the "calls" output +void StaleTranEntry::header(ostream& os) +{ + std::string fmtBuf(""); + char buf[BUFSIZ]; + + fmtBuf += TranFmt::lblfmt_Active; + fmtBuf += TranFmt::lblfmt_TranId; + fmtBuf += TranFmt::lblfmt_L3TI; + fmtBuf += TranFmt::lblfmt_Service; + fmtBuf += TranFmt::lblfmt_To; + fmtBuf += TranFmt::lblfmt_From; + fmtBuf += TranFmt::lblfmt_AgeSec; + fmtBuf += TranFmt::lblfmt_StartTime; + fmtBuf += TranFmt::lblfmt_EndTime; + fmtBuf += TranFmt::lblfmt_Message; + fmtBuf += "\n"; + + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + "Active", + "TranId", + "L3TI", + "Service", + "To", + "From", + "AgeSec", + "Start Time", + "End Time", + "Message"); + os << buf; + + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + "======", + "==========", + "=========", + "=========", + "================", + "================", + "=========", + "=====================================", + "=====================================", + "========================================================"); + os << buf; +} + +// Use this for the column data for the "calls" output +void StaleTranEntry::textTable(ostream& os) const +{ + std::string fmtBuf(""); + char buf[BUFSIZ]; + + fmtBuf += TranFmt::fmt_Active; + fmtBuf += TranFmt::fmt_TranId; + fmtBuf += TranFmt::fmt_L3TI; + fmtBuf += TranFmt::fmt_Service; + fmtBuf += TranFmt::fmt_To; + fmtBuf += TranFmt::fmt_From; + fmtBuf += TranFmt::fmt_AgeSec; + fmtBuf += TranFmt::fmt_StartTime; + if (endTime) + fmtBuf += TranFmt::fmt_EndTime; + else + fmtBuf += TranFmt::fmt_EndTime2; + fmtBuf += TranFmt::fmt_Message; + fmtBuf += "\n"; + + struct tm startTm, endTm; + localtime_r(&startTime, &startTm); + std::ostringstream svc; + mService.text(svc); + const char *psSvc = svc.str().c_str(); + if (endTime) + { + localtime_r(&endTime, &endTm); + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + endTime == 0 ? "yes" : "no", + tranID(), + mL3TI, + psSvc, + mCalled.digits()[0] ? mCalled.digits() : "", + mCalling.digits()[0] ? mCalling.digits() : "", + (stateAge()+500)/1000, + startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday, + startTm.tm_hour, startTm.tm_min, startTm.tm_sec, + startTime, + endTm.tm_year + 1900, endTm.tm_mon + 1, endTm.tm_mday, + endTm.tm_hour, endTm.tm_min, endTm.tm_sec, + endTime, + mMessage.c_str()); + } else + { + snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(), + endTime == 0 ? "yes" : "no", + tranID(), + mL3TI, + psSvc, + mCalled.digits()[0] ? mCalled.digits() : "", + mCalling.digits()[0] ? mCalling.digits() : "", + (stateAge()+500)/1000, + startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday, + startTm.tm_hour, startTm.tm_min, startTm.tm_sec, + startTime, + "", // no end time + mMessage.c_str()); + } + os << buf; +} + +unsigned StaleTranEntry::getL3TI() const +{ + return mL3TI; +} + + + +// Used by MMLayer to immediately remove the transaction, without notifying MM layer. +// An assumption is that the dialog pointer is valid as long as the transaction exists, +// so we dont zero out the dialog pointer until we kill the dialog. +void TranEntry::teRemove(CancelCause cause) +{ + CallState state = getGSMState(); + + SipDialog *dialog = getDialog(); + mDialog = 0; + + if (dialog) { + // For the special case of outbound handover we must destroy the dialog immediately + // in case a new handover comes back to us in the reverse direction. + // just drop the dialog, dont send a BYE. + //bool terminate = (state == CCState::HandoverOutbound); + if (state == CCState::HandoverOutbound) { + cause = CancelCauseHandoverOutbound; + } + dialog->dialogCancel(cause); // Does nothing if dialog not yet started. + } + + // It is important to make this transaction no longer point at the dialog, because the dialog + // will not destroy itself while a transaction still points at it. Taking the transaction + // out of the TransactionTable prevents the dialog from finding the transaction any longer. + // However to prevent a race we must do this after using the dialog, which we did above. + setGSMState(CCState::NullState); // redundant, transaction is being deleted. + gNewTransactionTable.ttRemove(this->tranID()); + + while (currentProcedure()) { + delete tran()->tePopMachine(); + } + + if (mContext) { mContext->mmDisconnectTran(this); } // DANGER: this deletes the transaction as a side effect. +} + +// Send closure messages for a transaction that is known to be a CS transaction, using the specified CC cause. +// Must only call from the thread running the channel. +// To close all transactions on a channel, see L3LogicalChannel::chanClose() +void TranEntry::teCloseCallNow(L3Cause l3cause) +{ + WATCHINFO("CloseCallNow"<descriptiveString()); + LOG(DEBUG) <getGSMState() != CCState::NullState && tran()->getGSMState() != CCState::ReleaseRequest) { + unsigned l3ti = getL3TI(); + // 24.008 5.4.2: Permitted method to close call immediately. + channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,l3cause)); // This is a CC message that releases this Transaction immediately. + } + setGSMState(CCState::NullState); // redundant, we are deleting this transaction. +} + +}; + +// vim: ts=4 sw=4 diff --git a/Control/L3TranEntry.h b/Control/L3TranEntry.h new file mode 100644 index 0000000..9f5d03e --- /dev/null +++ b/Control/L3TranEntry.h @@ -0,0 +1,890 @@ +/**@file Declarations for TransactionTable and related classes. */ +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2011, 2012, 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 distribuion. +* +* 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. + +*/ + + + +#ifndef L3TRANSACTIONTABLE_H +#define L3TRANSACTIONTABLE_H + + +#include +#include + +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +//#include +#include "ControlTransfer.h" +#include "L3StateMachine.h" +#include "L3MobilityManagement.h" +#include "L3Utils.h" + + +/**@namespace Control This namepace is for use by the control layer. */ +namespace Control { +class CSL3StateMachine; +class MachineBase; +class MMContext; + +typedef std::map TimerTable; // Not used for l3rewrite + + + +// During handover from BS1 to BS2, BS1 sends a message and BS2 stores the info here. +class HandoverEntry { + protected: + friend class TranEntry; + friend class NewTransactionTable; + + TranEntryId mMyTranID; // Back pointer to TranEntry that owns this, only we use the ID not a pointer. + public: TranEntryId tranID() const { return mMyTranID; } + protected: + unsigned mHandoverOtherBSTransactionID; + + public: + struct ::sockaddr_in mInboundPeer; ///< other BTS in inbound handover + // The inboundReference is generated in BS2 and placed in the L3HandoverCommand + // and sent to L1FEC to be recognized as the horeference, and is not further needed, + // but here it is anyway: + unsigned mInboundReference; ///< handover reference. + + string mSipReferStr; + string mHexEncodedL3HandoverCommand; + + // (pat) This is used to encode the L3 Handover Command. + struct ::sockaddr_in mOutboundPeer; ///< other BTS in outbound handover + + //GSM::L3CellDescription mOutboundCell; + //GSM::L3ChannelDescription2 mOutboundChannel; + //GSM::L3HandoverReference mOutboundReference; + //GSM::L3PowerCommandAndAccessType mOutboundPowerCmd; + //GSM::L3SynchronizationIndication mOutboundSynch; + + protected: + void initHandoverEntry( + const struct sockaddr_in* peer, + unsigned wHandoverReference, + unsigned wHandoverOtherBSTransactionID, + SimpleKeyValue ¶ms); + + public: + HandoverEntry(const TranEntry*tran); +}; + +// Comments by pat: +// === Prior to l3write: === +// The various Procedures (MTC, MOC, etc) were handled by functions, and the fact that +// a procedure was underway was implicit in the fact that a handler function (eg MOCController) was being run. +// The TransactionEntry creation was delayed until the IMSI was known. +// All the DCCHDispatch code is only involved in the initial message starting an L3 Procedure - after that the handler functions poll. +// The TransactionEntry was not involved in message delivery except for the responses to ImmediateAssignment, paging, and handover. +// The LocationUpdating procedure did not use a TransactionEntry at all. +// The TI [Transaction Identifiers] were simply allocated round robin, which is incorrect but at least +// reduces the risk of accidently using an active one with an MS. + +// === Changes with l3rewrite: === +// The TranEntry is always associated with a radio-link to an MS or UE, represented by an L3LogicalChannel. +// I repeat: If we have a link to a phone, we have one or more TranEntrys, which is the defining characteristic of TranEntry. +// In GSM the L3LogicalChannel is identified by association with an L2LogicalChannel (SDCCH or TCH.) +// In UMTS the L3LogicalChannel is identified by a UNRTI identity. +// (fyi, in UMTS many UE share the physical radio channel and the URNTI is used to tell them apart; +// the URNTI is assigned by the BTS to the UE the very first thing, before the UE identity is known.) +// A TranEntry is always created for each L3 procedure, which is a series of L3 messages with the phone, +// including LUR [Location Update Request.] +// In 3GPP parlance an "L3 Transaction" involves a CC or SMS transaction with an associated TI [Transaction Identifier]". +// Our TranEntry may or may not represent an "L3 Transaction" in this sense, for example, the LUR case is not an "L3 Transaction", +// and we also allocate TranEntrys for the MO case as soon as we have a channel, which is before the TI is allocated. +// A TranEntry is not allocated a TI until it is started on the MS, so queued MT transactions do not yet have a TI. +// There may be multiple TranEntrys per L3LogicalChannel. +// Specifically, the spec allows four simultaneous active L3 Transactions on the phone: +// two CS [Circuit Switched, ie voice call] Transactions (the active one and the "on-hold" one) plus one SMS transaction in each direction. +// Additional MT CS or SMS transactions are queued in the MM Context until they can be started. +// The TranEntry may optionally be associated with a SipDialog, which is created only if/when the SIP Dialog is created. +// Specifically, BTS originated SMS transactions never have a SIP Dialog, and the LUR only uses a SipDialog briefly for registration purposes. + +// === Mobility Management === +// The MM Context is used to group all the transactions occuring for a single MS, identified by IMSI, +// to support and coordinate simultaneous Transactions. +// The specs do not clearly articulate the massive differences in MobilityManagement between MT and MO transactions. +// For MT transactions, the transaction identity is by IMSI, even if a TMSI is used for communication with the phone. +// The incoming MT Transactions are queued in an MM Context until an "MM Connection" +// is established with the phone, which just means that we have a radio-link with an established IMSI identity. +// For MO transactions, including LUR, the MS identity is not initially known and the TranEntry is identified only by the channel. +// (This is true for GSM, UMTS, and even GPRS.) +// Rarely, the MS may identify itself immediately by IMSI, but usually we must run an identification and/or authorization process. +// At some point we will learn the MS identity (IMSI), at which time we can attach it to its IMSI-based MM Context, and may initiate +// transactions that are queued in the MM Context. (Except for MO-SOS calls, which may never have an IMSI identity, +// which means they may never link to an MM Context.) +// The MM Context is responsible for deciding what channel to use for a transaction, specifically, if there is already +// a channel to an MS then a queued MT transaction will try to start on that channel, otherwise page the MS. +// When a CS connection ends, if there are queued SMS transactions we are supposed to move the MS from TCH to SDCCH. +// When a radio-link is lost, the MM Context must be notified so it can clean up the SIP side of all queued MT transactions. +// NOTE: 3GPP 04.11 2.2 indicates that SMS messages use the following channels: +// TCH not allocated : use SDCCH +// TCH not allocated -> TCH allocated : SDCCH -> SACCH +// TCH allocated : SACCH +// TCH allocated -> TCH not allocated : SACCH -> SACCH opt. SDCCH3 +// I believe this means when we start a call we have to recognize the new SACCH channel for the existing SMS procedures. +// It looks like SMS over GPRS uses separate transport with different messages. + +// There are weird cases related to TMSIs, because in the MO case the TMSI->IMSI mapping, even if saved in the TMSI table or received from the VLR, +// is not known authoritatively until we have authenticated the phone. While we are doing any MM procedure with an MS identified by TMSI, +// any MT transactions queued in the MM Context for the IMSI associated with the TMSI must be blocked until the TMSI is resolved +// one way or the other. +// TMSI collisions are possible. For example, multiple MS could answer a page by TMSI; we could process them serially +// or conceivably we could run authorization procedures on both of them simultaneously. +// An MS that is initially identified by TMSI and subsequently fails authentication is supposed to stop using that TMSI, +// allowing other MS in the cell with the same TMSI to try answering the page. +// Reasons for TMSI collisions include reboot of the TMSI allocation authority (the VLR or SubscriberRegistry), +// or because phones roaming in have established TMSIs from a previously visited VLR with the same PLMN+LAC. +// The MS remembers its assigned TMSI *forever* in the SIM card until it is reassigned +// (or over-written by running out of memory slots to remember TMSIs as a result of visiting too many different PLMNs), +// so even in a perfect world it would still be impossible to avoid TMSI collisions. + +// === State Machines === +// Each active TranEntry is running a single L3Procedure state machine. +// L3Procedures may call others, so there is a stack of L3Procedures in the TranEntry, but only the top one is active. +// Almost all message delivery (excluding the initial response to paging and ImmediateAssignment) is done by +// funneling all messages through a dispatcher (CSL3StateMachine) which assocates the message with a TranEntry, +// and then invokes the current L3Procedure that is pointed to by that TranEntry. +// For GSM: +// The uplink messages are associated with the TranEntry by arriving on a L3LogicalChannel dedicated to that MS. +// This works because each L3LogicalChannel (SDCCH, SACCH, FACCH) is dedicated to a single MS. +// The same MS may have both a SACCH and either a FACCH or DCCH; these messages are steered to the same TransactionEntries. +// Note that there does not appear to be anything preventing an MS from allocating multiple TCH+FACCH or SDCCH, +// but we dont worry about that. I believe the current code would even work as long as the MS used the same +// LogicalChannels for all the messages associated with a particular Procedure, but we dont currently bother to check +// the IMSI to try to match up possible multiple logical channels. +// Multiple TranEntrys on the same MS are differentiated by the TI [Transaction Identifier], which was formerly just ignored. + +// Destruction: Formerly TransactionEntrys were kept around a long time partly because they were also the parent of SIP session. +// Now when the radio-link to the MS is released, all TransactionEntries can be released immediately. +// The MM Context must also be deleted immediately - it is not designed to be a permanent TMSI-IMSI mapping repository. +// SIP Dialog destruction is handled separately in the SIP directory by timer. +// There are timers on the underlying L2LogicalChannel that prevent channel reuse which prevents stray messages +// from reaching L3 after a channel release. + +// These variables may not be modified even by TranEntry except via the accessor methods. +class TranEntryProtected +{ + friend class StaleTranEntry; + // No one may set mGSMState directly, call setGSMState + CallState mGSMState; ///< the GSM/ISDN/Q.931 call state (pat) No it is not; the enum has been contaminated. + Timeval mStateTimer; ///< timestamp of last state change. + // (pat) The mRemoved could be the final in CallState, but then we would have to be careful about the order of setting + // it during cleanup, so its safer to use a separate bool for mRemoved. + //volatile bool mRemoved; ///< true if being removed. + + public: + CallState getGSMState() const; // (pat) This is the old call state and will eventually just go away. + void setGSMState(CallState wState); + //void GSMState(CallState wState) { setGSMState(wState); } + bool isStuckOrRemoved() const; + + // (pat) This is called after the TranEntry has been removed from the TransactionTable. + // We are tagging it for deletion, not removal. + //void tagForDeletion() { mRemoved=true; mStateTimer.now(); } + + unsigned stateAge() const { /*ScopedLock lock(mLock);*/ return mStateTimer.elapsed(); } + //bool removed() const { return mRemoved; } + //bool isRemoved() const; + + /** Return true if clearing is in progress in the GSM side. */ + bool clearingGSM() const; + void stateText(ostream &os) const; + void stateText(unsigned &state, std::string &deleted) const; + + virtual TranEntryId tranID() const = 0; // This is our private transaction id, not the layer 3 TI mL3TI + TranEntryProtected() : mGSMState(CCState::NullState) /*, mRemoved(false)*/ { mStateTimer.now(); } + TranEntryProtected(TranEntryProtected &old) + { + mGSMState = old.mGSMState; + mStateTimer = old.mStateTimer; + } +}; + + +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(TranEntry,MemCheckTranEntry) +class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryProtected, public L3TimerList +{ + friend class NewTransactionTable; + friend class StaleTranEntry; + friend class MachineBase; + friend class MMContext; + //mutable Mutex mLock; ///< thread-safe control, shared from gTransactionTable + mutable HandoverEntry *mHandover; // Usually null. see getHandoverEntry() + + + protected: + + // (pat) After l3rewrite sip-side and radio-side contention is handled by funneling all messages into a single queue. + // I am keeping a lock for safety anyway in case contention is added later, for example, by handling timers in a different thread. + // We lock the TranEntry while we are running a state machine procedure. + // Cant use the old lock without either breaking the existing code or rewriting all the accessor functions, + // so here is a new lock for the l3rewrite. + mutable Mutex mL3RewriteLock; ///< thread-safe control, shared from gTransactionTable + mutable Mutex mAnotherLock; // This one is for the few items in TranEntry that are shared between threads. + + private: + /**@name Stable variables, fixed in the constructor or written only once. */ + //@{ + TranEntryId mID; ///< the internal transaction ID, assigned by a TransactionTable + + // (pat) Even though this is of type L3MobileIdentity, it is the subscriber id used for SIP messages, + // and currently it must be an IMSI. It is not something sent to the MS. + FullMobileId mSubscriber; ///< some kind of subscriber ID, preferably IMSI + private: + GSM::L3CMServiceType mService; ///< the associated service type + + // 24.007 11.2.3.1.3: The TI [Transaction Identifier] is 3 bits plus a flag. + // The flag is 0 when it belongs to a transaction initiated by its sender, else 1. + // The same TI can be used simultaneously in both direction, distinguished by the flag. + // The TI 7 is reserved for an extension mechanism which only applies to certain protocols (ie, Call Control) + // and is an error otherwise, but we should not use that value. + // The value of the flag we store here is 0 if we initiated the transaction, 1 if MS initiated. + unsigned mL3TI; ///< the L3 short transaction ID, the version we *send* to the MS + static const unsigned cL3TIInvalid = 16; // valid values are 0-7 + + GSM::L3CalledPartyBCDNumber mCalled; ///< the associated called party number, if known + GSM::L3CallingPartyBCDNumber mCalling; ///< the associated calling party number, if known + + CodecSet mCodecs; // (pat) This comment is wrong: This is for the MS, saved from the L3Setup message. + public: CodecSet getCodecs() { return mCodecs; } + public: void setCodecs(CodecSet wCodecs) { mCodecs = wCodecs; } + + public: + InterthreadQueue mTranInbox; + // SIP Message processing is blocked during the AssignTCHF procedure. + //Bool_z mSipDialogMessagesBlocked; + std::string mMessage; ///< text message payload + std::string mContentType; ///< text message payload content type + //@} + + // (pat) In v1 (Pre-l3-rewrite), this was a value, not a pointer. + // In v2, the TranEntry represents a connection to an MS, + // and the SipDialog is a decoupled separate structure managed in the SIP directory. + // The TranEntry usually becomes tied to a SipDialog + // when the SipDialog is started, but some transactions + // (notably MTSMS initiated from the BTS) dont ever have a SipDialog. + // SipDialog and TranEntry are decoupled, so running all + // the SIP messages through TranEntry is no longer necessary. + // In the near future, SipDialog will survive past the end of the + // transaction and we will be able to destroy TranEntrys + // immediately upon loss of radio link. + private: + SIP::SipDialog *mDialog; // (pat) post-l3-rewrite only. + + SIP::SipDialog *getDialog() { return mDialog; } + SIP::SipDialog *getDialog() const { return mDialog; } // grrr + + public: + void setDialog(SIP::SipDialog *dialog); // Also passed through to here from MachineBase + void teCloseDialog(CancelCause cause=CancelCauseUnknown); + + private: + mutable SIP::SipState mPrevSipState; ///< previous SIP state, prior to most recent transactions + + unsigned mNumSQLTries; ///< number of SQL tries for DB operations + + MMContext *mContext; + // For MO Transactions the Context is set at construction. + // For MT Transactions the Transacton sits in a MMUser until it is started on a channel, + // and the Context is set then. This is used by MMContext. + friend class MMUser; + protected: void teSetContext(MMContext *wContext) { mContext = wContext; } + + private: + + Bool_z mTerminationRequested; + + public: // But only used by MobilityManagement routines. + // TODO: Maybe this should move into the MMContext. + MMSharedData *mMMData; + + // Constructor Methodology: + // The actual C++ constructors are private, which means they may not be used directly + // to create TranEntrys. You must use one of the static constructor methods below. + private: + void TranEntryInit(); // Basic initialization for TranEntry. + + TranEntry( + SIP::SipDialog *wDialog, + //const GSM::L3MobileIdentity& wSubscriber, + const GSM::L3CMServiceType& wService); + + private: + static TranEntry *newMO(MMContext *wChan, const GSM::L3CMServiceType& wService); + public: + static TranEntry *newMOSMS(MMContext* wChannel); + static TranEntry *newMOC(MMContext* wChannel, L3CMServiceType::TypeCode serviceType); + static TranEntry *newMOC(MMContext* wChannel); + static TranEntry *newMOMM(MMContext* wChannel); + static TranEntry *newMOSSD(MMContext* wChannel); + + + // This is the post-l3-rewrite + static TranEntry *newMTC( + SIP::SipDialog *wDialog, + const FullMobileId& msid, + const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI. + string wCallerId); + + static TranEntry *newMTSMS( + SIP::SipDialog *dialog, + const FullMobileId& msid, + const GSM::L3CallingPartyBCDNumber& wCalling, + string smsBody, + string smsContentType); + + + static TranEntry *newHandover(const struct sockaddr_in* peer, + unsigned wHandoverReference, + SimpleKeyValue ¶ms, + L3LogicalChannel *wChannel, + unsigned wHandoverOtherBSTransactionID); + + /** Form used for handover requests; argument is taken from the message string. */ + /** unused + TranEntry(const struct ::sockaddr_in* peer, + unsigned wHandoverReference, + SimpleKeyValue ¶ms, + const char *proxy, + L3LogicalChannel* wChannel, + unsigned otherTransactionID); + **/ + + + /** Set the outbound handover parameters and set the state to HandoverOutbound. */ + void setOutboundHandover( + const GSM::L3HandoverReference& reference, + const GSM::L3CellDescription& cell, + const GSM::L3ChannelDescription2& chan, + const GSM::L3PowerCommandAndAccessType& pwrCmd, + const GSM::L3SynchronizationIndication& synch + ); + + /** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */ + void setInboundHandover( + float wRSSI, + float wTimingError + ); + +private: + /** Delete the database entry upon destruction. */ + ~TranEntry(); +public: + + /**@name Accessors. */ + //@{ + unsigned getL3TI() const; + void setL3TI(unsigned wL3TI); + bool matchL3TI(unsigned ti, bool fromMS); // Does this ti match this transaction? + bool isL3TIValid() const { + return getL3TI() != TranEntry::cL3TIInvalid; + } + + const L3LogicalChannel* channel() const; + L3LogicalChannel* channel(); + GSM::L2LogicalChannel* getL2Channel() const; + L3LogicalChannel *getTCHFACCH(); // return channel but make sure it is a TCH/FACCH. + + //void setChannel(L3LogicalChannel* wChannel); + + FullMobileId& subscriber() { return mSubscriber; } + void setSubscriberImsi(string imsi, bool andAttach); + string subscriberIMSI() { return mSubscriber.mImsi; } + + //const GSM::L3CMServiceType& service() const { return mService; } + const GSM::L3CMServiceType service() const { return mService; } + GSM::CMServiceTypeCode servicetype() const { return service().type(); } + + const GSM::L3CalledPartyBCDNumber& called() const { return mCalled; } + void setCalled(const GSM::L3CalledPartyBCDNumber&); + + const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; } + + //const char* message() const { return mMessage.c_str(); } + //void message(const char *wMessage, size_t length); + //const char* messageType() const { return mContentType.c_str(); } + //void messageType(const char *wContentType); + + TranEntryId tranID() const { return mID; } + + // FIXME: This is where we need to do choose the codec from the ones in the SDP message + // the capabilities of the MS. + CodecSet chooseCodec() { + return CodecSet(GSM_FR); // punt for the moment. + } + + //@} + + bool terminationRequested(); + + /**@name SIP-side operations */ + //@{ + + //unused SIP::SipState getSipState() const; + short getRTPPort() const; + + + // Obviously, these are only for TransactionEntries for voice calls. + //void txFrame(GSM::AudioFrame* frame) { ScopedLock lock(mLock); return mSIP->txFrame(frame); } + // Obviously, these are only for TransactionEntries for voice calls. + void txFrame(GSM::AudioFrame* frame, unsigned numFlushed); + GSM::AudioFrame *rxFrame(); + + // (pat) NOTE: This is unused except by the temporary code csl3HandleSipMsg + //const std::string SIPCallID() const { return mDialog->callID(); } + + //@} + + /** Retrns true if the transaction is "dead". */ + //bool teDead() const; + + /** Returns true if dead, or if removal already requested. */ + bool deadOrRemoved() const; + + /** Dump information as text for debugging. */ + void textTable(std::ostream&) const; + void text(std::ostream&) const; + string text() const; + static void header(std::ostream&); + + /** Genrate an encoded string for handovers. */ + std::string handoverString(string peer) const; + + private: + + /** Run a database query. */ + void runQuery(const char* query) const; + + /** Echo latest SipState to the database. */ + SIP::SipState echoSipState(SIP::SipState state) const; + + // (pat) This is genuine removal, and partial cleanup if the TranEntry is dirty. + + /** Removal status. */ + private: void teRemove(CancelCause cause); // Keep private. Dont call from Procedures. Call teCloseCall or teCancel or chanClose instead. + public: + void teCancel(CancelCause cause = CancelCauseUnknown) { teRemove(cause); } // Used from MM. Cancel dialog and delete transaction, do not notify MM layer. Used within MMLayer. + // If the channel ends being released, this is the cause. + void teCloseCallNow(GSM::L3Cause cause); // Same as teCancel but notify MM layer; used from Procedures.. + //void teCloseCall(GSM::L3Cause cause, bool faster); // Same as teCancel but notify MM layer; used from Procedures.. + + ////////////////////////////////// l3rwrite /////////////////////////////////////////// + // (pat) l3rewrite stuff: + //MachineBase *mCurrentProcedure; // TODO: Delete in destructor. + //MachineBase *currentProcedure() const { return mCurrentProcedure; } + private: + list mProcStack; + MachineBase *tePopMachine(); + void tePushProcedure(MachineBase *); + public: + MachineBase *currentProcedure() const { return mProcStack.size() ? mProcStack.back() : NULL; } + + TranEntry *tran() { return this; } // virtual callback for class ProcCommon + + void teSetProcedure(MachineBase *wProc, bool wDeleteCurrent=false); + MachineStatus handleRecursion(MachineStatus status); + bool handleMachineStatus(MachineStatus status); + + // Start the specified Procedure. If it was invoked because of a L3Message, include it. + //void teStartProcedure(MachineBase *wProc, const GSM::L3Message *l3msg=0); + + // Send a message to the current Procedure, either l3msg or lch. + // lch is the channel this message arrived on. It is information we have, but I dont think it is useful. + // I wonder if there are any cases where lch may not be the L3LogicalChannel that initiated the Procedure? + // It probably doesnt matter - we use the L3LogicalChannel to send return messages to the MS, + // and the initial channel that created the Procedure is probably the correct one. + // For example if lch is FACCH, we cannot send anything downstrem on that. + public: + // Update: We dont need to lock these in GSM because each channel runs in a separate thread. + bool lockAndInvokeL3Msg(const GSM::L3Message *l3msg /*, const L3LogicalChannel *lch*/); + bool lockAndInvokeFrame(const L3Frame *frame, const L3Message *l3msg); + bool lockAndInvokeSipMsg(const SIP::DialogMessage *sipmsg); + bool lockAndInvokeSipMsgs(); + bool lockAndStart(MachineBase *wProc=NULL); + bool lockAndStart(MachineBase *wProc, GSM::L3Message *l3msg); + //bool teInvokeState(unsigned state); + //bool teInvokeStart(MachineBase *proc); + bool lockAndInvokeTimeout(L3Timer *timer); + void terminateHook(); + MMContext *teGetContext() { return mContext; } + HandoverEntry *getHandoverEntry(bool create) const; // Creates if necessary. + TranEntry *unconst() const { return const_cast(this); } + + // The following block is used to contain the fields from the TRANSACTION_TABLE that are useful for statistics + private: + time_t startTime, endTime; // "call" start and end time + public: + inline void setEndTime(time_t t) { endTime = t; } + inline time_t getEndTime(void) { return endTime; } +}; + +namespace TranFmt +{ + // These are the format strings for the various columns being output by + // header() and text(). Make sure that they have trailing spaces. + // TODO: Figure out what the correct lengths to use are + // lblfmt_ is for the label lines - everything are strings + // fmt_ is for the data - make the format letter consistent with the actual + // data. + static const char lblfmt_Active[] = "%6.6s "; + static const char lblfmt_TranId[] = "%10.10s "; + static const char lblfmt_L3TI[] = "%9.9s "; + static const char lblfmt_Service[] = "%9.9s "; + static const char lblfmt_To[] = "%16.16s "; + static const char lblfmt_From[] = "%16.16s "; + static const char lblfmt_AgeSec[] = "%9.9s "; + static const char lblfmt_StartTime[] = "%36.36s "; + static const char lblfmt_EndTime[] = "%36.36s "; + static const char lblfmt_Message[] = "%16.16s "; + + static const char fmt_Active[] = "%6.6s "; + static const char fmt_TranId[] = "%10u "; + static const char fmt_L3TI[] = "%9u "; + static const char fmt_Service[] = "%9.9s "; + static const char fmt_To[] = "%16.16s "; + static const char fmt_From[] = "%16.16s "; + static const char fmt_AgeSec[] = "%9u "; + static const char fmt_StartTime[] = "%04.4d/%02d/%02d %02d:%02d:%02d %16d "; + static const char fmt_EndTime[] = "%04.4d/%02d/%02d %02d:%02d:%02d %16d "; + static const char fmt_EndTime2[] = "%36.36s "; // used if time is 0 + static const char fmt_Message[] = "%s "; // TODO +} + + +std::ostream& operator<<(std::ostream& os, const TranEntry&); +std::ostream& operator<<(std::ostream& os, const TranEntry*); + + + +/** A map of transactions keyed by ID. */ +class NewTransactionMap : public std::map {}; + +class StaleTranEntry; // forward reference +/** A map of transactions keyed by ID, used for completed tasks. */ +class StaleTransactionMap : public std::map {}; + +/** + A table for tracking the states of active transactions. +*/ +class NewTransactionTable { + friend class TranEntry; + friend class CSL3StateMachine; + + private: + + NewTransactionMap mTable; + mutable RWLock rwLock; + unsigned mIDCounter; + + public: + + /** + Initialize a transaction table. + */ + void ttInit(); + + /** + Return a new ID for use in the table. + */ + unsigned ttNewID(); + + /** + Insert a new entry into the table; deleted by the table later. + @param value The entry to insert into the table; will be deleted by the table later. + */ + void ttAdd(TranEntry* value); + + /** + Find an entry and return a pointer into the table. + @param wID The TransactioEntry:mID to search + @return NULL if ID is not found or was dead + */ + TranEntry* ttFindById(TranEntryId wID); + bool ttIsDialogReleased(TranEntryId wID); + bool ttSetDialog(TranEntryId tid, SIP::SipDialog *dialog); // Unlike TranEntry::setDialog(), this can be used from other directories, other threads. + + // (pat added) + // unused: TranEntry* ttFindByDialog(SIP::SipDialog *psip); + void ttAddMessage(TranEntryId tranid,SIP::DialogMessage *sipmsg); + // (pat added) + //TranEntry *ttFindByL3Msg(GSM::L3Message *l3msg, L3LogicalChannel *lch); + + /** + Find the longest-running non-SOS call. + @return NULL if there are no calls or if all are SOS. + */ + TranEntryId findLongestCall(); + + /** + Return the availability of this particular RTP port + @return True if Port is available, False otherwise + */ + bool RTPAvailable(short rtpPort); + + /** + Fand an entry by its handover reference. + @param ref The 8-bit handover reference. + @return NULL if ID is not found or was dead + */ + TranEntry* ttFindByInboundHandoverRef(unsigned ref); + + /** + Remove an entry from the table and from gSIPMessageMap. + @param wID The transaction ID to search. + @return True if the ID was really in the table and deleted. + */ + bool ttRemove(unsigned wID); // this marks the completion time + + // Use this ONLY when deleting from the Stale table in the main process + void ttErase(NewTransactionMap::iterator itr); + + bool ttTerminate(TranEntryId tid); + + + //bool remove(TranEntry* transaction) { return remove(transaction->tranID()); } + + /** + Remove an entry from the table and from gSIPMessageMap, + if it is in the Paging state. + @param wID The transaction ID to search. + @return True if the ID was really in the table and deleted. + */ + //bool removePaging(unsigned wID); + + + /** + Find an entry by its channel pointer; returns first entry found. + Also clears dead entries during search. + @param chan The channel pointer. + @return pointer to entry or NULL if no active match + */ + //TranEntry* ttFindByLCH(const L3LogicalChannel *chan); + + // The channel died. Clean up all the TransactionEntries. This is a mobility management function. + //void ttLostChannel(const L3LogicalChannel *chan); + + /** Find a transaction in the HandoverInbound state on the given channel. */ + //TranEntry* ttFindByInboundHandoverChan(const L3LogicalChannel *chan); + + /** + Find an entry by its SACCH channel pointer; returns first entry found. + Also clears dead entries during search. + @param chan The channel pointer. + @return pointer to entry or NULL if no active match + */ + //TranEntry* ttFindBySACCH(const GSM::SACCHLogicalChannel *chan); + + /** + Find an entry by its channel type and offset. + Also clears dead entries during search. + @param chan The channel pointer to the first record found. + @return pointer to entry or NULL if no active match + */ + // (pat) unused, removed. + //TranEntry* ttFindByTypeAndOffset(GSM::TypeAndOffset chanDesc); + + /** + Find an entry in the given state by its mobile ID. + Also clears dead entries during search. + @param mobileID The mobile to search for. + @return pointer to entry or NULL if no match + */ + // (pat) This one is unused too. + // TranEntry* ttFindByMobileIDState(const GSM::L3MobileIdentity& mobileID, CallState state); + + /** Return true if there is an ongoing call for this user. */ + //bool isBusy(const GSM::L3MobileIdentity& mobileID); + + + /** Find by subscriber and SIP call ID. */ + //TranEntry* ttFindBySIPCallId(const GSM::L3MobileIdentity& mobileID, const char* callID); + + /** Find by subscriber and handover other BS transaction ID. */ + TranEntry* ttFindHandoverOther(const GSM::L3MobileIdentity& mobileID, unsigned transactionID); + + /** Check for duplicated SMS delivery attempts. */ + //bool duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage); + + + /** + Find the channel, if any, used for current transactions by this mobile ID. + @param mobileID The target mobile subscriber. + @return pointer to TCH/FACCH, SDCCH or NULL. + */ + //L3LogicalChannel* findChannel(const GSM::L3MobileIdentity& mobileID); + + /** Count the number of transactions using a particular channel. */ + //unsigned countChan(const L3LogicalChannel*); + + size_t size() { size_t iSize; rwLock.rlock(); iSize = mTable.size(); rwLock.unlock(); return iSize; } + + // old style dump (calls command) + size_t dump(std::ostream& os, bool showAll=false) const; + + // new style dump (trans command) + size_t dumpTable(std::ostream& os) const; + + /** Generate a unique handover reference. */ + //unsigned generateInboundHandoverReference(TranEntry* transaction); + + private: + + + /** + Remove "dead" entries from the table. + A "dead" entry is a transaction that is no longer active. + The caller should hold mLock. + */ + //void clearDeadEntries(); + + /** + Remove and entry from the table and from gSIPInterface. + */ + //void innerRemove(TransactionMap::iterator); + + + /** Check to see if a given outbound handover reference is in use. */ + //bool outboundReferenceUsed(unsigned ref); + + // pat added - create an L3TI that is not currently in use. + // The gTMSITable.nextL3TI(wSubscriber.digits()) is just wrong - if anything it should be a bit-mask + // of the TIs that are currently in use. + // I dont think it needs to be in the TMSI table at all - if the BTS crashes all transactions are dead + // so we can start over with TIs. + //unsigned makeNewTI() { + //} +}; + +/** + A table for tracking the states of stale transactions. +*/ +class StaleTransactionTable { + private: + + StaleTransactionMap mTable; + mutable RWLock rwLock; + + public: + + /** + Insert a new entry into the table; deleted by the table later. + @param value The entry to insert into the table; will be deleted by the table later. + */ + void ttAdd(StaleTranEntry* value); + + // Use this ONLY when deleting from the Stale table in the main process + void ttErase(StaleTransactionMap::iterator itr); + + size_t size() { size_t iSize; rwLock.rlock(); iSize = mTable.size(); rwLock.unlock(); return iSize; } + + // new style dump (trans command) + size_t dumpTable(std::ostream& os) const; + void clearTable(); + + public: // these are needed for the main event loop, and should be used + // with extreme caution. + StaleTransactionMap *ttMap(void) { return &mTable; }; + void ttLock(void) { rwLock.wlock(); } + void ttUnlock(void) { rwLock.unlock(); } +}; + + +} // namespace Control + +/**@addtogroup Globals */ +//@{ +/** A single global transaction table in the global namespace. */ +extern Control::NewTransactionTable gNewTransactionTable; +//@} + +//@{ +/** Transactions that have been deleted are cloned into here. */ +extern Control::StaleTransactionTable gStaleTransactionTable; +//@} + + +namespace Control +{ +class StaleTranEntry +{ + private: + // From TranEntryProtected + Timeval mStateTimer; ///< timestamp of last state change. + public: + unsigned stateAge() const { /*ScopedLock lock(mLock);*/ return mStateTimer.elapsed(); } + void stateText(ostream &os) const; + void stateText(unsigned &state, std::string &deleted) const; + + private: + TranEntryId mID; ///< the internal transaction ID, assigned by a TransactionTable + GSM::L3CMServiceType mService; ///< the associated service type + + // 24.007 11.2.3.1.3: The TI [Transaction Identifier] is 3 bits plus a flag. + // The flag is 0 when it belongs to a transaction initiated by its sender, else 1. + // The same TI can be used simultaneously in both direction, distinguished by the flag. + // The TI 7 is reserved for an extension mechanism which only applies to certain protocols (ie, Call Control) + // and is an error otherwise, but we should not use that value. + // The value of the flag we store here is 0 if we initiated the transaction, 1 if MS initiated. + unsigned mL3TI; ///< the L3 short transaction ID, the version we *send* to the MS + static const unsigned cL3TIInvalid = 16; // valid values are 0-7 + + GSM::L3CalledPartyBCDNumber mCalled; ///< the associated called party number, if known + GSM::L3CallingPartyBCDNumber mCalling; ///< the associated calling party number, if known + + std::string mMessage; ///< text message payload + + public: + StaleTranEntry(TranEntry &old); + ~StaleTranEntry(); + + public: + unsigned getL3TI() const; + + TranEntryId tranID() const { return mID; } + + /** Dump information as text for debugging. */ + void textTable(std::ostream&) const; + static void header(std::ostream&); + + // The following block is used to contain the fields from the TRANSACTION_TABLE that are useful for statistics + private: + time_t startTime, endTime; // "call" start and end time + + public: + inline void setEndTime(time_t t) { endTime = t; } + inline time_t getEndTime(void) { return endTime; } +}; +}; // Control namespace + + + +#endif + +// vim: ts=4 sw=4 diff --git a/Control/L3Utils.cpp b/Control/L3Utils.cpp new file mode 100644 index 0000000..bdf9810 --- /dev/null +++ b/Control/L3Utils.cpp @@ -0,0 +1,110 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ +#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging +#include "L3Utils.h" +#include "L3StateMachine.h" +#define CASENAME(x) case x: return #x; + +namespace Control { + + const char *TimerId2Str(L3TimerId tid) + { + switch (tid) { + CASENAME(T301) + CASENAME(T302) + CASENAME(T303) + CASENAME(T304) + CASENAME(T305) + CASENAME(T308) + CASENAME(T310) + CASENAME(T313) + CASENAME(T3101) + CASENAME(T3113) + CASENAME(T3260) + CASENAME(T3270) + CASENAME(TR1M) + CASENAME(TR2M) + CASENAME(THandoverComplete) + CASENAME(TSipHandover) + CASENAME(TMisc1) + CASENAME(TCancel) + CASENAME(TMMCancel) + case cNumTimers: break; + // The last entry is not a timer - it is the number of max number of timers defined. + }; + return "invalid"; + } + const char * L3Timer::tName() const { return TimerId2Str(mTimerId); } + + // Start a timer that will abort the procedure. + //void L3TimerList::timerStartAbortTran(L3TimerId tid, long wEndtime) { + //mtlTimers[tid].tStart(tid,wEndtime,L3Timer::AbortTran); + //} + void L3TimerList::timerStart(L3TimerId tid, unsigned wEndtime, int nextState) { + mtlTimers[tid].tStart(tid,wEndtime,nextState); + } + void L3TimerList::timerStop(L3TimerId tid) { + mtlTimers[tid].tStop(); + } + void L3TimerList::timerStopAll() { + for (int tid = 0; tid < cNumTimers; tid++) { + mtlTimers[tid].tStop(); + } + } + bool L3TimerList::timerExpired(L3TimerId tid) { + return mtlTimers[tid].tIsExpired(); + } + + // Trigger any expired timers. Return true if any have gone off. + bool L3TimerList::checkTimers() { + for (int tid = 0; tid < cNumTimers; tid++) { + if (mtlTimers[tid].tIsExpired()) { + mtlTimers[tid].tStop(); + lockAndInvokeTimeout(&mtlTimers[tid]); + // Since the timer can result in the transaction being killed, only trigger one timer + // then we return to let the caller invoke us again if the transaction is still active. + return true; + } + } + return false; + } + + void L3TimerList::text(std::ostream &os) const { + for (int tid = 0; tid < cNumTimers; tid++) { + const L3Timer *timer = &mtlTimers[tid]; + if (timer->tIsExpired()) { os <tName(),"expired"); } + if (timer->tRemaining()) { os <tName(),timer->tRemaining()); } + } + } + + // Return the remaining time of any timers, or nextTimeout if none active. + // The idiom is to pass it -1, and it returns it if no timers running. + // This was meant for the single-thread version of the State Machines and is not currently used. + int L3TimerList::remainingTime() { + int nextTimeout = -1; + for (L3TimerId tid = (L3TimerId)0; tid < cNumTimers; tid = (L3TimerId)(tid + 1)) { + if (mtlTimers[tid].tIsActive()) { + int remaining = mtlTimers[tid].tRemaining(); + if (nextTimeout == -1) { + nextTimeout = remaining; + } else { + nextTimeout = min(nextTimeout,remaining); + } + } + } + return nextTimeout; + } +}; diff --git a/Control/L3Utils.h b/Control/L3Utils.h new file mode 100644 index 0000000..e933392 --- /dev/null +++ b/Control/L3Utils.h @@ -0,0 +1,133 @@ +/**@file Declarations for Circuit Switched State Machine and related classes. */ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ +#ifndef _L3UTILS_H_ +#define _L3UTILS_H_ +#include // Where Z100Timer lives. + +namespace Control { +enum L3TimerId { + T301, ///< recv ALERT --> recv CONN + T302, ///< send SETUP ACK --> any progress + T303, ///< send SETUP --> recv CALL CONF or REL COMP On network side, MTC only. + T304, // TODO: (pat)cant find it ///< recv SETUP ACK --> any progress + T305, ///< send DISC --> recv REL or DISC + T308, ///< send REL --> rev REL or REL COMP + T310, ///< recv CALL CONF --> recv ALERT, CONN, or DISC + T313, ///< send CONNECT --> recv CONNECT ACK + TCancel, // Generic cancellation timer. + + // Mobility Management Timers. TODO: These should be in the MMContext. + T3101, // Reassignment failure. + T3113, // for paging. + // Note that there is a 10s timeout in the MS between any two MM commands. + T3260, // AuthenticationRequest -> AuthenticationResponse. 12sec. + T3270, // IdentityRequest -> IdentityResponse. 12sec. + // THandoverComplete is not a GSM timer. We use it to make sure we get a HandoverComplete message. + THandoverComplete, + // TSipHandover is not a GSM timer - it is how long we wait on the SIP side for the peer response during handover + // before dropping the GSM side connection. It should be nearly instantaneous but we must avoid hanging in this state. + TSipHandover, + + TMisc1, // Generic timer for use by whoever needs one. + TMMCancel, // Generic cancellation timer. + TR1M, // MO-SMS timer. + TR2M, // MT-SMS timer. + cNumTimers // The last entry is not a timer - it is the number of max number of timers defined. + }; + +typedef enum L3TimerId MMTimerId; +typedef enum L3TimerId TranTimerId; + +// The timer table used for L3Rewrite. +#if 0 +class L3TimerTable { + const char *mNames[cNumTimers]; + ml3Timers[cNumTimers]; + //L3Timer mT301, mT302, mT303, mT304, mT305, mT308, mT310, mT313, mT3113, mTRIM; + + initValues { + ml3Timers[T301].load(T301ms); + ml3Timers[T302].load(T302ms); + ml3Timers[T303].load(T303ms); + ml3Timers[T304].load(T304ms); + ml3Timers[T305].load(T305ms); + ml3Timers[T308].load(T308ms); + ml3Timers[T310].load(T310ms); + ml3Timers[T313].load(T313ms); + ml3Timers[T3113].load(T3113ms); + }; + static initNames() { + for (TimerTag t = T301; t < cNumTimers; t++) { + switch (t) { // Using a switch forces this map to be kept up-to-date in this idiotic language. + case T301: mNames[T301] = "T301"; break; + case T302: mNames[T302] = "T302"; break; + case T303: mNames[T303] = "T303"; break; + case T304: mNames[T304] = "T304"; break; + case T305: mNames[T305] = "T305"; break; + case T308: mNames[T308] = "T308"; break; + case T313: mNames[T313] = "T313"; break; + case T3113: mNames[T3113] = "T3113"; break; + case TRIM: mNames[TRIM] = "TRIM"; break; + case cNumTimers: break; + } + } + } + public: + L3TimerTable() { initValues(); } +}; +#endif + +const int TimerAbortTran = -1; // Abort the transaction; If there are other transactions pending, they may be able to run. +const int TimerAbortChan = -2; // Abort the entire MMChannel. + +class L3Timer : GSM::Z100Timer +{ + L3TimerId mTimerId; + // Payload. Used as Procedure state to be invoked on timeout. Negative value is one of these: + int mNextState; + + public: + void tStart(L3TimerId wTimerId, long wEndtime, int wNextState) { Z100Timer::set(wEndtime); mTimerId = wTimerId; mNextState = wNextState; } + void tStop() { if (active()) Z100Timer::reset(); } + bool tIsActive() const { return Z100Timer::active(); } + bool tIsExpired() const { return Z100Timer::active() ? Z100Timer::expired() : false; } + long tRemaining() const { return Z100Timer::remaining(); } + int tNextState() const { return mNextState; } + const char *tName() const; +}; + + +class L3TimerList { + L3Timer mtlTimers[cNumTimers]; + protected: + virtual bool lockAndInvokeTimeout(L3Timer *timer) = 0; + + public: + //void timerStartAbort(L3TimerId id, long wEndtime); + void timerStart(L3TimerId id, unsigned wEndtime, int wNextState); + void timerStop(L3TimerId id); + void timerStopAll(); + bool timerExpired(L3TimerId id); + // Trigger any expired timers. + bool checkTimers(); + // Return the remaining time of any timers, or -1 if none active. + int remainingTime(); + void text(std::ostream&os) const; +}; // class L3TimerList + +}; // namespace +#endif diff --git a/Control/Makefile.am b/Control/Makefile.am index 98b9888..99d1fbb 100644 --- a/Control/Makefile.am +++ b/Control/Makefile.am @@ -29,13 +29,21 @@ EXTRA_DIST = README.Control noinst_LTLIBRARIES = libcontrol.la + +# L3CallControl.cpp libcontrol_la_SOURCES = \ - TransactionTable.cpp \ + L3SMSControl.cpp \ + L3MobilityManagement.cpp \ + L3CallControl.cpp \ + L3StateMachine.cpp \ + L3SupServ.cpp \ + L3MMLayer.cpp \ + L3TranEntry.cpp \ + L3LogicalChannel.cpp \ + L3Utils.cpp \ + L3Handover.cpp \ TMSITable.cpp \ - CallControl.cpp \ - SMSControl.cpp \ - ControlCommon.cpp \ - MobilityManagement.cpp \ + ControlTransfer.cpp \ RadioResource.cpp \ DCCHDispatch.cpp \ SMSCB.cpp \ @@ -45,12 +53,18 @@ libcontrol_la_SOURCES = \ # TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory. noinst_HEADERS = \ + L3SMSControl.h \ + L3CallControl.h \ + L3MobilityManagement.h \ + L3StateMachine.h \ + L3SupServ.h \ + L3MMLayer.h \ + L3TranEntry.h \ + L3LogicalChannel.h \ + L3Utils.h \ ControlCommon.h \ - SMSControl.h \ - TransactionTable.h \ + ControlTransfer.h \ TMSITable.h \ RadioResource.h \ - MobilityManagement.h \ - CallControl.h \ TMSITable.h \ RRLPServer.h diff --git a/Control/MobilityManagement.cpp b/Control/MobilityManagement.cpp deleted file mode 100644 index 62ca587..0000000 --- a/Control/MobilityManagement.cpp +++ /dev/null @@ -1,474 +0,0 @@ -/**@file GSM/SIP Mobility Management, GSM 04.08. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2011, 2012 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -#include - -#include "ControlCommon.h" -#include "MobilityManagement.h" -#include "SMSControl.h" -#include "CallControl.h" -#include "RRLPServer.h" - -#include -#include -#include -#include -#include - -using namespace std; - -#include -#include -#include -#include -#include - -using namespace SIP; - -#include -#include -#include -#undef WARNING - - -using namespace GSM; -using namespace Control; - - -/** Controller for CM Service requests, dispatches out to multiple possible transaction controllers. */ -void Control::CMServiceResponder(const L3CMServiceRequest* cmsrq, LogicalChannel* DCCH) -{ - assert(cmsrq); - assert(DCCH); - LOG(INFO) << *cmsrq; - switch (cmsrq->serviceType().type()) { - case L3CMServiceType::MobileOriginatedCall: - gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOC"); - MOCStarter(cmsrq,DCCH); - break; - case L3CMServiceType::ShortMessage: - gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.MOSMS"); - MOSMSController(cmsrq,DCCH); - break; - default: - gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.Unhandled"); - LOG(NOTICE) << "service not supported for " << *cmsrq; - // Cause 0x20 means "serivce not supported". - DCCH->send(L3CMServiceReject(0x20)); - DCCH->send(L3ChannelRelease()); - } - // The transaction may or may not be cleared, - // depending on the assignment type. -} - - - - -/** Controller for the IMSI Detach transaction, GSM 04.08 4.3.4. */ -void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalChannel* DCCH) -{ - assert(idi); - assert(DCCH); - LOG(INFO) << *idi; - - // The IMSI detach maps to a SIP unregister with the local Asterisk server. - try { - // FIXME -- Resolve TMSIs to IMSIs. (pat) And when you do call GPRSNotifyGsmActivity() on it. - if (idi->mobileID().type()==IMSIType) { - const char *digits = idi->mobileID().digits(); - GPRS::GPRSNotifyGsmActivity(digits); - SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), digits); - engine.unregister(); - } - } - catch(SIPTimeout) { - LOG(ALERT) "SIP registration timed out. Is Asterisk running?"; - } - // No reponse required, so just close the channel. - DCCH->send(L3ChannelRelease()); - // Many handsets never complete the transaction. - // So force a shutdown of the channel. - DCCH->send(HARDRELEASE); -} - - - - -/** - Send a given welcome message from a given short code. - @return true if it was sent -*/ -bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, const char *IMSI, LogicalChannel* DCCH, const char *whiteListCode = NULL) -{ - if (!gConfig.defines(messageName) || !gConfig.defines(shortCodeName)) return false; - if (!gConfig.getStr(messageName).length() || !gConfig.getStr(shortCodeName).length()) return false; - LOG(INFO) << "sending " << messageName << " message to handset"; - ostringstream message; - message << gConfig.getStr(messageName) << " IMSI:" << IMSI; - if (whiteListCode) { - message << ", white-list code: " << whiteListCode; - } - // This returns when delivery is acked in L3. - deliverSMSToMS( - gConfig.getStr(shortCodeName).c_str(), - message.str().c_str(), "text/plain", - random()%7,DCCH); - return true; -} - - -/** - Check if a phone is white-listed. - @param name name of subscriber - @return true if phone was already white-listed -*/ -bool isPhoneWhiteListed(string name) -{ - - // if name isn't in SR, then put it in with white-list flag off - string id = gSubscriberRegistry.imsiGet(name, "id"); - if (id.empty()) { - //we used to create a user here, but that's almost certainly wrong. -kurtis - LOG(CRIT) << "Checking whitelist of a user that doesn't exist. Reject"; - //return not-white-listed - return false; - } - // check flag - string whiteListFlag = gSubscriberRegistry.imsiGet(name, "whiteListFlag"); - if (whiteListFlag.empty()){ - LOG(CRIT) << "SR query error"; - return false; - } - return (whiteListFlag == "0"); -} - -/** - Generate white-list code. - @param name name of subscriber - @return the white-list code. - Also put it into the SR database. -*/ -string genWhiteListCode(string name) -{ - // generate code - uint32_t wlc = (uint32_t)rand(); - ostringstream os2; - os2 << hex << wlc; - string whiteListCode = os2.str(); - // write to SR - if (gSubscriberRegistry.imsiSet(name, "whiteListCode", whiteListCode)){ - LOG(CRIT) << "SR update error"; - return ""; - } - // and return it - return whiteListCode; -} - - - -/** - Controller for the Location Updating transaction, GSM 04.08 4.4.4. - @param lur The location updating request. - @param DCCH The Dm channel to the MS, which will be released by the function. -*/ -void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, LogicalChannel* DCCH) -{ - assert(DCCH); - assert(lur); - LOG(INFO) << *lur; - - // The location updating request gets mapped to a SIP - // registration with the SIP registrar. - - // We also allocate a new TMSI for every handset we encounter. - // If the handset is allowed to register it may receive a TMSI reassignment. - gReports.incr("OpenBTS.GSM.MM.LUR.Start"); - - // Resolve an IMSI and see if there's a pre-existing IMSI-TMSI mapping. - // This operation will throw an exception, caught in a higher scope, - // if it fails in the GSM domain. - L3MobileIdentity mobileID = lur->mobileID(); - bool sameLAI = (lur->LAI() == gBTS.LAI()); - unsigned preexistingTMSI = resolveIMSI(sameLAI,mobileID,DCCH); - const char *IMSI = mobileID.digits(); - // IMSIAttach set to true if this is a new registration. - bool IMSIAttach = (preexistingTMSI==0); - - // We assign generate a TMSI for every new phone we see, - // even if we don't actually assign it. - unsigned newTMSI = 0; - if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur); - - // White-listing. - const string name = "IMSI" + string(IMSI); - if (gConfig.getBool("Control.LUR.WhiteList")) { - LOG(INFO) << "checking white-list for " << name; - if (!isPhoneWhiteListed(name)) { - // not white-listed. reject phone. - LOG(INFO) << "is NOT white-listed"; - DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.WhiteListing.RejectCause"))); - if (!preexistingTMSI) { - // generate code (and put in SR) and send message if first time. - string whiteListCode = genWhiteListCode(name); - LOG(INFO) << "generated white-list code: " << whiteListCode; - sendWelcomeMessage("Control.LUR.WhiteListing.Message", "Control.LUR.WhiteListing.ShortCode", IMSI, DCCH, whiteListCode.c_str()); - } - // Release the channel and return. - DCCH->send(L3ChannelRelease()); - return; - } else { - LOG(INFO) << "IS white-listed"; - } - } else { - LOG(INFO) << "not checking white-list for " << name; - } - - // Try to register the IMSI. - // This will be set true if registration succeeded in the SIP world. - bool success = false; - string RAND; - try { - SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI); - LOG(DEBUG) << "waiting for registration of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration"); - success = engine.Register(SIPEngine::SIPRegister, DCCH, &RAND); - } - catch(SIPTimeout) { - LOG(ALERT) "SIP registration timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration"); - // Reject with a "network failure" cause code, 0x11. - DCCH->send(L3LocationUpdatingReject(0x11)); - gReports.incr("OpenBTS.GSM.MM.LUR.Timeout"); - // HACK -- wait long enough for a response - // FIXME -- Why are we doing this? - sleep(4); - // Release the channel and return. - DCCH->send(L3ChannelRelease()); - return; - } - - // Query for classmark? - // This needs to happen before encryption. - // It can't be optional if encryption is desired. - if (IMSIAttach && (gConfig.getBool("GSM.Cipher.Encrypt") || gConfig.getBool("Control.LUR.QueryClassmark"))) { - DCCH->send(L3ClassmarkEnquiry()); - L3Message* msg = getMessage(DCCH); - L3ClassmarkChange *resp = dynamic_cast(msg); - if (!resp) { - if (msg) { - LOG(WARNING) << "Unexpected message " << *msg; - delete msg; - } - throw UnexpectedMessage(); - } - LOG(INFO) << *resp; - const L3MobileStationClassmark2& classmark = resp->classmark(); - if (!gTMSITable.classmark(IMSI,classmark)) - LOG(WARNING) << "failed access to TMSITable"; - delete msg; - } - - // Did we get a RAND for challenge-response? - if (RAND.length() != 0) { - // Get the mobile's SRES. - LOG(INFO) << "sending " << RAND << " to mobile"; - uint64_t uRAND; - uint64_t lRAND; - gSubscriberRegistry.stringToUint(RAND, &uRAND, &lRAND); - gReports.incr("OpenBTS.GSM.MM.Authenticate.Request"); - DCCH->send(L3AuthenticationRequest(0,L3RAND(uRAND,lRAND))); - L3Message* msg = getMessage(DCCH); - L3AuthenticationResponse *resp = dynamic_cast(msg); - if (!resp) { - if (msg) { - LOG(WARNING) << "Unexpected message " << *msg; - delete msg; - } - // FIXME -- We should differentiate between wrong message and no message at all. - throw UnexpectedMessage(); - } - LOG(INFO) << *resp; - uint32_t mobileSRES = resp->SRES().value(); - delete msg; - // verify SRES - try { - SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI); - LOG(DEBUG) << "waiting for authentication of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration"); - ostringstream os; - os << hex << mobileSRES; - string SRESstr = os.str(); - success = engine.Register(SIPEngine::SIPRegister, DCCH, &RAND, IMSI, SRESstr.c_str()); - if (!success) { - gReports.incr("OpenBTS.GSM.MM.Authenticate.Failure"); - LOG(CRIT) << "authentication failure for IMSI " << IMSI; - DCCH->send(L3AuthenticationReject()); - DCCH->send(L3ChannelRelease()); - return; - } - if (gConfig.getBool("GSM.Cipher.Encrypt")) { - int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(IMSI); - if (!encryptionAlgorithm) { - LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *DCCH << " for " << mobileID; - } else if (DCCH->decryptUplink_maybe(mobileID.digits(), encryptionAlgorithm)) { - LOG(DEBUG) << "sending Ciphering Mode Command on " << *DCCH << " for " << mobileID; - DCCH->send(GSM::L3CipheringModeCommand( - GSM::L3CipheringModeSetting(true, encryptionAlgorithm), - GSM::L3CipheringModeResponse(false))); - } else { - LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *DCCH << " for " << mobileID; - } - } - gReports.incr("OpenBTS.GSM.MM.Authenticate.Success"); - } - catch(SIPTimeout) { - LOG(ALERT) "SIP authentication timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration"); - // Reject with a "network failure" cause code, 0x11. - DCCH->send(L3LocationUpdatingReject(0x11)); - // HACK -- wait long enough for a response - // FIXME -- Why are we doing this? - sleep(4); - // Release the channel and return. - DCCH->send(L3ChannelRelease()); - return; - } - } - - // This allows us to configure Open Registration - bool openRegistration = false; - string openRegistrationPattern = gConfig.getStr("Control.LUR.OpenRegistration"); - if (openRegistrationPattern.length()) { - Regexp rxp(openRegistrationPattern.c_str()); - openRegistration = rxp.match(IMSI); - string openRegistrationRejectPattern = gConfig.getStr("Control.LUR.OpenRegistration.Reject"); - if (openRegistrationRejectPattern.length()) { - Regexp rxpReject(openRegistrationRejectPattern.c_str()); - bool openRegistrationReject = rxpReject.match(IMSI); - openRegistration = openRegistration && !openRegistrationReject; - } - } - - // Query for IMEI? - // FIXME -- This needs to happen before sending the REGISTER method, so we can put it in a SIP header. - // See ticket #1101. - unsigned rejectCause = gConfig.getNum("Control.LUR.UnprovisionedRejectCause"); - if (gConfig.getBool("Control.LUR.QueryIMEI")) { - DCCH->send(L3IdentityRequest(IMEIType)); - L3Message* msg = getMessage(DCCH); - L3IdentityResponse *resp = dynamic_cast(msg); - if (!resp) { - if (msg) { - LOG(WARNING) << "Unexpected message " << *msg; - delete msg; - } - throw UnexpectedMessage(); - } - LOG(INFO) << *resp; - string new_imei = resp->mobileID().digits(); - if (!gTMSITable.IMEI(IMSI,new_imei.c_str())){ - LOG(WARNING) << "failed access to TMSITable"; - } - //query subscriber registry for old imei, update if necessary - string old_imei = gSubscriberRegistry.imsiGet(name, "hardware"); - - //if we have a new imei and either there's no old one, or it is different... - if (!new_imei.empty() && (old_imei.empty() || old_imei != new_imei)){ - LOG(INFO) << "Updating IMSI" << IMSI << " to IMEI:" << new_imei; - if (gSubscriberRegistry.imsiSet(name,"RRLPSupported", "1")) { - LOG(INFO) << "SR RRLPSupported update problem"; - } - if (gSubscriberRegistry.imsiSet(name,"hardware", new_imei)) { - LOG(INFO) << "SR hardware update problem"; - } - } - delete msg; - } - - // We fail closed unless we're configured otherwise - if (!success && !openRegistration) { - LOG(INFO) << "registration FAILED: " << mobileID; - DCCH->send(L3LocationUpdatingReject(rejectCause)); - if (!preexistingTMSI) { - sendWelcomeMessage( "Control.LUR.FailedRegistration.Message", - "Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH); - } - // Release the channel and return. - DCCH->send(L3ChannelRelease()); - return; - } - - // If success is true, we had a normal registration. - // Otherwise, we are here because of open registration. - // Either way, we're going to register a phone if we arrive here. - - if (success) { - LOG(INFO) << "registration SUCCESS: " << mobileID; - } else { - LOG(INFO) << "registration ALLOWED: " << mobileID; - } - - - // Send the "short name" and time-of-day. - if (IMSIAttach && gConfig.defines("GSM.Identity.ShortName")) { - DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str())); - } - // Accept. Make a TMSI assignment, too, if needed. - if (preexistingTMSI || !gConfig.getBool("Control.LUR.SendTMSIs")) { - DCCH->send(L3LocationUpdatingAccept(gBTS.LAI())); - } else { - assert(newTMSI); - DCCH->send(L3LocationUpdatingAccept(gBTS.LAI(),newTMSI)); - // Wait for MM TMSI REALLOCATION COMPLETE (0x055b). - L3Frame* resp = DCCH->recv(1000); - // FIXME -- Actually check the response type. - if (!resp) { - LOG(NOTICE) << "no response to TMSI assignment"; - } else { - LOG(INFO) << *resp; - } - delete resp; - } - - if (gConfig.getBool("Control.LUR.QueryRRLP")) { - // Query for RRLP - if (!sendRRLP(mobileID, DCCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - - // If this is an IMSI attach, send a welcome message. - if (IMSIAttach) { - if (success) { - sendWelcomeMessage( "Control.LUR.NormalRegistration.Message", - "Control.LUR.NormalRegistration.ShortCode", IMSI, DCCH); - } else { - sendWelcomeMessage( "Control.LUR.OpenRegistration.Message", - "Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH); - } - // set unix time of most recent registration - // No - this happending in the registration proxy. - //gSubscriberRegistry.setRegTime(name); - } - - // Release the channel and return. - DCCH->send(L3ChannelRelease()); - return; -} - - - - -// vim: ts=4 sw=4 diff --git a/Control/MobilityManagement.h b/Control/MobilityManagement.h deleted file mode 100644 index 2f0e296..0000000 --- a/Control/MobilityManagement.h +++ /dev/null @@ -1,39 +0,0 @@ -/**@file GSM/SIP Mobility Management, GSM 04.08. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. -* -* 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. - -*/ - -#ifndef MOBILITYMANAGEMENT_H -#define MOBILITYMANAGEMENT_H - - -namespace GSM { -class LogicalChannel; -class L3CMServiceRequest; -class L3LocationUpdatingRequest; -class L3IMSIDetachIndication; -}; - -namespace Control { - -void CMServiceResponder(const GSM::L3CMServiceRequest* cmsrq, GSM::LogicalChannel* DCCH); - -void IMSIDetachController(const GSM::L3IMSIDetachIndication* idi, GSM::LogicalChannel* DCCH); - -void LocationUpdatingController(const GSM::L3LocationUpdatingRequest* lur, GSM::LogicalChannel* DCCH); - -} - - -#endif diff --git a/Control/RRLPServer.cpp b/Control/RRLPServer.cpp index 3e33d4d..b7b1ac0 100644 --- a/Control/RRLPServer.cpp +++ b/Control/RRLPServer.cpp @@ -1,7 +1,7 @@ /**@file RRLPServer */ /* * Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc. -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. @@ -23,23 +23,37 @@ along with this program. If not, see . */ +#define LOG_GROUP LogGroup::Control #include using namespace std; #include "ControlCommon.h" +#include "L3MMLayer.h" #include "RRLPServer.h" #include #include - -#include +#include // for L3ApplicationInformation #include #include +namespace Control { using namespace GSM; -using namespace Control; + +// (pat) Notes: +// Relevant documents are GSM 3.71 and 4.31. +// LCS [Location Services] includes positioning using TOA [Time of Arrival] or E-OTD or GPS. +// E-OTD Enhanced Observed Time Difference - MS determines postion from synchronized cell beacons. +// MO-LR Mobile Originated Location Request. +// MT-LR Moble Terminated, where LCS client is external to PLMN +// NI-LR Network Induced, where LCS client is within LCS servier, eg, Emergency call Origination. +// NA-EXRD North American Emergency Services Routing Digits, identifies emergency services provider and LCS client, including cell. +// MLC, SMLC, GMLC: [Serving, Gateway] Mobile Location Center - the thing on the network that does this. +// Location Service Request may be LIR [Location Immediate Request] or LDR [Location Deferred Request]. +// Location Preparation Procedure: +// NA void clean(char *line) { @@ -85,220 +99,183 @@ string getConfig() return config; } -RRLPServer::RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH) +RRLPServer::RRLPServer(string wSubscriber) { - trouble = false; - url = gConfig.getStr("GSM.RRLP.SERVER.URL"); - if (url.length() == 0) { + mTrouble = false; + mURL = gConfig.getStr("GSM.RRLP.SERVER.URL"); + if (mURL.length() == 0) { LOG(INFO) << "RRLP not configured"; - trouble = true; + mTrouble = true; return; } - mobileID = wMobileID; - DCCH = wDCCH; - // name of subscriber - name = string("IMSI") + mobileID.digits(); + //mDCCH = wLCH; + mName = wSubscriber; // don't continue if IMSI has a RRLP support flag and it's false //if IMEI tagging enabled, check if this IMEI (which is updated elsewhere) has RRLP disabled //otherwise just go on if (gConfig.getBool("Control.LUR.QueryIMEI")){ //check supported bit - string supported= gSubscriberRegistry.imsiGet(name, "RRLPSupported"); - if(supported.empty() || supported == "0"){ - LOG(INFO) << "RRLP not supported for " << name; - trouble = true; - } + // TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access + //string supported= gSubscriberRegistry.imsiGet(mName, "RRLPSupported"); + //if(supported.empty() || supported == "0"){ + // LOG(INFO) << "RRLP not supported for " << mName; + // mTrouble = true; + //} } } -bool RRLPServer::assist() +// send a location request +void RRLPServer::rrlpSend(L3LogicalChannel *chan) { - if (trouble) return false; - query = "query=assist"; - return transact(); -} - -bool RRLPServer::locate() -{ - if (trouble) return false; - query = "query=loc"; - return transact(); -} - -bool RRLPServer::transact() -{ - vector apdus; - while (true) { - // bounce off server - string esc = "'"; - string config = getConfig(); - if (config.length() == 0) return false; - string cmd = "wget -qO- " + esc + url + "?" + query + config + esc; - LOG(INFO) << "*************** " << cmd; - FILE *result = popen(cmd.c_str(), "r"); - if (!result) { - LOG(CRIT) << "popen call \"" << cmd << "\" failed"; - return NULL; - } - // build map of responses, and list of apdus - map response; - size_t nbytes = 1500; - char *line = (char*)malloc(nbytes+1); - while (!feof(result)) { - if (!fgets(line, nbytes, result)) break; - clean(line); - LOG(INFO) << "server return: " << line; - char *p = strchr(line, '='); - if (!p) continue; - string lhs = string(line, 0, p-line); - string rhs = string(line, p+1-line, string::npos); - if (lhs == "apdu") { - apdus.push_back(rhs); - } else { - response[lhs] = rhs; - } - } - free(line); - pclose(result); - - // quit if error - if (response.find("error") != response.end()) { - LOG(INFO) << "error from server: " << response["error"]; - return false; - } - - // quit if ack from assist unless there are more apdu messages - if (response.find("ack") != response.end()) { - LOG(INFO) << "ack from mobile, decoded by server"; - if (apdus.size() == 0) { - return true; - } else { - LOG(INFO) << "more apdu messages"; - } - } - - // quit if location decoded - if (response.find("latitude") != response.end() && - response.find("longitude") != response.end() && - response.find("positionError") != response.end()) { - if (!gSubscriberRegistry.RRLPUpdate(name, response["latitude"], - response["longitude"], - response["positionError"])){ - LOG(INFO) << "SR update problem"; - return false; - } - return true; - } - - // bounce off mobile - if (apdus.size() == 0) { - LOG(INFO) << "missing apdu for mobile"; - LOG(ERR) << "MS did not respond gracefully to RRLP message."; - return false; - } - string apdu = apdus[0]; - apdus.erase(apdus.begin()); - BitVector bv(apdu.size()*4); - if (!bv.unhex(apdu.c_str())) { - LOG(INFO) << "BitVector::unhex problem"; - LOG(ERR) << "MS did not respond gracefully to RRLP message."; - return false; - } - - DCCH->send(L3ApplicationInformation(bv)); - // Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds. - L3Frame* resp = DCCH->recv(130000); - if (!resp) { - LOG(INFO) << "timed out"; - LOG(ERR) << "MS did not respond gracefully to RRLP message."; - return false; - } - LOG(INFO) << "RRLPQuery returned " << *resp; - if (resp->primitive() != DATA) { - LOG(INFO) << "didn't receive data"; - switch (resp->primitive()) { - case ESTABLISH: LOG(INFO) << "channel establishment"; break; - case RELEASE: LOG(INFO) << "normal channel release"; break; - case DATA: LOG(INFO) << "multiframe data transfer"; break; - case UNIT_DATA: LOG(INFO) << "datagram-type data transfer"; break; - case ERROR: LOG(INFO) << "channel error"; break; - case HARDRELEASE: LOG(INFO) << "forced release after an assignment"; break; - default: LOG(INFO) << "unrecognized primitive response"; break; - } - delete resp; - LOG(ERR) << "MS did not respond gracefully to RRLP message."; - return false; - } - const unsigned PD_RR = 6; - LOG(INFO) << "resp.pd = " << resp->PD(); - if (resp->PD() != PD_RR) { - LOG(INFO) << "didn't receive an RR message"; - delete resp; - LOG(ERR) << "MS did not respond gracefully to RRLP message."; - return false; - } - const unsigned MTI_RR_STATUS = 18; - if (resp->MTI() == MTI_RR_STATUS) { - ostringstream os2; - int cause = resp->peekField(16, 8); - delete resp; - switch (cause) { - case 97: - LOG(INFO) << "MS says: message not implemented"; - //Rejection code only useful if we're gathering IMEIs - if (gConfig.getBool("Control.LUR.QueryIMEI")){ - // flag unsupported in SR so we don't waste time on it again - // flag unsupported in SR so we don't waste time on it again - if (gSubscriberRegistry.imsiSet(name, "RRLPSupported", "0")) { - LOG(INFO) << "SR update problem"; - } - } - return false; - case 98: - LOG(INFO) << "MS says: message type not compatible with protocol state"; - return false; - default: - LOG(INFO) << "unknown RR_STATUS response, cause = " << cause; - return false; - } - } - const unsigned MTI_RR_APDU = 56; - if (resp->MTI() != MTI_RR_APDU) { - LOG(INFO) << "received unexpected RR Message " << resp->MTI(); - delete resp; - return false; - } - - // looks like a good APDU - BitVector *bv2 = (BitVector*)resp; - BitVector bv3 = bv2->tail(32); - ostringstream os; - bv3.hex(os); - apdu = os.str(); - delete resp; - - // next query for server - query = "query=apdu&apdu=" + apdu; + mQuery = "query=loc"; + // server encodes location request into apdu + serve(); + if (mAPDUs.size() == 0) { + LOG(ERR) << "no apdu to send to mobile"; + return; } - // not reached + if (mAPDUs.size() > 1) { + // there should only be extra apdus for query=assist + LOG(ERR) << "ignoring extra apdus for mobile"; + } + + string apdu = mAPDUs[0]; + mAPDUs.clear(); + BitVector2 bv(apdu.size()*4); + if (!bv.unhex(apdu.c_str())) { + LOG(INFO) << "BitVector::unhex problem"; + return; + } + chan->l3sendm(L3ApplicationInformation(bv)); } -bool sendRRLP(GSM::L3MobileIdentity mobileID, GSM::LogicalChannel *LCH) +// decode a RRLP response from the mobile +void RRLPServer::rrlpRecv(const GSM::L3Message *resp) { - // Query for RRLP - RRLPServer wRRLPServer(mobileID, LCH); - if (!wRRLPServer.locate()) { - LOG(INFO) << "locate problem"; - if (!wRRLPServer.assist()) { - LOG(INFO) << "assist problem"; - // Failure of assist will almost invariably lead to failure in locate. - // Don't even try. It causes too much confusion. - return false; - } - if (!wRRLPServer.locate()) { - LOG(INFO) << "locate problem"; - return false; + if (!resp) { + LOG(INFO) << "NULL message"; + return; + } + LOG(INFO) << "resp.pd = " << resp->PD(); + if (resp->PD() != GSM::L3RadioResourcePD) { + LOG(INFO) << "didn't receive an RR message"; + return; + } + if (resp->MTI() != L3RRMessage::ApplicationInformation) { + LOG(INFO) << "received unexpected RR Message " << resp->MTI(); + return; + } + + // looks like a good APDU + BitVector2 *bv2 = (BitVector2*)resp; + BitVector2 bv3 = bv2->tail(32); + ostringstream os; + bv3.hex(os); + string apdu = os.str(); + mQuery = "query=apdu&apdu=" + apdu; + // server will decode the apdu + serve(); + if (mResponse.find("latitude") != mResponse.end() && + mResponse.find("longitude") != mResponse.end() && + mResponse.find("positionError") != mResponse.end()) { + // TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access + //if (!gSubscriberRegistry.RRLPUpdate(mName, mResponse["latitude"], + // mResponse["longitude"], + // mResponse["positionError"])){ + // LOG(INFO) << "SR update problem"; + //} + } + // TODO - a failed location request would be a good time to send assistance + // TODO - an "ack" response means send the next apdu if there is another; otherwise send a location request + +} + +// encodes or decodes apdus +void RRLPServer::serve() +{ + string esc = "'"; + string config = getConfig(); + if (config.length() == 0) return; + string cmd = "wget -qO- " + esc + mURL + "?" + mQuery + config + esc; // (pat) Could use format() here. + LOG(INFO) << "*************** " << cmd; + FILE *result = popen(cmd.c_str(), "r"); + if (!result) { + LOG(CRIT) << "popen call \"" << cmd << "\" failed"; + return; + } + // build map of responses, and list of apdus + size_t nbytes = 1500; + char *line = (char*)malloc(nbytes+1); + while (!feof(result)) { + if (!fgets(line, nbytes, result)) break; + clean(line); + LOG(INFO) << "server return: " << line; + char *p = strchr(line, '='); + if (!p) continue; + string lhs = string(line, 0, p-line); + string rhs = string(line, p+1-line, string::npos); + if (lhs == "apdu") { + mAPDUs.push_back(rhs); + } else { + mResponse[lhs] = rhs; } } - return true; + free(line); + pclose(result); + if (mResponse.find("error") != mResponse.end()) { + LOG(INFO) << "error from server: " << mResponse["error"]; + } } + +// (pat 9-20-2013) Message recommended by doug for RLLP testing. +void sendPirates(L3LogicalChannel *chan) +{ + string bvs = + "0010010000010000010000000100111000000100000000000000111110000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000011011000011100101101100" + "1000000010000000000110111011110111010111010101011110100001110010110100101100100011011" + "1101010111110001111110111011110101111010100000000110000000010111111001100100001101100" + "0000101000010000110010010011111101100101011000100010000000011111111111110011001001111" + "0010000100111011011101000000000100000101001110110001100000000011000111010100000001101" + "0101011110011000101110001011111101111111101000110000111101110110100111011000000010000" + "0000000001010001000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000100011000011100101101100100000001000000000100100100100111000000000" + "0011100010111100111110101101011010000001101111000101110010111101111110001010100001100" + "0000001011010000011000000111100100100110000001010100001000011011100011100011111010101" + "1000100010000000100000000000000010011100111101111111010011010010011111111110111110100" + "1111011000000101001110011011001101001110100100111100010010011111001101100100111111110" + "1010010011110110011001100000000100000010001000000000110011100000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000001101011001110010110110" + "0100000001000000000000001010010111010000000100110001100101111101011011101010110111001" + "0110111110000111010101000010001011001011010000000110101010110000000001001110010010100" + "1100110100001000011011010011101100110010101100010001000000001111111111000110001110100" + "0100101001011101110100011111111111111110100110101000111111001101001000100110010100000" + "0100010110000111001011101111010100111111110100100101110111001110010000000"; + BitVector2 bv(bvs.c_str()); + chan->l3sendm(L3ApplicationInformation(bv)); + //L3Frame* resp = channel()->l2recv(130000); + //LOG(ALERT) << "RRLP returned " << *resp; +} + +bool sendRRLP(GSM::L3MobileIdentity wMobileID, L3LogicalChannel *wLCH) +{ + // (pat) DEBUG TEST... + sendPirates(wLCH); return true; + + string sub = wMobileID.digits(); + RRLPServer wRRLPServer(sub); + if (wRRLPServer.trouble()) return false; + wRRLPServer.rrlpSend(wLCH); + return true; // TODO - what to return here? +} + +void recvRRLP(MMContext *wLCH, const GSM::L3Message *wMsg) +{ + LOG(DEBUG) < // For L3MobileIdentity +#include "ControlCommon.h" -namespace GSM { -class LogicalChannel; -class L3MobileIdentity; -}; +namespace Control { class RRLPServer { public: - RRLPServer(GSM::L3MobileIdentity wMobileID, GSM::LogicalChannel *wDCCH); - // tell server to send location assistance to mobile - bool assist(); - // tell server to ask mobile for location - bool locate(); + RRLPServer(string wSubscriber); + bool trouble() { return mTrouble; } + void rrlpSend(L3LogicalChannel *mmchan); + void rrlpRecv(const GSM::L3Message*); private: RRLPServer(); // not allowed - string url; - GSM::L3MobileIdentity mobileID; - GSM::LogicalChannel *DCCH; - string query; - string name; - bool transact(); - bool trouble; + void serve(); + string mURL; + // GSM::L3MobileIdentity mMobileID; + //L3LogicalChannel *mDCCH; (pat) The channel is passed as an arg every time this is called, and it could change, so dont cache it. + string mQuery; + string mName; + bool mTrouble; + map mResponse; + // Before assist can be restored, mAPDUs is going to have to go into more persistent storage. + // TransactionEntry for call? Dunno for sms or lur. + vector mAPDUs; }; -bool sendRRLP(GSM::L3MobileIdentity mobileID, GSM::LogicalChannel *LCH); +bool sendRRLP(GSM::L3MobileIdentity wMobileID, L3LogicalChannel *wLCH); +void recvRRLP(MMContext *wLCH, const GSM::L3Message *wMsg); + +}; #endif diff --git a/Control/RadioResource.cpp b/Control/RadioResource.cpp index 4fa10e2..5817166 100644 --- a/Control/RadioResource.cpp +++ b/Control/RadioResource.cpp @@ -3,7 +3,7 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2014 Range Networks, Inc. * This program is distributed in the hope that it will be useful, @@ -19,6 +19,7 @@ */ +#define LOG_GROUP LogGroup::Control #include @@ -27,10 +28,9 @@ #include #include "ControlCommon.h" -#include "TransactionTable.h" #include "RadioResource.h" -#include "SMSControl.h" -#include "CallControl.h" +#include "L3CallControl.h" +#include "L3MMLayer.h" #include #include @@ -38,8 +38,7 @@ #include #include -#include - +#include #include #include @@ -53,8 +52,6 @@ using namespace GSM; using namespace Control; -extern unsigned allocateRTPPorts(); - /** @@ -69,12 +66,15 @@ extern unsigned allocateRTPPorts(); static ChannelType decodeChannelNeeded(unsigned RA) { - // This code is based on GSM 04.08 Table 9.9. + // This code is based on GSM 04.08 Table 9.9. section 9.1.8 unsigned RA4 = RA>>4; unsigned RA5 = RA>>5; + // We use VEA for all emergency calls, regardless of configuration. + if (RA5 == 0x05) return TCHFType; // emergency call + // Answer to paging, Table 9.9a. // We don't support TCH/H, so it's wither SDCCH or TCH/F. // The spec allows for "SDCCH-only" MS. We won't support that here. @@ -120,6 +120,10 @@ bool requestingLUR(unsigned RA) } + + + + /** Decode RACH bits and send an immediate assignment; may block waiting for a channel for an SOS call. */ static void AccessGrantResponder( @@ -133,6 +137,7 @@ void AccessGrantResponder( // and send the assignment to the handset on the CCCH. // This GSM's version of medium access control. // Papa Legba, open that door... + // The RA bits are defined in 44.018 9.1.8 gReports.incr("OpenBTS.GSM.RR.RACH.TA.All",(int)(timingError)); gReports.incr("OpenBTS.GSM.RR.RACH.RA.All",RA); @@ -160,7 +165,7 @@ void AccessGrantResponder( LOG(NOTICE) << "RACH" <maxAge) { LOG(WARNING) << "ignoring RACH bust with age " << age; // FIXME -- What was supposed to be happening here? - gBTS.growT3122()/1000; // Hmmm... + //gBTS.growT3122()/1000; // Hmmm... + gBTS.growT3122(); // (pat) removed the /1000 return; } - // Screen for delay. if (timingError>gConfig.getNum("GSM.MS.TA.Max")) { LOG(NOTICE) << "ignoring RACH burst with delay="<send(reject); + AGCH->l2sendm(reject); return; } } // Allocate the channel according to the needed type indicated by RA. // The returned channel is already open and ready for the transaction. - LogicalChannel *LCH = NULL; + L2LogicalChannel *LCH = NULL; switch (chtype) { case TCHFType: LCH = gBTS.getTCH(); break; case SDCCHType: LCH = gBTS.getSDCCH(); break; @@ -228,7 +239,7 @@ void AccessGrantResponder( L3RequestReference(RA,when), RSSI,timingError,AGCH); if (msg) { - AGCH->send(*msg); + AGCH->l2sendm(*msg); delete msg; } return; @@ -243,15 +254,18 @@ void AccessGrantResponder( default: assert(0); } + // Nothing available? if (!LCH) { // Rejection, GSM 04.08 3.3.1.1.3.2. - unsigned waitTime = gBTS.growT3122()/1000; - // TODO: If all channels are statically allocated for gprs, dont throw an alert. - LOG(CRIT) << "congestion, RA=" << RA << " T3122=" << waitTime; - const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime); - LOG(DEBUG) << "rejection, sending " << reject; - AGCH->send(reject); + { + unsigned waitTime = gBTS.growT3122()/1000; + // TODO: If all channels are statically allocated for gprs, dont throw an alert. + LOG(CRIT) << "congestion, RA=" << RA << " T3122=" << waitTime; + const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime); + LOG(DEBUG) << "rejection, sending " << reject; + AGCH->l2sendm(reject); + } return; } @@ -279,8 +293,12 @@ void AccessGrantResponder( // (pat) This call appears to block. // (david) Not anymore. It got fixed in the trunk while you were working on GPRS. // (doug) Adding in a delay to make sure SI5/6 get out before IA. - sleepFrames(20); - AGCH->send(assign); + // (pat) I believe this sleep is necessary partly because of a delay in starting the SI5/SI6 service loop; + // see SACCHLogicalChannel::serviceLoop() which sleeps when the channel is inactive. + // I reduced the delays in both places. + //sleepFrames(20); + sleepFrames(4); + AGCH->l2sendm(assign); // On successful allocation, shrink T3122. gBTS.shrinkT3122(); @@ -302,476 +320,73 @@ void* Control::AccessGrantServiceLoop(void*) return NULL; } -void abortInboundHandover(TransactionEntry* transaction, unsigned cause, GSM::LogicalChannel *LCH=NULL) + + +GSM::ChannelType NewPagingEntry::getChanType() { - LOG(DEBUG) << "aborting inbound handover " << *transaction; - char ind[100]; - unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff"); - sprintf(ind,"IND HANDOVER_FAILURE %u %u %u", transaction->ID(),cause,holdoff); - gPeerInterface.sendUntilAck(transaction,ind); - - if (LCH) LCH->send(HARDRELEASE); - - gTransactionTable.remove(transaction); -} - - - -bool Control::SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp) -{ - // In this function, we are "BS2" in the ladder diagram. - // This is called from L1 when a handover burst arrives. - - // We will need to use the transaction record to carry the parameters. - // We put this here to avoid dealing with the transaction table in L1. - TransactionEntry *transaction = gTransactionTable.inboundHandover(handoverReference); - if (!transaction) { - LOG(ERR) << "no inbound handover with reference " << handoverReference; - return false; - } - - if (timingError > gConfig.getNum("GSM.MS.TA.Max")) { - // Handover failure. - LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction; - // RR cause 8: Handover impossible, timing advance out of range - abortInboundHandover(transaction,8); - return false; - } - - LOG(INFO) << "saving handover access for " << *transaction; - transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp)); - return true; -} - - - - -void Control::ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH) -{ - // In this function, we are "BS2" in the ladder diagram. - // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive. - // The information it needs was saved in the transaction table by Control::SaveHandoverAccess. - - - assert(TCH); - LOG(DEBUG) << *TCH; - - TransactionEntry *transaction = gTransactionTable.inboundHandover(TCH); - if (!transaction) { - LOG(WARNING) << "handover access with no inbound transaction on " << *TCH; - TCH->send(HARDRELEASE); - return; - } - - // clear handover in transceiver - LOG(DEBUG) << *transaction; - transaction->channel()->handoverPending(false); - - // Respond to handset with physical information until we get Handover Complete. - int TA = (int)(transaction->inboundTimingError() + 0.5F); - if (TA<0) TA=0; - if (TA>62) TA=62; - unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105"); - unsigned sendCount = gConfig.getNum("GSM.Ny1"); - L3Frame* frame = NULL; - while (!frame && sendCount) { - TCH->send(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA); - sendCount--; - frame = TCH->recv(repeatTimeout); - if (frame && frame->primitive() == HANDOVER_ACCESS) { - LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; - delete frame; - frame = NULL; - } - } - - // Timed out? - if (!frame) { - LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction; - // RR cause 4: Abnormal release, no activity on the radio path - abortInboundHandover(transaction,4,TCH); - return; - } - - // Screwed up channel? - if (frame->primitive()!=ESTABLISH) { - LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on " - << *TCH << ": " << *frame << " for " << *transaction; - delete frame; - // RR cause 0x62: Message not compatible with protocol state - abortInboundHandover(transaction,0x62,TCH); - return; - } - - // Get the next frame, should be HandoverComplete. - delete frame; - frame = TCH->recv(); - L3Message* msg = parseL3(*frame); - if (!msg) { - LOG(NOTICE) << "unparsable message waiting for Handover Complete on " - << *TCH << ": " << *frame << " for " << *transaction; - delete frame; - // RR cause 0x62: Message not compatible with protocol state - TCH->send(L3ChannelRelease(0x62)); - abortInboundHandover(transaction,0x62,TCH); - return; - } - delete frame; - - L3HandoverComplete* complete = dynamic_cast(msg); - if (!complete) { - LOG(NOTICE) << "expecting for Handover Complete on " - << *TCH << "but got: " << *msg << " for " << *transaction; - delete frame; - // RR cause 0x62: Message not compatible with protocol state - TCH->send(L3ChannelRelease(0x62)); - abortInboundHandover(transaction,0x62,TCH); - } - delete msg; - - // Send re-INVITE to the remote party. - unsigned RTPPort = allocateRTPPorts(); - SIP::SIPState st = transaction->inboundHandoverSendINVITE(RTPPort); - if (st == SIP::Fail) { - abortInboundHandover(transaction,4,TCH); - return; - } - - transaction->GSMState(GSM::HandoverProgress); - - while (1) { - // FIXME - the sip engine should be doing this - // FIXME - and checking for timeout - // FIXME - and checking for proceeding (stop sending the resends) - st = transaction->inboundHandoverCheckForOK(); - if (st == SIP::Active) break; - if (st == SIP::Fail) { - LOG(NOTICE) << "received Fail while waiting for OK"; - abortInboundHandover(transaction,4,TCH); - return; - } - } - st = transaction->inboundHandoverSendACK(); - LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *transaction; - - // Send completion to peer BTS. - char ind[100]; - sprintf(ind,"IND HANDOVER_COMPLETE %u", transaction->ID()); - gPeerInterface.sendUntilAck(transaction,ind); - - // Update subscriber registry to reflect new registration. - if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) { - gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str()); - } - - // The call is running. - LOG(INFO) << "succesful inbound handover " << *transaction; - transaction->GSMState(GSM::Active); - callManagementLoop(transaction,TCH); -} - - -void Control::HandoverDetermination(const L3MeasurementResults& measurements, SACCHLogicalChannel* SACCH) -{ - // This is called from the SACCH service loop. - - // Valid measurements? - if (measurements.MEAS_VALID()) return; - - // Got neighbors? - unsigned N = measurements.NO_NCELL(); - if (N==0) return; - - if (N == 7) { - LOG(DEBUG) << "neighbor cell information not available"; - return; - } - - // Is our current signal OK? - int myRxLevel = measurements.RXLEV_SUB_SERVING_CELL_dBm(); - int localRSSIMin = gConfig.getNum("GSM.Handover.LocalRSSIMin"); - LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm localRSSIMin=" << localRSSIMin << " dBm"; - if (myRxLevel > localRSSIMin) return; - - - // Look at neighbor cell rx levels - int best = 0; - int bestRxLevel = measurements.RXLEV_NCELL_dBm(best); - for (unsigned int i=1; ibestRxLevel) { - bestRxLevel = thisRxLevel; - best = i; - } - } - - // Does the best exceed the current by more than the threshold? - int threshold = gConfig.getNum("GSM.Handover.ThresholdDelta"); - LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm, best neighbor=" << - bestRxLevel << " dBm, threshold=" << threshold << " dB"; - if (bestRxLevel < (myRxLevel + threshold)) return; - - - // OK. So we will initiate a handover. - LOG(DEBUG) << measurements; - int BCCH_FREQ_NCELL = measurements.BCCH_FREQ_NCELL(best); - int BSIC = measurements.BSIC_NCELL(best); - char* peer = gNeighborTable.getAddress(BCCH_FREQ_NCELL,BSIC); - if (!peer) { - LOG(CRIT) << "measurement for unknown neighbor BCCH_FREQ_NCELL " << BCCH_FREQ_NCELL << " BSIC " << BSIC; - return; - } - if (gNeighborTable.holdingOff(peer)) { - LOG(NOTICE) << "skipping handover to " << peer << " due to holdoff"; - return; - } - - // Find the transaction record. - TransactionEntry* transaction = gTransactionTable.findBySACCH(SACCH); - if (!transaction) { - LOG(ERR) << "active SACCH with no transaction record: " << *SACCH; - return; - } - if (transaction->GSMState() != GSM::Active) { - LOG(DEBUG) << "skipping handover for transaction " << transaction->ID() - << " due to state " << transaction->GSMState(); - return; - } - LOG(INFO) << "preparing handover of " << transaction->ID() - << " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm"; - - // The handover reference will be generated by the other BTS. - // We don't set the handover reference or state until we get RSP HANDOVER. - - // Form and send the message. - string msg = string("REQ HANDOVER ") + transaction->handoverString(); - struct sockaddr_in peerAddr; - if (!resolveAddress(&peerAddr,peer)) { - LOG(ALERT) << "cannot resolve peer address " << peer; - return; - } - gPeerInterface.sendMessage(&peerAddr,msg.c_str()); -} - - - - -void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel* DCCH) -{ - assert(resp); - assert(DCCH); - LOG(INFO) << *resp; - - // If we got a TMSI, find the IMSI. - L3MobileIdentity mobileID = resp->mobileID(); - if (mobileID.type()==TMSIType) { - char *IMSI = gTMSITable.IMSI(mobileID.TMSI()); - if (IMSI) { - mobileID = L3MobileIdentity(IMSI); - // (pat) Whenever the MS RACHes, we need to alert the SGSN. - // Not sure this is necessary in this particular case, but be safe. - GPRS::GPRSNotifyGsmActivity(IMSI); - free(IMSI); - } else { - // Don't try too hard to resolve. - // The handset is supposed to respond with the same ID type as in the request. - // This could be the sign of some kind of DOS attack. - LOG(CRIT) << "Paging Reponse with non-valid TMSI"; - // Cause 0x60 "Invalid mandatory information" - DCCH->send(L3ChannelRelease(0x60)); - return; - } - } - else if(mobileID.type()==IMSIType){ - //Cause the tmsi table to be touched - gTMSITable.TMSI(resp->mobileID().digits()); - } - - // Delete the Mobile ID from the paging list to free up CCCH bandwidth. - // ... if it was not deleted by a timer already ... - gBTS.pager().removeID(mobileID); - - // Find the transction table entry that was created when the phone was paged. - // We have to look up by mobile ID since the paging entry may have been - // erased before this handler was called. That's too bad. - // HACK -- We also flush stray transactions until we find what we - // are looking for. - TransactionEntry* transaction = gTransactionTable.answeredPaging(mobileID); - if (!transaction) { - LOG(WARNING) << "Paging Reponse with no transaction record for " << mobileID; - // Cause 0x41 means "call already cleared". - DCCH->send(L3ChannelRelease(0x41)); - return; - } - LOG(INFO) << "paging reponse for transaction " << *transaction; - // Set the transaction channel. - transaction->channel(DCCH); - // We are looking for a mobile-terminated transaction. - // The transaction controller will take it from here. - switch (transaction->service().type()) { - case L3CMServiceType::MobileTerminatedCall: - MTCStarter(transaction, DCCH); - return; - case L3CMServiceType::MobileTerminatedShortMessage: - MTSMSController(transaction, DCCH); - return; - case L3CMServiceType::TestCall: - TestCall(transaction, DCCH); - return; - default: - // Flush stray MOC entries. - // There should not be any, but... - LOG(ERR) << "non-valid paging-state transaction: " << *transaction; - gTransactionTable.remove(transaction); - // FIXME -- Send a channel release on the DCCH. + if (mWantCS && gConfig.getBool("Control.VEA")) { + // Very early assignment. + return GSM::TCHFType; + } else { + return GSM::SDCCHType; } } - - -void Control::AssignmentCompleteHandler(const L3AssignmentComplete *confirm, TCHFACCHLogicalChannel *TCH) +// This does a lot of mallocing. +L3MobileIdentity NewPagingEntry::getMobileId() { - // The assignment complete handler is used to - // tie together split transactions across a TCH assignment - // in non-VEA call setup. - - assert(TCH); - assert(confirm); - LOG(DEBUG) << *confirm; - - // Check the transaction table to know what to do next. - TransactionEntry* transaction = gTransactionTable.find(TCH); - if (!transaction) { - LOG(WARNING) << "No transaction matching channel " << *TCH << " (" << TCH << ")."; - throw UnexpectedMessage(); + if (mTmsi.valid()) { + // page by tmsi + return L3MobileIdentity(mTmsi.value()); + } else { + // page by imsi + return L3MobileIdentity(mImsi.c_str()); } - LOG(INFO) << "service="<service().type(); - - // These "controller" functions don't return until the call is cleared. - switch (transaction->service().type()) { - case L3CMServiceType::MobileOriginatedCall: - MOCController(transaction,TCH); - break; - case L3CMServiceType::MobileTerminatedCall: - MTCController(transaction,TCH); - break; - default: - LOG(WARNING) << "unsupported service " << transaction->service(); - throw UnsupportedMessage(transaction->ID()); - } - // If we got here, the call is cleared. } - - - - - - - -void Pager::addID(const L3MobileIdentity& newID, ChannelType chanType, - TransactionEntry& transaction, unsigned wLife) +static unsigned newPageAll() { - transaction.GSMState(GSM::Paging); - transaction.setTimer("3113",wLife); - // Add a mobile ID to the paging list for a given lifetime. - ScopedLock lock(mLock); - // If this ID is already in the list, just reset its timer. - // Uhg, another linear time search. - // This would be faster if the paging list were ordered by ID. - // But the list should usually be short, so it may not be worth the effort. - for (PagingEntryList::iterator lp = mPageIDs.begin(); lp != mPageIDs.end(); ++lp) { - if (lp->ID()==newID) { - LOG(DEBUG) << newID << " already in table"; - lp->renew(wLife); - mPageSignal.signal(); - return; - } - } - // If this ID is new, put it in the list. - mPageIDs.push_back(PagingEntry(newID,chanType,transaction.ID(),wLife)); - LOG(INFO) << newID << " added to table"; - mPageSignal.signal(); -} + LOG(DEBUG); + NewPagingList_t pages; + gMMLayer.mmGetPages(pages,true); // Blocks until there are some. - -unsigned Pager::removeID(const L3MobileIdentity& delID) -{ - // Return the associated transaction ID, or 0 if none found. - LOG(INFO) << delID; - ScopedLock lock(mLock); - for (PagingEntryList::iterator lp = mPageIDs.begin(); lp != mPageIDs.end(); ++lp) { - if (lp->ID()==delID) { - unsigned retVal = lp->transactionID(); - mPageIDs.erase(lp); - return retVal; - } - } - return 0; -} - - - -unsigned Pager::pageAll() -{ - // Traverse the full list and page all IDs. - // Remove expired IDs. - // Return the number of IDs paged. - // This is a linear time operation. - - ScopedLock lock(mLock); - - // Clear expired entries. - PagingEntryList::iterator lp = mPageIDs.begin(); - while (lp != mPageIDs.end()) { - bool expired = lp->expired(); - bool defunct = gTransactionTable.find(lp->transactionID()) == NULL; - if (!expired && !defunct) ++lp; - else { - LOG(INFO) << "erasing " << lp->ID(); - // Non-responsive, dead transaction? - gTransactionTable.removePaging(lp->transactionID()); - // remove from the list - lp=mPageIDs.erase(lp); - } - } - - LOG(INFO) << "paging " << mPageIDs.size() << " mobile(s)"; + LOG(INFO) << "paging " << pages.size() << " mobile(s)"; // Page remaining entries, two at a time if possible. // These PCH send operations are non-blocking. - lp = mPageIDs.begin(); - while (lp != mPageIDs.end()) { + for (NewPagingList_t::iterator lp = pages.begin(); lp != pages.end(); ) { // FIXME -- This completely ignores the paging groups. // HACK -- So we send every page twice. // That will probably mean a different Pager for each subchannel. // See GSM 04.08 10.5.2.11 and GSM 05.02 6.5.2. - const L3MobileIdentity& id1 = lp->ID(); - ChannelType type1 = lp->type(); + const L3MobileIdentity& id1 = lp->getMobileId(); + ChannelType type1 = lp->getChanType(); ++lp; - if (lp==mPageIDs.end()) { + if (lp==pages.end()) { // Just one ID left? LOG(DEBUG) << "paging " << id1; - gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1)); - gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1)); + gBTS.getPCH(0)->l2sendm(L3PagingRequestType1(id1,type1)); + gBTS.getPCH(0)->l2sendm(L3PagingRequestType1(id1,type1)); break; } // Page by pairs when possible. - const L3MobileIdentity& id2 = lp->ID(); - ChannelType type2 = lp->type(); + const L3MobileIdentity& id2 = lp->getMobileId(); + ChannelType type2 = lp->getChanType(); ++lp; LOG(DEBUG) << "paging " << id1 << " and " << id2; - gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1,id2,type2)); - gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1,id2,type2)); + gBTS.getPCH(0)->l2sendm(L3PagingRequestType1(id1,type1,id2,type2)); + gBTS.getPCH(0)->l2sendm(L3PagingRequestType1(id1,type1,id2,type2)); } - return mPageIDs.size(); + return pages.size(); } + size_t Pager::pagingEntryListSize() { - ScopedLock lock(mLock); - return mPageIDs.size(); + NewPagingList_t pages; + gMMLayer.mmGetPages(pages,false); + return pages.size(); } void Pager::start() @@ -792,32 +407,15 @@ void* Control::PagerServiceLoopAdapter(Pager *pager) void Pager::serviceLoop() { while (mRunning) { - - LOG(DEBUG) << "Pager blocking for signal"; - mLock.lock(); - while (mPageIDs.size()==0) mPageSignal.wait(mLock); - mLock.unlock(); - - // page everything - pageAll(); - // Wait for pending activity to clear the channel. // This wait is what causes PCH to have lower priority than AGCH. - unsigned load = gBTS.getPCH()->load(); - LOG(DEBUG) << "Pager waiting for " << load << " multiframes"; - if (load) sleepFrames(51*load); - } -} - - - -void Pager::dump(ostream& os) const -{ - ScopedLock lock(mLock); - PagingEntryList::const_iterator lp = mPageIDs.begin(); - while (lp != mPageIDs.end()) { - os << lp->ID() << " " << lp->type() << " " << lp->expired() << endl; - ++lp; + // getPCH returns the minimum load PCH. Each PCH sends one paging message per 51-multiframe. + if (unsigned load = gBTS.getPCH()->load()) { + LOG(DEBUG) << "Pager waiting with load " << load; + sleepFrames(51); // There could be multiple paging channels, in which case this is too long. + continue; + } + newPageAll(); } } diff --git a/Control/RadioResource.h b/Control/RadioResource.h index 42b58b0..a08b0df 100644 --- a/Control/RadioResource.h +++ b/Control/RadioResource.h @@ -21,8 +21,9 @@ #define RADIORESOURCE_H #include -#include #include +#include "ControlCommon.h" +#include namespace GSM { @@ -33,6 +34,7 @@ class L3PagingResponse; class L3AssignmentComplete; class L3HandoverComplete; class L3HandoverAccess; +class L3MeasurementResults; }; namespace Control { @@ -47,11 +49,11 @@ class TransactionEntry; @param measurements The measurement results from the SACCH. @param SACCH The SACCH in question. */ -void HandoverDetermination(const GSM::L3MeasurementResults &measurements, GSM::SACCHLogicalChannel* SACCH); +void HandoverDetermination(const GSM::L3MeasurementResults &measurements, float myRxLev, GSM::SACCHLogicalChannel* SACCH); /** Find and complete the in-process transaction associated with a paging repsonse. */ -void PagingResponseHandler(const GSM::L3PagingResponse*, GSM::LogicalChannel*); +void PagingResponseHandler(const GSM::L3PagingResponse*, L3LogicalChannel*); /** Find and compelte the in-process transaction associated with a completed assignment. */ void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLogicalChannel*); @@ -60,7 +62,8 @@ void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLo bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp); /** Process the handover access; returns when the transaction is cleared. */ -void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH); +void ProcessHandoverAccess(L3LogicalChannel *chan); +bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH); /**@ Access Grant mechanisms */ //@{ @@ -109,44 +112,19 @@ void* AccessGrantServiceLoop(void*); //@{ /** An entry in the paging list. */ -class PagingEntry { - private: - - GSM::L3MobileIdentity mID; ///< The mobile ID. - GSM::ChannelType mType; ///< The needed channel type. - unsigned mTransactionID; ///< The associated transaction ID. - Timeval mExpiration; ///< The expiration time for this entry. - - public: - - /** - Create a new entry, with current timestamp. - @param wID The ID to be paged. - @param wLife The number of milliseconds to keep paging. - */ - PagingEntry(const GSM::L3MobileIdentity& wID, GSM::ChannelType wType, - unsigned wTransactionID, unsigned wLife) - :mID(wID),mType(wType),mTransactionID(wTransactionID),mExpiration(wLife) - {} - - /** Access the ID. */ - const GSM::L3MobileIdentity& ID() const { return mID; } - - /** Access the channel type needed. */ - GSM::ChannelType type() const { return mType; } - - unsigned transactionID() const { return mTransactionID; } - - /** Renew the timer. */ - void renew(unsigned wLife) { mExpiration = Timeval(wLife); } - - /** Returns true if the entry is expired. */ - bool expired() const { return mExpiration.passed(); } +struct NewPagingEntry { + bool mWantCS; // true for CS service requested, false for anything else, which is SMS. + std::string mImsi; // Always provided. + TMSI_t mTmsi; // Provided if known and we are sure it has been assigned to the MS. + // Such a clever language. + NewPagingEntry(bool wWantCS, std::string &wImsi, TMSI_t &wTmsi) : mWantCS(wWantCS), mImsi(wImsi), mTmsi(wTmsi) {} + GSM::ChannelType getChanType(); + GSM::L3MobileIdentity getMobileId(); }; +typedef std::vector NewPagingList_t; -typedef std::list PagingEntryList; /** @@ -160,9 +138,6 @@ class Pager { private: - PagingEntryList mPageIDs; ///< List of ID's to be paged. - mutable Mutex mLock; ///< Lock for thread-safe access. - Signal mPageSignal; ///< signal to wake the paging loop Thread mPagingThread; ///< Thread for the paging loop. volatile bool mRunning; @@ -175,35 +150,8 @@ class Pager { /** Set the output FIFO and start the paging loop. */ void start(); - /** - Add a mobile ID to the paging list. - @param addID The mobile ID to be paged. - @param chanType The channel type to be requested. - @param transaction The transaction record, which will be modified. - @param wLife The paging duration in ms, default is SIP Timer B. - */ - void addID( - const GSM::L3MobileIdentity& addID, - GSM::ChannelType chanType, - TransactionEntry& transaction, - unsigned wLife=gConfig.getNum("GSM.Timer.T3113") - ); - - /** - Remove a mobile ID. - This is used to stop the paging when a phone responds. - @return The transaction ID associated with this entry. - */ - unsigned removeID(const GSM::L3MobileIdentity&); - private: - /** - Traverse the paging list, paging all IDs. - @return Number of IDs paged. - */ - unsigned pageAll(); - /** A loop that repeatedly calls pageAll. */ void serviceLoop(); @@ -214,9 +162,6 @@ public: /** return size of PagingEntryList */ size_t pagingEntryListSize(); - - /** Dump the paging list to an ostream. */ - void dump(std::ostream&) const; }; diff --git a/Control/SMSCB.cpp b/Control/SMSCB.cpp index 14678c8..b1a8ef0 100644 --- a/Control/SMSCB.cpp +++ b/Control/SMSCB.cpp @@ -14,6 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::Control #include "ControlCommon.h" #include @@ -23,6 +24,9 @@ #include #include +// (pat) See GSM 03.41. SMSCB is a broadcast message service on a dedicated broadcast channel and unrelated to anything else. +// Broadcast messages are completely unacknowledged. They are repeated perpetually. + static const char* createSMSCBTable = { "CREATE TABLE IF NOT EXISTS SMSCB (" @@ -143,7 +147,7 @@ void SMSCBSendMessage(sqlite3* DB, sqlite3_stmt* stmt, GSM::CBCHLogicalChannel* ); // Send it. LOG(DEBUG) << "sending L3 message page " << page+1 << ": " << message; - CBCH->send(message); + CBCH->l2sendm(message); } free(messageText); diff --git a/Control/SMSControl.cpp b/Control/SMSControl.cpp deleted file mode 100644 index b99ead2..0000000 --- a/Control/SMSControl.cpp +++ /dev/null @@ -1,628 +0,0 @@ -/**@file SMS Control (L3), GSM 03.40, 04.11. */ -/* -* Copyright 2008, 2009 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -/* - Abbreviations: - MOSMS -- Mobile Originated Short Message Service - MTSMS -- Mobile Terminated Short Message Service - - Verbs: - "send" -- Transfer to the network. - "receive" -- Transfer from the network. - "submit" -- Transfer from the MS. - "deliver" -- Transfer to the MS. - - MOSMS: The MS "submits" a message that OpenBTS then "sends" to the network. - MTSMS: OpenBTS "receives" a message from the network that it then "delivers" to the MS. - -*/ - - -#include -#include -#include -#include -#include "SMSControl.h" -#include "ControlCommon.h" -#include "TransactionTable.h" -#include "RRLPServer.h" -#include -#include - - - -using namespace std; -using namespace Control; - -#include "SMSMessages.h" -using namespace SMS; - -#include "SIPInterface.h" -#include "SIPUtility.h" -#include "SIPMessage.h" -#include "SIPEngine.h" -using namespace SIP; - -#include -#undef WARNING - -/** - Read an L3Frame from SAP3. - Throw exception on failure. Will NOT return a NULL pointer. -*/ -GSM::L3Frame* getFrameSMS(GSM::LogicalChannel *LCH, GSM::Primitive primitive=GSM::DATA) -{ - GSM::L3Frame *retVal = LCH->recv(LCH->N200()*LCH->T200(),3); - if (!retVal) { - LOG(NOTICE) << "channel read time out on " << *LCH << " SAP3"; - throw ChannelReadTimeout(); - } - LOG(DEBUG) << "getFrameSMS on " << *LCH << " in frame " << *retVal; - if (retVal->primitive() != primitive) { - LOG(NOTICE) << "unexpected primitive on " << *LCH << ", expecting " << primitive << ", got " << *retVal; - throw UnexpectedPrimitive(); - } - if ((retVal->primitive() == GSM::DATA) && (retVal->PD() != GSM::L3SMSPD)) { - LOG(NOTICE) << "unexpected (non-SMS) protocol on " << *LCH << " in frame " << *retVal; - throw UnexpectedMessage(); - } - return retVal; -} - - -bool sendSIP(TransactionEntry *transaction, const char* address, const char* body, const char* contentType) -{ - // Steps: - // 1 -- Complete transaction record. - // 2 -- Send TL-SUBMIT to the server. - // 3 -- Wait for response or timeout. - // 4 -- Return true for OK or ACCEPTED, false otherwise. - - // Step 1 -- Complete the transaction record. - // Form the TLAddress into a CalledPartyNumber for the transaction. - GSM::L3CalledPartyBCDNumber calledParty(address); - // Attach calledParty and message body to the transaction. - transaction->called(calledParty); - transaction->message(body,strlen(body)); - - // Step 2 -- Send the message to the server. - transaction->MOSMSSendMESSAGE(address,gConfig.getStr("SIP.Local.IP").c_str(),contentType); - - // Step 3 -- Wait for OK or ACCEPTED. - SIPState state = transaction->MOSMSWaitForSubmit(); - - // Step 4 -- Done - return state==SIP::Cleared; -} - -/** - Process the RPDU. - @param mobileID The sender's IMSI. - @param RPDU The RPDU to process. - @return true if successful. -*/ -bool handleRPDU(TransactionEntry *transaction, const RLFrame& RPDU) -{ - LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI(); - switch ((RPMessage::MessageType)RPDU.MTI()) { - case RPMessage::Data: { - string contentType = gConfig.getStr("SMS.MIMEType"); - ostringstream body; - - if (contentType == "text/plain") { - // TODO: Clean this mess up! - RPData data; - data.parse(RPDU); - TLSubmit submit; - submit.parse(data.TPDU()); - - body << submit.UD().decode(); - } else if (contentType == "application/vnd.3gpp.sms") { - LOG(DEBUG) << "RPDU: " << RPDU; - RPDU.hex(body); - LOG(DEBUG) << "RPDU result: " << body; - } else { - LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type"; - } - const char* address = NULL; - string tmpAddress = gConfig.getStr("SIP.SMSC"); - if (tmpAddress.length()) { - address = tmpAddress.c_str(); - } - - /* The SMSC is not defined, we are using an older version */ - if (address == NULL) { - RPData data; - data.parse(RPDU); - TLSubmit submit; - submit.parse(data.TPDU()); - - address = submit.DA().digits(); - } - return sendSIP(transaction, address, body.str().data(),contentType.c_str()); - } - case RPMessage::Ack: - case RPMessage::SMMA: - return true; - case RPMessage::Error: - default: - return false; - } -} - - - - -void Control::MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalChannel *LCH) -{ - assert(req); - assert(req->serviceType().type() == GSM::L3CMServiceType::ShortMessage); - assert(LCH); - assert(LCH->type() != GSM::SACCHType); - - LOG(INFO) << "MOSMS, req " << *req; - - // If we got a TMSI, find the IMSI. - // Note that this is a copy, not a reference. - GSM::L3MobileIdentity mobileID = req->mobileID(); - resolveIMSI(mobileID,LCH); - - // Create a transaction record. - TransactionEntry *transaction = new TransactionEntry(gConfig.getStr("SIP.Proxy.SMS").c_str(),mobileID,LCH); - gTransactionTable.add(transaction); - LOG(DEBUG) << "MOSMS: transaction: " << *transaction; - - // See GSM 04.11 Arrow Diagram A5 for the transaction - // Step 1 MS->Network CP-DATA containing RP-DATA - // Step 2 Network->MS CP-ACK - // Step 3 Network->MS CP-DATA containing RP-ACK - // Step 4 MS->Network CP-ACK - - // LAPDm operation, from GSM 04.11, Annex F: - // """ - // Case A: Mobile originating short message transfer, no parallel call: - // The mobile station side will initiate SAPI 3 establishment by a SABM command - // on the DCCH after the cipher mode has been set. If no hand over occurs, the - // SAPI 3 link will stay up until the last CP-ACK is received by the MSC, and - // the clearing procedure is invoked. - // """ - - // FIXME: check provisioning - - // Let the phone know we're going ahead with the transaction. - LOG(INFO) << "sending CMServiceAccept"; - LCH->send(GSM::L3CMServiceAccept()); - // Wait for SAP3 to connect. - // The first read on SAP3 is the ESTABLISH primitive. - delete getFrameSMS(LCH,GSM::ESTABLISH); - - gReports.incr("OpenBTS.GSM.SMS.MOSMS.Start"); - - // Step 1 - // Now get the first message. - // Should be CP-DATA, containing RP-DATA. - GSM::L3Frame *CM = getFrameSMS(LCH); - LOG(DEBUG) << "data from MS " << *CM; - if (CM->MTI()!=CPMessage::DATA) { - LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI(); - delete CM; - throw UnexpectedMessage(); - } - unsigned L3TI = CM->TI() | 0x08; - transaction->L3TI(L3TI); - - // Step 2 - // Respond with CP-ACK. - // This just means that we got the message. - LOG(INFO) << "sending CPAck"; - LCH->send(CPAck(L3TI),3); - - // Parse the message in CM and process RP part. - // This is where we actually parse the message and send it out. - // FIXME -- We need to set the message ref correctly, - // even if the parsing fails. - // The compiler gives a warning here. Let it. It will remind someone to fix it. - unsigned ref; - bool success = false; - try { - CPData data; - data.parse(*CM); - LOG(INFO) << "CPData " << data; - // Transfer out the RPDU -> TPDU -> delivery. - ref = data.RPDU().reference(); - // This handler invokes higher-layer parsers, too. - success = handleRPDU(transaction,data.RPDU()); - } - catch (SMSReadError) { - LOG(WARNING) << "SMS parsing failed (above L3)"; - // Cause 95, "semantically incorrect message". - LCH->send(CPData(L3TI,RPError(95,ref)),3); - delete CM; - throw UnexpectedMessage(); - } - catch (GSM::L3ReadError) { - LOG(WARNING) << "SMS parsing failed (in L3)"; - delete CM; - throw UnsupportedMessage(); - } - delete CM; - - // Step 3 - // Send CP-DATA containing RP-ACK and message reference. - if (success) { - LOG(INFO) << "sending RPAck in CPData"; - LCH->send(CPData(L3TI,RPAck(ref)),3); - } else { - LOG(INFO) << "sending RPError in CPData"; - // Cause 127 is "internetworking error, unspecified". - // See GSM 04.11 Table 8.4. - LCH->send(CPData(L3TI,RPError(127,ref)),3); - } - - // Step 4 - // Get CP-ACK from the MS. - CM = getFrameSMS(LCH); - if (CM->MTI()!=CPMessage::ACK) { - LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI(); - throw UnexpectedMessage(); - } - LOG(DEBUG) << "ack from MS: " << *CM; - CPAck ack; - ack.parse(*CM); - delete CM; - LOG(INFO) << "CPAck " << ack; - - gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete"); - - /* MOSMS RLLP request */ - if (gConfig.getBool("Control.SMS.QueryRRLP")) { - // Query for RRLP - if (!sendRRLP(mobileID, LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - - // Done. - LCH->send(GSM::L3ChannelRelease()); - gTransactionTable.remove(transaction); - LOG(INFO) << "closing the Um channel"; -} - - - - -bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message, const char* contentType, unsigned L3TI, GSM::LogicalChannel *LCH) -{ - if (!LCH->multiframeMode(3)) { - // Start ABM in SAP3. - LCH->send(GSM::ESTABLISH,3); - // Wait for SAP3 ABM to connect. - // The next read on SAP3 should the ESTABLISH primitive. - // This won't return NULL. It will throw an exception if it fails. - delete getFrameSMS(LCH,GSM::ESTABLISH); - } - -#if 0 - // HACK -- Check for "Easter Eggs" - // TL-PID - unsigned TLPID=0; - if (strncmp(message,"#!TLPID",7)==0) sscanf(message,"#!TLPID%d",&TLPID); - - // Step 1 - // Send the first message. - // CP-DATA, containing RP-DATA. - unsigned reference = random() % 255; - CPData deliver(L3TI, - RPData(reference, - RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()), - TLDeliver(callingPartyDigits,message,TLPID))); -#else - // TODO: Read MIME Type from smqueue!! - unsigned reference = random() % 255; - RPData rp_data; - - if (strncmp(contentType,"text/plain",10)==0) { - rp_data = RPData(reference, - RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()), - TLDeliver(callingPartyDigits,message,0)); - } else if (strncmp(contentType,"application/vnd.3gpp.sms",24)==0) { - BitVector RPDUbits(strlen(message)*4); - if (!RPDUbits.unhex(message)) { - LOG(WARNING) << "Hex string parsing failed (in incoming SIP MESSAGE)"; - throw UnexpectedMessage(); - } - - try { - RLFrame RPDU(RPDUbits); - LOG(DEBUG) << "SMS RPDU: " << RPDU; - - rp_data.parse(RPDU); - LOG(DEBUG) << "SMS RP-DATA " << rp_data; - } - catch (SMSReadError) { - LOG(WARNING) << "SMS parsing failed (above L3)"; - // Cause 95, "semantically incorrect message". - LCH->send(CPData(L3TI,RPError(95,reference)),3); - throw UnexpectedMessage(); - } - catch (GSM::L3ReadError) { - LOG(WARNING) << "SMS parsing failed (in L3)"; - // TODO:: send error back to the phone - throw UnsupportedMessage(); - } - } else { - LOG(WARNING) << "Unsupported content type (in incoming SIP MESSAGE) -- type: " << contentType; - throw UnexpectedMessage(); - } - - CPData deliver(L3TI,rp_data); - -#endif - - gReports.incr("OpenBTS.GSM.SMS.MTSMS.Start"); - - // Start ABM in SAP3. - //LCH->send(GSM::ESTABLISH,3); - // Wait for SAP3 ABM to connect. - // The next read on SAP3 should the ESTABLISH primitive. - // This won't return NULL. It will throw an exception if it fails. - //delete getFrameSMS(LCH,GSM::ESTABLISH); - - LOG(INFO) << "sending " << deliver; - LCH->send(deliver,3); - - // Step 2 - // Get the CP-ACK. - // FIXME -- Check TI. - LOG(DEBUG) << "MTSMS: waiting for CP-ACK"; - GSM::L3Frame *CM = getFrameSMS(LCH); - LOG(DEBUG) << "MTSMS: ack from MS " << *CM; - if (CM->MTI()!=CPMessage::ACK) { - LOG(WARNING) << "MS rejected our RP-DATA with CP message with TI=" << CM->MTI(); - delete CM; - throw UnexpectedMessage(); - } - delete CM; - - // Step 3 - // Get CP-DATA containing RP-ACK and message reference. - LOG(DEBUG) << "MTSMS: waiting for RP-ACK"; - CM = getFrameSMS(LCH); - LOG(DEBUG) << "MTSMS: data from MS " << *CM; - if (CM->MTI()!=CPMessage::DATA) { - LOG(NOTICE) << "Unexpected SMS CP message with TI=" << CM->MTI(); - delete CM; - throw UnexpectedMessage(); - } - - // FIXME -- Check L3 TI. - - // Parse to check for RP-ACK. - CPData data; - try { - data.parse(*CM); - LOG(DEBUG) << "CPData " << data; - } - catch (SMSReadError) { - LOG(WARNING) << "SMS parsing failed (above L3)"; - // Cause 95, "semantically incorrect message". - LCH->send(CPError(L3TI,95),3); - throw UnexpectedMessage(); - } - catch (GSM::L3ReadError) { - LOG(WARNING) << "SMS parsing failed (in L3)"; - throw UnsupportedMessage(); - } - delete CM; - - // FIXME -- Check SMS reference. - - bool success = true; - if (data.RPDU().MTI()!=RPMessage::Ack) { - LOG(WARNING) << "unexpected RPDU " << data.RPDU(); - success = false; - } - - gReports.incr("OpenBTS.GSM.SMS.MTSMS.Complete"); - - // Step 4 - // Send CP-ACK to the MS. - LOG(INFO) << "MTSMS: sending CPAck"; - LCH->send(CPAck(L3TI),3); - return success; -} - - - - -void Control::MTSMSController(TransactionEntry *transaction, GSM::LogicalChannel *LCH) -{ - assert(LCH); - assert(transaction); - - // See GSM 04.11 Arrow Diagram A5 for the transaction - // Step 1 Network->MS CP-DATA containing RP-DATA - // Step 2 MS->Network CP-ACK - // Step 3 MS->Network CP-DATA containing RP-ACK - // Step 4 Network->MS CP-ACK - - // LAPDm operation, from GSM 04.11, Annex F: - // """ - // Case B: Mobile terminating short message transfer, no parallel call: - // The network side, i.e. the BSS will initiate SAPI3 establishment by a - // SABM command on the DCCH when the first CP-Data message is received - // from the MSC. If no hand over occurs, the link will stay up until the - // MSC has given the last CP-ack and invokes the clearing procedure. - // """ - - - // Attach the channel to the transaction and update the state. - LOG(DEBUG) << "transaction: "<< *transaction; - transaction->channel(LCH); - transaction->GSMState(GSM::SMSDelivering); - LOG(INFO) << "transaction: "<< *transaction; - - /* MTSMS RLLP request */ - if (gConfig.getBool("Control.SMS.QueryRRLP")) { - // Query for RRLP - if(!sendRRLP(transaction->subscriber(), LCH)){ - LOG(INFO) << "RRLP request failed"; - } - } - - bool success = deliverSMSToMS(transaction->calling().digits(),transaction->message(), - transaction->messageType(),transaction->L3TI(),LCH); - - // Close the Dm channel? - if (LCH->type()!=GSM::SACCHType) { - LCH->send(GSM::L3ChannelRelease()); - LOG(INFO) << "closing the Um channel"; - } - - // Ack in SIP domain. - if (success) transaction->MTSMSSendOK(); - - // Done. - gTransactionTable.remove(transaction); -} - - - - -void Control::InCallMOSMSStarter(TransactionEntry *parallelCall) -{ - GSM::LogicalChannel *hostChan = parallelCall->channel(); - assert(hostChan); - GSM::LogicalChannel *SACCH = hostChan->SACCH(); - assert(SACCH); - - // Create a partial transaction record. - TransactionEntry *newTransaction = new TransactionEntry( - gConfig.getStr("SIP.Proxy.SMS").c_str(), - parallelCall->subscriber(), - SACCH); - gTransactionTable.add(newTransaction); -} - - - -void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* transaction, GSM::SACCHLogicalChannel *LCH) -{ - LOG(INFO) << *cpData; - - // See GSM 04.11 Arrow Diagram A5 for the transaction - // Step 1 MS->Network CP-DATA containing RP-DATA - // Step 2 Network->MS CP-ACK - // Step 3 Network->MS CP-DATA containing RP-ACK - // Step 4 MS->Network CP-ACK - - // LAPDm operation, from GSM 04.11, Annex F: - // """ - // Case C: Mobile originating short message transfer, parallel call. - // The mobile station will send a SABM command on the SACCH when a CM_SERV_ACC - // message has been received from the network, allowing the short message - // transfer to start. If no hand over occurs the link will stay up until the - // MSC orders a explicit release, or the clearing procedure is invoked. If the - // parallel call is cleared before the short message transfer is finalized, the - // MSC will delay the clearing procedure toward the BSS, i.e. the channel - // release procedure is delayed. - // """ - - // Since there's a parallel call, we will assume correct provisioning. - // And we know that CM and SABM are established. - - // Step 1 already happened in the SACCH service loop. - // Just get the L3 TI and set the high bit since it originated in the MS. - unsigned L3TI = cpData->TI() | 0x08; - transaction->L3TI(L3TI); - - // Step 2 - // Respond with CP-ACK. - // This just means that we got the message. - LOG(INFO) << "sending CPAck"; - LCH->send(CPAck(L3TI),3); - - // Parse the message in CM and process RP part. - // This is where we actually parse the message and send it out. - // FIXME -- We need to set the message ref correctly, - // even if the parsing fails. - // The compiler gives a warning here. Let it. It will remind someone to fix it. - unsigned ref; - bool success = false; - try { - CPData data; - data.parse(*cpData); - LOG(INFO) << "CPData " << data; - // Transfer out the RPDU -> TPDU -> delivery. - ref = data.RPDU().reference(); - // This handler invokes higher-layer parsers, too. - success = handleRPDU(transaction,data.RPDU()); - } - catch (SMSReadError) { - LOG(WARNING) << "SMS parsing failed (above L3)"; - // Cause 95, "semantically incorrect message". - LCH->send(CPData(L3TI,RPError(95,ref)),3); - throw UnexpectedMessage(transaction->ID()); - } - catch (GSM::L3ReadError) { - LOG(WARNING) << "SMS parsing failed (in L3)"; - throw UnsupportedMessage(transaction->ID()); - } - - // Step 3 - // Send CP-DATA containing RP-ACK and message reference. - if (success) { - LOG(INFO) << "sending RPAck in CPData"; - LCH->send(CPData(L3TI,RPAck(ref)),3); - } else { - LOG(INFO) << "sending RPError in CPData"; - // Cause 127 is "internetworking error, unspecified". - // See GSM 04.11 Table 8.4. - LCH->send(CPData(L3TI,RPError(127,ref)),3); - } - - // Step 4 - // Get CP-ACK from the MS. - GSM::L3Frame* CM = getFrameSMS(LCH); - if (CM->MTI()!=CPMessage::ACK) { - LOG(NOTICE) << "unexpected SMS CP message with MTI=" << CM->MTI() << " " << *CM; - throw UnexpectedMessage(transaction->ID()); - } - LOG(DEBUG) << "ack from MS: " << *CM; - CPAck ack; - ack.parse(*CM); - LOG(INFO) << "CPAck " << ack; - - /* I had a hell of a time testing this with my B100 - I know it went off, that's all. If things fail, look - here -kurtis */ - - /* MOSMS RLLP request */ - if (gConfig.getBool("Control.SMS.QueryRRLP")) { - // Query for RRLP - if (!sendRRLP(transaction->subscriber(), LCH)) { - LOG(INFO) << "RRLP request failed"; - } - } - - gTransactionTable.remove(transaction); -} - -// vim: ts=4 sw=4 diff --git a/Control/SMSControl.h b/Control/SMSControl.h deleted file mode 100644 index 69e61fc..0000000 --- a/Control/SMSControl.h +++ /dev/null @@ -1,65 +0,0 @@ -/**@file Declarations for common-use control-layer functions. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - - -#ifndef SMSCONTROL_H -#define SMSCONTROL_H - -#include - -namespace GSM { -class L3Message; -class LogicalChannel; -class SDCCHLogicalChannel; -class SACCHLogicalChannel; -class TCHFACCHLogicalChannel; -class L3CMServiceRequest; -}; - - -namespace Control { - -/** MOSMS state machine. */ -void MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalChannel *LCH); - -/** MOSMS-with-parallel-call state machine. */ -void InCallMOSMSStarter(TransactionEntry *parallelCall); - -/** MOSMS-with-parallel-call state machine. */ -void InCallMOSMSController(const SMS::CPData *msg, TransactionEntry* transaction, GSM::SACCHLogicalChannel *LCH); -/** - Basic SMS delivery from an established CM. - On exit, SAP3 will be in ABM and LCH will still be open. - Throws exception for failures in connection layer or for parsing failure. - @return true on success in relay layer. -*/ -bool deliverSMSToMS(const char *callingPartyDigits, const char* message, const char* contentType, unsigned TI, GSM::LogicalChannel *LCH); - -/** MTSMS */ -void MTSMSController(TransactionEntry* transaction, GSM::LogicalChannel *LCH); - -} - - - - -#endif - -// vim: ts=4 sw=4 diff --git a/Control/TMSITable.cpp b/Control/TMSITable.cpp index 8e9030f..fffc3d0 100644 --- a/Control/TMSITable.cpp +++ b/Control/TMSITable.cpp @@ -16,7 +16,7 @@ */ -#include "TMSITable.h" +#define LOG_GROUP LogGroup::Control #include #include #include @@ -31,164 +31,722 @@ #include #include +#include "TMSITable.h" +#include "ControlTransfer.h" +#include "L3MobilityManagement.h" using namespace std; -using namespace Control; + +// (pat) For l3rewrite the TMSI will be assigned by the VLR, which will be either a new SubscriberRegistry program or Yate. +// As a result, everything here has to change. +// (pat) The access to any database needs to be message oriented if there is any chance of stalling, which could goof up the invoking state machine. + +Control::TMSITable gTMSITable; -static const char* createTMSITable = { - "CREATE TABLE IF NOT EXISTS TMSI_TABLE (" - "TMSI INTEGER PRIMARY KEY AUTOINCREMENT, " - "CREATED INTEGER NOT NULL, " // Unix time of record creation - "ACCESSED INTEGER NOT NULL, " // Unix time of last encounter - "APP_FLAGS INTEGER DEFAULT 0, " // Application-specific flags - "IMSI TEXT UNIQUE NOT NULL, " // IMSI - "IMEI TEXT, " // IMEI - "L3TI INTEGER DEFAULT 0," // L3 transaction identifier - "A5_SUPPORT INTEGER, " // encryption support - "POWER_CLASS INTEGER, " // power class - "OLD_TMSI INTEGER, " // previous TMSI in old network - "PREV_MCC INTEGER, " // previous network MCC - "PREV_MNC INTEGER, " // previous network MNC - "PREV_LAC INTEGER, " // previous network LAC - "RANDUPPER INTEGER, " // authentication token - "RANDLOWER INTEGER, " // authentication token - "SRES INTEGER, " // authentication token - "DEG_LAT FLOAT, " // RRLP result - "DEG_LONG FLOAT, " // RRLP result - "kc varchar(33) default '' " - ")" +namespace Control { +static Mutex sTmsiMutex; // We have to serialize tmsi creation. + +static unsigned sHighestFakeTmsi; // The highest fake tmsi value in the table to create an entry with no tmsi. +#define MAX_VALID_TMSI 0x3fffffff // 30 bits. We dont make TMSIs this high, but the invalid TMSIs start one higher than this. + +// Note: we are using the TMSI as the primary key for efficiency, which means we must have a unique tmsi value in the +// TMSITable for every entry whether it is assigned to the MS or not. +// We keep valid TMSIs that we are not sure the MS received, and also TMSIs that may have authorized in the past and subsequently +// become unauthorized. +// For unauthorized phones, we used to go ahead and create valid TMSIs anyway, but I dont want to do that now that TMSIs are +// genuine random numbers and somewhat expensive to allocate, plus there could be many of them and I dont want them +// using up TMSI address space, because eventually we may receive TMSIs from neighbors as well. +// Another alternative was to have a separate TMSI->IMSI mapping table, but I dont think that is any better. +// If you do not specify a primary key, sqlite assigns a new key one higher than the highest existing in the table. +// So I will do is let it go ahead and assign TMSIs 1,2,3,4 for the case where the TMSI is not used, +// Fine, so we will use values up to 64K as valid TMSIs, and values above that for invalid TMSIs. +// This seems to do a good job and sqlite does not seem to increase the database size much when you +// assign primary keys that are not tightly clustered. +// The TMSI_ASSIGNED field indicates if the MS has confirmed receipt of the TMSI. +// Eventually we may receive TMSIs from neighbors as well. +namespace TmsiTableDefinition { + + static const char *tmsiTableVersion = "7"; // Change this version whenever you change anything in the TMSI table. + // The fields from the tmsi table that are printed by the CLI 'tmsis' command. + // TMSITabDump assumes the names of some of these header fields to decide the appropriate formatting. + static string shortHeader = + "IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED"; + static string longHeader = shortHeader + " PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT"; + //"IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT"; + static string longerHeader = longHeader + " A5_SUPPORT POWER_CLASS RRLP_STATUS OLD_TMSI OLD_MCC OLD_MNC OLD_LAC"; + //"IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT A5_SUPPORT POWER_CLASS RRLP_STATUS OLD_TMSI OLD_MCC OLD_MNC OLD_LAC"; + static const char* create = + "CREATE TABLE IF NOT EXISTS TMSI_TABLE (" + // Valid TMSIs use negative values. Small positive auto-incremented numbers are dummy values + // automatically assigned by sqlite used for entries where the TMSI is unspecified. + "TMSI INTEGER PRIMARY KEY, " // AUTOINCREMENT This will also be used as the P-TMSI + // IMSI is just the digits without "IMSI" in front. + // (pat) the UNIQUE tells sqlite to create an index on this field to optimize queries. + "IMSI TEXT UNIQUE NOT NULL, " // IMSI + "IMEI TEXT, " // IMEI + "CREATED INTEGER NOT NULL, " // Unix time (seconds) of record creation + "ACCESSED INTEGER NOT NULL, " // Unix time (seconds) of last encounter + // "APP_FLAGS INTEGER DEFAULT 0, " // Application-specific flags (pat) No one is using this so I took it out. + "A5_SUPPORT INTEGER, " // encryption support + "POWER_CLASS INTEGER, " // power class + "OLD_TMSI INTEGER, " // previous TMSI in old network + "OLD_MCC INTEGER, " // previous network MCC + "OLD_MNC INTEGER, " // previous network MNC + "OLD_LAC INTEGER, " // previous network LAC +#if CACHE_AUTH // This code is eroded and no longer functional. + // (pat 9-2013): We are not storing the authentication info in the TMSI table. + // We need cached authorization for the case where we are using open registration and TMSIs are being sent, + // to verify the TMSI to avoid TMSI collisions, however, this should never have been the in the BTS - + // needs to be in sipauthserve if anywhere, so we always call the Registrar to get fresh challenges + // For UMTS this is a requirement, so there is no point putting in a special case for 2G GSM to cache a single RAND here. + // Cached authentication could also be used in the case where the Registrar is unreachable; but in that case we + // should fall back to using IMSIs and fail open. We dont handle that well at the moment. + //"RANDUPPER INTEGER, " // authentication token + //"RANDLOWER INTEGER, " // authentication token + //"SRES INTEGER, " // authentication token +#endif + "kc varchar(33) default '', " // returned by the Registrar, needed for ciphering. + "RRLP_STATUS INTEGER DEFAULT 0, " // Does MS support RRLP? If so, where are we in the RRLP state machine? + "DEG_LAT FLOAT, " // RRLP result + "DEG_LONG FLOAT, " // RRLP result + "ASSOCIATED_URI text default '', " // Saved from the SIP REGISTER message and inserted into MOC SIP INVITE. + "ASSERTED_IDENTITY text default '', " // Saved from the SIP REGISTER message and inserted into MOC SIP INVITE. + "WELCOME_SENT INTEGER DEFAULT 0," // 0 == welcome message not sent yet; 1 == sent by us; 2 == sent by someone else. + // (pat) We keep the unauthorized entries for several reasons: + // 1. so we can cache the unauthorized status to reduce loading on the Registrar by using cached unauthorization + // 2. to prevent sending the (un)welcome message multiple times. + // 3. if a previously authorized MS becomes unauthorized we want to keep the entry here to reserve the TMSI permanently. + "AUTH INTEGER DEFAULT 0," // Authorization result, 0 == unauthorized. See enum Authorization for other values. + "AUTH_EXPIRY INTEGER DEFAULT 0," // Absolute time in seconds when authorization expires or 0 for single-use. + "REJECT_CODE INTEGER DEFAULT 0," // Reject code, or 0 if authorized. + "TMSI_ASSIGNED INTEGER DEFAULT 0," // Set when the TMSI has been successfully assigned to the MS, ie, the MS knows it. + "PTMSI_ASSIGNED INTEGER DEFAULT 0" // Set when the P-TMSI has been successfully assigned to the MS by the SGSN, ie, the MS knows it. + + // TODO: A bunch of stuff for GPRS. GPRS could use the TMSI as the P-TMSI if it is limited to 30 bits. + ")"; }; +// These are the mappings of allocated tmsis to/from the TMSI_TABLE. +static int tmsi2table(uint32_t tmsi) +{ + return (int) tmsi; +} +static uint32_t table2tmsi(int storedValue) +{ + return storedValue; +} +static bool tmsiIsValid(int rawTmsi) +{ + return rawTmsi != 0 && rawTmsi <= MAX_VALID_TMSI; +} +bool configTmsiTestMode() +{ + return gConfig.getNum("Control.LUR.TestMode"); +} -int TMSITable::open(const char* wPath) +bool configSendTmsis() +{ + return gConfig.getBool("Control.LUR.SendTMSIs"); +} + + +// return true on success +bool TMSITable::runQuery(const char *query, int checkChanges) const +{ + int resultCode, changes=0; + LOG(DEBUG)<= 99999) {return;} // punt if disabled or ridiculous. + unsigned oldest_allowed = time(NULL) - (maxage * 60*60); + char query[102]; + snprintf(query,100,"DELETE FROM TMSI_TABLE WHERE ACCESSED <= %u",oldest_allowed); + runQuery(query,false); + int changes = sqlite3_changes(mTmsiDB); + if (changes) { + LOG(INFO) << "Deleted "< (int)sHighestFakeTmsi) { sHighestFakeTmsi = rawtmsi; } + if (! qinit.sqlStep()) break; + } + } +} + + +int TMSITable::tmsiTabOpen(const char* wPath) { // FIXME -- We can't call the logger here because it has not been initialized yet. + // (pat) I think this has been fixed - the TMSITable initialization is now in main(). + //printf("TMSITable::open(%s)\n",wPath); fflush(stdout); + LOG(INFO) << "TMSITable::open "<append(","); + this->appendf("%s='%s'",name,sval); + } + void addc(const char *name,unsigned ival) { + if (cnt++) this->append(","); + this->appendf("%s=%u",name,ival); + } + void finish() {} +}; + +struct TSqlInsert : public TSqlQuery { + TSqlString values; + Int_z cnt; // Count of commas. + void addc(const char *name, string sval) { + if (cnt++) { this->append(","); values.append(","); } + this->append(name); + values.appendf("'%s'",sval); + } + void addc(const char *name, unsigned ival) { + if (cnt++) { this->append(","); values.append(","); } + this->append(name); + values.appendf("%u",ival); + } + void finish() { + append(") VALUES ("); + append(this->values); + append(")"); + } +}; + +// Flush anything that has changed out to the TMSI table in this SQL query. +void TSqlQuery::addStore(TmsiTableStore *store) +{ + if (store->imei_changed) { addc("IMEI",store->imei); store->imei_changed = false; } + if (store->auth_changed) { addc("AUTH",store->auth); store->auth_changed = false; } + if (store->authExpiry_changed) { addc("AUTH_EXPIRY",store->authExpiry); store->authExpiry_changed = false; } + if (store->rejectCode_changed) { addc("REJECT_CODE",store->rejectCode); store->rejectCode_changed = false; } + if (store->assigned_changed) { addc("TMSI_ASSIGNED",store->assigned); store->assigned_changed = false; } + if (store->a5support_changed) { addc("A5_SUPPORT",store->a5support); store->a5support_changed = false; } + if (store->powerClass_changed) { addc("POWER_CLASS",store->powerClass); store->powerClass_changed = false; } + if (store->kc_changed) { addc("kc",store->kc); store->kc_changed = false; } + if (store->associatedUri_changed) { addc("ASSOCIATED_URI",store->associatedUri); store->associatedUri_changed = false; } + if (store->assertedIdentity_changed) { addc("ASSERTED_IDENTITY",store->assertedIdentity); store->assertedIdentity_changed = false; } + if (store->welcomeSent_changed) { addc("WELCOME_SENT",store->welcomeSent); store->welcomeSent_changed = false; } } +unsigned TMSITable::allocateTmsi() +{ + int tmsi; -unsigned TMSITable::assign(const char* IMSI, const GSM::L3LocationUpdatingRequest* lur) + // For testing, we will deliberately screw something up. + if (configTmsiTestMode()) { + // Deliberately over-write an existing entry to create a TMSI collision. + // Must use at least two phones - the second one will be assigned the same tmsi as the first. + sqlQuery q8(mTmsiDB,"TMSI_TABLE","TMSI",""); // Returns all the table rows one by one. + if (q8.sqlSuccess() == false) { + WATCH("TMSI table is empty"); + } else { + int rawtmsi = q8.getResultInt(); + if (tmsiIsValid(rawtmsi)) { + unsigned tmsi = table2tmsi(rawtmsi); + WATCH("TMSI table test mode: created deliberate tmsi collision for"<auth_changed)<auth)<assigned_changed)<assigned); + + ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures. + TSqlUpdate q1; + q1.append("UPDATE TMSI_TABLE SET "); + q1.addStore(store); + if (! q1.cnt) { return; } // Nothing changed. + q1.addc("ACCESSED",(unsigned)time(NULL)); + q1.appendf(" WHERE IMSI='%s'",imsi); // You must include the quotes around IMSI + runQuery(q1.c_str(),true); +} + +// Update or create a new entry in the TMSI table. +// If we are assigning TMSIs to the MS, return the new TMSI, else 0. +// This routine checks to see if the record for this IMSI already exists; the caller actually knows this and could inform us, +// however there is a great deal of logic with many options between the original TMSITable lookup and updating +// the TMSITable entry here, so I deemed it more robust and resistant to future changes if this routine ignores that +// and does another lookup of the IMSI to see if the record already exists. +uint32_t TMSITable::tmsiTabCreateOrUpdate( + const string imsi, + TmsiTableStore *store, + const GSM::L3LocationAreaIdentity * lai, + uint32_t oldTmsi) { // Create or find an entry based on IMSI. // Return assigned TMSI. - assert(mDB); + assert(mTmsiDB); + bool sendTmsis = configSendTmsis(); + unsigned now = (unsigned)time(NULL); + + ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures. + + unsigned oldRawTmsi = tmsiTabGetTMSI(imsi,false); + bool isNewRecord = (oldRawTmsi == 0); + + TSqlQuery *queryp; // dufus language + TSqlInsert foo; + TSqlUpdate bar; + if (isNewRecord) { + // Create a new record. + queryp = &foo; queryp->reserve(200); + queryp->append("INSERT INTO TMSI_TABLE ("); + queryp->addc("IMSI",imsi); + queryp->addc("CREATED",now); + queryp->addc("ACCESSED",now); + } else { + queryp = &bar; queryp->reserve(200); + // Update existing record. + queryp->append("UPDATE TMSI_TABLE SET "); + } + uint32_t tmsi = 0; + + if (sendTmsis) { + // We are handling several cases here. If it is a new record we are allocating a new tmsi for the first time, + // or if it is an existing record that was created without an assigned tmsi, ie, it has a fake tmsi, + // then we are updating it to a real tmsi. + // We never go backwards, ie, once we allocate a real tmsi, we never change it back to a fake tmsi, + // because we want to reserve the tmsi for this handset effectively forever. + if (! tmsiIsValid(oldRawTmsi)) { + gReports.incr("OpenBTS.GSM.MM.TMSI.Assigned"); + tmsi = allocateTmsi(); + queryp->addc("TMSI",tmsi2table(tmsi)); + } else { + tmsi = oldRawTmsi; + } + } else { + if (isNewRecord) { + // We are creating a new record. + // sqlite3 would auto-assign a unique TMSI higher than any other in + // the table, but we need to handle the initial case for the first fake tmsi. + tmsi = ++sHighestFakeTmsi; + queryp->addc("TMSI",tmsi2table(tmsi)); + } + } + queryp->addStore(store); + + if (isNewRecord && lai) { + queryp->addc("OLD_MCC",lai->MCC()); + queryp->addc("OLD_MNC",lai->MNC()); + queryp->addc("OLD_LAC",lai->LAC()); + queryp->addc("OLD_TMSI",oldTmsi); + queryp->finish(); + } else { + queryp->append(format(" WHERE IMSI=='%s'",imsi)); + } + + if (!runQuery(queryp->c_str(),1)) { + LOG(ALERT) << "TMSI creation failed for"<LAI(); - const GSM::L3MobileIdentity &mid = lur->mobileID(); - if (mid.type()==GSM::TMSIType) { - sprintf(query, - "INSERT INTO TMSI_TABLE (IMSI,CREATED,ACCESSED,PREV_MCC,PREV_MNC,PREV_LAC,OLD_TMSI) " - "VALUES ('%s',%u,%u,%u,%u,%u,%u)", - IMSI,now,now,lai.MCC(),lai.MNC(),lai.LAC(),mid.TMSI()); - } else { - sprintf(query, - "INSERT INTO TMSI_TABLE (IMSI,CREATED,ACCESSED,PREV_MCC,PREV_MNC,PREV_LAC) " - "VALUES ('%s',%u,%u,%u,%u,%u)", - IMSI,now,now,lai.MCC(),lai.MNC(),lai.LAC()); + // Nothing needed. sqlite3 woult auto-assign a unique TMSI higher than any other in + // the table, but we need to handle the initial case for the first fake tmsi. + tmsi = ++sHighestFakeTmsi; + query.addc("TMSI",tmsi2table(tmsi)); + } + query.addStore(store); + + if (lai) { + query.addc("OLD_MCC",lai->MCC()); + query.addc("OLD_MNC",lai->MNC()); + query.addc("OLD_LAC",lai->LAC()); + query.addc("OLD_TMSI",oldTmsi); + } + query.finish(); + if (!runQuery(query.c_str(),1)) { + LOG(ALERT) << "TMSI creation failed, query:"<MCC()); + query.add("OLD_MNC",lai->MNC()); + query.add("OLD_LAC",lai->LAC()); + query.add("OLD_TMSI",oldTmsi); + } + query.append(") VALUES ("); + query.append(values); + query.append(")"); + if (!runQuery(query.c_str(),1)) { + LOG(ALERT) << "TMSI creation failed, query:"<store_valid = true; // We have either updated the store or confirmed the imsi does not exist in the TMSI_TABLE. + sqlQuery q11(mTmsiDB,"TMSI_TABLE","AUTH,AUTH_EXPIRY,TMSI_ASSIGNED,REJECT_CODE,WELCOME_SENT", "IMSI",imsi.c_str()); + if (!q11.sqlSuccess()) { + LOG(INFO) << "No TMSI_TABLE table entry for"<auth = (Authorization) q11.getResultInt(0); //store->auth_valid = true; + store->authExpiry = q11.getResultInt(1); //store->authExpiry_valid = true; + store->assigned = q11.getResultInt(2); //store->assigned_valid = true; + store->rejectCode = q11.getResultInt(3); //store->rejectCode_valid = true; + store->welcomeSent = q11.getResultInt(4); //store->welcomeSent_valid = true; + LOG(DEBUG) <auth)<authExpiry) + <assigned)<rejectCode)<welcomeSent); + return true; +} + + +string TMSITable::tmsiTabGetIMSI(unsigned tmsi, unsigned *pAuthorizationResult) const +{ + string imsi; + if (pAuthorizationResult) tmsiTabTouchTmsi(tmsi); + sqlQuery q4(mTmsiDB,"TMSI_TABLE","IMSI,AUTH", "TMSI",tmsi2table(tmsi)); + if (!q4.sqlSuccess()) { + if (pAuthorizationResult) { *pAuthorizationResult = 0; } + } else { + imsi = q4.getResultText(0); + int auth = q4.getResultInt(1); + if (pAuthorizationResult) { *pAuthorizationResult = auth; } + LOG(DEBUG) < > TMSITable::tmsiTabView(int verbosity, bool rawFlag, unsigned maxrows) const +{ + vector< vector > view; + vector vh1; + + string header1 = verbosity >= 2 ? TmsiTableDefinition::longerHeader : + verbosity == 1 ? TmsiTableDefinition::longHeader : TmsiTableDefinition::shortHeader; + vector headers = stringSplit(vh1,header1.c_str()); + view.push_back(headers); + + string columns = replaceAll(header1," ",","); + sqlQuery query(mTmsiDB,"TMSI_TABLE",columns.c_str(),"ORDER BY ACCESSED DESC"); // descending + unsigned nrows = 1; + time_t now = time(NULL); + // If the table is completely empty this returns a row of all empty values. + while (query.sqlResultSize() && (nrows++ < maxrows)) { + // Print out the columns. + vector row; row.clear(); + unsigned col = 0; + for (col = 0; col < vh1.size(); col++) { + string header = headers[col]; + //if (header.find("TMSI") != string::npos) // Needed if we add PTMSI + if (header == "TMSI") { + // TMSI. Use hex and fix negative numbers. + int rawtmsi = query.getResultInt(col); + if (rawFlag || tmsiIsValid(rawtmsi)) { + unsigned tmsi = table2tmsi(rawtmsi); + row.push_back(format("0x%x",(unsigned) tmsi)); + } else { + // This is a dummy tmsi created by sqlite. + row.push_back("-"); + } + } else if (header == "CREATED" || header == "ACCESSED") { + // Print seconds as a time value. + row.push_back(prettyAge(now - query.getResultInt(col))); + } else if (header == "AUTH_EXPIRY") { + // The expiry is the absolute time when it expires, or 0 if unknown. + int expiry = query.getResultInt(col); + int remaining = expiry - now; + if (remaining < 0) remaining = 0; + row.push_back(expiry ? prettyAge(remaining) : "-"); + } else { + // All other column types. If they are integer they are converted to "" if NULL else decimal. + row.push_back(query.getResultText(col)); + } + } + view.push_back(row); + if (! query.sqlStep()) break; + } + return view; +} + + +void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll) const +{ + // Dump the TMSI table. + unsigned maxrows = showAll ? 2^31 : 100; + vector< vector > view = tmsiTabView(verbosity, rawFlag, maxrows); + +#if unused + // Add the IMSI authorization failures. They dont have TMSIs, or any other information. + vector failList; + getAuthFailures(failList); + for (vector::iterator it = failList.begin(); it != failList.end(); it++) { + if (view.size() >= maxrows) break; + vector failureRow; + failureRow.push_back(*it); // The IMSI; + failureRow.push_back(string("-")); // TMSI + failureRow.push_back(string("0")); // AUTH + view.push_back(failureRow); + } +#endif + + if (view.size() >= maxrows) { + vector tmp; + tmp.push_back(string("...")); + view.push_back(tmp); + } + + printPrettyTable(view,os); + +#if 0 // previous tmsi table dumping code. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE ORDER BY ACCESSED DESC")) { + if (sqlite3_prepare_statement(mTmsiDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE ORDER BY ACCESSED DESC")) { LOG(ERR) << "sqlite3_prepare_statement failed"; return; } time_t now = time(NULL); - while (sqlite3_run_query(mDB,stmt)==SQLITE_ROW) { + while (sqlite3_run_query(mTmsiDB,stmt)==SQLITE_ROW) { os << hex << setw(8) << sqlite3_column_int64(stmt,0) << ' ' << dec; os << sqlite3_column_text(stmt,1) << ' '; printAge(now-sqlite3_column_int(stmt,2),os); os << ' '; @@ -228,24 +878,28 @@ void TMSITable::dump(ostream& os) const os << endl; } sqlite3_finalize(stmt); +#endif } -void TMSITable::clear() +#if UNUSED +void TMSITable::setIMEI(string IMSI, string IMEI) { - sqlite3_command(mDB,"DELETE FROM TMSI_TABLE WHERE 1"); -} - - - -bool TMSITable::IMEI(const char* IMSI, const char *IMEI) -{ - char query[100]; - sprintf(query,"UPDATE TMSI_TABLE SET IMEI=\"%s\",ACCESSED=%u WHERE IMSI=\"%s\"", - IMEI,(unsigned)time(NULL),IMSI); - return sqlite3_command(mDB,query); + // If the IMEI has changed, update it and also reset RRLP_STATUS. + // The A5_SUPPORT and POWER_CLASS have to change too, but that is done by classmark(). + if (IMEI.size()) { + sqlQuery qu(mTmsiDB,"TMSI_TABLE","IMEI","IMSI",IMSI.c_str()); + string oldIMEI = qu.getResultText(0); + if (oldIMEI != IMEI) { + LOG(INFO) << "Updating" < +#include #include #include +#include struct sqlite3; namespace GSM { -class L3LocationUpdatingRequest; +class L3LocationAreaIdentity; class L3MobileStationClassmark2; class L3MobileIdentity; } namespace Control { +using namespace std; +class MMSharedData; +class TMSITable; +class TSqlQuery; + +enum Authorization { + AuthUnauthorized, + AuthAuthorized, // genuinely authorized by Registrar + AuthOpenRegistration, + AuthFailOpen, +}; + +class TmsiTableStore { + friend class TMSITable; + friend class TSqlQuery; + + Bool_z store_valid; // Set when we have either updated the store or confirmed the imsi does not exist in the TMSI_TABLE. + string imei; Bool_z imei_changed; + string kc; Bool_z kc_changed; + string associatedUri; Bool_z associatedUri_changed; + string assertedIdentity; Bool_z assertedIdentity_changed; + Int_z assigned; Bool_z assigned_changed; + Int_z a5support; Bool_z a5support_changed; + Int_z powerClass; Bool_z powerClass_changed; + // non-zero auth means authorized or accepted via openregistration or network failure. + Enum_z auth; Bool_z auth_changed; + + Int_z authExpiry; Bool_z authExpiry_changed; + Int_z rejectCode; Bool_z rejectCode_changed; + // We dont have a welcomeSent_valid because if the IMSI is not in the TMSI_TABLE welcomeSent defaults to 0 which is correct. + Int_z welcomeSent; Bool_z welcomeSent_changed; + + public: + Authorization getAuth() { devassert(store_valid); return auth; } + bool isAuthorized() { return getAuth() != AuthUnauthorized; } // authorized or passed open registration or network failure and FailOpen + int getAuthExpiry() { devassert(store_valid); return authExpiry; } + int getRejectCode() { devassert(store_valid); return rejectCode; } + int getWelcomeSent() { devassert(store_valid); return welcomeSent; } + + void setImei(string wImei) { imei = wImei; imei_changed = true; } + // Argument type is MMRejectCause but I dont want to include GSML3MMElements.h to avoid circular includes. + void setRejectCode(int val) { + auth = AuthUnauthorized; auth_changed = true; + rejectCode = (int) val; rejectCode_changed = true; + } + void setAuthorized(Authorization wAuth) { + auth = wAuth; auth_changed = true; + rejectCode = 0; rejectCode_changed = true; + } + void setAssigned(int val) { assigned = val; assigned_changed = true; } + void setKc(string wKc) { kc = wKc; kc_changed = true; } + void setAssociatedUri(string wAssociatedUri) { associatedUri = wAssociatedUri; associatedUri_changed = true; } + void setAssertedIdentity(string wAssertedIdentity) { assertedIdentity = wAssertedIdentity; assertedIdentity_changed = true; } + void setWelcomeSent(int val) { if (welcomeSent != val) { welcomeSent = val; welcomeSent_changed = true; } } + void setClassmark(int wa5support,int wpowerClass) { + a5support = wa5support; a5support_changed = true; + powerClass = wpowerClass; powerClass_changed = true; + } + string getImei() { return imei; } +}; class TMSITable { private: - sqlite3 *mDB; ///< database connection + sqlite3 *mTmsiDB; ///< database connection + void tmsiTabCleanup(); + void tmsiTabInit(); public: @@ -51,8 +115,10 @@ class TMSITable { @param wPath Path to sqlite3 database file. @return 0 if the database was successfully opened and initialized; 1 otherwise */ - int open(const char* wPath); + std::string mTablePath; ///< The path used to create the table. + int tmsiTabOpen(const char* wPath); + TMSITable() : mTmsiDB(0) {} ~TMSITable(); /** @@ -61,7 +127,17 @@ class TMSITable { @param The associated LUR, if any. @return The assigned TMSI. */ - unsigned assign(const char* IMSI, const GSM::L3LocationUpdatingRequest* lur=NULL); + //uint32_t tmsiTabAssign(const string IMSI, const GSM::L3LocationAreaIdentity * lur, uint32_t oldTmsi, TmsiTableStore *store); + //uint32_t assign(const string IMSI, const GSM::L3LocationAreaIdentity * lur, uint32_t oldTmsi, const string imei); + //uint32_t assign(const string imsi, const MMSharedData *shdata); + void tmsiTabUpdate(string imsi, TmsiTableStore *store); + uint32_t tmsiTabCreateOrUpdate(const string imsi, TmsiTableStore *store, const GSM::L3LocationAreaIdentity * lai, uint32_t oldTmsi); + bool dropTmsi(uint32_t tmsi); + bool dropImsi(const char *imsi); + unsigned allocateTmsi(); + //void tmsiTabSetAuthAndAssign(string imsi, int auth, int assigned); + //void tmsiTabSetAuth(string imsi, int auth); + void tmsiTabSetRejected(string imsi,int rejectCode); /** Find an IMSI in the table. @@ -69,7 +145,9 @@ class TMSITable { @param TMSI The TMSI to find. @return Pointer to IMSI to be freed by the caller, or NULL. */ - char* IMSI(unsigned TMSI) const; + bool tmsiTabGetStore(string imsi, TmsiTableStore *store) const; + std::string tmsiTabGetIMSI(unsigned TMSI, unsigned *pAuthorizationResult) const; + unsigned tmsiTabCheckAuthorization(string imsi) const; /** Find a TMSI in the table. @@ -77,46 +155,62 @@ class TMSITable { @param IMSI The IMSI to mach. @return A TMSI value or zero on failure. */ - unsigned TMSI(const char* IMSI) const; + unsigned tmsiTabGetTMSI(const string IMSI, bool onlyIfKnown) const; + void tmsiTabReallocationComplete(unsigned TMSI) const; + std::vector< std::vector > tmsiTabView(int verbosity, bool rawFlag, unsigned maxrows) const; /** Write entries as text to a stream. */ - void dump(std::ostream&) const; + void tmsiTabDump(int verbosity,bool rawFlag,std::ostream&, bool ShowAll = false) const; /** Clear the table completely. */ - void clear(); + void tmsiTabClear(); + // Clear the authorization cache. + void tmsiTabClearAuthCache(); /** Set the IMEI. */ - bool IMEI(const char* IMSI, const char* IMEI); + //void setIMEI(string IMSI, string IMEI); /** Set the classmark. */ bool classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark); /** Get the preferred A5 algorithm (3, 1, or 0). */ - int getPreferredA5Algorithm(const char* IMSI); + int tmsiTabGetPreferredA5Algorithm(const char* IMSI); +#if UNUSED /** Save a RAND/SRES pair. */ void putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES); /** Get a RAND/SRES pair. */ - bool getAuthTokens(const char* IMSI, uint64_t &upperRAND, uint64_t &lowerRAND, uint32_t &SRES); + bool getAuthTokens(const char* IMSI, uint64_t &upperRAND, uint64_t &lowerRAND, uint32_t &SRES) const; /** Save Kc. */ - void putKc(const char* IMSI, std::string Kc); + void putKc(const char* imsi, string Kc, string pAssociatedUri, string pAssertedIdentity); +#endif /** Get Kc. */ - std::string getKc(const char* IMSI); + std::string getKc(const char* IMSI) const; - /** Get the next TI value to use for this IMSI or TMSI. */ - unsigned nextL3TI(const char* IMSI); + void getSipIdentities(string imsi, string &pAssociatedUri,string &pAssertedIdentity) const; + bool runQuery(const char *query,int checkChanges=0) const; private: + bool tmsiTabCheckVersion(); /** Update the "accessed" time on a record. */ - void touch(unsigned TMSI) const; + void tmsiTabTouchTmsi(unsigned TMSI) const; + void tmsiTabTouchImsi(string IMSI) const; }; -} +bool configTmsiTestMode(); +bool configSendTmsis(); + +extern unsigned getPreferredA5Algorithm(unsigned A5Bits); + +} // namespace Control + +// This gTMSITable is historically in the global namespace. +extern Control::TMSITable gTMSITable; #endif diff --git a/Control/TransactionTable.cpp b/Control/TransactionTable.cpp deleted file mode 100644 index 29d323f..0000000 --- a/Control/TransactionTable.cpp +++ /dev/null @@ -1,1611 +0,0 @@ -/**@file TransactionTable and related classes. */ - -/* -* Copyright 2008, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Process, Inc. -* Copyright 2011, 2012 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -#include "TransactionTable.h" -#include "ControlCommon.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#undef WARNING - - -using namespace std; -using namespace GSM; -using namespace Control; -using namespace SIP; - - - -static const char* createTransactionTable = { - "CREATE TABLE IF NOT EXISTS TRANSACTION_TABLE (" - "ID INTEGER PRIMARY KEY, " // internal transaction ID - "CHANNEL TEXT DEFAULT NULL," // channel description string (cross-refs CHANNEL_TABLE) - "CREATED INTEGER NOT NULL, " // Unix time of record creation - "CHANGED INTEGER NOT NULL, " // time of last state change - "TYPE TEXT, " // transaction type - "SUBSCRIBER TEXT, " // IMSI, if known - "L3TI INTEGER, " // GSM L3 transaction ID, +0x08 if generated by MS - "SIP_CALLID TEXT, " // SIP-side call id tag - "SIP_PROXY TEXT, " // SIP proxy IP - "CALLED TEXT, " // called party number - "CALLING TEXT, " // calling party number - "GSMSTATE TEXT, " // GSM/Q.931 state - "SIPSTATE TEXT " // SIP state - ")" -}; - - - - -void TransactionEntry::initTimers() -{ - // Call this only once, from the constructor. - // TODO -- It would be nice if these were all configurable. - assert(mTimers.size()==0); - mTimers["301"] = Z100Timer(T301ms); - mTimers["302"] = Z100Timer(T302ms); - mTimers["303"] = Z100Timer(T303ms); - mTimers["304"] = Z100Timer(T304ms); - mTimers["305"] = Z100Timer(T305ms); - mTimers["308"] = Z100Timer(T308ms); - mTimers["310"] = Z100Timer(T310ms); - mTimers["313"] = Z100Timer(T313ms); - mTimers["3113"] = Z100Timer(gConfig.getNum("GSM.Timer.T3113")); - mTimers["TR1M"] = Z100Timer(TR1Mms); -} - - - - - -// Form for MT transactions. -TransactionEntry::TransactionEntry( - const char* proxy, - const L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const L3CMServiceType& wService, - const L3CallingPartyBCDNumber& wCalling, - GSM::CallState wState, - const char *wMessage) - :mID(gTransactionTable.newID()), - mSubscriber(wSubscriber),mService(wService), - mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())), - mCalling(wCalling), - mSIP(proxy,mSubscriber.digits()), - mGSMState(wState), - mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), - mChannel(wChannel), - mTerminationRequested(false), - mHandoverOtherBSTransactionID(0), - mRemoved(false) -{ - if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); - else mMessage.assign(""); //mMessage[0]='\0'; - initTimers(); -} - -// Form for MOC transactions. -TransactionEntry::TransactionEntry( - const char* proxy, - const L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const L3CMServiceType& wService, - unsigned wL3TI, - const L3CalledPartyBCDNumber& wCalled) - :mID(gTransactionTable.newID()), - mSubscriber(wSubscriber),mService(wService), - mL3TI(wL3TI), - mCalled(wCalled), - mSIP(proxy,mSubscriber.digits()), - mGSMState(GSM::MOCInitiated), - mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), - mChannel(wChannel), - mTerminationRequested(false), - mHandoverOtherBSTransactionID(0), - mRemoved(false) -{ - assert(mSubscriber.type()==GSM::IMSIType); - mMessage.assign(""); //mMessage[0]='\0'; - initTimers(); -} - - -// Form for MO-SMS transactions. -TransactionEntry::TransactionEntry( - const char* proxy, - const L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const L3CalledPartyBCDNumber& wCalled, - const char* wMessage) - :mID(gTransactionTable.newID()), - mSubscriber(wSubscriber), - mService(GSM::L3CMServiceType::ShortMessage), - mL3TI(7),mCalled(wCalled), - mSIP(proxy,mSubscriber.digits()), - mGSMState(GSM::SMSSubmitting), - mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), - mChannel(wChannel), - mTerminationRequested(false), - mHandoverOtherBSTransactionID(0), - mRemoved(false) -{ - assert(mSubscriber.type()==GSM::IMSIType); - if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); - else mMessage.assign(""); //mMessage[0]='\0'; - initTimers(); -} - -// Form for MO-SMS transactions with parallel call. -TransactionEntry::TransactionEntry( - const char* proxy, - const L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel) - :mID(gTransactionTable.newID()), - mSubscriber(wSubscriber), - mService(GSM::L3CMServiceType::ShortMessage), - mL3TI(7), - mSIP(proxy,mSubscriber.digits()), - mGSMState(GSM::SMSSubmitting), - mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), - mChannel(wChannel), - mTerminationRequested(false), - mHandoverOtherBSTransactionID(0), - mRemoved(false) -{ - assert(mSubscriber.type()==GSM::IMSIType); - mMessage[0]='\0'; - initTimers(); -} - - - -// Form for inbound handovers. -TransactionEntry::TransactionEntry(const struct sockaddr_in* peer, - unsigned wHandoverReference, - SimpleKeyValue ¶ms, - const char *proxy, - GSM::LogicalChannel *wChannel, - unsigned wHandoverOtherBSTransactionID) - :mID(gTransactionTable.newID()), - mService(GSM::L3CMServiceType::HandoverCall), - mSIP(proxy), - mGSMState(GSM::HandoverInbound), - mInboundReference(wHandoverReference), - mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), - mChannel(wChannel), - mTerminationRequested(false), - mHandoverOtherBSTransactionID(wHandoverOtherBSTransactionID), - mRemoved(false) -{ - // This is used for inbound handovers. - // We are "BS2" in the handover ladder diagram. - // The message string was formed by the handoverString method. - - // Save the peer address. - bcopy(peer,&mInboundPeer,sizeof(mInboundPeer)); - - // Break into space-delimited tokens, stuff into a SimpleKeyValue and then unpack it. - //SimpleKeyValue params; - //params.addItems(args); - - const char* IMSI = params.get("IMSI"); - if (IMSI) mSubscriber = GSM::L3MobileIdentity(IMSI); - - const char* called = params.get("called"); - if (called) { - mCalled = GSM::L3CallingPartyBCDNumber(called); - mService = GSM::L3CMServiceType::MobileOriginatedCall; - } - - const char* calling = params.get("calling"); - if (calling) { - mCalling = GSM::L3CallingPartyBCDNumber(calling); - mService = GSM::L3CMServiceType::MobileTerminatedCall; - } - - const char* ref = params.get("ref"); - if (ref) mInboundReference = strtol(ref,NULL,10); - - const char* L3TI = params.get("L3TI"); - if (L3TI) mL3TI = strtol(L3TI,NULL,10); - - // Set the SIP state. - mSIP.state(SIP::HandoverInbound); - - const char* codec = params.get("codec"); - if (codec) mCodec = atoi(codec); - - const char* remoteUsername = params.get("remoteUsername"); - if (remoteUsername) mRemoteUsername = strdup(remoteUsername); - - const char* remoteDomain = params.get("remoteDomain"); - if (remoteDomain) mRemoteDomain = strdup(remoteDomain); - - const char* SIPUsername = params.get("SIPUsername"); - if (SIPUsername) mSIPUsername = strdup(SIPUsername); - - const char* SIPDisplayname = params.get("SIPDisplayname"); - if (SIPDisplayname) mSIPDisplayname = strdup(SIPDisplayname); - - const char* FromTag = params.get("FromTag"); - if (FromTag) mFromTag = strdup(FromTag); - - const char* FromUsername = params.get("FromUsername"); - if (FromUsername) mFromUsername = strdup(FromUsername); - - const char* FromIP = params.get("FromIP"); - if (FromIP) mFromIP = strdup(FromIP); - - const char* ToTag = params.get("ToTag"); - if (ToTag) mToTag = strdup(ToTag); - - const char* ToUsername = params.get("ToUsername"); - if (ToUsername) mToUsername = strdup(ToUsername); - - const char* ToIP = params.get("ToIP"); - if (ToIP) mToIP = strdup(ToIP); - - const char* CSeq = params.get("CSeq"); - if (CSeq) mCSeq = atoi(CSeq); - - const char * CallID = params.get("CallID"); - if (CallID) mCallID = CallID; - mSIP.callID(CallID); - - const char * CallIP = params.get("CallIP"); - if (CallIP) mCallIP = CallIP; - - const char * RTPState = params.get("RTPState"); - if (RTPState) mRTPState = RTPState; - - const char * SessionID = params.get("SessionID"); - if (SessionID) mSessionID = SessionID; - - const char * SessionVersion = params.get("SessionVersion"); - if (SessionVersion) mSessionVersion = SessionVersion; - - const char * RTPRemPort = params.get("RTPRemPort"); - if (RTPRemPort) mRTPRemPort = atoi(RTPRemPort); - - const char * RTPRemIP = params.get("RTPRemIP"); - if (RTPRemIP) mRTPRemIP = RTPRemIP; - - const char * RmtIP = params.get("RmtIP"); - if (RmtIP) mRmtIP = RmtIP; - - const char * RmtPort = params.get("RmtPort"); - if (RmtPort) mRmtPort = atoi(RmtPort); - - const char * SRIMSI = params.get("SRIMSI"); - if (SRIMSI) mSRIMSI = SRIMSI; - - const char * SRCALLID = params.get("SRCALLID"); - if (SRCALLID) mSRCALLID = SRCALLID; - - initTimers(); - -} - - -TransactionEntry::~TransactionEntry() -{ - // This should go out of scope before the object is actually destroyed. - ScopedLock lock(mLock); - - // Remove any FIFO from the gPeerInterface. - gPeerInterface.removeFIFO(mID); - // Remove the associated SIP message FIFO. - gSIPInterface.removeCall(mSIP.callID()); - - // Delete the SQL table entry. - char query[100]; - sprintf(query,"DELETE FROM TRANSACTION_TABLE WHERE ID=%u",mID); - runQuery(query); - -} - - -void TransactionEntry::resetTimer(const char* name) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mTimers[name].reset(); -} - - -void TransactionEntry::setTimer(const char* name) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mTimers[name].set(); -} - -void TransactionEntry::setTimer(const char* name, long newLimit) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mTimers[name].set(newLimit); -} - - -bool TransactionEntry::timerExpired(const char* name) const -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - TimerTable::const_iterator itr = mTimers.find(name); - assert(itr!=mTimers.end()); - return (itr->second).expired(); -} - - -bool TransactionEntry::anyTimerExpired() const -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - TimerTable::const_iterator itr = mTimers.begin(); - while (itr!=mTimers.end()) { - if ((itr->second).expired()) { - LOG(INFO) << itr->first << " expired in " << *this; - return true; - } - ++itr; - } - return false; -} - - -void TransactionEntry::resetTimers() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - TimerTable::iterator itr = mTimers.begin(); - while (itr!=mTimers.end()) { - (itr->second).reset(); - ++itr; - } -} - - -bool TransactionEntry::clearingGSM() const -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - return (mGSMState==GSM::ReleaseRequest) || (mGSMState==GSM::DisconnectIndication); -} - - -bool TransactionEntry::deadOrRemoved() const -{ - if (mRemoved) return true; - ScopedLock lock(mLock); - return dead(); -} - - -bool TransactionEntry::dead() const -{ - // Get the state information and release the locks. - - // If it's locked, we assume someone has locked it, - // so it's not dead. - // And if someone locked in permanently, - // the resulting deadlock would spread through the whole system. - - if (!mLock.trylock()) return false; - SIP::SIPState lSIPState = mSIP.state(); - GSM::CallState lGSMState = mGSMState; - unsigned age = mStateTimer.elapsed(); - mLock.unlock(); - - // Now check states against the timer. - - - // 30-second tests - if (age < 30*1000) return false; - // Failed? - if (lSIPState==SIP::Fail) return true; - // Bad handover? - if (lSIPState==SIP::HandoverInbound) return true; - // SIP Null state? - if (lSIPState==SIP::NullState) return true; - // SIP stuck in proceeding? - if (lSIPState==SIP::Proceeding) return true; - // SIP cancelled? - if (lSIPState==SIP::Canceled) return true; - // SIP Cleared? - if (lSIPState==SIP::Cleared) return true; - - // 180-second tests - if (age < 180*1000) return false; - // Dead if someone requested removal >3 min ago. - if (mRemoved) return true; - // Any GSM state other than Active for >3 min? - if (lGSMState!=GSM::Active) return true; - // Any SIP stte other than active for >3 min? - if (lSIPState !=SIP::Active) return true; - - // If we got here, the state-vs-timer relationship - // appears to be valid. - return false; -} - - - -ostream& Control::operator<<(ostream& os, const TransactionEntry& entry) -{ - entry.text(os); - return os; -} - - - -void TransactionEntry::text(ostream& os) const -{ - ScopedLock lock(mLock); - os << mID; - if (mRemoved) os << " (removed)"; - else if (dead()) os << " (defunct)"; - if (mChannel) os << " " << *mChannel; - else os << " no chan"; - os << " " << mSubscriber; - os << " L3TI=" << mL3TI; - os << " SIP-call-id=" << mSIP.callID(); - os << " SIP-proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort(); - os << " " << mService; - if (mCalled.digits()[0]) os << " to=" << mCalled.digits(); - if (mCalling.digits()[0]) os << " from=" << mCalling.digits(); - os << " GSMState=" << mGSMState; - os << " SIPState=" << mSIP.state(); - os << " (" << (stateAge()+500)/1000 << " sec)"; - if (mMessage[0]) os << " message=\"" << mMessage << "\""; -} - -void TransactionEntry::message(const char *wMessage, size_t length) -{ - /*if (length>520) { - LOG(NOTICE) << "truncating long message: " << wMessage; - length=520; - }*/ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - //memcpy(mMessage,wMessage,length); - //mMessage[length]='\0'; - mMessage.assign(wMessage, length); -} - -void TransactionEntry::messageType(const char *wContentType) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mContentType.assign(wContentType); -} - - - -void TransactionEntry::runQuery(const char* query) const -{ - // Caller should hold mLock and should have already checked mRemoved.. - if (sqlite3_command(gTransactionTable.DB(),query,mNumSQLTries)) return; - LOG(ALERT) << "transaction table access failed after " << mNumSQLTries << "attempts. query:" << query << " error: " << sqlite3_errmsg(gTransactionTable.DB()); -} - - - -void TransactionEntry::insertIntoDatabase() -{ - // This should be called only from gTransactionTable::add. - // Caller should hold mLock. - - ostringstream serviceTypeSS; - serviceTypeSS << mService; - - ostringstream sipStateSS; - sipStateSS << mSIP.state(); - mPrevSIPState = mSIP.state(); - - char subscriber[25]; - switch (mSubscriber.type()) { - case IMSIType: sprintf(subscriber,"IMSI%s",mSubscriber.digits()); break; - case IMEIType: sprintf(subscriber,"IMEI%s",mSubscriber.digits()); break; - case TMSIType: sprintf(subscriber,"TMSI%x",mSubscriber.TMSI()); break; - default: - sprintf(subscriber,"invalid"); - LOG(ERR) << "non-valid subscriber ID in transaction table: " << mSubscriber; - } - - const char* stateString = GSM::CallStateString(mGSMState); - assert(stateString); - - // FIXME -- This should be done in a single SQL transaction. - - char query[500]; - unsigned now = (unsigned)time(NULL); - sprintf(query,"INSERT INTO TRANSACTION_TABLE " - "(ID,CREATED,CHANGED,TYPE,SUBSCRIBER,L3TI,CALLED,CALLING,GSMSTATE,SIPSTATE,SIP_CALLID,SIP_PROXY) " - "VALUES (%u,%u, %u, '%s','%s', %u,'%s', '%s', '%s', '%s', '%s', '%s')", - mID,now,now, - serviceTypeSS.str().c_str(), - subscriber, - mL3TI, - mCalled.digits(), - mCalling.digits(), - stateString, - sipStateSS.str().c_str(), - mSIP.callID().c_str(), - mSIP.proxyIP().c_str() - ); - - runQuery(query); - - if (!mChannel) return; - sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANNEL='%s' WHERE ID=%u", - mChannel->descriptiveString(), mID); - runQuery(query); -} - - - -void TransactionEntry::channel(GSM::LogicalChannel* wChannel) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mChannel = wChannel; - - char query[500]; - if (mChannel) { - sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL='%s' WHERE ID=%u", - (unsigned)time(NULL), mChannel->descriptiveString(), mID); - } else { - sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL=NULL WHERE ID=%u", - (unsigned)time(NULL), mID); - } - - runQuery(query); -} - - -GSM::LogicalChannel* TransactionEntry::channel() -{ - if (mRemoved) throw RemovedTransaction(mID); - return mChannel; -} - -const GSM::LogicalChannel* TransactionEntry::channel() const -{ - if (mRemoved) throw RemovedTransaction(mID); - return mChannel; -} - - - -unsigned TransactionEntry::L3TI() const -{ - if (mRemoved) throw RemovedTransaction(mID); - return mL3TI; -} - -GSM::CallState TransactionEntry::GSMState() const -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - return mGSMState; -} - - -void TransactionEntry::GSMState(GSM::CallState wState) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mStateTimer.now(); - unsigned now = mStateTimer.sec(); - - mGSMState = wState; - const char* stateString = GSM::CallStateString(wState); - assert(stateString); - - char query[150]; - sprintf(query, - "UPDATE TRANSACTION_TABLE SET GSMSTATE='%s',CHANGED=%u WHERE ID=%u", - stateString,now, mID); - runQuery(query); -} - - -SIP::SIPState TransactionEntry::echoSIPState(SIP::SIPState state) const -{ - // Caller should hold mLock. - if (mPrevSIPState==state) return state; - mPrevSIPState = state; - - const char* stateString = SIP::SIPStateString(state); - assert(stateString); - - unsigned now = time(NULL); - - char query[150]; - sprintf(query, - "UPDATE TRANSACTION_TABLE SET SIPSTATE='%s',CHANGED=%u WHERE ID=%u", - stateString,now,mID); - runQuery(query); - - return state; -} - - - - -SIP::SIPState TransactionEntry::MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCSendINVITE(calledUser,calledDomain,rtpPort,codec,channel()); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MOCResendINVITE() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCResendINVITE(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MOCCheckForOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCCheckForOK(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MOCSendACK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOCSendACK(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTCSendTrying() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCSendTrying(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTCSendRinging() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCSendRinging(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTCCheckForACK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCCheckForACK(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTCCheckForCancel() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCCheckForCancel(); - echoSIPState(state); - return state; -} - - -SIP::SIPState TransactionEntry::MTCSendOK(short rtpPort, unsigned codec) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTCSendOK(rtpPort,codec,channel()); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODSendBYE() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODSendBYE(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODSendERROR(cause, code, reason, cancel); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODSendCANCEL() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODSendCANCEL(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODResendBYE() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODResendBYE(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODResendCANCEL() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODResendCANCEL(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODResendERROR(bool cancel) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODResendERROR(cancel); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODWaitForBYEOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForBYEOK(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODWaitForCANCELOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForCANCELOK(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODWaitForERRORACK(bool cancel) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForERRORACK(cancel,&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODWaitFor487() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitFor487(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MODWaitForResponse(vector *validResponses) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MODWaitForResponse(validResponses, &mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTDCheckBYE() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTDCheckBYE(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTDSendBYEOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTDSendBYEOK(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTDSendCANCELOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTDSendCANCELOK(); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOSMSSendMESSAGE(calledUser,calledDomain,mMessage.c_str(),contentType,channel()); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MOSMSWaitForSubmit() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MOSMSWaitForSubmit(&mLock); - echoSIPState(state); - return state; -} - -SIP::SIPState TransactionEntry::MTSMSSendOK() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - SIP::SIPState state = mSIP.MTSMSSendOK(channel()); - echoSIPState(state); - return state; -} - -bool TransactionEntry::sendINFOAndWaitForOK(unsigned info) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - return mSIP.sendINFOAndWaitForOK(info,&mLock); -} - -void TransactionEntry::SIPUser(const char* IMSI) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mSIP.user(IMSI); -} - -void TransactionEntry::SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mSIP.user(callID,IMSI,origID,origHost); -} - -void TransactionEntry::called(const L3CalledPartyBCDNumber& wCalled) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mCalled = wCalled; - - char query[151]; - snprintf(query,150, - "UPDATE TRANSACTION_TABLE SET CALLED='%s' WHERE ID=%u", - mCalled.digits(), mID); - runQuery(query); -} - - -void TransactionEntry::L3TI(unsigned wL3TI) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mL3TI = wL3TI; - - char query[151]; - snprintf(query,150, - "UPDATE TRANSACTION_TABLE SET L3TI=%u WHERE ID=%u", - mL3TI, mID); - runQuery(query); -} - - -bool TransactionEntry::terminationRequested() -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - bool retVal = mTerminationRequested; - mTerminationRequested = false; - return retVal; -} - -string TransactionEntry::handoverString() const -{ - // This string is a set of key-value pairs. - // It needs to carry all of the information of the GSM Abis Handover Request message, - // as well as all of the information of the SIP REFER message. - // We call this as "BS1" in the handover ladder diagram. - // It is decoded at the other end by a TransactionEnty constructor. - - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - ostringstream os; - os << mID; - os << " IMSI=" << mSubscriber.digits(); - if (mGSMState==GSM::HandoverInbound) os << " inbound-ref=" << mInboundReference; - if (mGSMState==GSM::HandoverOutbound) os << " outbound-ref=" << mOutboundReference.value(); - os << " L3TI=" << mL3TI; - if (mCalled.digits()[0]) os << " called=" << mCalled.digits(); - if (mCalling.digits()[0]) os << " calling=" << mCalling.digits(); - - osip_message_t *ok = mSIP.LastResponse(); - if (!ok) ok = mSIP.INVITE(); - osip_cseq_t *cseq = osip_message_get_cseq(ok); - - char *cseqStr; - osip_cseq_to_str(cseq, &cseqStr); - os << " CSeq=" << cseqStr; - // FIXME - this should be extracted from a= attribute of sdp message - os << " codec=" << SIP::RTPGSM610; - - os << " CallID=" << osip_call_id_get_number(ok->call_id); - if (osip_call_id_get_host(ok->call_id)) { - os << " CallIP=" << osip_call_id_get_host(ok->call_id); - } else { - os << " CallIP="; - } - - const char *fromLabel = " From"; - const char *toLabel = " To"; - // FIXME? - is there a better way to detect moc vs mtc? - if (!mSIP.LastResponse()) { - fromLabel = " To"; - toLabel = " From"; - } - osip_from_t *from = osip_message_get_from(ok); - char *fromStr; - osip_from_to_str(from, &fromStr); - char *fromTag = index(fromStr, ';'); - // FIXME? - is there a better way to get the tag? - os << " " << fromLabel << "Tag=" << fromTag+5; - os << " " << fromLabel << "Username=" << osip_uri_get_username(ok->from->url); - os << " " << fromLabel << "IP=" << osip_uri_get_host(ok->from->url); - osip_to_t *to = osip_message_get_to(ok); - char *toStr; - osip_to_to_str(to, &toStr); - char *toTag = index(toStr, ';'); - // FIXME? - is there a better way to get the tag? - os << " " << toLabel << "Tag=" << toTag+5; - os << " " << toLabel << "Username=" << osip_uri_get_username(ok->to->url); - os << " " << toLabel << "IP=" << osip_uri_get_host(ok->to->url); - - // FIXME? - is there a better way to extract this info? - osip_body_t * osipBodyT; - osip_message_get_body (ok, 0, &osipBodyT); - char *osipBodyTStr; - size_t osipBodyTStrLth; - osip_body_to_str (osipBodyT, &osipBodyTStr, &osipBodyTStrLth); - char *SessionIDStr = index(osipBodyTStr, ' ')+1; - char *SessionVersionStr = index(SessionIDStr, ' ')+1; - long SessionID = strtol(SessionIDStr, NULL, 10); - long SessionVersion = strtol(SessionVersionStr, NULL, 10)+1; - os << " SessionID=" << SessionID; - os << " SessionVersion=" << SessionVersion; - - // getting the remote port from the m= line of the OK - char d_ip_addr[20]; - char d_port[10]; - SIP::get_rtp_params(ok, d_port, d_ip_addr); - os << " RTPRemIP=" << d_ip_addr; - os << " RTPRemPort=" << d_port; - - // proxy - os << " Proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort(); - - // remote ip and port - osip_contact_t * con = (osip_contact_t*)osip_list_get(&ok->contacts, 0); - os << " RmtIP=" << osip_uri_get_host(con->url); - os << " RmtPort=" << osip_uri_get_port(con->url); - - os << " RTPState=" << - mSIP.RTPSession()->rtp.snd_time_offset << "," << - mSIP.RTPSession()->rtp.snd_ts_offset << "," << - mSIP.RTPSession()->rtp.snd_rand_offset << "," << - mSIP.RTPSession()->rtp.snd_last_ts << "," << - mSIP.RTPSession()->rtp.rcv_time_offset << "," << - mSIP.RTPSession()->rtp.rcv_ts_offset << "," << - mSIP.RTPSession()->rtp.rcv_query_ts_offset << "," << - mSIP.RTPSession()->rtp.rcv_last_ts << "," << - mSIP.RTPSession()->rtp.rcv_last_app_ts << "," << - mSIP.RTPSession()->rtp.rcv_last_ret_ts << "," << - mSIP.RTPSession()->rtp.hwrcv_extseq << "," << - mSIP.RTPSession()->rtp.hwrcv_seq_at_last_SR << "," << - mSIP.RTPSession()->rtp.hwrcv_since_last_SR << "," << - mSIP.RTPSession()->rtp.last_rcv_SR_ts << "," << - mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_sec << "," << mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_usec << "," << - mSIP.RTPSession()->rtp.snd_seq << "," << - //mSIP.RTPSession()->rtp.last_rtcp_report_snt_r << "," << - //mSIP.RTPSession()->rtp.last_rtcp_report_snt_s << "," << - //mSIP.RTPSession()->rtp.rtcp_report_snt_interval << "," << - mSIP.RTPSession()->rtp.last_rtcp_packet_count << "," << - mSIP.RTPSession()->rtp.sent_payload_bytes; - - return os.str(); -} - -void TransactionTable::init(const char* path) -{ - // This assumes the main application uses sdevrandom. - mIDCounter = random(); - // Connect to the database. - int rc = sqlite3_open(path,&mDB); - if (rc) { - LOG(ALERT) << "Cannot open Transaction Table database at " << path << ": " << sqlite3_errmsg(mDB); - sqlite3_close(mDB); - mDB = NULL; - return; - } - // Create a new table, if needed. - if (!sqlite3_command(mDB,createTransactionTable)) { - LOG(ALERT) << "Cannot create Transaction Table"; - } - // Set high-concurrency WAL mode. - if (!sqlite3_command(mDB,enableWAL)) { - LOG(ALERT) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(mDB); - } - // Clear any previous entires. - if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE")) - LOG(WARNING) << "cannot clear previous transaction table"; -} - - - -void TransactionEntry::setOutboundHandover( - const GSM::L3HandoverReference& reference, - const GSM::L3CellDescription& cell, - const GSM::L3ChannelDescription2& chan, - const GSM::L3PowerCommandAndAccessType& pwrCmd, - const GSM::L3SynchronizationIndication& synch - ) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mOutboundReference = reference; - mOutboundCell = cell; - mOutboundChannel = chan; - mOutboundPowerCmd = pwrCmd; - mOutboundSynch = synch; - GSMState(GSM::HandoverOutbound); - return; -} - - -void TransactionEntry::setInboundHandover(float RSSI, float timingError, double timestamp) -{ - if (mRemoved) throw RemovedTransaction(mID); - ScopedLock lock(mLock); - mChannel->setPhy(RSSI,timingError,timestamp); - mInboundRSSI = RSSI; - mInboundTimingError = timingError; -} - - - - - - -TransactionTable::~TransactionTable() -{ - // Don't bother disposing of the memory, - // since this is only invoked when the application exits. - if (mDB) sqlite3_close(mDB); -} - - - - -unsigned TransactionTable::newID() -{ - ScopedLock lock(mLock); - return mIDCounter++; -} - - -void TransactionTable::add(TransactionEntry* value) -{ - LOG(INFO) << "new transaction " << *value; - ScopedLock lock(mLock); - mTable[value->ID()]=value; - value->insertIntoDatabase(); -} - - - -TransactionEntry* TransactionTable::find(unsigned key) -{ - // Since this is a log-time operation, we don't screw that up by calling clearDeadEntries. - - // ID==0 is a non-valid special case. - LOG(DEBUG) << "by key: " << key; - assert(key); - ScopedLock lock(mLock); - TransactionMap::iterator itr = mTable.find(key); - if (itr==mTable.end()) return NULL; - if (itr->second->deadOrRemoved()) return NULL; - return (itr->second); -} - - -void TransactionTable::innerRemove(TransactionMap::iterator itr) -{ - // This should not be called anywhere but from clearDeadEntries. - LOG(DEBUG) << "removing transaction: " << *(itr->second); - TransactionEntry *t = itr->second; - mTable.erase(itr); - delete t; -} - - -bool TransactionTable::remove(unsigned key) -{ - // ID==0 is a non-valid special case, and it shouldn't be passed here. - if (key==0) { - LOG(ERR) << "called with key==0"; - return false; - } - - ScopedLock lock(mLock); - TransactionMap::iterator itr = mTable.find(key); - if (itr==mTable.end()) return false; - itr->second->remove(); - return true; -} - -bool TransactionTable::removePaging(unsigned key) -{ - // ID==0 is a non-valid special case and should not be passed here. - assert(key); - ScopedLock lock(mLock); - TransactionMap::iterator itr = mTable.find(key); - if (itr==mTable.end()) return false; - if (itr->second->removed()) return true; - if (itr->second->GSMState()!=GSM::Paging) return false; - itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true); - itr->second->remove(); - return true; -} - - - - -void TransactionTable::clearDeadEntries() -{ - // Caller should hold mLock. - TransactionMap::iterator itr = mTable.begin(); - while (itr!=mTable.end()) { - if (!itr->second->dead()) ++itr; - else { - LOG(DEBUG) << "erasing " << itr->first; - TransactionMap::iterator old = itr; - itr++; - innerRemove(old); - } - } -} - - - - -TransactionEntry* TransactionTable::find(const GSM::LogicalChannel *chan) -{ - LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")"; - - ScopedLock lock(mLock); - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - // This search assumes in order by transaction ID. - TransactionEntry *retVal = NULL; - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - const GSM::LogicalChannel* thisChan = itr->second->channel(); - if ((void*)thisChan != (void*)chan) continue; - retVal = itr->second; - } - //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; - return retVal; -} - - -TransactionEntry* TransactionTable::findBySACCH(const GSM::SACCHLogicalChannel *chan) -{ - LOG(DEBUG) << "by SACCH: " << *chan << " (" << chan << ")"; - - ScopedLock lock(mLock); - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - TransactionEntry *retVal = NULL; - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - const GSM::LogicalChannel* thisChan = itr->second->channel(); - if (thisChan->SACCH() != chan) continue; - retVal = itr->second; - } - return retVal; -} - - -TransactionEntry* TransactionTable::find(GSM::TypeAndOffset desc) -{ - LOG(DEBUG) << "by type and offset: " << desc; - - ScopedLock lock(mLock); - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - const GSM::LogicalChannel* thisChan = itr->second->channel(); - if (thisChan->typeAndOffset()!=desc) continue; - return itr->second; - } - //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")"; - return NULL; -} - - -TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, GSM::CallState state) -{ - LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state; - - ScopedLock lock(mLock); - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->GSMState() != state) continue; - if (itr->second->subscriber() != mobileID) continue; - return itr->second; - } - return NULL; -} - -bool TransactionTable::isBusy(const L3MobileIdentity& mobileID) -{ - LOG(DEBUG) << "id: " << mobileID << "?"; - - ScopedLock lock(mLock); - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->subscriber() != mobileID) continue; - GSM::L3CMServiceType service = itr->second->service(); - bool speech = - service==GSM::L3CMServiceType::MobileOriginatedCall || - service==GSM::L3CMServiceType::MobileTerminatedCall; - if (!speech) continue; - // OK, so we found a transaction for this call. - bool inCall = - itr->second->GSMState() == GSM::Paging || - itr->second->GSMState() == GSM::AnsweredPaging || - itr->second->GSMState() == GSM::MOCInitiated || - itr->second->GSMState() == GSM::MOCProceeding || - itr->second->GSMState() == GSM::MTCConfirmed || - itr->second->GSMState() == GSM::CallReceived || - itr->second->GSMState() == GSM::CallPresent || - itr->second->GSMState() == GSM::ConnectIndication || - itr->second->GSMState() == GSM::HandoverInbound || - itr->second->GSMState() == GSM::HandoverProgress || - itr->second->GSMState() == GSM::HandoverOutbound || - itr->second->GSMState() == GSM::Active; - if (inCall) return true; - } - return false; -} - - - - -TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, const char* callID) -{ - assert(callID); - LOG(DEBUG) << "by ID and call-ID: " << mobileID << ", call " << callID; - - string callIDString = string(callID); - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - ScopedLock lock(mLock); - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->mSIP.callID() != callIDString) continue; - if (itr->second->subscriber() != mobileID) continue; - return itr->second; - } - return NULL; -} - - -TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsigned transactionID) -{ - LOG(DEBUG) << "by ID and transaction-ID: " << mobileID << ", transaction " << transactionID; - - // Yes, it's linear time. - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - ScopedLock lock(mLock); - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->HandoverOtherBSTransactionID() != transactionID) continue; - if (itr->second->subscriber() != mobileID) continue; - return itr->second; - } - return NULL; -} - - -TransactionEntry* TransactionTable::answeredPaging(const L3MobileIdentity& mobileID) -{ - // Yes, it's linear time. - // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. - - ScopedLock lock(mLock); - - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->GSMState() != GSM::Paging) continue; - if (itr->second->subscriber() == mobileID) { - // Stop T3113 and change the state. - itr->second->GSMState(AnsweredPaging); - itr->second->resetTimer("3113"); - return itr->second; - } - } - return NULL; -} - - -GSM::LogicalChannel* TransactionTable::findChannel(const L3MobileIdentity& mobileID) -{ - // Yes, it's linear time. - // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. - - ScopedLock lock(mLock); - - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->subscriber() != mobileID) continue; - GSM::LogicalChannel* chan = itr->second->channel(); - if (!chan) continue; - if (chan->type() == FACCHType) return chan; - if (chan->type() == SDCCHType) return chan; - } - return NULL; -} - - -unsigned TransactionTable::countChan(const GSM::LogicalChannel* chan) -{ - ScopedLock lock(mLock); - clearDeadEntries(); - unsigned count = 0; - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->channel() == chan) count++; - } - return count; -} - - - -size_t TransactionTable::dump(ostream& os, bool showAll) const -{ - ScopedLock lock(mLock); - size_t sz = 0; - for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if ((!showAll) && itr->second->deadOrRemoved()) continue; - sz++; - os << *(itr->second) << endl; - } - return sz; -} - - -TransactionEntry* TransactionTable::findLongestCall() -{ - ScopedLock lock(mLock); - clearDeadEntries(); - long longTime = 0; - TransactionMap::iterator longCall = mTable.end(); - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (!(itr->second->channel())) continue; - if (itr->second->GSMState() != GSM::Active) continue; - long runTime = itr->second->stateAge(); - if (runTime > longTime) { - runTime = longTime; - longCall = itr; - } - } - if (longCall == mTable.end()) return NULL; - return longCall->second; -} - -/* linear, we should move the actual search into this structure */ -bool TransactionTable::RTPAvailable(short rtpPort) -{ - ScopedLock lock(mLock); - clearDeadEntries(); - bool avail = true; - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->mSIP.RTPPort() == rtpPort){ - avail = false; - break; - } - } - return avail; -} - -TransactionEntry* TransactionTable::inboundHandover(unsigned ref) -{ - // Yes, it's linear time. - // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. - - ScopedLock lock(mLock); - - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->GSMState() != GSM::HandoverInbound) continue; - if (itr->second->inboundReference() == ref) { - return itr->second; - } - } - return NULL; -} - -TransactionEntry* TransactionTable::inboundHandover(const GSM::LogicalChannel* chan) -{ - // Yes, it's linear time. - // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. - - ScopedLock lock(mLock); - - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->GSMState() != GSM::HandoverInbound) continue; - if (itr->second->channel() == chan) return itr->second; - } - return NULL; -} - - -bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage) -{ - - ScopedLock lock(mLock); - - // Since clearDeadEntries is also linear, do that here, too. - clearDeadEntries(); - - // Brute force search. - for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->subscriber() != mobileID) continue; - if (itr->second->message() == wMessage) return true; - } - return false; - -} - - - -#if 0 -bool TransactionTable::outboundReferenceUsed(unsigned ref) -{ - // Called is expected to hold mLock. - for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - if (itr->second->deadOrRemoved()) continue; - if (itr->second->GSMState() != GSM::HandoverOutbound) continue; - if (itr->second->handoverReference() == ref) return true; - } - return false; -} - - -unsigned TransactionTable::generateHandoverReference(TransactionEntry *transaction) -{ - ScopedLock lock(mLock); - clearDeadEntries(); - unsigned ref = random() % 256; - while (outboundReferenceUsed(ref)) { ref = (ref+1) % 256; } - transaction->handoverReference(ref); - return ref; -} -#endif - - -// vim: ts=4 sw=4 diff --git a/Control/TransactionTable.h b/Control/TransactionTable.h deleted file mode 100644 index cac3f91..0000000 --- a/Control/TransactionTable.h +++ /dev/null @@ -1,622 +0,0 @@ -/**@file Declarations for TransactionTable and related classes. */ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - - -#ifndef TRANSACTIONTABLE_H -#define TRANSACTIONTABLE_H - - -#include -#include - -#include -#include -#include -#include - - -#include -#include -#include -#include -#include - -namespace GSM { -class LogicalChnanel; -class SACCHLogicalChannel; -} - - - -struct sqlite3; - - -/**@namespace Control This namepace is for use by the control layer. */ -namespace Control { - -typedef std::map TimerTable; - - - - -/** - A TransactionEntry object is used to maintain the state of a transaction - as it moves from channel to channel. - The object itself is not thread safe. -*/ -class TransactionEntry { - - private: - - mutable Mutex mLock; ///< thread-safe control, shared from gTransactionTable - - /**@name Stable variables, fixed in the constructor or written only once. */ - //@{ - unsigned mID; ///< the internal transaction ID, assigned by a TransactionTable - - GSM::L3MobileIdentity mSubscriber; ///< some kind of subscriber ID, preferably IMSI - GSM::L3CMServiceType mService; ///< the associated service type - unsigned mL3TI; ///< the L3 short transaction ID, the version we *send* to the MS - - GSM::L3CalledPartyBCDNumber mCalled; ///< the associated called party number, if known - GSM::L3CallingPartyBCDNumber mCalling; ///< the associated calling party number, if known - - // TODO -- This should be expaned to deal with long messages. - //char mMessage[522]; ///< text messaging payload - std::string mMessage; ///< text message payload - std::string mContentType; ///< text message payload content type - //@} - - SIP::SIPEngine mSIP; ///< the SIP IETF RFC-3621 protocol engine - mutable SIP::SIPState mPrevSIPState; ///< previous SIP state, prior to most recent transactions - GSM::CallState mGSMState; ///< the GSM/ISDN/Q.931 call state - Timeval mStateTimer; ///< timestamp of last state change. - TimerTable mTimers; ///< table of Z100-type state timers - - /**@name Handover parameters */ - //@{ - /**@name Inbound */ - //@{ - struct ::sockaddr_in mInboundPeer; ///< other BTS in inbound handover - unsigned mInboundReference; ///< handover reference - float mInboundRSSI; ///< access burst RSSI in dB wrt full scale - float mInboundTimingError; ///< access burst timing error in symbol periods - unsigned mCSeq; - string mCallID; - unsigned mCodec; - string mRTPState; - string mSessionID; - string mSessionVersion; - string mRemoteUsername; - string mRemoteDomain; - string mSIPDisplayname; - string mSIPUsername; - string mFromTag; - string mFromUsername; - string mFromIP; - string mToTag; - string mToUsername; - string mToIP; - string mCallIP; - short mRTPRemPort; - string mRTPRemIP; - short mRmtPort; - string mRmtIP; - string mSRIMSI; - string mSRCALLID; - - //@} - /**@name Outbound */ - //@{ - struct ::sockaddr_in mOutboundPeer; ///< other BTS in outbound handover - GSM::L3CellDescription mOutboundCell; - GSM::L3ChannelDescription2 mOutboundChannel; - GSM::L3HandoverReference mOutboundReference; - GSM::L3PowerCommandAndAccessType mOutboundPowerCmd; - GSM::L3SynchronizationIndication mOutboundSynch; - //@} - //@} - - unsigned mNumSQLTries; ///< number of SQL tries for DB operations - - GSM::LogicalChannel *mChannel; ///< current channel of the transaction - - bool mTerminationRequested; - - unsigned mHandoverOtherBSTransactionID; - - volatile bool mRemoved; ///< true if ready for removal - - public: - - /** This form is used for MTC or MT-SMS with TI generated by the network. */ - TransactionEntry(const char* proxy, - const GSM::L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const GSM::L3CMServiceType& wService, - const GSM::L3CallingPartyBCDNumber& wCalling, - GSM::CallState wState = GSM::NullState, - const char *wMessage = NULL); - - /** This form is used for MOC, setting mGSMState to MOCInitiated. */ - TransactionEntry(const char* proxy, - const GSM::L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const GSM::L3CMServiceType& wService, - unsigned wL3TI, - const GSM::L3CalledPartyBCDNumber& wCalled); - - /** Form for MO-SMS; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */ - TransactionEntry(const char* proxy, - const GSM::L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel, - const GSM::L3CalledPartyBCDNumber& wCalled, - const char* wMessage); - - /** Form for MO-SMS with a parallel call; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */ - TransactionEntry(const char* proxy, - const GSM::L3MobileIdentity& wSubscriber, - GSM::LogicalChannel* wChannel); - - /** Form used for handover requests; argument is taken from the message string. */ - TransactionEntry(const struct ::sockaddr_in* peer, - unsigned wHandoverReference, - SimpleKeyValue ¶ms, - const char *proxy, - GSM::LogicalChannel* wChannel, - unsigned otherTransactionID); - - - /** Delete the database entry upon destruction. */ - ~TransactionEntry(); - - /**@name Accessors. */ - //@{ - unsigned L3TI() const; - void L3TI(unsigned wL3TI); - - const GSM::LogicalChannel* channel() const; - GSM::LogicalChannel* channel(); - - void channel(GSM::LogicalChannel* wChannel); - - const GSM::L3MobileIdentity& subscriber() const { return mSubscriber; } - - const GSM::L3CMServiceType& service() const { return mService; } - - const GSM::L3CalledPartyBCDNumber& called() const { return mCalled; } - void called(const GSM::L3CalledPartyBCDNumber&); - - const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; } - - const char* message() const { return mMessage.c_str(); } - void message(const char *wMessage, size_t length); - const char* messageType() const { return mContentType.c_str(); } - void messageType(const char *wContentType); - - unsigned ID() const { return mID; } - - GSM::CallState GSMState() const; - void GSMState(GSM::CallState wState); - - /** Inbound reference set in the constructor, no lock needed. */ - // FIXME -- These should probably all the const references. - unsigned inboundReference() const { return mInboundReference; } - unsigned Codec() { return mCodec; } - unsigned CSeq() { return mCSeq; } - string CallID() { return mCallID; } - string RemoteUsername() { return mRemoteUsername; } - string RemoteDomain() { return mRemoteDomain; } - string SIPUsername() { return mSIPUsername; } - string SIPDisplayname() { return mSIPDisplayname; } - string FromTag() { return mFromTag; } - string FromUsername() { return mFromUsername; } - string FromIP() { return mFromIP; } - string ToTag() { return mToTag; } - string ToUsername() { return mToUsername; } - string ToIP() { return mToIP; } - string CallIP() { return mCallIP; } - short RTPRemPort() { return mRTPRemPort; } - string RTPRemIP() { return mRTPRemIP; } - string RTPState() { return mRTPState; } - string SessionID() { return mSessionID; } - string SessionVersion() { return mSessionVersion; } - short RmtPort() { return mRmtPort; } - string RmtIP() { return mRmtIP; } - string SRIMSI() { return mSRIMSI; } - string SRCALLID() { return mSRCALLID; } - unsigned HandoverOtherBSTransactionID() { return mHandoverOtherBSTransactionID; } - - GSM::L3HandoverReference outboundReference() const { ScopedLock lock(mLock); return mOutboundReference; } - GSM::L3CellDescription outboundCell() const { ScopedLock lock(mLock); return mOutboundCell; } - GSM::L3ChannelDescription2 outboundChannel() const { ScopedLock lock(mLock); return mOutboundChannel; } - GSM::L3PowerCommandAndAccessType outboundPowerCmd() const { ScopedLock lock(mLock); return mOutboundPowerCmd; } - GSM::L3SynchronizationIndication outboundSynch() const { ScopedLock lock(mLock); return mOutboundSynch; } - - /** Set the outbound handover parameters and set the state to HandoverOutbound. */ - void setOutboundHandover( - const GSM::L3HandoverReference& reference, - const GSM::L3CellDescription& cell, - const GSM::L3ChannelDescription2& chan, - const GSM::L3PowerCommandAndAccessType& pwrCmd, - const GSM::L3SynchronizationIndication& synch - ); - - /** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */ - void setInboundHandover( - float wRSSI, - float wTimingError, - double wTimestamp - ); - - // This is thread-safe because mInboundPeer is only modified in the constructor. - const struct ::sockaddr_in* inboundPeer() const { return &mInboundPeer; } - - float inboundTimingError() const { ScopedLock lock(mLock); return mInboundTimingError; } - - //@} - - - /** Initiate the termination process. */ - void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; } - - bool terminationRequested(); - - /**@name SIP-side operations */ - //@{ - - SIP::SIPState SIPState() { ScopedLock lock(mLock); return mSIP.state(); } - - bool SIPFinished() { ScopedLock lock(mLock); return mSIP.finished(); } - - bool instigator() { ScopedLock lock(mLock); return mSIP.instigator(); } - - SIP::SIPState MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec); - SIP::SIPState MOCResendINVITE(); - SIP::SIPState MOCCheckForOK(); - SIP::SIPState MOCSendACK(); - void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); } - - SIP::SIPState MTCSendTrying(); - SIP::SIPState MTCSendRinging(); - SIP::SIPState MTCCheckForACK(); - SIP::SIPState MTCCheckForCancel(); - SIP::SIPState MTCSendOK(short rtpPort, unsigned codec); - void MTCInitRTP() { ScopedLock lock(mLock); mSIP.MTCInitRTP(); } - - SIP::SIPState MODSendBYE(); - SIP::SIPState MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel); - SIP::SIPState MODSendCANCEL(); - SIP::SIPState MODResendBYE(); - SIP::SIPState MODResendCANCEL(); - SIP::SIPState MODResendERROR(bool cancel); - SIP::SIPState MODWaitForBYEOK(); - SIP::SIPState MODWaitForCANCELOK(); - SIP::SIPState MODWaitForERRORACK(bool cancel); - SIP::SIPState MODWaitFor487(); - SIP::SIPState MODWaitForResponse(vector *validResponses); - - SIP::SIPState MTDCheckBYE(); - SIP::SIPState MTDSendBYEOK(); - SIP::SIPState MTDSendCANCELOK(); - - // TODO: Remove contentType from here and use the setter above. - SIP::SIPState MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType); - SIP::SIPState MOSMSWaitForSubmit(); - - SIP::SIPState MTSMSSendOK(); - - SIP::SIPState inboundHandoverSendINVITE(unsigned RTPPort) - { ScopedLock lock(mLock); return mSIP.inboundHandoverSendINVITE(this, RTPPort); } - SIP::SIPState inboundHandoverCheckForOK() - { ScopedLock lock(mLock); return mSIP.inboundHandoverCheckForOK(&mLock); } - SIP::SIPState inboundHandoverSendACK() - { ScopedLock lock(mLock); return mSIP.inboundHandoverSendACK(); } - - bool sendINFOAndWaitForOK(unsigned info); - - void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); } - int rxFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.rxFrame(frame); } - bool startDTMF(char key) { ScopedLock lock(mLock); return mSIP.startDTMF(key); } - void stopDTMF() { ScopedLock lock(mLock); mSIP.stopDTMF(); } - - void SIPUser(const std::string& IMSI) { ScopedLock lock(mLock); SIPUser(IMSI.c_str()); } - void SIPUser(const char* IMSI); - void SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost); - - const std::string SIPCallID() const { ScopedLock lock(mLock); return mSIP.callID(); } - - // These are called by SIPInterface. - void saveINVITE(const osip_message_t* invite, bool local) - { ScopedLock lock(mLock); mSIP.saveINVITE(invite,local); } - void saveBYE(const osip_message_t* bye, bool local) - { ScopedLock lock(mLock); mSIP.saveBYE(bye,local); } - - bool sameINVITE(osip_message_t * msg) - { ScopedLock lock(mLock); return mSIP.sameINVITE(msg); } - - //@} - - unsigned stateAge() const { ScopedLock lock(mLock); return mStateTimer.elapsed(); } - - /**@name Timer access. */ - //@{ - - bool timerExpired(const char* name) const; - - void setTimer(const char* name); - - void setTimer(const char* name, long newLimit); - - void resetTimer(const char* name); - - /** Return true if any Q.931 timer is expired. */ - bool anyTimerExpired() const; - - /** Reset all Q.931 timers. */ - void resetTimers(); - - //@} - - /** Return true if clearing is in progress in the GSM side. */ - bool clearingGSM() const; - - /** Retrns true if the transaction is "dead". */ - bool dead() const; - - /** Returns true if dead, or if removal already requested. */ - bool deadOrRemoved() const; - - /** Dump information as text for debugging. */ - void text(std::ostream&) const; - - /** Genrate an encoded string for handovers. */ - std::string handoverString() const; - - private: - - friend class TransactionTable; - - /** Create L3 timers from GSM and Q.931 (network side) */ - void initTimers(); - - /** Set up a new entry in gTransactionTable's sqlite3 database. */ - void insertIntoDatabase(); - - /** Run a database query. */ - void runQuery(const char* query) const; - - /** Echo latest SIPSTATE to the database. */ - SIP::SIPState echoSIPState(SIP::SIPState state) const; - - /** Tag for removal. */ - void remove() { mRemoved=true; mStateTimer.now(); } - - /** Removal status. */ - bool removed() { return mRemoved; } -}; - - -std::ostream& operator<<(std::ostream& os, const TransactionEntry&); - - -/** A map of transactions keyed by ID. */ -class TransactionMap : public std::map {}; - -/** - A table for tracking the states of active transactions. -*/ -class TransactionTable { - - private: - - sqlite3 *mDB; ///< database connection - - TransactionMap mTable; - mutable Mutex mLock; - unsigned mIDCounter; - - public: - - /** - Initialize a transaction table. - @param path Path fto sqlite3 database file. - */ - void init(const char* path); - - ~TransactionTable(); - - // TransactionTable does not need a destructor. - - /** - Return a new ID for use in the table. - */ - unsigned newID(); - - /** - Insert a new entry into the table; deleted by the table later. - @param value The entry to insert into the table; will be deleted by the table later. - */ - void add(TransactionEntry* value); - - /** - Find an entry and return a pointer into the table. - @param wID The transaction ID to search - @return NULL if ID is not found or was dead - */ - TransactionEntry* find(unsigned wID); - - /** - Find the longest-running non-SOS call. - @return NULL if there are no calls or if all are SOS. - */ - TransactionEntry* findLongestCall(); - - /** - Return the availability of this particular RTP port - @return True if Port is available, False otherwise - */ - bool RTPAvailable(short rtpPort); - - /** - Fand an entry by its handover reference. - @param ref The 8-bit handover reference. - @return NULL if ID is not found or was dead - */ - TransactionEntry* inboundHandover(unsigned ref); - - /** - Remove an entry from the table and from gSIPMessageMap. - @param wID The transaction ID to search. - @return True if the ID was really in the table and deleted. - */ - bool remove(unsigned wID); - - bool remove(TransactionEntry* transaction) { return remove(transaction->ID()); } - - /** - Remove an entry from the table and from gSIPMessageMap, - if it is in the Paging state. - @param wID The transaction ID to search. - @return True if the ID was really in the table and deleted. - */ - bool removePaging(unsigned wID); - - - /** - Find an entry by its channel pointer; returns first entry found. - Also clears dead entries during search. - @param chan The channel pointer. - @return pointer to entry or NULL if no active match - */ - TransactionEntry* find(const GSM::LogicalChannel *chan); - - /** Find a transaction in the HandoverInbound state on the given channel. */ - TransactionEntry* inboundHandover(const GSM::LogicalChannel *chan); - - /** - Find an entry by its SACCH channel pointer; returns first entry found. - Also clears dead entries during search. - @param chan The channel pointer. - @return pointer to entry or NULL if no active match - */ - TransactionEntry* findBySACCH(const GSM::SACCHLogicalChannel *chan); - - /** - Find an entry by its channel type and offset. - Also clears dead entries during search. - @param chan The channel pointer to the first record found. - @return pointer to entry or NULL if no active match - */ - TransactionEntry* find(GSM::TypeAndOffset chanDesc); - - /** - Find an entry in the given state by its mobile ID. - Also clears dead entries during search. - @param mobileID The mobile to search for. - @return pointer to entry or NULL if no match - */ - TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, GSM::CallState state); - - /** Return true if there is an ongoing call for this user. */ - bool isBusy(const GSM::L3MobileIdentity& mobileID); - - - /** Find by subscriber and SIP call ID. */ - TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, const char* callID); - - /** Find by subscriber and handover other BS transaction ID. */ - TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, unsigned transactionID); - - /** Check for duplicated SMS delivery attempts. */ - bool duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage); - - /** - Find an entry in the Paging state by its mobile ID, change state to AnsweredPaging and reset T3113. - Also clears dead entries during search. - @param mobileID The mobile to search for. - @return pointer to entry or NULL if no match - */ - TransactionEntry* answeredPaging(const GSM::L3MobileIdentity& mobileID); - - - /** - Find the channel, if any, used for current transactions by this mobile ID. - @param mobileID The target mobile subscriber. - @return pointer to TCH/FACCH, SDCCH or NULL. - */ - GSM::LogicalChannel* findChannel(const GSM::L3MobileIdentity& mobileID); - - /** Count the number of transactions using a particular channel. */ - unsigned countChan(const GSM::LogicalChannel*); - - size_t size() { ScopedLock lock(mLock); return mTable.size(); } - - size_t dump(std::ostream& os, bool showAll=false) const; - - /** Generate a unique handover reference. */ - //unsigned generateInboundHandoverReference(TransactionEntry* transaction); - - private: - - friend class TransactionEntry; - - /** Accessor to database connection. */ - sqlite3* DB() { return mDB; } - - /** - Remove "dead" entries from the table. - A "dead" entry is a transaction that is no longer active. - The caller should hold mLock. - */ - void clearDeadEntries(); - - /** - Remove and entry from the table and from gSIPInterface. - */ - void innerRemove(TransactionMap::iterator); - - - /** Check to see if a given outbound handover reference is in use. */ - //bool outboundReferenceUsed(unsigned ref); - -}; - - - - - -} //Control - - - -/**@addtogroup Globals */ -//@{ -/** A single global transaction table in the global namespace. */ -extern Control::TransactionTable gTransactionTable; -//@} - - - -#endif - -// vim: ts=4 sw=4 - - diff --git a/Control/ladders.awk b/Control/ladders.awk new file mode 100644 index 0000000..d11e051 --- /dev/null +++ b/Control/ladders.awk @@ -0,0 +1,617 @@ +#!/bin/sh + +# Draw ladder diagrams. Used for the MOC, MTC, etc diagrams + +# First, copy the program part of this file itself: +cp $0 $0.bk +sed -n "1,/^'.*EOF/p" $0 + + +awk ' +BEGIN { FS = "|"; + spaces = " "; + dashes = "----------------------------------------------------------"; + split("0|40|30|30",WIDTH) +} +# Center arg in a width field, and add arrows if necessary. +function debug (a,b,c,e,f) { if (0) print a,b,c,d,e,f } +function fixup(arg,width, filllen1) { + leftarrow = (arg ~ //); + double = (arg ~ /<<|>>/); + filler = spaces; + if (leftarrow || rightarrow) { + filler = dashes; + gsub(/(^[<-][<-]*)|([->]*[->]$)/,"",arg) + #gsub(/(^<-*)|(^[->]*)|([->]*$)/,"",arg) + } + gsub(/^ *| *$/,"",arg) + width -= leftarrow + rightarrow + double + filllen1 = (width - length(arg))/2 + debug("fixup(",arg,width,filllen1,")") + arg = substr(filler,1,filllen1) arg + arg = arg substr(filler,1,width - length(arg)) + if (leftarrow) { if (double) {sub(/^/,"<<",arg)} else {sub(/^/,"<",arg)} } + if (rightarrow) { if (double) {sub(/$/,">>",arg)} else {sub(/$/,">",arg)} } + #return sprintf("%" width "." width "%s",arg); + return arg +} +/^#/ || /^[^|]*$/ { print;next } +/^WIDTH/ { + print # Preserve this command in the output. + sub("WIDTH *","") + debug("BEFORE, WIDTH[1]=" WIDTH[1]) + split($0,WIDTH) + debug("AFTER, WIDTH[1]=" WIDTH[1]) + debug("$0=" $0) + next +} +{ + spaces = " " + # Note: $1 is the stuff before the first |, which we just ignore. + w[1] = WIDTH[1]; w[2] = WIDTH[2]; w[3] = WIDTH[3]; w[4] = WIDTH[4] + arg0 = fixup($1,WIDTH[1]) + if ($2 ~ />>|<>|<= 5) { + printf("%" w[1] "s|%s|%s|%s|%s\n",$1,fixup($2,w[2]),fixup($3,w[3]),fixup($4,w[4]),$5) + #arg1 = (length($2)) ? fixup($2,40) : "" + #arg2 = (length($3)) ? fixup($3,30) : "" + #sub(/^[ \t]*/," ",$4) + #printf("%s|%40.40s|%30.30s|%s\n",$1, arg1,arg2,$4) + } +} +END { print "EOF" } +' << \EOF +=================================================================================== +See GSM 4.08 (or new 23.108) sec 7.3 +Handset OpenBTS SIP Switch +WIDTH 0|40|30 +========= MOC Mobile-Originated Call +AccessGrantResponsder decodes the chtype request, and if a voice call: + gets a channel (TCH or SDCCH), opens the LogicalChannel, LAPDm waits for a SABM establish, then sends ESTABLISH primitive to L3. +|---------ChannelRequest(RACH)---------->| | +|<-------ImmediateAssignment(AGCH)-------| | AccessGrantResponder +| allocate Control.VEA ? TCH : SDCCH | | decodeChannelNeeded +|CMServiceRequest(veryEarly?FACCH:SDCCH)>| | DCCHDispatcher,DCCHDIspatchMessage,DCCHDispatchMM,CMServiceResponder calls MOCStarter +|<------------IdentityRequest------------| | only if TMSI unrecognized +|-----------IdentityResponse------------>| | +| Requests MM Connection | | MS CCState=MMPending, but that doesnt matter to us. +== MOCStarter +|<-------Authentication Procedure------->| | resolveIMSI +| Allocate TCH,FACH | | MOCStarter +|<------------CMServiceAccept------------| | MOCStarter +| Establishes MM Connection | | MS CCState=MMPending, but that doesnt matter to us. +|-------------L3Setup(SDCH)------------->| | MOCStarter +| new TranscationEntry | allocateRTPPorts | MOCStarter +| GSMState=MOCInitiated | SIPState=NullState | constructors +|<----------CC-CALL PROCEEDING-----------|-----------INVITE------------>| MOCStarter,MOCSendINVITE +|<-----------ChannelModeModify-----------| SIPState=Starting | MOCStarter if veryEarly +|---------ChannelModeModifyAck---------->| | MOCStarter if veryEarly +| call assignTCHF | | (used only for EA; for VEA we assigned TCH in AccessGrantResponder) +|<----------L3AssignmentCommand----------| | assignTCHF, repeated until answered +assignTCHF handles FACCH and waits for call to terminate while DCCHDispatchter handles DCCH. Both call callManagementDispatchGSM + timeout on DCCH. +|-------AssignmentComplete(FACCH)------->| | DCCHDispatchRR,AssignmentCompleteHandler, calls MOCController or MTCController +| call MOC or MTCController | | AssignmentCompleteHandler +== MOCController +while GSMState != CallReceived { switch (SIPState) + case SIPState==MOCBusy: +| |<----------486 Busy-----------| MOCCheckForOk +| | SIPState = Busy | MOCCheckForOK +| |-------------ACK------------->| MOCController,MOCSendAck +|<-------------L3Disconnect--------------| | forceGSMClearing +|<-----------L3ReleaseComplete-----------| | forceGSMClearing +|<-----------L3ChannelRelease------------| | forceGSMClearing +| GSMState=NullState | | forceGSMClearing + case SIPState==Fail: +| |<------------many-------------| MOCCheckForOK +| | SIPState=Fail | MOCCheckForOK +| forceGSMClearing | forceSIPClearing | abortAndRemoveCall,abortCall + case SIPState==Ringing +| |<---------180 Ringing---------| MOCCheckForOK +| | SIPState=Ringing | MOCCheckForOK +| GSMState = CallReceived | MOCController | +|<---L3Alerting GSMState=CallReceived----| | MOCController + Note: Call Waiting notification in Alerting message. + case SIPState==Active +| |<-----------200 OK------------| MOCCheckForOK +| | SIPState=Active | MOCCheckForOK +| GSMState=CallReceived | | MOCController + case SIPState==Proceeding +| |<---------100 Trying----------| MOCCheckForOK +| |<--------OR 181 Trying--------| MOCCheckForOK +| |<--------OR 182 Queued--------| MOCCheckForOK +| |<---OR 183 Session Progress---| MOCCheckForOK +| | SIPState=Proceeding | MOCCheckForOK +|<--------------L3Progress---------------| | MOCController + case SIPState==Timeout +| |--------resend INVITE-------->| MOCResendINVITE +| | continue waiting | +} // end while + +// Wait for SIP session to start +while SIPState != Active { switch (SIPState) + case SIPState==Busy: +| |<----------600 busy-----------| MOCCheckForOK +| |-------------ACK------------->| MOCCheckForOK,MOCSendACK +| abortAndRemoveCall | | + case SIPState==Fail: +| |<------------many-------------| MOCCheckForOK +| |-------------ACK------------->| MOCCheckForOK,MOCSendACK +| abortAndRemoveCall | | + case SIPState==Proceeding: +| |<---------100 Trying----------| MOCCheckForOK +| |<--181 CallIsBeingForwarded---| MOCCheckForOK +| |<--182 CallIsBeingForwarded---| MOCCheckForOK +| |<-----183 SessionProgress-----| MOCCheckForOK +| continue | | MOCController + case SIPState==Timeout: +| break, let MS deal with it | | MOCController + case SIPState==Timeout: +| break, success | | MOCController +} // end while + +|<---------------L3Connect---------------| | MOCController +| GSMState=ConnectIndication | | MOCController +| | init rtp | MOCInitRTP +| |-------------ACK------------->| MOCSendACK +while GSMState != Active { switch (GSMState)) +|-----L3ConnectAcknowledge(either)------>| | MOCController,updateGSMSignalling,callManagementDispatchGSM +| GSMState=Active | | MOCController,callManagementDispatchGSM +} // end while +| call callManagementLoop | | MOCController + + +================ MTC Procedure +| |<-----------INVITE------------| checkINVITE called from drive +| new TransactionEntry | | checkINVITE +| if (callWaiting) goto L3Setup | | +| pager.addID | | checkINVITE +| GSMState=Paging | | Pager::addID +|<-------------PagingRequst--------------| | pageAll +|---------ChannelRequest(RACH)---------->| | +|<-------ImmediateAssignment(AGCH)-------| | AccessGrantResponder +|----PagingResponse(SDCCH or FACCH)----->| | DCCHDispatchRR, PagingResponseHandler +| call MTCStarter | | PagingResponseHandler +== MTCStarter +| if (!veryEarly) alloc TCH | | MTCStarter if (!veryEarly) +|<-------optional authentication-------->| | not in OpenBTS +|<----------------L3Setup----------------| | MTCStarter +| GSMState=CallPresent | | MTCStarter +| |---------100 Trying---------->| MTCSendTrying +| | SIPState = Proceeding | +|-------------CallConfirmed------------->| | updateGSMSignalling,callManagementDispatchGSM +| GSMState=MTCallConfirmed | | MTCStarter + if (veryEarly) { +|<-----------ChannelModeModify-----------| | MTCStarter +|---------ChannelModeModifyAck---------->| | MTCStarter +| call MTCController | | MTCStarter + } else { +| call assignTCHF | | MTCStarter +|<----------L3AssignmentCommand----------| | assignTCHF, repeated until answered +|-------AssignmentComplete(DCCH)-------->| | DCCHDispatchRR,AssignmentCompleteHandler, calls MOCController or MTCController +| call MOC or MTCController | | AssignmentCompleteHandler + } +== MTCController (early assignment) +|--------------L3Alerting--------------->| | callManagementDispatchGSM from updateGSMSignalling from MTCController +| GSMState=CallReceived | | +| |-----------Ringing----------->| MTCSendRinging +|---------------L3Connect--------------->| | callManagementDispatchGSM from updateGSMSignalling from MTCController +| old:GSMState=Active | | +| | allocate rtpports | MTCController +| |-------200 OK with SDP------->| MTCController,MTCSendOK,sip_okay_sdp +| | SIPState=Connecting | +| |<-------------ACK-------------| MTCCheckForACK +| | SIPState=Active | ? +|<-------------L3ConnectAck--------------| | MTCController +| GSMState=Active | | +| call callManagementLoop | | MTCController + + +======= updateGSMSignalling, called many places in MOCController. What messages occur here? +======= callManagementDispatchGSM, called from updateGSMSignalling for DCCH and assignTCH for FACCH messages. +|--------L3CallConfirmed(either)-------->| | callManagementDispatchGSM, is this used? +| GSMState=MTConfirmed | | callManagementDispatchGSM is this used? +|----------L3Alerting(either)----------->| | callManagementDispatchGSM is this used? +| GSMState=CallReceived | | callManagementDispatchGSM +|--------------many others-------------->| | callManagementDispatchGSM +|-----------L3Connect(either)----------->| | MOCController,callManagementDispatchGSM,GSMState=Active I think this is impossible, a bug in the code. + +================ Disconnect Procedure +|----------L3Disconnect(DCCH)----------->| | +TODO + +================ Authentication Procedure +|<--------Identity Request(SDCCH)--------| | resolveIMSI +|-------Identity Response(SDCCH)-------->| | resolveIMSI +|<----------Auth Req. (SDCCH)?-----------| | We skip, see MOCStarter +|----------Auth Resp (SDCCH)?----------->| | We skip, see MOCStarter +|<------RR-Cipher Mode Cmd (SDCCH)-------| | We skip, see MOCStarter +|----RR-Cipher Mode Complete (SDCCH)---->| | We skip, see MOCStarter +|<---MM-TMSI Reallocation Cmd (SDCHH)----| | We dont use +|-MM-TMSI Reallocation Complete (SDCHH)->| | We dont use + + +============ MT-SMS +| |<-----------MESSAGE-----------| checkINVITE + +========= Stuff from Samir + +|<-------RR-Assignment Cmd (SDCCH)-------| From Samir | +|<----------FACCH, L2 establish----------| From Samir | +FACCH, RR-Assignment complete-> +|<----------------CONNECT----------------|<-------Status: 200 OK--------| +|-------------CONNECT ACK.-------------->| | +|<-------------RTP traffic-------------->|<--------GSM traffic--------->| + +======== Call Hold 3GPP 4.83 2 and 4.80 sec 9.3. SIP procedures in 24.228 10.1 +== MO-Hold +| Stop media flow | | +|-----------------Hold------------------>| | +| |--------UPDATE(HOLD)--------->| +| | Stop media flow | +| |<-------------OK--------------| +|<------------HoldAcknowledge------------| | +|<--------------HoldReject---------------| | +== Notification of held party +| |<--------UPDATE(HOLD)---------| +|<------------Facility(Hold)-------------|-------------OK?------------->| +== Retrieval of Held call (using the same TI of original held call) +|---------------Retrieve---------------->| | +| |-------UPDATE(Resume)-------->| +| | Resume media flow | +| |<-------------OK--------------| +|<----------RetrieveAcknowledge----------| | +| Resume media flow | | +|<------------RetrieveReject-------------| | +== Notification of held party +| |<--------UPDATE(HOLD)---------| +|<------------Facility(Hold)-------------| | +== Switch +|-------------Hold(TI A-B)-------------->| ... | +|-----------Retrieve(TI A-C)------------>| | +|<--------HoldAcknowledge(TI A-B)--------| | +|<------RetreiveAcknowledge(TI A-C)------| | + +======== Call Waiting 3GPP 4.83 1 and messages in 4.80 sec 9.3: +Note: Can only have one held call at a time, so if call waiting arrives when there is a call held, +the MS must release the held call before accepting the new call. +|<----------------L3Setup----------------| | +| Signal Information IE #7 | | +|-------------CallConfirmed------------->| | +| cause#17(busy) | | +|--------------L3Alerting--------------->| | +== Notification of held party +|----------------L3Setup---------------->| | +|<------------CallProceeding-------------| | +| | | +== If the MS wants to put existing call on hold: +|-------------Hold(TI A-B)-------------->| | +|<--------HoldAcknowledge(TI A-B)--------| | +|-----------L3Connect(TI C-B)----------->| | +|<---------L3ConnectAck(TI C-B)----------| | +== If the MS wants to drop current call and accept waiting call: +Normal procedure to drop, then normal procedure to accept. +== Regsiter with network for Supplementary Service (eg Call Waiting) 4.83 1.4 +|---------------Register---------------->| | +|<------------ReleaseComplete------------| | + +======== Codec and Media flow negotiaion 24.228 10.3 +| Determine UE1 codec set | | +| |----------re-INVITE---------->| +| |Determine subset of code suppo| +| |<----183 Session Progress-----| +| Determine Initial codec | | +| |------------PRACK------------>| +| |<-------------OK--------------| +| stop sending with old codec |reserve resource for new codec| +| |-----------UPDATE------------>| +| |<-------------OK--------------| +| |<-----------Ringing-----------| +| |------PRACK(optional)??------>| +| |<------200 OK (to PRACK)------| +| | stop sending with old codec | +| | OK | +| start sending with new codec | | +| |-------------ACK------------->| +| | start sending with new codec | + +=== HANDOVER: pre-existing peer based. This is based on Dougs diagrams. +MS BS1 BS2 +|---------RR MeasurementReport---------->| | +| |--------REQ HANDOVER--------->| BS2:PeerInterface::processHandoverRequest +| |<--------RSP HANDOVER---------| BS1:PeerInterface::processHandoverResponse +|<----------RR HandoverCommand-----------| | +|-------------------------RR HandoverAccess-------------------------->>| +|<<-----------------------RR PhysicalInformation------------------------| +|-------------------------RR HandoverComplete------------------------->>| +| | | --------- re-INVITE -------> +| | | <-------- OK ------------ +| | | --------- ACK ----------> +| |----IND HANDOVER_COMPLETE---->| BS2:PeerInterface::processHandoverComplete +| |<----ACK HANDOVER_COMPLETE----| + +=== HANDOVER: post-L3-Rewrite Peer Based Handover. +WIDTH 3|30|35 +MS BS1 BS2 +A |----RR MeasurementReport----->| | + | |-----------REQ HANDOVER----------->| BS2:processHandoverRequest + | | (INFO with params) | Note: transfers both BSS->BSS and MSC->MSC info. + | | (SIP REFER) | +B | |<-----------RSP HANDOVER-----------| BS1:processHandoverResponse + | | (200 OK with L3HandoverCommand) | + | | (or 4xx failure) | + |<-----RR HandoverCommand------| | + |-----------------------RR HandoverAccess----------------------->>| + |<<---------------------RR PhysicalInformation---------------------| + |----------------------RR HandoverComplete----------------------->>| +C | |<-----IND HANDOVER_COMPLETE--------| BS1:processHandoverComplete +D | |-------ACK HANDOVER_COMPLETE------>| + | | (SIP REFER) | + | | | ----- re-INVITE -----> + | | | <------- OK ---------- + | | | -------- ACK --------> + +=== HANDOVER using MAP. +HANDOVER using MAP from BSC perspective +See 08.08 3.1.5 External Handover and section 6 Figure 4,5,6,13,16 +WIDTH 0|30|25|30 +MS BS1 BS2 MSC +|----L3 MeasurementReport----->| | | +| |-------------------HandoverRequired------------------->>| +| |(with cellid+transparent container)| +| | |<-------HandoverRequest-------| +| | | (with transparent container) | +| | |-----HandoverRequestAck------>| +| | | (with L3HandoverCommand) | +| |<<-------------------HandoverCommand--------------------| +| |(with L3HandoverCommand) | +|<------L3HandoverCommand------| | | +|------------------RR HandoverAccess------------------->>| | +| | |-------HandoverDetect-------->| +|<<----------------L3 PhysicalInformation----------------| | +|-----------------L3 HandoverComplete------------------>>| | +| | |------HandoverComplete------->| +| |<<---------------------ClearCommand---------------------| +| |--------------------ClearComplete--------------------->>| +FAILURE due to BS2 congestion: +| | |<-------HandoverRequest-------| +| | |-------HandoverFailure------->| +FAILURE to handover, interference: +| |<<-------------------HandoverCommand--------------------| +| |-------------------HandoverFailure-------------------->>| +Support for autonomous handover BS1->BS2, subsequently notify MSC: +| | |------HandoverPerformed------>| + + +WIDTH 0|30|30|30 +HANDOVER using MAP from MSC perspective +Initial MSC to MSC Handover. This picture omits the MS messages. 3GPP 23.009 7.1 +BS1 MSC-A MSC-B BS2 VLR-B +|-----BSSMAP-HO-Required------>| | | +| |---MAP-Prepare-HO-Required--->| | +| | (includes HO-Required msg) | | +| | |---MAP-AllocateHandoverNumberRequest --> +| | |------BSSMAP-HO-Request------>| +| | |<----BSSMAP-HO-RequestAck-----| +| | |<---MAP-SendHandoverReportRequest ------ +| |<---MAP-Prepare-HO-Response---| | +| |-----------SS7 IAM----------->| | +| | |---MAP-SendHandoverReportResponse ----> +| |<-----------SS7 ACM-----------| | +|<------BSSMAP-HO-Command------| | | +| | |<----------HO-Detect----------| +| || | | +| |<---------SS7 ANSWER----------| | +Subsequent Handover from MSC-B to MSC-B' is the same as above except that: + All MAP messages from MSC-B to MSC-B' are relayed through MSC-A. + The SS7 IAM, ACM, etc go through MSC-A too. I dont understand this. + MAP-PrepareSubsequentHandoverRequest replaces MAP-PrepareHandove-Request + MAP-PrepareSubsequentHandoverResponse replaces MAP-PrepareHandoverResponse + + +Messages: +3.2.1.9 HandoverRequired(with transparent container) BS1->MSC + 3.2.1.7 HandoverRequest(with transparent container) BS2<-MSC + Note: HandoverRequest identifies cells by by CellIdentity or LAI+CellIdentity. + CellIdentity as defined in 8.08 BSSMAP documentation 3.2.2.27 refers to a non-existent reference in 4.08 + CellIdentity appears to be what the MS returns, 4.08/44.018 10.5.2.2, ie BCCH-ARFCN-C0+BCC+NCC + HandoverRequestAck (with L3 HandoverCommand) BS2->MSC + or: HandoverFailure BS2->MSC + +3.2.1.11 HandoverCommand (with L3 HandoverCommand) BS1<-MSC +or: 3.2.1.37 HandoverRequiredReject(with cause) BS1<-MSC + + 3.2.1.40 HandoverDetect BS2->MSC + 3.2.1.11 HandoverComplete BS2->MSC + +HandoverFailure BS1->MSC 3.1.5.3.2: BS1 sends this if it detects that handover failed, ie, the MS returns and sends HandoverFailure. + ClearCommand with cause "Radio interface failure, reversion to old channel" BS2<-MSC + ClearComplete BS2->MSC +NO: 3.2.1.13 HandoverSucceeded BS1<-MSC Used for group calls instead of ClearCommand +ClearCommand with cause "Handover Successful" BS1<-MSC +ClearComplete BS1->MSC + +HandoverPerformed BS->MSC + Message is sent for inter-cell handover procedure to indicate new cell MS is on. + This is congruent with an OpenBTS->OpenBTS handover. + +We can ignore: + # These messages bracket a group of multiple HandoverRequired for MSC initiated handovers, see 3.1.8. + 3.2.1.14 HandoverCandidateEnquire BS1<-MSC + 3.2.1.15 HandoverCandidateResponse BS1->MSC + HandoverCandidateEnquiry BS1<-MSC + HandoverCandidateResponse BS1->MSC + +BS1 -HandoverRequired->MSC +# MS BS1 BS2 YATE +#|--------- RR MeasurementReport--------->| | | +#| |--------------- BSSMAP HandoverRequest------------------>| +#| | |<- BSSMAP Handover--------------- BSSMAP HandoverRequest------------------>| +#| | + + + +======== Session Redirection 24.228 10.4 with 4 sub-cases +== Session Forward Unconditional +This is done by the network, and the ladder diagram is the same, but the alternate +destination is returned in the SIP 183 SessionProgress message +== Session Forward No Answer +Instead of 183 SessionProgress the network returns 302 Moved Temporarily, then the UE retries through CS domain. +This is for IP based multimedia services. How do we get the new address to an MS? +== Session Forward Variable +Instead of 183 SessionProgress the network returns 302 Redirect +== Session Forward Busy + +WIDTH 0|40|30| +== Layer 3 Authentication +| Look up Kc by imsi. | | +|<-GMM-AuthenticationAndCipheringRequest-| | +|-GMM-AuthenticaionAndCipheringResponse->| | + +== Layer 3 == +Simplest case: OpenBTS is a single Location Area and Routing Area. +Class-A or Class-B MS: +|----------GMM-AttachRequest,----------->| | +| MobileID, MS Caps | | +If MobileID is an unrecognized MCC+MNC+LAI+RAI+P-TMSI, then: +|<-----GMM-IdentityRequest for IMSI------| | +|-------GMM-IdentityResponse,IMSI------->| | +Note: The above establishes a database in the SGSN of mobileID to IMSI +Later: +|-----------GMM-AttachRequest----------->| | +| MobileID is recognized | | +|<-------Optional Authentication-------->| | +|<-------GMM-AttachAccept, P-TMSI--------| | +|----------GMM-AttachComplete----------->| | +|New P-TMSI replaces previous, old P-TMS | | + + +When MS changes Routing Area or Periodic timer expires: +|-----GMM-RoutingAreaUpdateRequest------>| | +| If MobileID not recognized: | | +|<------GMM-RoutingAreaUpdateReject------| | +| If MobileId recognized: | | +|<------GMM-RoutingAreaUpdateAccept------| | + +How it should work: +If the MS is in GMM-STANDBY state, it does an RA Update when it enters a new Routing Area. +|-----GMM-RoutingAreaUpdateRequest------>| | +If MobileID not recognized by this SGSN, and we can determine old SGSN, ask old SGSN +and perform a PS handover. In our current system GGSN is integrated so this may include PDP Context Info. + +If the MS is in GMM-READY state, MS does something (what? maybe any layer 2 activity at all?) +when it enters a new cell, and we do a PS handover from the old SGSN. + +Methods to find old-SGSN, no SGSN pool: + o SGSN sever number can be encoded in the P-TMSI. + o query a central server with the old P-TMSI. + o include SGSN address in P-TMSI signature, which is only 24 bits. +Methods to find old-SGSN with SGSN pool: + o Pool is established at startup. + +== Suspension Request +== Detach + +| find or allocate P-TMSI when? | | + +======================= REGISTRATION =========================== + +WIDTH 3|46 +Standard registration messages +BTS REGISTRAR +R1 |-------------REGISTER with IMSI-------------->| +A1 |<--------------200 OK with IMSI---------------| IMSI recognized and pre-authorized, no TMSI assignment. +F1 |<---------401 Unauthorized with IMSI----------| Permanent fail. Unspecified if IMSI recognized or not. +F2 |<------401 Unauthorized with IMSI, nonce------| authorization request +R2 |----------REGISTER with IMSI, SRES----------->| standard response to F2. + +New Registration messages to support TMSI: +R3 |------------REGISTER with oldTMSI------------>| Initial Register by TMSI. +F2 |<-401 Unauthorized with oldTMSI, IMSI, nonce--| authorization request +A2 |<----------200 OK with IMSI, oldTMSI----------| authorized, TMSI ok. +A3 |<----------200 OK with IMSI, newTMSI----------| authorized, TMSI reassignment requested. +F3 |<---------485 Ambiguous with oldTMSI----------| TMSI not recognized or ambiguous. BTS must query for IMSI. +R4 |---------REGISTER with oldTMSI, SRES--------->| response to F2. +R5 |---------REGISTER with IMSI, oldTMSI--------->| response to F3: 485 Ambiguous +R6 |---------REGISTER with IMSI, newTMSI--------->| response to A3: TMSI reassignment confirmation. + + +Notes: +Reply A1 is the standard Registrar reply, but will not be used by Yate-MSC. +Reply A2 and A3 are distguinshable by the BTS by whether the returned TMSI matches the existing or not. +Reply A3 should generally not be used to authorize an LUR using tmsi without challenge, however, +this is currently how follow-on requests are handled, ie, after LUR, +a CMServiceRequest with TMSI proceeds without challenge. + +The TMSI will be indicated by "TMSI" followed by exactly 8 hex digits, case irrelevant. When allocating TMSIs It is slightly preferable that the top two bits of the TMSI be invariant, for example, two 0 bits. +When TMSI and IMSI are both included in a SIP message, one of them will be put in a new header field. +I do nto think it is adisable to use any existing field names that proxies may recognize. +I suggest the header name: "P-Alt-Id". + +Registration with IMEI: +In addition, any REGISTER message may include IMEI. +Since a customer may not want IMEI sent in clear any more than IMSI sent in clear, +I believe this should be an option controlled by the MSC. +To implement that a flag needs to be sent from the REGISTRAR to the BTS, and the BTS will respond +by returning the IMEI in the next message. +I suggest the header name: "P-IMEI". If present in a reply from the registrar it is a request for IMEI. +The BTS will return the IMEI, which is in decimal, prefixed by "IMEI", which is slightly verbose. +Normally the Registrar would include P-IMEI in the 401 or 485 replies. +The BTS will not automatically query the IMEI before sending the initial REGISTER message. + +Next Topic: TMSI Caching. +Under what circumstances should the BTS cache the TMSI, and for how long, and will it work in UMTS? +TMSI caching is meant to allow the BTS to respond to an MS request without requesting authorization from the Registrar. +Possible reasons include: +To reduce network traffic. +To allow the BTS to be used during temporary loss of contact with the Registrar. +Currently OpenBTS requires a IMSI for LUR but allows a subsequent CMServiceRequest by TMSI. +Currently OpenBTS caches TMSIs forever. Note that the BTS is not informed when an MS leaves the cell, +although a powered MS will continue to perform periodic LUR every hour. + +Reasons not to cache TMSIs: +A UMTS UE with a U-SIM will refuse to connect unless it is challenged. +I did not look up the exact circumstances, but there is no value in TMSI caching when +we need to contact the Registrar for new authenticaion anyway. +We have also talked about caching multiple nonce. +The TMSI may collide with an existing TMSI. Performing authorization is a way to detect this. +For this reason we authorize MS by TMSI even when using open-registration. + +I suggest that reducing network traffic is hardly worth the effort at this point. +Here are the possibilities as I see it: +1. Punt. The BTS always contacts the MSC every time the MS establishes a new connection. This means the only +way a GSM CMServiceRequest would not require a new authorization is if it immediately follows LUR as a follow-on request. +2. Have an option that allows CMServiceRequest without authorization for GSM MS that have had a successful LUR within the hour. +3. Put a lifetime on the TMSI returned by the Registrar. This needs to be returned by the MSC, not be an OpenBTS option, because it will depend on whether the SIM is a U-SIM. + + +How to configure OpenBTS. +Two sets of options, one for normal use and one for loss of contact with Registrar. +Service: + LUR, CS, SMS, GPRS. +For OpenRegistration: + Allow TMSI without authorization. + Allow TMSI with authorization using cached nonce. + Options are: allow with OpenRegistration. +I suggest that OpenRegistration + +THE END +EOF diff --git a/GPRS/BSSG.cpp b/GPRS/BSSG.cpp index 5c0f004..71301b3 100644 --- a/GPRS/BSSG.cpp +++ b/GPRS/BSSG.cpp @@ -26,6 +26,8 @@ #include #include +#if INTERNAL_SGSN == 0 + namespace BSSG { BSSGMain gBSSG; @@ -368,3 +370,4 @@ void BSSGWriteLowSide(NSMsg *ulmsg) } }; +#endif diff --git a/GPRS/GPRSCLI.cpp b/GPRS/GPRSCLI.cpp index f921c31..b756959 100644 --- a/GPRS/GPRSCLI.cpp +++ b/GPRS/GPRSCLI.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * All Rights Reserved. * * This software is distributed under multiple licenses; @@ -23,24 +23,24 @@ #include "Interthread.h" #include "BSSG.h" #include "LLC.h" +#include "CLI.h" #define strmatch(what,pat) (0==strncmp(what,pat,strlen(pat))) -using namespace BSSG; -using namespace SGSN; - -#define BAD_NUM_ARGS 1 // See CLI/CLI.cpp #define RN_CMD_OPTION(opt) (argi1 && 0==strncmp(argv[1],o,strlen(o)) ? argc--,argv++,1 : 0) namespace GPRS { +using namespace BSSG; +using namespace SGSN; +using namespace CommandLine; -static int gprsMem(int argc, char **argv, int argi, ostream&os) +static CLIStatus gprsMem(int argc, char **argv, int argi, ostream&os) { gMemStats.text(os); - return 0; + return SUCCESS; } static void printChans(bool verbose, ostream&os) @@ -58,10 +58,10 @@ static void printChans(bool verbose, ostream&os) // return 0; // } // printChans(verbose,os); -// return 0; +// return SUCCESS; //} -static int gprsList(int argc, char **argv, int argi, ostream&os) +static CLIStatus gprsList(int argc, char **argv, int argi, ostream&os) { bool xflag=0, aflag=0, listms=0, listtbf=0, listch=0; int options = 0; @@ -82,7 +82,7 @@ static int gprsList(int argc, char **argv, int argi, ostream&os) } oops: os << "oops! unrecognized arg:" << argv[argi] << "\n"; - return 0; + return SUCCESS; } bool all = !(listch|listtbf|listms); @@ -106,10 +106,10 @@ static int gprsList(int argc, char **argv, int argi, ostream&os) if (all|listch) { printChans(options&printVerbose,os); } - return 0; + return SUCCESS; } -static int gprsFree(int argc, char **argv, int argi, ostream&os) +static CLIStatus gprsFree(int argc, char **argv, int argi, ostream&os) { char *what = RN_CMD_ARG; char *idstr = RN_CMD_ARG; @@ -121,7 +121,7 @@ static int gprsFree(int argc, char **argv, int argi, ostream&os) if (ms->msDebugId == (unsigned)id) { os << "Deleting " <msDelete(1); - return 0; + return SUCCESS; } } os << "MS# "<mtDebugId == (unsigned)id) { os << "Deleting " <mtDelete(1); - return 0; + return SUCCESS; } } os << "TBF# "< itr(gL2MAC.macExpiredMSs); itr.next(ms); ) { @@ -163,14 +163,14 @@ static int gprsFreeExpired(int argc, char **argv, int argi, ostream&os) itr.erase(); delete tbf; } - return 0; + return SUCCESS; } -static int gprsStats(int argc, char **argv, int argi, ostream&os) +static CLIStatus gprsStats(int argc, char **argv, int argi, ostream&os) { if (!GPRSConfig::IsEnabled()) { os << "GPRS is not enabled. See 'GPRS.Enable' option.\n"; - return 0; + return FAILURE; } GSM::Time now = gBTS.time(); os << "GSM FN=" << now.FN() << " GPRS BSN=" << gBSNNext << "\n"; @@ -187,7 +187,7 @@ static int gprsStats(int argc, char **argv, int argi, ostream&os) << "\n"; os << "Downlink utilization=" << gL2MAC.macDownlinkUtilization << "\n"; os << LOGVAR2("ServiceLoopTime",Stats.macServiceLoopTime) << "\n"; - return 0; + return SUCCESS; } #if 0 // pinghttp test code not linked in yet. @@ -196,19 +196,19 @@ static int gprsPingHttp(int argc, char **argv, int argi, ostream&os) if (argi >= argc) { os << "syntax: gprs pinghttp address\n"; return 1; } //char *addr = argv[argi++]; os << "pinghttp unimplemented\n"; - return 0; + return SUCCESS; } #endif // Start the service and allocate a channel. // This is redundant - can call rach. -static int gprsStart(int argc, char **argv, int argi, ostream&os) +static CLIStatus gprsStart(int argc, char **argv, int argi, ostream&os) { // Start the thread, if not running. char *modearg = RN_CMD_ARG; if (modearg) { gL2MAC.macSingleStepMode = strmatch(modearg,"s"); - if (!gL2MAC.macSingleStepMode) { os << "Unrecognized arg: "< verbose; -c => include MS Capabilities -x => list expired rather than active" }, @@ -661,17 +661,17 @@ static void debugdefaults() // Should return: SUCCESS (0), BAD_NUM_ARGS(1), BAD_VALUE(2), FAILURE (5) // but sadly, these are defined in CLI.cpp, so I guess we just return 0. // Note: argv includes command name so argc==1 implies no args. -int gprsCLI(int argc, char **argv, std::ostream&os) +CLIStatus gprsCLI(int argc, char **argv, std::ostream&os) { //debugdefaults(); ScopedLock lock(gL2MAC.macLock); - if (argc <= 1) { help(os); return 1; } + if (argc <= 1) { help(os); return BAD_NUM_ARGS; } int argi = 1; // The number of arguments consumed so far; argv[0] was "gprs" char *subcmd = argv[argi++]; struct GprsSubCmds *gscp; - int status = 0; // maybe success + CLIStatus status = SUCCESS; // maybe success for (gscp = gprsSubCmds; gscp->name; gscp++) { if (0 == strcasecmp(subcmd,gscp->name)) { status = gscp->subcmd(argc,argv,argi,os); @@ -687,7 +687,7 @@ int gprsCLI(int argc, char **argv, std::ostream&os) if (strcasecmp(subcmd,"help")) { os << "gprs: unrecognized sub-command: "< +#include // The user of this file must include these first, to avoid circular .h files: //#include "GSMConfig.h" // For Time //#include "GSMCommon.h" // For ChannelType @@ -39,6 +40,7 @@ namespace GPRS { struct GPRSConfig { static unsigned GetRAColour(); static bool IsEnabled(); + static bool IsSupported(); static bool sgsnIsInternal(); }; @@ -84,7 +86,7 @@ extern void GPRSSetDebug(int value); extern void GPRSNotifyGsmActivity(const char *imsi); // Hook into CLI/CLI.cpp:Parser class for GPRS sub-command. -int gprsCLI(int,char**,std::ostream&); +CommandLine::CLIStatus gprsCLI(int,char**,std::ostream&); int configGprsChannelsMin(); void gprsStart(); // External entry point to start gprs service. diff --git a/GPRS/GPRSInternal.h b/GPRS/GPRSInternal.h index 22e6398..bde8c90 100644 --- a/GPRS/GPRSInternal.h +++ b/GPRS/GPRSInternal.h @@ -123,6 +123,7 @@ namespace GPRS { #define GLOG(wLevel) if (GPRSDebug || IS_LOG_LEVEL(wLevel)) _LOG(wLevel) << " "<setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); GPRSLOG(1) << "GPRS serviceRACH sending L3ImmediateAssignment:" << result; - AGCH->send(result); + AGCH->l2sendm(result); break; } default: diff --git a/GPRS/RLCEngine.cpp b/GPRS/RLCEngine.cpp index 210ac44..35ba9f8 100644 --- a/GPRS/RLCEngine.cpp +++ b/GPRS/RLCEngine.cpp @@ -452,7 +452,7 @@ void RLCUpEngine::engineUpAdvanceWindow() segs[n].E = payload.LIByteE(); end = segs[n].E; segs[n].M = payload.LIByteM(); - payload.set(payload.tail(8)); + payload.dup(payload.tail(8)); } unsigned original_size = payload.size(); @@ -486,7 +486,7 @@ void RLCUpEngine::engineUpAdvanceWindow() BitVector foo(payload.segment(0,8*lenbytes)); addUpPDU(foo); if (segs[i].LI) { sendPDU(); } - payload.set(payload.tail(8*lenbytes)); + payload.dup(payload.tail(8*lenbytes)); } // Final M bit means add rest of the payload to the nextpdu. if (payload.size() && segs[n-1].M) { diff --git a/GPRS/ScalarTypes.h b/GPRS/ScalarTypes.h index 077d889..dafab45 100644 --- a/GPRS/ScalarTypes.h +++ b/GPRS/ScalarTypes.h @@ -14,6 +14,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +// (pat) This file defines initialized scalar types: Bool_z, Int_z, UInt_z, Size_t_z etc., which function like +// their normal counterparts (with exceptions noted below) but are guaranteed initialized to 0. + #ifndef SCALARTYPES_H #define SCALARTYPES_H #include // For size_t diff --git a/GPRS/TBF.cpp b/GPRS/TBF.cpp index 59c1903..f9cf7c9 100644 --- a/GPRS/TBF.cpp +++ b/GPRS/TBF.cpp @@ -538,15 +538,10 @@ void sendAssignmentCcch( // This code should be replaced with real paging channels. // Our BS_PA_MFRMS is 2, so send the paging message twice to make // sure it is sent in all (2) available paging 51-multiframes. - currentAGCH->send(amsg); - currentAGCH->send(amsg); - // DEBUG: try just plastering these messages out there. - //for (int ii = 0; ii < 6; ii++) { - // GSM::CCCHLogicalChannel *anyagch = gBTS.getAGCH(); - // anyagch->send(amsg); - //} + currentAGCH->l2sendm(amsg); + currentAGCH->l2sendm(amsg); } else { - currentAGCH->send(amsg); // send() takes care of converting it to a BitVector. + currentAGCH->l2sendm(amsg); // send() takes care of converting it to a BitVector. } tbf->mtCcchAssignCounter++; } diff --git a/GPRS/TBF.h b/GPRS/TBF.h index de7ac12..7d6e2ac 100644 --- a/GPRS/TBF.h +++ b/GPRS/TBF.h @@ -241,7 +241,7 @@ std::ostream& operator<<(std::ostream& os, const TBFState::type &type) // These are the message transaction types. // Each TBFState only uses one type of message transaction, so we could use -// the TBFState as the message transaction type, but the code is clearly +// the TBFState as the message transaction type, but the code is clearer // if the message types are seprate from the TBF states. // When we change state there may be outstanding messages that belong to the previous state, // especially on error conditions. diff --git a/GSM/GSM610Tables.cpp b/GSM/GSM610Tables.cpp index 8cee779..52cf394 100644 --- a/GSM/GSM610Tables.cpp +++ b/GSM/GSM610Tables.cpp @@ -202,7 +202,7 @@ const unsigned int x4_12_p = x4_11_p + x_l; the d[] bits of the GSM TCH/F. RTP[4+g610BitOrder[i]] <=> GSM[i] */ -unsigned int GSM::g610BitOrder[260] = { +const unsigned int GSM::g610BitOrder[260] = { /**@name importance class 1 */ //@{ /** LAR1:5 */ LAR1_p+LAR1_l-1-5, /* bit 0 */ @@ -482,4 +482,3 @@ unsigned int GSM::g610BitOrder[260] = { /** LAR6:0 */ LAR6_p+LAR6_l-1-0 //@} }; - diff --git a/GSM/GSM610Tables.h b/GSM/GSM610Tables.h index 4f634d0..98b1894 100644 --- a/GSM/GSM610Tables.h +++ b/GSM/GSM610Tables.h @@ -22,7 +22,7 @@ namespace GSM { /** Table #2 from GSM 05.03 */ -extern unsigned int g610BitOrder[260]; +extern const unsigned int g610BitOrder[260]; } diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp index fa02108..08b76fc 100644 --- a/GSM/GSMCommon.cpp +++ b/GSM/GSMCommon.cpp @@ -23,40 +23,6 @@ using namespace GSM; using namespace std; -const char* GSM::CallStateString(GSM::CallState state) -{ - switch (state) { - case NullState: return "null";; - case Paging: return "paging"; - case AnsweredPaging: return "answered-paging"; - case MOCInitiated: return "MOC-initiated"; - case MOCProceeding: return "MOC-proceeding"; - case MTCConfirmed: return "MTC-confirmed"; - case CallReceived: return "call-received"; - case CallPresent: return "call-present"; - case ConnectIndication: return "connect-indication"; - case Active: return "active"; - case DisconnectIndication: return "disconnect-indication"; - case ReleaseRequest: return "release-request"; - case SMSDelivering: return "SMS-delivery"; - case SMSSubmitting: return "SMS-submission"; - case HandoverInbound: return "HANDOVER Inbound"; - case HandoverProgress: return "HANDOVER Progress"; - case HandoverOutbound: return "HANDOVER Outbound"; - case BusyReject: return "Busy Reject"; - default: return NULL; - } -} - -ostream& GSM::operator<<(ostream& os, GSM::CallState state) -{ - const char* str = GSM::CallStateString(state); - if (str) os << str; - else os << "?" << ((int)state) << "?"; - return os; -} - - ostream& GSM::operator<<(ostream& os, L3PD val) { switch (val) { @@ -69,20 +35,20 @@ ostream& GSM::operator<<(ostream& os, L3PD val) } -const BitVector GSM::gTrainingSequence[] = { - BitVector("00100101110000100010010111"), - BitVector("00101101110111100010110111"), - BitVector("01000011101110100100001110"), - BitVector("01000111101101000100011110"), - BitVector("00011010111001000001101011"), - BitVector("01001110101100000100111010"), - BitVector("10100111110110001010011111"), - BitVector("11101111000100101110111100"), +const BitVector2 GSM::gTrainingSequence[] = { + BitVector2("00100101110000100010010111"), + BitVector2("00101101110111100010110111"), + BitVector2("01000011101110100100001110"), + BitVector2("01000111101101000100011110"), + BitVector2("00011010111001000001101011"), + BitVector2("01001110101100000100111010"), + BitVector2("10100111110110001010011111"), + BitVector2("11101111000100101110111100"), }; -const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000"); +const BitVector2 GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000"); -const BitVector GSM::gRACHSynchSequence("01001011011111111001100110101010001111000"); +const BitVector2 GSM::gRACHSynchSequence("01001011011111111001100110101010001111000"); @@ -121,12 +87,30 @@ char GSM::encodeBCDChar(char ascii) } +// Must be unsigned char, not signed char, or the conversion in sprintf below will be negative +string GSM::data2hex(const unsigned char *data, unsigned nbytes) +{ + //LOG(DEBUG) << LOGVAR(nbytes); + string result; + result.reserve(2+2*nbytes); + result.append("0x"); + if (nbytes == 0) { result.append("0"); return result; } + for (unsigned i = 0; i < nbytes; i++) { + char buf[20]; // Paranoid, only need 3. + sprintf(buf,"%02x",*data++); + //LOG(DEBUG) << LOGVAR(buf) <129)); // this was a real bug, ticket #1420. assert((ARFCN>=128)&&(ARFCN<=251)); return 824200+200*(ARFCN-128); case EGSM900: @@ -415,4 +399,11 @@ void Z100Timer::wait() const while (!expired()) msleep(remaining()); } +std::ostream& GSM::operator<<(std::ostream& os, const Z100Timer&zt) +{ + if (zt.active()) { os << zt.remaining(); } + else { os << "inactive"; } + return os; +} + // vim: ts=4 sw=4 diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h index 2baf842..7c2afbb 100644 --- a/GSM/GSMCommon.h +++ b/GSM/GSMCommon.h @@ -41,38 +41,9 @@ namespace GSM { /* forward references */ class L1FEC; class L2LAPDm; -class L3Processor; -class LogicalChannel; +//class L3Processor; class L2Header; -/** Call states based on GSM 04.08 5 and ITU-T Q.931 */ -enum CallState { - NullState, - Paging, - AnsweredPaging, - MOCInitiated, - MOCProceeding, - MTCConfirmed, - CallReceived, - CallPresent, - ConnectIndication, - Active, - DisconnectIndication, - ReleaseRequest, - SMSDelivering, - SMSSubmitting, - HandoverInbound, - HandoverProgress, - HandoverOutbound, - BusyReject, -}; - - -/** Return a human-readable string for a GSM::CallState. */ -const char* CallStateString(CallState state); - -std::ostream& operator<<(std::ostream& os, CallState state); - /** A base class for GSM exceptions. */ class GSMError {}; @@ -92,13 +63,13 @@ inline void sleepFrame() /** GSM Training sequences from GSM 05.02 5.2.3. */ -extern const BitVector gTrainingSequence[]; +extern const BitVector2 gTrainingSequence[]; /** C0T0 filler burst, GSM 05.02, 5.2.6 */ -extern const BitVector gDummyBurst; +extern const BitVector2 gDummyBurst; /** Random access burst synch. sequence */ -extern const BitVector gRACHSynchSequence; +extern const BitVector2 gRACHSynchSequence; enum GSMAlphabet { ALPHABET_7BIT, @@ -136,8 +107,8 @@ const unsigned T200ms = 900; ///< LAPDm ACK timeout, set for typical turnaround //@} /**@name GSM timeouts for radio resource management, GSM 04.08 11.1. */ //@{ -const unsigned T3101ms = 4000; ///< L1 timeout for SDCCH assignment -const unsigned T3107ms = 3000; ///< L1 timeout for TCH/FACCH assignment +const unsigned T3101ms = 4000; ///< L1 timeout for SDCCH assignment (pat) Started on Immediate Assignment, stopped when MS siezes channel. +const unsigned T3107ms = 3000; ///< L1 timeout for TCH/FACCH assignment (pat) or any change of channel assignment. const unsigned T3109ms = 30000; ///< L1 timeout for an existing channel const unsigned T3111ms = 2*T200ms; ///< L1 timeout for reassignment of a channel //@} @@ -304,17 +275,18 @@ std::ostream& operator<<(std::ostream& os, TypeAndOffset); enum L3PD { L3GroupCallControlPD=0x00, L3BroadcastCallControlPD=0x01, - L3PDSS1PD=0x02, - L3CallControlPD=0x03, - L3PDSS2PD=0x04, + // L3PDSS1PD=0x02, // 2 is EPS (4G) session management + L3CallControlPD=0x03, // call control, call related SSD [Supplementary Service Data] messages. + // L3PDSS2PD=0x04, // 4 is GPRS Transparent Transport Protocol. L3MobilityManagementPD=0x05, L3RadioResourcePD=0x06, + // 7 is EPS (4G) mobility mananagement messages. L3GPRSMobilityManagementPD=0x08, L3SMSPD=0x09, L3GPRSSessionManagementPD=0x0a, - L3NonCallSSPD=0x0b, - L3LocationPD=0x0c, - L3ExtendedPD=0x0e, + L3NonCallSSPD=0x0b, // non-call SSD [Supplementary Service Data] messages. + L3LocationPD=0x0c, // Location services specified in 3GPP TS 44.071 + L3ExtendedPD=0x0e, // reserved to extend PD to a full octet. L3TestProcedurePD=0x0f, L3UndefinedPD=-1 }; @@ -625,8 +597,11 @@ class Z100Timer { */ void wait() const; }; +std::ostream& operator<<(std::ostream& os, const Z100Timer&); +std::string data2hex(const unsigned char *data, unsigned nbytes); +std::string inline data2hex(const char *data, unsigned nbytes) { return data2hex((const unsigned char*)data,nbytes); } diff --git a/GSM/GSMConfig.cpp b/GSM/GSMConfig.cpp index 7616ce9..10219ee 100644 --- a/GSM/GSMConfig.cpp +++ b/GSM/GSMConfig.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2010, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * @@ -42,6 +42,36 @@ GSMConfig::GSMConfig() void GSMConfig::init() { + long changed = 0; + long band = gConfig.getNum("GSM.Radio.Band"); + long c0 = gConfig.getNum("GSM.Radio.C0"); + + // adjust to an appropriate band if C0 is bogus + if (c0 >= 128 && c0 <= 251 && band != 850) { + changed = band; + band = 850; + } else if (c0 <= 124 && band != 900) { + changed = band; + band = 900; + } else if (c0 >= 975 && c0 <= 1023 && band != 900) { + changed = band; + band = 900; + } else if (c0 >= 512 && c0 <= 810 && band != 1800 && band != 1900) { + changed = band; + band = 1800; + } else if (c0 >= 811 && c0 <= 885 && band != 1800) { + changed = band; + band = 1800; + } + + if (changed) { + if (gConfig.set("GSM.Radio.Band", band)) { + LOG(NOTICE) << "crisis averted: automatically adjusted GSM.Radio.Band from " << changed << " to " << band << " so GSM.Radio.C0 is in range"; + } else { + LOG(ERR) << "unable to automatically adjust GSM.Radio.Band, config write error"; + } + } + mBand = (GSMBand)gConfig.getNum("GSM.Radio.Band"); mT3122 = gConfig.getNum("GSM.Timer.T3122Min"); regenerateBeacon(); @@ -60,6 +90,8 @@ void GSMConfig::start() } // Do not call this until AGCHs are installed. mAccessGrantThread.start(Control::AccessGrantServiceLoop,NULL); + + Control::l3start(); // (pat) For the L3 rewrite: start the L3 state machine dispatcher. } @@ -95,7 +127,7 @@ void GSMConfig::regenerateBeacon() if (mSI1) delete mSI1; mSI1 = SI1; LOG(INFO) << *SI1; - L3Frame SI1L3(UNIT_DATA); + L3Frame SI1L3(UNIT_DATA,0); SI1->write(SI1L3); L2Header SI1Header(L2Length(SI1L3.L2Length())); mSI1Frame = L2Frame(SI1Header,SI1L3); @@ -106,7 +138,7 @@ void GSMConfig::regenerateBeacon() if (mSI2) delete mSI2; mSI2 = SI2; LOG(INFO) << *SI2; - L3Frame SI2L3(UNIT_DATA); + L3Frame SI2L3(UNIT_DATA,0); SI2->write(SI2L3); L2Header SI2Header(L2Length(SI2L3.L2Length())); mSI2Frame = L2Frame(SI2Header,SI2L3); @@ -117,7 +149,7 @@ void GSMConfig::regenerateBeacon() if (mSI3) delete mSI3; mSI3 = SI3; LOG(INFO) << *SI3; - L3Frame SI3L3(UNIT_DATA); + L3Frame SI3L3(UNIT_DATA,0); SI3->write(SI3L3); L2Header SI3Header(L2Length(SI3L3.L2Length())); mSI3Frame = L2Frame(SI3Header,SI3L3,true); @@ -129,7 +161,7 @@ void GSMConfig::regenerateBeacon() mSI4 = SI4; LOG(INFO) << *SI4; LOG(INFO) << SI4; - L3Frame SI4L3(UNIT_DATA); + L3Frame SI4L3(UNIT_DATA,0); SI4->write(SI4L3); //printf("SI4 bodylength=%d l2len=%d\n",SI4.l2BodyLength(),SI4L3.L2Length()); //printf("SI4L3.size=%d\n",SI4L3.size()); @@ -141,7 +173,7 @@ void GSMConfig::regenerateBeacon() // SI13. pat added 8-2011 to advertise GPRS support. L3SystemInformationType13 *SI13 = new L3SystemInformationType13; LOG(INFO) << *SI13; - L3Frame SI13L3(UNIT_DATA); + L3Frame SI13L3(UNIT_DATA,0); //printf("start=%d\n",SI13L3.size()); SI13->write(SI13L3); //printf("end=%d\n",SI13L3.size()); @@ -380,7 +412,7 @@ TCHFACCHLogicalChannel *GSMConfig::getTCH( chan->debugGetL1()->setGPRS(true,NULL); return chan; } - chan->open(); // (pat) LogicalChannel::open(); Opens mSACCH also. + chan->open(); // (pat) LogicalChannel::open(); Opens mSACCH also. Starts T3101. gReports.incr("OpenBTS.GSM.RR.ChannelAssignment"); } else { //LOG(DEBUG)<<"getTCH returns NULL"; @@ -643,7 +675,7 @@ void GSMConfig::sendPCH(const L3RRMessage& msg,unsigned imsiMod1000) crackPagingFromImsi(imsiMod1000,paging_block_index, multiframe_index); assert(multiframe_index < sMax_BS_PA_MFRMS); CCCHLogicalChannel* ch = getPCH(paging_block_index); - ch->mPagingQ[multiframe_index].write(new L3Frame((const L3Message&)msg,UNIT_DATA)); + ch->mPagingQ[multiframe_index].write(new L3Frame((const L3Message&)msg,UNIT_DATA,0)); } Time GSMConfig::getPchSendTime(imsiMod1000) @@ -657,5 +689,21 @@ Time GSMConfig::getPchSendTime(imsiMod1000) } #endif +// (pat) Return a vector of the available channels. +// Use to avoid publishing these iterators to the universe. +void GSMConfig::getChanVector(L2ChanList &result) +{ + result.clear(); + result.reserve(64); // Enough for a 2 ARFCN system. + for (GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin(); sChanItr != gBTS.SDCCHPool().end(); ++sChanItr) { + result.push_back(*sChanItr); + } + + // TCHs + for (GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin(); tChanItr != gBTS.TCHPool().end(); ++tChanItr) { + result.push_back(*tChanItr); + } +} + // vim: ts=4 sw=4 diff --git a/GSM/GSMConfig.h b/GSM/GSMConfig.h index 3504b6f..00ea30b 100644 --- a/GSM/GSMConfig.h +++ b/GSM/GSMConfig.h @@ -50,6 +50,7 @@ class TCHFACCHLogicalChannel; class CCCHList : public std::vector {}; class SDCCHList : public std::vector {}; class TCHList : public std::vector {}; +typedef std::vector L2ChanList; /** This object carries the top-level GSM air interface configuration. @@ -347,6 +348,7 @@ class GSMConfig { /** Get a handle to the power manager. */ PowerManager& powerManager() { return mPowerManager; } + void getChanVector(std::vector &result); }; diff --git a/GSM/GSML1FEC.cpp b/GSM/GSML1FEC.cpp index b5f746b..047cd68 100644 --- a/GSM/GSML1FEC.cpp +++ b/GSM/GSML1FEC.cpp @@ -1,7 +1,7 @@ /* * Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2012 Range Networks, Inc. +* Copyright 2012, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -16,6 +16,7 @@ */ +#define TESTTCHL1FEC #include "GSML1FEC.h" @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -38,10 +40,25 @@ #undef WARNING +namespace GSM { using namespace std; -using namespace GSM; + +// (pat) David says this is the initial power level we want to send to handsets. +// 33 translates to 2 watts, which is the max in the low band. +// The power control loop will rapidly turn down the power once SACCH is established. +// This value is in DB. It is converted to an ordered MS power based on tables in GSM 5.05 4.1. +const float cInitialPower = 33; + + +// The actual phone settings change every 4 bursts, so average over at least 4. +static const unsigned cAveragePeriodTiming = 8; // How many we measurement reports over which we average timing, minus 1. +// (pat) RSSI is used +static const unsigned cAveragePeriodRSSI = 8; // How many we measurement reports over which we average RSSI, minus 1. +static const unsigned cAveragePeriodSNR = 208; // How many we frames we average SNR, minus 1. +static const unsigned cFERMemory = 208; // How many we frames we average FER, minus 1. + /* Notes on reading the GSM specifications. @@ -71,14 +88,12 @@ using namespace GSM; Bit ordering in d is LSB-first in each octet. Bit ordering everywhere else in the OpenBTS code is MSB-first in every field to give contiguous fields across byte boundaries. - We use the BitVector::LSB8MSB() method to translate. + We use the BitVector2::LSB8MSB() method to translate. */ - - /**@name Power control utility functions based on GSM 05.05 4.1.1 */ //@{ @@ -126,6 +141,7 @@ const int* pickTable() { unsigned band = gBTS.band(); + switch (band) { case GSM850: case EGSM900: @@ -195,15 +211,18 @@ L1Encoder::L1Encoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1 mEncryptionAlgorithm(0) { assert((int)mCNclearHandover(mTN); - if (!ok) LOG(WARNING) << "handover clear failed"; + if (!ok) LOG(ALERT) << "handover clear failed"; } } -void L1FEC::handoverPending(bool flag) +HandoverRecord& L1FEC::handoverPending(bool flag, unsigned handoverRef) { assert(mEncoder); assert(mDecoder); mEncoder->handoverPending(flag); - mDecoder->handoverPending(flag); + return mDecoder->handoverPending(flag, handoverRef); } @@ -497,6 +551,12 @@ void L1FEC::close() if (mDecoder) mDecoder->close(); } +//bool L1FEC::reallyActive() const +//{ +// // I dont think mDecoder can be NULL because this is called only from CLI on channels with a decoder, but test anyway. +// return mDecoder ? mDecoder->reallyActive() : false; +//} + bool L1FEC::active() const { // Encode-only channels are always active. @@ -526,7 +586,7 @@ void RACHL1Decoder::serviceLoop() } -void *GSM::RACHL1DecoderServiceLoopAdapter(RACHL1Decoder* obj) +void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder* obj) { obj->serviceLoop(); return NULL; @@ -553,14 +613,15 @@ void RACHL1Decoder::writeLowSideRx(const RxBurst& burst) // Decode the burst. const SoftVector e(burst.segment(49,36)); - e.decode(mVCoder,mU); + //e.decode(mVCoder,mU); + mVCoder.decode(e,mU); // To check validity, we have 4 tail bits and 6 parity bits. // False alarm rate for random inputs is 1/1024. // Check the tail bits -- should all the zero. if (mU.peekField(14,4)) { - countBadFrame(); + countBadFrame(1); return; } @@ -570,7 +631,7 @@ void RACHL1Decoder::writeLowSideRx(const RxBurst& burst) unsigned checkParity = mD.parity(mParity); unsigned encodedBSIC = (sentParity ^ checkParity) & 0x03f; if (encodedBSIC != gBTS.BSIC()) { - countBadFrame(); + countBadFrame(1); return; } @@ -581,7 +642,8 @@ void RACHL1Decoder::writeLowSideRx(const RxBurst& burst) // The RACH L2 is so thin that we don't even need code for it. // Just pass the required information directly to the control layer. - countGoodFrame(); + countGoodFrame(1); + countBER(mVCoder.getBEC(),36); mD.LSB8MSB(); unsigned RA = mD.peekField(0,8); OBJLOG(INFO) <<"RACHL1Decoder received RA=" << RA << " at time " << burst.time() @@ -624,8 +686,8 @@ SharedL1Decoder::SharedL1Decoder() mE[i] = SoftVector(114); mI[i] = SoftVector(114); // Fill with zeros just to make Valgrind happy. - mE[i].fill(.0); - mI[i].fill(.0); + mE[i].fill(0); + mI[i].fill(0); } } @@ -639,6 +701,7 @@ void XCCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) OBJLOG(DEBUG) <<"XCCHL1Decoder not active, ignoring input"; return; } + mDecoderStats.countSNR(inBurst); // save frame number for possible decrypting int B = mMapping.reverseMapping(inBurst.time().FN()) % 4; mFN[B] = inBurst.time().FN(); @@ -654,7 +717,8 @@ void XCCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) } deinterleave(); if (decode()) { - countGoodFrame(); + countGoodFrame(1); + countBER(mVCoder.getBEC(),mC.size()); mD.LSB8MSB(); handleGoodFrame(); } else { @@ -674,14 +738,15 @@ void XCCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) // Also start encrypting downlink frames. parent()->encoder()->mEncrypted = ENCRYPT_YES; parent()->encoder()->mEncryptionAlgorithm = mEncryptionAlgorithm; - countGoodFrame(); + countGoodFrame(1); mD.LSB8MSB(); handleGoodFrame(); + countBER(mVCoder.getBEC(),mC.size()); } else { - countBadFrame(); + countBadFrame(1); } } else { - countBadFrame(); + countBadFrame(1); } } } @@ -733,7 +798,6 @@ void XCCHL1Decoder::decrypt() } } - bool XCCHL1Decoder::processBurst(const RxBurst& inBurst) { OBJLOG(DEBUG) <<"XCCHL1Decoder " << inBurst; @@ -743,6 +807,10 @@ bool XCCHL1Decoder::processBurst(const RxBurst& inBurst) // TODO -- One quick test of burst validity is to look at the tail bits. // We could do that as a double-check against putting garbage into // the interleaver or accepting bad parameters. + // (pat) Wrong! Dont do that. The tail bits are there to help the viterbi decoder by steering + // it with known final bits, something we dont use at present. But if you discard frames based + // on non-zero tail bits you could be incorrectly discarding perfectly good frames because + // of one bad inconsequential bit. // The reverse index runs 0..3 as the bursts arrive. // It is the "B" index of GSM 05.03 4.1.4 and 4.1.5. @@ -766,7 +834,7 @@ bool XCCHL1Decoder::processBurst(const RxBurst& inBurst) // TODO -- This is sub-optimal because it ignores the case // where the B==3 burst is simply missing, even though the soft decoder - // might actually be able to recover the frame. + // might actually be able to recover the frame. (pat) Dream on. // It also allows for the mixing of bursts from different frames. // If we were more clever, we'd be watching for B to roll over as well. } @@ -798,7 +866,8 @@ bool SharedL1Decoder::decode() // Convolutional decoding c[] to u[]. // GSM 05.03 4.1.3 OBJLOG(DEBUG) <<"XCCHL1Decoder << mC"; - mC.decode(mVCoder,mU); + //mC.decode(mVCoder,mU); + mVCoder.decode(mC,mU); OBJLOG(DEBUG) <<"XCCHL1Decoder << mU"; // The GSM L1 u-frame has a 40-bit parity field. @@ -853,7 +922,7 @@ void XCCHL1Decoder::handleGoodFrame() gWriteGSMTAP(ARFCN(),TN(),mReadTime.FN(),typeAndOffset(),mMapping.repeatLength()>51,true,mD); } // Build an L2 frame and pass it up. - const BitVector L2Part(mD.tail(headerOffset())); + const BitVector2 L2Part(mD.tail(headerOffset())); OBJLOG(DEBUG) <<"XCCHL1Decoder L2=" << L2Part; mUpstream->writeLowSide(L2Frame(L2Part,DATA)); } else { @@ -861,8 +930,25 @@ void XCCHL1Decoder::handleGoodFrame() } } +// Get the physical parameters of the burst. +void MSPhysReportInfo::processPhysInfo(const RxBurst &inBurst) +{ + // RSSI is dB wrt full scale. (pat) No it is not - RSSI is dB wrt the noise floor. + unsigned count = min(mRSSICount,cAveragePeriodRSSI); + mRSSI = (inBurst.RSSI() + count * mRSSI) / (count+1); + // Timing error is a float in symbol intervals. + count = min(mRSSICount,cAveragePeriodTiming); + mTimingError = (inBurst.timingError() + count * mTimingError) / (count+1); + // Timestamp + mTimestamp = gBTS.clock().systime(inBurst.time()); + + OBJLOG(INFO) << "SACCHL1Decoder " << " RSSI=" <open(); return; case RELEASE: + // (pat) How do we get here? The RELEASE is handled by L2LAPDm. // Normally, we get here after a DISC-DM handshake in L2. // Close both sides of the link, knowing that the phone will do the same. close(); @@ -991,7 +1065,7 @@ void XCCHL1Encoder::writeHighSide(const L2Frame& frame) break; case HARDRELEASE: // This means that a higher layer released the link, - // with certainty that is has been cleared of activity. + // with certainty that is has been cleared of activity, for example, handover. close(); if (sibling()) sibling()->close(true); break; @@ -1049,7 +1123,8 @@ void SharedL1Encoder::encode41() OBJLOG(DEBUG) << "XCCHL1Encoder u[]=" << mU; // GSM 05.03 4.1.3 // Apply the convolutional encoder. - mU.encode(mVCoder,mC); + //mU.encode(mVCoder,mC); + mVCoder.encode(mU,mC); OBJLOG(DEBUG) << "XCCHL1Encoder c[]=" << mC; } @@ -1073,7 +1148,7 @@ void SharedL1Encoder::interleave41() // before each transmission, rather than having them be static. // The qbits, also called stealing bits, are defined in GSM05.03. // For GPRS they specify the encoding type: CS-1 through CS-4. -void L1Encoder::transmit(BitVector *mI, BitVector *mE, const int *qbits) +void L1Encoder::transmit(BitVector2 *mI, BitVector2 *mE, const int *qbits) { // Format the bits into the bursts. // GSM 05.03 4.1.5, 05.02 5.2.3 @@ -1126,15 +1201,17 @@ void L1Encoder::transmit(BitVector *mI, BitVector *mE, const int *qbits) } // Copy in the "encrypted" bits, GSM 05.03 4.1.5, 05.02 5.2.3. - OBJLOG(DEBUG) << "transmit mE["<serviceLoop(); // DONTREACH @@ -1182,7 +1259,7 @@ SCHL1Encoder::SCHL1Encoder(L1FEC* wParent) { // The SCH extended training sequence. // GSM 05.02 5.2.5. - static const BitVector xts("1011100101100010000001000000111100101101010001010111011000011011"); + static const BitVector2 xts("1011100101100010000001000000111100101101010001010111011000011011"); xts.copyToSegment(mBurst,42); // Set the tail bits in u[] now, just once. mU.fillField(35,0,4); @@ -1205,7 +1282,8 @@ void SCHL1Encoder::generate() // Parity mBlockCoder.writeParityWord(mD,mP); // Convolutional encoding - mU.encode(mVCoder,mE); + //mU.encode(mVCoder,mE); + mVCoder.encode(mU,mE); // Mapping onto a burst, GSM 05.02 5.2.5. mBurst.time(mNextWriteTime); mE1.copyToSegment(mBurst,3); @@ -1252,7 +1330,7 @@ void NDCCHL1Encoder::start() -void *GSM::NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder* gen) +void *NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder* gen) { gen->serviceLoop(); // DONTREACH @@ -1294,15 +1372,13 @@ void BCCHL1Encoder::generate() +// TCH_FS TCHFACCHL1Decoder::TCHFACCHL1Decoder( unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC *wParent) - :XCCHL1Decoder(wCN,wTN, wMapping, wParent), - mTCHU(189),mTCHD(260), - mClass1_c(mC.head(378)),mClass1A_d(mTCHD.head(50)),mClass2_c(mC.segment(378,78)), - mTCHParity(0x0b,3,50) + :XCCHL1Decoder(wCN,wTN, wMapping, wParent) { for (int i=0; i<8; i++) { mE[i] = SoftVector(114); @@ -1313,7 +1389,51 @@ TCHFACCHL1Decoder::TCHFACCHL1Decoder( } } +ViterbiBase *newViterbi(AMRMode mode) +{ + switch (mode) { + case TCH_AFS12_2: return new ViterbiTCH_AFS12_2(); + case TCH_AFS10_2: return new ViterbiTCH_AFS10_2(); + case TCH_AFS7_95: return new ViterbiTCH_AFS7_95(); + case TCH_AFS7_4: return new ViterbiTCH_AFS7_4(); + case TCH_AFS6_7: return new ViterbiTCH_AFS6_7(); + case TCH_AFS5_9: return new ViterbiTCH_AFS5_9(); + case TCH_AFS5_15: return new ViterbiTCH_AFS5_15(); + case TCH_AFS4_75: return new ViterbiTCH_AFS4_75(); + case TCH_FS: return new ViterbiR2O4(); + default: assert(0); + } +}; +void TCHFRL1Decoder::setAmrMode(AMRMode wMode) +{ + mAMRMode = wMode; + int kd = gAMRKd[wMode]; // decoded payload size. + mTCHD.resize(kd); + mPrevGoodFrame.resize(kd); + mPrevGoodFrame.zero(); // When switching modes the contents of this are garbage, so zero. + mNumBadFrames = 0; + setViterbi(wMode); + if (wMode == TCH_FS) { + assert(kd == 260); + mTCHU.resize(189); + //mClass1_c.dup(mC.head(378)); // no longer used + mClass1A_d.dup(mTCHD.head(50)); + //mClass2_c.dup(mC.segment(378,78)); // no longer used. + mTCHParity = Parity(0x0b,3,50); + } else { + mTCHU.resize(kd+6); // why +6? + mClass1A_d.dup(mTCHD.head(mClass1ALth)); + mTCHParity = Parity(0x06f,6,gAMRClass1ALth[wMode]); + mAMRBitOrder = gAMRBitOrder[wMode]; + mClass1ALth = gAMRClass1ALth[wMode]; + mClass1BLth = gAMRKd[wMode] - gAMRClass1ALth[wMode]; + mTCHUC.resize(gAMRTCHUCLth[wMode]); + mPuncture = gAMRPuncture[wMode]; + mPunctureLth = gAMRPunctureLth[wMode]; + setViterbi(wMode); //mViterbiSet.getViterbi(wMode); + } +} void TCHFACCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) @@ -1325,23 +1445,25 @@ void TCHFACCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) } return; // done } - OBJLOG(DEBUG) << "TCHFACCHL1Decoder " << inBurst; + OBJLOG(DEBUG) << "TCHFACCHL1Decoder " << inBurst <encoder()->mEncryptionAlgorithm = mEncryptionAlgorithm; } } - if (ok) { + + if (okFACCH) { // This frame was stolen for sure. OBJLOG(DEBUG) <<"TCHFACCHL1Decoder good FACCH frame"; - countGoodFrame(); + //countGoodFrame(); mD.LSB8MSB(); // This also resets T3109. handleGoodFrame(); - } else { - OBJLOG(DEBUG) <<"TCHFACCHL1Decoder bad FACCH frame"; - countBadFrame(); } } // Always feed the traffic channel, even on a stolen frame. - // decodeTCH will handle the GSM 06.11 bad frmae processing. - bool traffic = decodeTCH(stolen); - if (traffic) { + // decodeTCH will handle the GSM 06.11 bad frame processing. + // (pat) If the frame was truly stolen but was too corrupt to decrypt we dont want to push it + // into the audio frame because there are only 3 parity bits on the audio frame so the chance of + // misinterpreting it is significant. To try to reduce that probability I am adding a totally + // arbitrary check on the number of stealing bits that were set; I made this number up from thin air. + bool traffic = decodeTCH(okFACCH || stolenbits > 4,&mC); + // Now keep statistics... + if (okFACCH) { + countStolenFrame(4); + countBER(mVCoder.getBEC(),378); + } else if (traffic) { OBJLOG(DEBUG) <<"TCHFACCHL1Decoder good TCH frame"; - countGoodFrame(); + countGoodFrame(4); + countBER(mVCoder.getBEC(),378); // Don't let the channel timeout. ScopedLock lock(mLock); mT3109.set(); } - else countBadFrame(); + else countBadFrame(4); - return true; + return true; // note: result not used by this class. } @@ -1488,7 +1645,6 @@ void TCHFACCHL1Decoder::restoreMi() } } - void TCHFACCHL1Decoder::decrypt(int B) { // decrypt x @@ -1518,7 +1674,8 @@ void TCHFACCHL1Decoder::decrypt(int B) } -void TCHFACCHL1Decoder::deinterleave(int blockOffset ) + +void TCHFACCHL1Decoder::deinterleaveTCH(int blockOffset ) { OBJLOG(DEBUG) <<"TCHFACCHL1Decoder blockOffset=" << blockOffset; for (int k=0; k<456; k++) { @@ -1529,11 +1686,140 @@ void TCHFACCHL1Decoder::deinterleave(int blockOffset ) } } +void TCHFACCHL1Decoder::addToSpeechQ(AudioFrame *newFrame) { mSpeechQ.write(newFrame); } +// (pat) See GSM 6.12 5.2 and 5.03 table 2 (in section 5.4) +// I did not get this to work in that I did not see any silence frames. +static bool isSIDFrame(BitVector &frame) +{ + // A SID frame is marked by all zeros in particular positions in the RPE pulse data. + // If all the above bits are 0, it is a SID frame. + const char *frameMarker[4] = { + // There are 13 RPE pulses per frame. + // In the first three sub-frames, two bits of each RPE pulse are considered. + // I dont know which direction the bits go within each variable. + "00x00x00x00x00x00x00x00x00x00x00x00x00x", + "00x00x00x00x00x00x00x00x00x00x00x00x00x", + "00x00x00x00x00x00x00x00x00x00x00x00x00x", + // In the fourth sub-frame bit one is included only for the first 4 RPE pulses (numbers 64-67 inclusive) + "00x00x00x00x0xx0xx0xx0xx0xx0xx0xx0xx0xx" }; + //"x00x00x00x00x00x00x00x00x00x00x00x00x00", + //"x00x00x00x00x00x00x00x00x00x00x00x00x00", + //"x00x00x00x00x00x00x00x00x00x00x00x00x00", + //"x00x00x00x00xx0xx0xx0xx0xx0xx0xx0xx0xx0" + if (0) { + // Print out the SID bits + char buf[200], *bp = buf; + for (unsigned f = 0; f < 4; f++) { // For each voice sub-frame + const char *fp = frame.begin() + 36 + 17 + f * 56; // RPE params start at bit 17. + const char *zb = frameMarker[f]; + for (; *zb; zb++) { + if (*zb == '0') *bp++ = '0' + *fp++; + } + } + *bp = 0; + printf("SID=%s\n",buf); + } -bool TCHFACCHL1Decoder::decodeTCH(bool stolen) + for (unsigned f = 0; f < 4; f++) { // For each voice sub-frame + const char *fp = frame.begin() + 36 + 17 + f * 56; // RPE params start at bit 17. + const char *zb = frameMarker[f]; + for (; *zb; zb++, fp++) { + if (*zb != 'x') { + if (*fp != 0) { return false; } // Not a SID frame. + } + } + } + return true; // All the non-X bits were 0 so it is a SID frame. +} + +// GSM Full Rate Speech Frame GSM 6.10 1.7 +struct BitPos { unsigned start; unsigned length; }; +static BitPos GSMFRS_LAR_Positions[8] = { // Log Area Ratio bit positions within full rate speech frame. + { 0, 6 }, + { 6, 6 }, + { 12, 5 }, + { 17, 5 }, + { 22, 4 }, + { 26, 4 }, + { 30, 3 }, + { 33, 3 } + }; + +struct GSMFRSpeechFrame { + BitVector &fr; + static const unsigned frsHeaderSize = 36; + static const unsigned frsSubFrameSize = 56; + GSMFRSpeechFrame(BitVector &other) : fr(other) {} + unsigned getLAR(unsigned n) { + assert(n >= 1 && n <= 8); + BitPos lar = GSMFRS_LAR_Positions[n-1]; + return fr.peekField(lar.start,lar.length); + } + void setLAR(unsigned n, unsigned value) { + assert(n >= 1 && n <= 8); + BitPos lar = GSMFRS_LAR_Positions[n-1]; + fr.fillField(lar.start,value,lar.length); + } + void setLTPLag(unsigned subFrame /*0..3*/, unsigned value) { + fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 0, value, 7); + } + void setLTPGain(unsigned subFrame, unsigned value) { + fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 7, value, 2); + } + void setRPEGridPosition(unsigned subFrame, unsigned value) { + fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 9, value, 2); + } + int getBlockAmplitude(unsigned subFrame) { + return (int) fr.peekField(frsHeaderSize + frsSubFrameSize * subFrame + 11, 6); + } + void setBlockAmplitude(unsigned subFrame, unsigned value) { + fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 11, value, 6); + } + void setLPEPulse(unsigned subFrame, unsigned pulseIndex /*1..13*/ , unsigned value) { + assert(pulseIndex >= 1 && pulseIndex <= 13); + fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 17 + 3*(pulseIndex-1), value, 3); + } +}; + +// (pat 1-2014) Make the BitVector into a silence frame. +// GSM 6.11 section 6 table 1 describes the silence frame, 6.10 1.7 the bit positions. +static void createSilenceFrame(BitVector &frame) +{ + GSMFRSpeechFrame sf(frame); + sf.setLAR(1,42); + sf.setLAR(2,39); + sf.setLAR(3,21); + sf.setLAR(4,10); + sf.setLAR(5,9); + sf.setLAR(6,4); + sf.setLAR(7,3); + sf.setLAR(8,2); + for (unsigned f = 0; f <= 3; f++) { + sf.setLTPGain(f,0); + sf.setLTPLag(f,40); + sf.setRPEGridPosition(f,1); + sf.setBlockAmplitude(f,0); + sf.setLPEPulse(f,1,3); + sf.setLPEPulse(f,2,4); + sf.setLPEPulse(f,3,3); + sf.setLPEPulse(f,4,4); + sf.setLPEPulse(f,5,4); + sf.setLPEPulse(f,6,3); + sf.setLPEPulse(f,7,3); + sf.setLPEPulse(f,8,3); + sf.setLPEPulse(f,9,4); + sf.setLPEPulse(f,10,4); + sf.setLPEPulse(f,11,4); + sf.setLPEPulse(f,12,3); + sf.setLPEPulse(f,13,3); + } +} + +// The input vector is an argument to make testing easier; we dont have to try to cram it into mC buried in the stack. +bool TCHFRL1Decoder::decodeTCH_GSM(bool stolen,const SoftVector *wC) { // GSM 05.02 3.1.2, but backwards @@ -1548,17 +1834,21 @@ bool TCHFACCHL1Decoder::decodeTCH(bool stolen) // Good or bad, we will be sending *something* to the speech channel. // Allocate it in this scope. - unsigned char * newFrame = new unsigned char[33]; + //unsigned char * newFrame = new unsigned char[33]; + AudioFrame *newFrame = new AudioFrameRtp(TCH_FS); if (!stolen) { // 3.1.2.2 // decode from c[] to u[] - mClass1_c.decode(mVCoder,mTCHU); + //mClass1_c.decode(mVCoder,mTCHU); + //wC->head(378).decode(mVCoder,mTCHU); + mVCoder.decode(wC->head(378),mTCHU); // 3.1.2.2 // copy class 2 bits c[] to d[] - mClass2_c.sliced().copyToSegment(mTCHD,182); + //mClass2_c.sliced().copyToSegment(mTCHD,182); + wC->segment(378,78).sliced().copyToSegment(mTCHD,182); // 3.1.2.1 // copy class 1 bits u[] to d[] @@ -1574,22 +1864,30 @@ bool TCHFACCHL1Decoder::decodeTCH(bool stolen) // 3.1.2.2 // Check the tail bits, too. - unsigned tail = mTCHU.peekField(185,4); + // (pat) Update: No we do not want to check the tail bits, because one of these + // being bad would cause discarding the vector. + //unsigned tail = mTCHU.peekField(185,4); - OBJLOG(DEBUG) <<"TCHFACCHL1Decoder c[]=" << mC; + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder c[]=" << mC.begin()<<"="<< mC; + //OBJLOG(DEBUG) <<"TCHFACCHL1Decoder mclass1_c[]=" << mClass1_c.begin()<< "="< 1) { xmax -= 4; } + if (xmax < 0) xmax = 0; + sf.setBlockAmplitude(f,xmax); + // randomize grid positions + sf.setRPEGridPosition(f,random()); + if (xmax == 0) { + // Dont kill the LTP gain until xmax is 0, or it sounds cruddy. And it still sounds cruddy anyway. + // mPrevGoodFrame.fillField(36 + 7 + f*56,0,2); + createSilenceFrame(mPrevGoodFrame); + mNumBadFrames = 32; // So we dont have to do this again... + } + } + } + } else { + printf("found SID frame\n"); fflush(stdout); + } + newFrame->append(mPrevGoodFrame); + } else { + //printf("good xmax=%u\n", (unsigned) mPrevGoodFrame.peekField(36 + 11,6)); + } + + // Good or bad, we must feed the speech channel. + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sending" <segment(0, wC->size() - 8); // 8 id bits + cMinus8.copyUnPunctured(mTCHUC, mPuncture, mPunctureLth); + + // 3.9.4.4 + // decode from uc[] to u[] + mViterbi->decode(mTCHUC,mTCHU); + + // 3.9.4.3 -- class 1a bits in u[] to d[] + for (unsigned k=0; k < mClass1ALth; k++) { + mTCHD[k] = mTCHU[k]; + } + + // 3.9.4.3 -- class 1b bits in u[] to d[] + for (unsigned k=0; k < mClass1BLth; k++) { + mTCHD[k+mClass1ALth] = mTCHU[k+mClass1ALth+6]; + } + + // 3.9.4.3 + // check parity of class 1A + unsigned sentParity = (~mTCHU.peekField(mClass1ALth,6)) & 0x3f; + BitVector2 class1A = mTCHU.segment(0, mClass1ALth); + unsigned calcParity = class1A.parity(mTCHParity) & 0x3f; + + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder c[]=" << *wC; // Does a copy. Gotta love it. + //OBJLOG(DEBUG) <<"TCHFACCHL1Decoder uc[]=" << mTCHUC; + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder u[]=" << mTCHU; + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder d[]=" << mTCHD; + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sentParity=" << sentParity + << " calcParity=" << calcParity; + + good = sentParity == calcParity; + if (good) { + // Undo Um's importance-sorted bit ordering. + // See GSM 05.03 3.9.4.2 and Tables 7-14. + //BitVector2 payload = mAmrVFrame.payload(); + //mTCHD.unmap(mAMRBitOrder,mKd,payload); + //mAmrVFrame.pack(newFrame->begin()); + //// Save a copy for bad frame processing. + //mAmrPrevGoodFrame.clone(mAmrVFrame); + + mTCHD.unmap(mAMRBitOrder,mPrevGoodFrame.size(),mPrevGoodFrame); // Put the completed decoded data in mPrevGoodFrame. + newFrame->append(mPrevGoodFrame); // And copy it into the RTP audio frame. + } + } + + // We end up here for bad frames. + // We also jump here directly for stolen frames. + if (!good) { + // FIXME: This cannot be correct for AMR. +#if FIXME + // Bad frame processing, GSM 06.11. + // Attenuate block amplitudes and randomize grid positions. + // The spec give the bit-packing format in GSM 06.10 1.7. + // Note they start counting bits from 1, not 0. int xmax = 0; for (unsigned i=0; i<4; i++) { - xmax += vbits.peekField(48+i*56-1,6); + xmax += mPrevGoodFrame.peekField(48+i*56-1,6); } xmax /= 4; // "Age" the frame. for (unsigned i=0; i<4; i++) { // decrement xmax if (xmax>0) xmax--; - vbits.fillField(48+i*56-1,xmax,6); + mPrevGoodFrame.fillField(48+i*56-1,xmax,6); // randomize grid positions - vbits.fillField(46+i*56-1,random(),2); + mPrevGoodFrame.fillField(46+i*56-1,random(),2); } - mPrevGoodFrame.pack(newFrame); +#endif + newFrame->append(mPrevGoodFrame); } // Good or bad, we must feed the speech channel. - mSpeechQ.write(newFrame); - + OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sending" <dispatch(); @@ -1639,22 +2067,56 @@ void GSM::TCHFACCHL1EncoderRoutine( TCHFACCHL1Encoder * encoder ) } +// (pat) Leaving this here as a comment. +//GSMFRL1Encoder::GSMFRL1Encoder() : +// mTCHU(189),mTCHD(260), +// mClass1_c(mC.head(378)), +// mClass1A_d(mTCHD.head(50)), +// mClass2_d(mTCHD.segment(182,78)), +// mTCHParity(0x0b,3,50) +//{ +//} +void TCHFRL1Encoder::setAmrMode(AMRMode wMode) +{ + assert(wMode <= TCH_FS); + mAMRMode = wMode; + int kd = gAMRKd[wMode]; // The decoded payload size. + mTCHRaw.resize(kd); + mTCHD.resize(kd); + if (wMode == TCH_FS) { + mTCHU.resize(189); + mClass1_c.dup(mC.head(378)); + mClass1A_d.dup(mTCHD.head(50)); + mClass2_d.dup(mTCHD.segment(182,78)); + mTCHParity = Parity(0x0b,3,50); + } else { + mTCHU.resize(kd+6); + mAMRBitOrder = gAMRBitOrder[wMode]; + mClass1ALth = gAMRClass1ALth[wMode]; + mClass1BLth = kd - gAMRClass1ALth[wMode]; + mTCHUC.resize(gAMRTCHUCLth[wMode]); + mPuncture = gAMRPuncture[wMode]; + mPunctureLth = gAMRPunctureLth[wMode]; + mTCHParity = Parity(0x06f,6,gAMRClass1ALth[wMode]); + setViterbi(wMode); //mViterbi = mViterbiSet.getViterbi(wMode); + } +} + + +// TCH_FS TCHFACCHL1Encoder::TCHFACCHL1Encoder( unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC *wParent) :XCCHL1Encoder(wCN, wTN, wMapping, wParent), - mPreviousFACCH(true),mOffset(0), - mTCHU(189),mTCHD(260), - mClass1_c(mC.head(378)),mClass1A_d(mTCHD.head(50)),mClass2_d(mTCHD.segment(182,78)), - mTCHParity(0x0b,3,50) + mPreviousFACCH(true),mOffset(0) { for(int k = 0; k<8; k++) { - mI[k] = BitVector(114); - mE[k] = BitVector(114); + mI[k].resize(114); + mE[k].resize(114); // Fill with zeros just to make Valgrind happy. mI[k].fill(0); mE[k].fill(0); @@ -1662,8 +2124,6 @@ TCHFACCHL1Encoder::TCHFACCHL1Encoder( } - - void TCHFACCHL1Encoder::start() { L1Encoder::start(); @@ -1676,24 +2136,30 @@ void TCHFACCHL1Encoder::start() void TCHFACCHL1Encoder::open() { - // There was over stuff here at one time to justify overriding the default. + // There was more stuff here at one time to justify overriding the default. // But it's gone now. XCCHL1Encoder::open(); mPreviousFACCH = true; } -void TCHFACCHL1Encoder::encodeTCH(const VocoderFrame& vFrame) -{ - // GSM 05.02 3.1.2 +void TCHFRL1Encoder::encodeTCH_GSM(const AudioFrame* aFrame) +{ + assert(mTCHRaw.size() == 260 && mTCHD.size() == 260); + // GSM 05.03 3.1.2 OBJLOG(DEBUG) <<"TCHFACCHL1Encoder"; + // The incoming ByteVector is an RTP payload type 3 (GSM), which uses the standard 4 bit RTP header. + AudioFrameRtp rtpFrame(TCH_FS,aFrame); + rtpFrame.getPayload(&mTCHRaw); // Get the RTP frame payload into a BitVector2. + mTCHRaw.map(g610BitOrder,260,mTCHD); + // Reorder bits by importance. // See GSM 05.03 3.1 and Table 2. - vFrame.payload().map(g610BitOrder,260,mTCHD); + //mGsmVFrame.payload().map(g610BitOrder,260,mTCHD); // 3.1.2.1 -- parity bits - BitVector p = mTCHU.segment(91,3); + BitVector2 p = mTCHU.segment(91,3); mTCHParity.writeParityWord(mClass1A_d,p); // 3.1.2.1 -- copy class 1 bits d[] to u[] @@ -1707,7 +2173,8 @@ void TCHFACCHL1Encoder::encodeTCH(const VocoderFrame& vFrame) for (unsigned k=185; k<=188; k++) mTCHU[k]=0; // 3.1.2.2 -- encode u[] to c[] for class 1 - mTCHU.encode(mVCoder,mClass1_c); + //mTCHU.encode(mVCoder,mClass1_c); + mVCoder.encode(mTCHU,mClass1_c); // 3.1.2.2 -- copy class 2 d[] to c[] mClass2_d.copyToSegment(mC,378); @@ -1716,8 +2183,54 @@ void TCHFACCHL1Encoder::encodeTCH(const VocoderFrame& vFrame) // and ready for the interleaver. } +void TCHFRL1Encoder::encodeTCH_AFS(const AudioFrame* aFrame) +{ + // We dont support SID frames. + // GSM 05.02 3.9 + OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH_AFS"; + // Reorder bits by importance while copying speech frame to d[] + // See GSM 05.03 3.9.4.2 and Tables 7-14. + //mAmrVFrame.payload().map(mAMRBitOrder, mKd, mTCHD); + AudioFrameRtp rtpFrame(mAMRMode,aFrame); + rtpFrame.getPayload(&mTCHRaw); + mTCHRaw.map(mAMRBitOrder, mTCHD.size(), mTCHD); + + // 3.9.4.3 -- class 1a bits in d[] to u[] + for (unsigned k=0; k < mClass1ALth; k++) { + mTCHU[k] = mTCHD[k]; + } + + // 3.9.4.3 -- parity bits from d[] to u[] + BitVector2 pFrom = mTCHD.segment(0, mClass1ALth); + BitVector2 pTo = mTCHU.segment(mClass1ALth, 6); + mTCHParity.writeParityWord(pFrom, pTo); + + // 3.9.4.3 -- class 1b bits in d[] to u[] + for (unsigned k=0; k < mClass1BLth; k++) { + mTCHU[k+mClass1ALth+6] = mTCHD[k+mClass1ALth]; + } + + // 3.9.4.4 -- encode u[] to uc[] + mViterbi->encode(mTCHU,mTCHUC); + + // 3.9.4.4 -- copy uc[] to c[] with puncturing + BitVector2 cMinus8 = mC.segment(0, mC.size()-8); // 8 id bits + mTCHUC.copyPunctured(cMinus8, mPuncture, mPunctureLth); + + // So the encoded speech frame is now in c[] + // and ready for the interleaver. + // Puncturing brought the frame size to 448 bits, regardless of mode. + // TCH_AFS interleaver (3.9.4.5) is same as TCH/FS (3.1.3). + // TCH_AFS mapper (3.9.4.6) is same as TCH/FS (3.1.4). +} + +void TCHFRL1Encoder::encodeTCH(const AudioFrame* aFrame) +{ + // (pat) Slight weirdness to avoid modifying existing GSM_FR code. + if (mAMRMode == TCH_FS) { encodeTCH_GSM(aFrame); } else { encodeTCH_AFS(aFrame); } +} void TCHFACCHL1Encoder::sendFrame( const L2Frame& frame ) @@ -1782,17 +2295,17 @@ void TCHFACCHL1Encoder::dispatch() delete fFrame; // Flush the vocoder FIFO to limit latency. while (mSpeechQ.size()>0) delete mSpeechQ.read(); - } else if (VocoderFrame *tFrame = mSpeechQ.readNoBlock()) { + } else if (AudioFrame *tFrame = mSpeechQ.readNoBlock()) { OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH " << *tFrame; // Encode the speech frame into c[] as per GSM 05.03 3.1.2. - encodeTCH(*tFrame); + encodeTCH(tFrame); delete tFrame; OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH c[]=" << mC; } else { // We have no ready data but must send SOMETHING. if (!mPreviousFACCH) { // This filler pattern was captured from a Nokia 3310, BTW. - static const BitVector fillerC("110100001000111100000000111001111101011100111101001111000000000000110111101111111110100110101010101010101010101010101010101010101010010000110000000000000000000000000000000000000000001101001111000000000000000000000000000000000000000000000000111010011010101010101010101010101010101010101010101001000011000000000000000000110100111100000000111001111101101000001100001101001111000000000000000000011001100000000000000000000000000000000000000000000000000000000001"); + static const BitVector2 fillerC("110100001000111100000000111001111101011100111101001111000000000000110111101111111110100110101010101010101010101010101010101010101010010000110000000000000000000000000000000000000000001101001111000000000000000000000000000000000000000000000000111010011010101010101010101010101010101010101010101001000011000000000000000000110100111100000000111001111101101000001100001101001111000000000000000000011001100000000000000000000000000000000000000000000000000000000001"); fillerC.copyTo(mC); } else { // FIXME -- This could be a lot more efficient. @@ -1889,7 +2402,7 @@ void TCHFACCHL1Encoder::interleave31(int blockOffset) } -bool TCHFACCHL1Decoder::uplinkLost() const +bool L1Decoder::uplinkLost() const { ScopedLock lock(mLock); return mT3109.expired(); @@ -1910,22 +2423,32 @@ void SACCHL1FEC::setPhy(float RSSI, float timingError, double wTimestamp) } +void MSPhysReportInfo::sacchInit() +{ + mActualMSTiming = 0.0F; + // (pat) 6-2013: Bug fix: RSSI must be inited each time SACCH is opened, and to RSSITarget, not to 0. + float RSSITarget = gConfig.getNum("GSM.Radio.RSSITarget"); + mRSSI= RSSITarget; + mActualMSPower = RSSITarget; + mRSSICount = 0; + mTimingError = 0; + mTimestamp = 0; +} void SACCHL1Decoder::open() { OBJLOG(DEBUG) << "SACCHL1Decoder"; - XCCHL1Decoder::open(); + XCCHL1Decoder::open(); // (pat) This appears to map to L1Decoder::open() // Set initial defaults for power and timing advance. // We know the handset sent the RACH burst at max power and 0 timing advance. - mActualMSPower = 33; - mActualMSTiming = 0; + sacchInit(); // Measured values should be set after opening with setPhy. } -void SACCHL1Decoder::setPhy(float wRSSI, float wTimingError, double wTimestamp) +void MSPhysReportInfo::setPhy(float wRSSI, float wTimingError, double wTimestamp) { // Used to initialize L1 phy parameters. mRSSI=wRSSI; @@ -1934,13 +2457,14 @@ void SACCHL1Decoder::setPhy(float wRSSI, float wTimingError, double wTimestamp) OBJLOG(INFO) << "SACCHL1Decoder RSSI=" << wRSSI << " timingError=" << wTimingError << " timestamp=" << wTimestamp; } -void SACCHL1Decoder::setPhy(const SACCHL1Decoder& other) +void MSPhysReportInfo::setPhy(const SACCHL1Decoder& other) { // Used to initialize a new SACCH L1 phy parameters // from those of a preexisting established channel. mActualMSPower = other.mActualMSPower; mActualMSTiming = other.mActualMSTiming; mRSSI=other.mRSSI; + mRSSICount = other.mRSSICount; mTimingError=other.mTimingError; mTimestamp=other.mTimestamp; OBJLOG(INFO) << "SACCHL1Decoder actuals RSSI=" << mRSSI << " timingError=" << mTimingError @@ -1979,6 +2503,15 @@ void SACCHL1Encoder::setPhy(float wRSSI, float wTimingError) " actual=" << actualTiming << " ordered=" << mOrderedMSTiming; } +const char* SACCHL1Encoder::descriptiveString() const +{ + // It is a const wannabe. + if (mSacchDescriptiveString.length() == 0) { + Unconst(this)->mSacchDescriptiveString = string(L1Encoder::descriptiveString()) + "-SACCH"; + } + return mSacchDescriptiveString.c_str(); +} + void SACCHL1Encoder::setPhy(const SACCHL1Encoder& other) { @@ -1991,12 +2524,10 @@ void SACCHL1Encoder::setPhy(const SACCHL1Encoder& other) - - SACCHL1Encoder::SACCHL1Encoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, SACCHL1FEC *wParent) :XCCHL1Encoder(wCN,wTN,wMapping,(L1FEC*)wParent), mSACCHParent(wParent), - mOrderedMSPower(33),mOrderedMSTiming(0) + mOrderedMSPower(cInitialPower),mOrderedMSTiming(0) { } @@ -2004,7 +2535,7 @@ void SACCHL1Encoder::open() { OBJLOG(INFO) <<"SACCHL1Encoder"; XCCHL1Encoder::open(); - mOrderedMSPower = 33; + mOrderedMSPower = cInitialPower; mOrderedMSTiming = 0; } @@ -2057,8 +2588,8 @@ void SACCHL1Encoder::sendFrame(const L2Frame& frame) if (mOrderedMSTiming<0.0F) mOrderedMSTiming=0.0F; else if (mOrderedMSTiming>maxTiming) mOrderedMSTiming=maxTiming; OBJLOG(DEBUG) << "SACCHL1Encoder timingError=" << timingError - << " actual=" << actualTiming << " ordered=" << mOrderedMSTiming - << " target=" << targetMSTiming; + << " actualTA=" << actualTiming << " orderedTA=" << mOrderedMSTiming + << " targetTA=" << targetMSTiming; // } // Write physical header into mU and then call base class. @@ -2073,9 +2604,6 @@ void SACCHL1Encoder::sendFrame(const L2Frame& frame) } - - - void CBCHL1Encoder::sendFrame(const L2Frame& frame) { // Sync to (FN/51)%8==0 at the start of a new block. @@ -2086,7 +2614,79 @@ void CBCHL1Encoder::sendFrame(const L2Frame& frame) XCCHL1Encoder::sendFrame(frame); } +#ifdef TESTTCHL1FEC +BitVector2 randomBitVector(int n) +{ + BitVector2 t(n); + for (int i = 0; i < n; i++) t[i] = random()%2; + return t; +} + +void TestTCHL1FEC() +{ + const TDMAMapping hack((TypeAndOffset)0,0,0,0,0,0,0,0); + TCHFACCHL1Encoder encoder(0, 0, hack, 0); + TCHFACCHL1Decoder decoder(0, 0, hack, 0); + for (unsigned modei = 0; modei <= TCH_FS; modei++) { + int modeii = modei == 0 ? TCH_FS : modei-1; // test full rate GSM first. + AMRMode mode = (AMRMode)modeii; + unsigned inSize = gAMRKd[mode]; + bool ok = true; + cout <append(r); + encoder.encodeTCH(aFrame); // Leaves the result in mC + LOG(INFO) < #include "BitVector.h" +#include "ViterbiR204.h" +#include "AmrCoder.h" #include "GSMCommon.h" #include "GSMTransfer.h" #include "GSMTDMA.h" -#include "a53.h" +#include #include "A51.h" #include "GSM610Tables.h" +#include "GSM503Tables.h" #include @@ -44,6 +48,7 @@ class ARFCNManager; namespace GSM { +ViterbiBase *newViterbi(AMRMode mode); /* forward refs */ class GSMConfig; @@ -125,7 +130,7 @@ class L1Encoder { // (pat) Moved to classes that need the convolutional coder. //ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code - char mDescriptiveString[100]; + std::string mDescriptiveString; public: @@ -152,7 +157,7 @@ class L1Encoder { ARFCNManager *getRadio() { return mDownstream; } // Used by XCCHEncoder - void transmit(BitVector *mI, BitVector *mE, const int *qbits); + void transmit(BitVector2 *mI, BitVector2 *mE, const int *qbits); /**@name Accessors. */ //@{ @@ -193,7 +198,7 @@ class L1Encoder { /** Start the service loop thread, if there is one. */ virtual void start() { mRunning=true; } - const char* descriptiveString() const { return mDescriptiveString; } + virtual const char* descriptiveString() const { return mDescriptiveString.c_str(); } L1FEC* parent() { return mParent; } @@ -224,6 +229,27 @@ class L1Encoder { }; +struct HandoverRecord { + bool mValid; + float mhrRSSI; + float mhrTimingError; + double mhrTimestamp; + HandoverRecord() : mValid(false) {} + HandoverRecord(float rssi,float te, double timestamp) : mValid(true), mhrRSSI(rssi), mhrTimingError(te), mhrTimestamp(timestamp) {} +}; + +struct DecoderStats { + float mAveFER; + float mAveBER; + float mAveSNR; + unsigned mSNRCount; // Number of SNR samples taken. + int mStatTotalFrames; + int mStatStolenFrames; + int mStatBadFrames; + void decoderStatsInit(); + void countSNR(const RxBurst &burst); +}; + /** An abstract class for L1 decoders. @@ -231,21 +257,23 @@ class L1Encoder { // (pat) base class for: RACHL1Decoder, XCCHL1Decoder // It would be more elegant to split this into two classes: a base class // for both GPRS and RR, and the rest of this class that is RR specific. + // Update: The thing to do here is to take RACCH out because it shares no code with this class. */ class L1Decoder { protected: - // (pat) Not used for GPRS + // (pat) Not used for GPRS. Or for RACCH. It should be in XCCHL1Decoder. SAPMux * mUpstream; /**@name Mutex-controlled state information. */ //@{ mutable Mutex mLock; ///< access control /**@name Timers from GSM 04.08 11.1.2 */ + // pat believes these timers should be in XCCHL1Decoder. //@{ Z100Timer mT3101; ///< timer for new channels - Z100Timer mT3109; ///< timer for existing channels + Z100Timer mT3109; ///< timer for existing uplink channels, uplinkLost on expiry Z100Timer mT3111; ///< timer for reuse of a closed channel Z100Timer mT3103; ///< timer for handover //@} @@ -256,9 +284,14 @@ class L1Decoder { // Yes, I realize we're violating our own rules here. -- DAB //@{ volatile bool mRunning; ///< true if all required service threads are started - volatile float mFER; ///< current FER estimate - static const int mFERMemory=20; ///< FER decay time, in frames + //volatile float mFER; ///< current FER estimate + //static const int mFERMemory=208; ///< FER decay time, in frames + // (pat) Uh, no. It is in frames for control frames, but in frames/4 for TCH frames. + // (pat) Upped it from 20 frames to 208 frames, ie, about a second. + static const int mSNRMemory = 208; volatile bool mHandoverPending; ///< if true, we are decoding handover bursts + volatile unsigned mHandoverRef; + HandoverRecord mHandoverRecord; //@} /**@name Parameters fixed by the constructor, not requiring mutex protection. */ @@ -277,6 +310,7 @@ class L1Decoder { unsigned char mKc[8]; int mFN[8]; + DecoderStats mDecoderStats; public: @@ -292,7 +326,7 @@ class L1Decoder { mT3103(gConfig.getNum("GSM.Timer.T3103")), mActive(false), mRunning(false), - mFER(0.0F), + //mFER(0.0F), mCN(wCN),mTN(wTN), mMapping(wMapping),mParent(wParent), mEncrypted(ENCRYPT_NO), @@ -324,6 +358,7 @@ class L1Decoder { Returns true if T3111 is not active. */ bool active() const; + //bool reallyActive() const { return mActive; } /** Return true if any timer is expired. */ bool recyclable() const; @@ -336,7 +371,8 @@ class L1Decoder { } /** Total frame error rate since last open(). */ - float FER() const { return mFER; } + float FER() const { return mDecoderStats.mAveFER; } // (pat) old routine, switched to new variable. + DecoderStats getDecoderStats() const { return mDecoderStats; } // (pat) new routine, more stats /** Return the multiplexing parameters. */ const TDMAMapping& mapping() const { return mMapping; } @@ -352,10 +388,14 @@ class L1Decoder { //@} /** Control the processing of handover access busts. */ - void handoverPending(bool flag) + // OK to pass reference to HandoverRecord since this struct is immortal. + HandoverRecord &handoverPending(bool flag, unsigned handoverRef) { - if (flag) mT3103.set(); + LOG(INFO) << LOGVAR(flag); + if (flag) { mT3103.set(); } else { mT3103.reset(); } mHandoverPending=flag; + mHandoverRef=handoverRef; + return mHandoverRecord; } public: @@ -376,8 +416,13 @@ class L1Decoder { virtual void start() { mRunning=true; } public: - void countGoodFrame(); - void countBadFrame(); + void countGoodFrame(unsigned nframes=1); + void countBadFrame(unsigned nframes = 1); + void countStolenFrame(unsigned nframes = 1); + void countBER(unsigned bec, unsigned frameSize); + bool uplinkLost() const; + // TODO: The RACH does not have an encoder sibling, we should put a descriptive string for it too. + virtual const char* descriptiveString() const { return sibling() ? sibling()->descriptiveString() : "?decoder?"; } bool decrypt_maybe(string wIMSI, int wA5Alg); unsigned char *kc() { return mKc; } @@ -531,7 +576,7 @@ class L1FEC { { if (mDecoder) mDecoder->upstream(mux); } /** set encoder and decoder handover pending mode. */ - void handoverPending(bool flag); + HandoverRecord& handoverPending(bool flag, unsigned handoverRef); /**@name Ganged actions. */ //@{ @@ -564,6 +609,7 @@ class L1FEC { { assert(mDecoder); return mDecoder->recyclable(); } bool active() const; // Channel in use? See L1Encoder + //bool reallyActive() const; // Channel really in use? See L1Decoder. // (pat) This lovely function is unsed. // TRXManager.cpp:installDecoder uses L1Decoder::mapping() directly. @@ -574,7 +620,7 @@ class L1FEC { const TDMAMapping& rcvMapping() const { assert(mDecoder); return mDecoder->mapping(); } - const char* descriptiveString() const + virtual const char* descriptiveString() const { assert(mEncoder); return mEncoder->descriptiveString(); } //@} @@ -582,11 +628,13 @@ class L1FEC { //void setDecoder(L1Decoder*me) { mDecoder = me; } //void setEncoder(L1Encoder*me) { mEncoder = me; } ARFCNManager *getRadio() { return mEncoder->getRadio(); } - bool inUseByGPRS() { return mGprsReserved; } + bool inUseByGPRS() const { return mGprsReserved; } void setGPRS(bool reserved, GPRS::PDCHL1FEC *pch) { mGprsReserved = reserved; mGPRSFEC = pch; } L1Decoder* decoder() { return mDecoder; } L1Encoder* encoder() { return mEncoder; } + bool radioFailure() const + { assert(mDecoder); return mDecoder->uplinkLost(); } }; @@ -620,8 +668,8 @@ class RACHL1Decoder : public L1Decoder //@{ ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code Parity mParity; ///< block coder - BitVector mU; ///< u[], as per GSM 05.03 2.2 - BitVector mD; ///< d[], as per GSM 05.03 2.2 + BitVector2 mU; ///< u[], as per GSM 05.03 2.2 + BitVector2 mD; ///< d[], as per GSM 05.03 2.2 //@} // The RACH channel uses an internal FIFO, @@ -673,18 +721,23 @@ void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder*); class SharedL1Encoder { protected: +#ifdef TESTTCHL1FEC + public: +#endif ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code Parity mBlockCoder; - BitVector mC; ///< c[], as per GSM 05.03 2.2 - BitVector mU; ///< u[], as per GSM 05.03 2.2 - //BitVector mDP; ///< d[]:p[] (data & parity) - BitVector mP; ///< p[], as per GSM 05.03 2.2 + BitVector2 mC; ///< c[], as per GSM 05.03 2.2 Data after second encoding step. + BitVector2 mU; ///< u[], as per GSM 05.03 2.2 Data after first encoding step. + //BitVector2 mDP; ///< d[]:p[] (data & parity) public: - BitVector mD; ///< d[], as per GSM 05.03 2.2 Incoming Data. - BitVector mI[4]; ///< i[][], as per GSM 05.03 2.2 Outgoing Data. - BitVector mE[4]; + BitVector2 mD; ///< d[], as per GSM 05.03 2.2 Incoming Data. + private: + BitVector2 mP; ///< p[], as per GSM 05.03 2.2 (pat) no such thing + public: + BitVector2 mI[4]; ///< i[][], as per GSM 05.03 2.2 Outgoing Data. + BitVector2 mE[4]; - /** + /** GSM 05.03 4.2 encoder Encode u[] to c[]. Includes LSB-MSB reversal within each octet. */ @@ -702,7 +755,7 @@ class SharedL1Encoder SharedL1Encoder(); //void encodeFrame41(const L2Frame &frame, int offset); - void encodeFrame41(const BitVector &frame, int offset, bool copy=true); + void encodeFrame41(const BitVector2 &frame, int offset, bool copy=true); void initInterleave(int); }; @@ -717,18 +770,18 @@ class SharedL1Decoder Parity mBlockCoder; public: SoftVector mC; ///< c[], as per GSM 05.03 2.2 - BitVector mU; ///< u[], as per GSM 05.03 2.2 - BitVector mP; ///< p[], as per GSM 05.03 2.2 - BitVector mDP; ///< d[]:p[] (data & parity) + BitVector2 mU; ///< u[], as per GSM 05.03 2.2 + BitVector2 mP; ///< p[], as per GSM 05.03 2.2 + BitVector2 mDP; ///< d[]:p[] (data & parity) public: - BitVector mD; ///< d[], as per GSM 05.03 2.2 + BitVector2 mD; ///< d[], as per GSM 05.03 2.2 SoftVector mE[4]; SoftVector mI[4]; ///< i[][], as per GSM 05.03 2.2 /**@name Handover Access Burst FEC state. */ //@{ Parity mHParity; ///< block coder for handover access bursts - BitVector mHU; ///< u[] for handover access, as per GSM 05.03 4.6 - BitVector mHD; ///< d[] for handover access, as per GSM 05.03 4.6 + BitVector2 mHU; ///< u[] for handover access, as per GSM 05.03 4.6 + BitVector2 mHD; ///< d[] for handover access, as per GSM 05.03 4.6 //@} //@} @@ -745,36 +798,14 @@ class SharedL1Decoder /** Abstract L1 decoder for most control channels -- GSM 05.03 4.1 */ +// (pat) That would be SDCCH, SACCH, and FACCH. TCH goes to TCHFRL1Decoder instead. class XCCHL1Decoder : - public SharedL1Decoder, + virtual public SharedL1Decoder, public L1Decoder { protected: - // Moved to SharedL1Decoder -#if 0 - /**@name FEC state. */ - //@{ - /**@name Normal Burst FEC state. */ - //@{ - Parity mBlockCoder; ///< block coder for normal bursts - SoftVector mI[4]; ///< i[][], as per GSM 05.03 2.2 - SoftVector mC; ///< c[], as per GSM 05.03 2.2 - BitVector mU; ///< u[], as per GSM 05.03 2.2 - BitVector mP; ///< p[], as per GSM 05.03 2.2 - BitVector mDP; ///< d[]:p[] (data & parity) - BitVector mD; ///< d[], as per GSM 05.03 2.2 - //@} - /**@name Handover Access Burst FEC state. */ - //@{ - Parity mHParity; ///< block coder for handover access bursts - BitVector mHU; ///< u[] for handover access, as per GSM 05.03 4.6 - BitVector mHD; ///< d[] for handover access, as per GSM 05.03 4.6 - //@} - //@} -#endif - public: XCCHL1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, @@ -803,23 +834,6 @@ class XCCHL1Decoder : */ virtual bool processBurst(const RxBurst&); - // Moved to SharedL1Encoder. -#if 0 - /** - Deinterleave the i[] to c[]. - This virtual method works for all block-interleaved channels (xCCHs). - A different method is needed for diagonally-interleaved channels (TCHs). - */ - virtual void deinterleave(); - - /** - Decode the frame and send it upstream. - Includes LSB-MSB reversal within each octet. - @return True if frame passed parity check. - */ - bool decode(); -#endif - /** Finish off a properly-received L2Frame in mU and send it up to L2. */ virtual void handleGoodFrame(); }; @@ -843,50 +857,26 @@ class SDCCHL1Decoder : public XCCHL1Decoder { }; - -/** - L1 decoder for the SACCH. - Like any other control channel, but with hooks for power/timing control. -*/ -class SACCHL1Decoder : public XCCHL1Decoder { - - private: - - SACCHL1FEC *mSACCHParent; - volatile float mRSSI; ///< most recent RSSI, dB wrt full scale - volatile float mTimingError; ///< Timing error history in symbols +// This is just the physical info sent on SACCH. The FER() is in the base class used by all channel types, +// and the measurement reports are up in Layer 2. +class MSPhysReportInfo { + protected: // dont know why we bother + unsigned mRSSICount; // Number of reports received so far, used for averaging RSSI and others. + volatile float mRSSI; ///< most recent RSSI, dB wrt full scale (Received Signal Strength derived from our measurement of the received burst). + volatile float mTimingError; ///< Timing error history in symbols (derived from our measurement of the received burst). volatile double mTimestamp; ///< system time of most recent received burst - volatile int mActualMSPower; ///< actual MS tx power in dBm - volatile int mActualMSTiming; ///< actual MS tx timing advance in symbols - + volatile int mActualMSPower; ///< actual MS tx power in dBm (that the MS reported to us) + volatile int mActualMSTiming; ///< actual MS tx timing advance in symbols (that the MS reported to us) + void sacchInit(); public: - - SACCHL1Decoder( - unsigned wCN, - unsigned wTN, - const TDMAMapping& wMapping, - SACCHL1FEC *wParent) - :XCCHL1Decoder(wCN,wTN,wMapping,(L1FEC*)wParent), - mSACCHParent(wParent), - mRSSI(0.0F), - mTimingError(0.0F), - mTimestamp(0.0), - mActualMSPower(0), - mActualMSTiming(0) - { } - - ChannelType channelType() const { return SACCHType; } - + MSPhysReportInfo() : + mRSSICount(0), // irrelevant, see sacchInit + mRSSI(0.0F), // irrelevant, see sacchInit + mTimingError(0.0F), // irrelevant, see sacchInit + mTimestamp(0.0) // irrelevant, see sacchInit + {} int actualMSPower() const { return mActualMSPower; } int actualMSTiming() const { return mActualMSTiming; } - - /** Override open() to set physical parameters with reasonable defaults. */ - void open(); - - /** - Override processBurst to catch the physical parameters. - */ - bool processBurst(const RxBurst&); /** Set pyshical parameters for initialization. */ void setPhy(float wRSSI, float wTimingError, double wTimestamp); @@ -899,14 +889,56 @@ class SACCHL1Decoder : public XCCHL1Decoder { /** Artificially push down RSSI to induce the handset to push more power. */ void RSSIBumpDown(float dB) { mRSSI -= dB; } + /** Timestamp of most recent received burst. */ + double timestamp() const { return mTimestamp; } + /** Timing error of most recent received burst, symbol units. Positive is late; negative is early. */ float timingError() const { return mTimingError; } + void processPhysInfo(const RxBurst &inBurst); + MSPhysReportInfo * getPhysInfo() { return this; } +}; - /** Timestamp of most recent received burst. */ - double timestamp() const { return mTimestamp; } + +/** + L1 decoder for the SACCH. + Like any other control channel, but with hooks for power/timing control. +*/ +class SACCHL1Decoder : public XCCHL1Decoder, public MSPhysReportInfo { + + private: + + SACCHL1FEC *mSACCHParent; + + + public: + + SACCHL1Decoder( + unsigned wCN, + unsigned wTN, + const TDMAMapping& wMapping, + SACCHL1FEC *wParent) + :XCCHL1Decoder(wCN,wTN,wMapping,(L1FEC*)wParent), + mSACCHParent(wParent) + { + // (pat) DONT init any dynamic data here. It is pointless - this object is immortal. + // TODO: on first measurement, make them all the same. + // David says: RACH has an option to ramp up power - should remove that option. + //sacchInit(); + } + + ChannelType channelType() const { return SACCHType; } + + + /** Override open() to set physical parameters with reasonable defaults. */ + void open(); + + /** + Override processBurst to catch the physical parameters. + */ + bool processBurst(const RxBurst&); protected: @@ -929,25 +961,9 @@ class SACCHL1Decoder : public XCCHL1Decoder { /** L1 encoder used for many control channels -- mostly from GSM 05.03 4.1 */ class XCCHL1Encoder : - public SharedL1Encoder, + virtual public SharedL1Encoder, public L1Encoder { - - protected: - - // Moved to SharedL1Encoder -#if 0 - /**@name FEC signal processing state. */ - //@{ - Parity mBlockCoder; ///< block coder for this channel - BitVector mI[4]; ///< i[][], as per GSM 05.03 2.2 - BitVector mC; ///< c[], as per GSM 05.03 2.2 - BitVector mU; ///< u[], as per GSM 05.03 2.2 - BitVector mD; ///< d[], as per GSM 05.03 2.2 - BitVector mP; ///< p[], as per GSM 05.03 2.2 - //@} -#endif - public: XCCHL1Encoder( @@ -968,56 +984,83 @@ class XCCHL1Encoder : /** Send a single L2 frame. */ virtual void sendFrame(const L2Frame&); // Moved to SharedL1Encoder - //virtual void transmit(BitVector *mI); -#if 0 - /** - Encode u[] to c[]. - Includes LSB-MSB reversal within each octet. - */ - void encode(); - - /** - Interleave c[] to i[]. - GSM 05.03 4.1.4. - */ - virtual void interleave(); - - /** - Format i[] into timeslots and send them down for transmission. - Set stealing flags assuming a control channel. - Also updates mWriteTime. - GSM 05.03 4.1.5, 05.02 5.2.3. - */ - virtual void transmit(); -#endif - + //virtual void transmit(BitVector2 *mI); }; + +// downstream flow: +// RTP => transaction->rxFrame(char[] rxFrame) +// updateCallTraffic(): TCHFACCHLogicalChannel *TCH->sendTCH(rxFrame); +// sendTCH packs as VocoderFrame and puts on SpeechQ. +// encodeTCH gets the payload from the VocoderFrame, result left in mC. +// then TCHFACCHL1Encoder::dispatch() sends mC result to radio. +// Alternative: +// Allocate as bytevector from rtp on, and decode to VocoderFrame in sendTCH. +// So speechQ type is ByteVector. +// +// upstream flow: +// speechQ + +class TCHFRL1Encoder : virtual public SharedL1Encoder +{ + // Shared AMR and GSM_FR variables: + ViterbiBase *mViterbi; // Points to current encoder in use. + AMRMode mAMRMode; + Parity mTCHParity; + BitVector2 mPrevGoodFrame; ///< current and previous good frame + + BitVector2 mTCHU; ///< u[], but for traffic + BitVector2 mTCHD; ///< d[], but for traffic + BitVector2 mTCHRaw; ///< Raw data before mapping. + + BitVector2 mClass1A_d; ///< the class 1A part of taffic d[] + BitVector2 mClass1_c; ///< the class 1 part of taffic c[] + BitVector2 mClass2_d; ///< the class 2 part of d[] + + // AMR variables: + const unsigned *mAMRBitOrder; + unsigned mClass1ALth; + unsigned mClass1BLth; + BitVector2 mTCHUC; + const unsigned *mPuncture; + unsigned mPunctureLth; + + void encodeTCH_AFS(const AudioFrame* vFrame); + void encodeTCH_GSM(const AudioFrame* vFrame); + void setViterbi(AMRMode wMode) { + if (mViterbi) { delete mViterbi; } + mViterbi = newViterbi(wMode); + } + public: + unsigned getTCHPayloadSize() { return GSM::gAMRKd[mAMRMode]; } // decoded payload size. + void setAmrMode(AMRMode wMode); + /** Encode a full speed AMR vocoder frame into c[]. */ + void encodeTCH(const AudioFrame* aFrame); // Not that the const does any good. + + // (pat) Irritating and pointless but harmless double-initialization of Parity and BitVector2s. Stupid language. + // (pat) Assume TCH_FS until someone changes the mode to something else. + TCHFRL1Encoder() : mViterbi(0), mTCHParity(0,0,0) { setAmrMode(TCH_FS); } +}; + + /** L1 encoder used for full rate TCH and FACCH -- mostry from GSM 05.03 3.1 and 4.2 */ -class TCHFACCHL1Encoder : public XCCHL1Encoder { - -private: - +class TCHFACCHL1Encoder : + public XCCHL1Encoder, + public TCHFRL1Encoder +{ bool mPreviousFACCH; ///< A copy of the previous stealing flag state. size_t mOffset; ///< Current deinterleaving offset. - BitVector mE[8]; + BitVector2 mE[8]; // (pat) Yes, the mI here duplicates but overrides the same // vector down in XCCHL1Encoder. - BitVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4 - BitVector mTCHU; ///< u[], but for traffic - BitVector mTCHD; ///< d[], but for traffic - BitVector mClass1_c; ///< the class 1 part of taffic c[] - BitVector mClass1A_d; ///< the class 1A part of taffic d[] - BitVector mClass2_d; ///< the class 2 part of d[] + BitVector2 mI[8]; ///< deinterleaving history, 8 blocks instead of 4 - BitVector mFillerC; ///< copy of previous c[] for filling dead time + BitVector2 mFillerC; ///< copy of previous c[] for filling dead time - Parity mTCHParity; - - VocoderFrameFIFO mSpeechQ; ///< input queue for speech frames + AudioFrameFIFO mSpeechQ; ///< input queue for speech frames L2FrameFIFO mL2Q; ///< input queue for L2 FACCH frames @@ -1030,9 +1073,8 @@ public: const TDMAMapping& wMapping, L1FEC* wParent); - /** Enqueue a traffic frame for transmission. */ - void sendTCH(const unsigned char *frame) - { mSpeechQ.write(new VocoderFrame(frame)); } + /** Enqueue a traffic frame for transmission by the FEC to be sent to the radio. */ + void sendTCH(AudioFrame *frame) { mSpeechQ.write(frame); } /** Extend open() to set up semaphores. */ void open(); @@ -1040,11 +1082,8 @@ public: protected: // GSM 05.03, 3.1.3 - void interleave31(int blockOffset); -#if 0 /** Interleave c[] to i[]. GSM 05.03 4.1.4. */ - virtual void interleave31(int blockOffset); -#endif + void interleave31(int blockOffset); /** Encode a FACCH and enqueue it for transmission. */ void sendFrame(const L2Frame&); @@ -1059,45 +1098,89 @@ protected: /** Will start the dispatch thread. */ void start(); - /** Encode a vocoder frame into c[]. */ - void encodeTCH(const VocoderFrame& vFrame); - }; /** The C adapter for pthreads. */ void TCHFACCHL1EncoderRoutine( TCHFACCHL1Encoder * encoder ); -/** L1 decoder used for full rate TCH and FACCH -- mostly from GSM 05.03 3.1 and 4.2 */ -class TCHFACCHL1Decoder : public XCCHL1Decoder { +// TCH full rate decoder. +class TCHFRL1Decoder : virtual public SharedL1Decoder +{ + // Shared AMR and GSM_FR variables: + AMRMode mAMRMode; + Parity mTCHParity; + BitVector2 mPrevGoodFrame; ///< current and previous good frame + unsigned mNumBadFrames; // Number of bad frames in a row. - protected: + BitVector2 mTCHU; ///< u[] (uncoded) in the spec + BitVector2 mTCHD; ///< d[] (data) in the spec + //SoftVector mClass1_c; ///< the class 1 part of c[] + BitVector2 mClass1A_d; ///< the class 1A part of d[] + //SoftVector mClass2_c; ///< the class 2 part of c[] + // AMR variables: + const unsigned *mAMRBitOrder; + unsigned mClass1ALth; + unsigned mClass1BLth; + SoftVector mTCHUC; + const unsigned *mPuncture; + unsigned mPunctureLth; + ViterbiBase *mViterbi; // Points to current decoder in use. SoftVector mE[8]; ///< deinterleaving history, 8 blocks instead of 4 SoftVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4 - BitVector mTCHU; ///< u[] (uncoded) in the spec - BitVector mTCHD; ///< d[] (data) in the spec - SoftVector mClass1_c; ///< the class 1 part of c[] - BitVector mClass1A_d; ///< the class 1A part of d[] - SoftVector mClass2_c; ///< the class 2 part of c[] - VocoderFrame mVFrame; ///< unpacking buffer for current vocoder frame - VocoderFrame mPrevGoodFrame; ///< previous good frame + bool decodeTCH_GSM(bool stolen, const SoftVector *wC); + bool decodeTCH_AFS(bool stolen, const SoftVector *wC); + protected: + virtual void addToSpeechQ(AudioFrame *newFrame) = 0; // Where the upstream result from decodeTCH goes. + void setViterbi(AMRMode wMode) { + if (mViterbi) { delete mViterbi; } + mViterbi = newViterbi(wMode); + } + public: + /** + Decode a traffic frame from TCHI[] and enqueue it. + Return true if there's a good frame. + */ + bool decodeTCH(bool stolen, const SoftVector *wC); // result goes to mSpeechQ + void setAmrMode(AMRMode wMode); - Parity mTCHParity; - - InterthreadQueue mSpeechQ; ///< output queue for speech frames + // (pat) Irritating and pointless but harmless double-initialization of Parity and BitVector2s. Stupid language. + // (pat) Assume TCH_FS until someone changes the mode to something else. + TCHFRL1Decoder() : mTCHParity(0,0,0), mViterbi(0) { setAmrMode(TCH_FS); } +}; +/** L1 decoder used for full rate TCH and FACCH -- mostly from GSM 05.03 3.1 and 4.2 */ +// (pat) This class is basically a switch to steer incoming uplink radio frames in one of three categories +// (FAACH, TCH, handover burst) into either the control or data upstream paths. +// If we are not waiting for handover, the 'stolen' flag inside the radio frame +// indicates whether the frame is FACCH (fast associated control channel message) or TCH (voice traffic) frame. +// Messages (both FACCH and handover) go to XCCHL1Decoder and TCH go to TCHFRL1Decoder. +// fyi: Oh, the TCHFACCHL1Decoder is connected to the XCCHL1Decoder, and the XCCHL1Decoder is connected to the...L1Decoder, +// and the L1Decoder is connected to the...SAPMux, and the SAPMux is connected to the...LogicalChannel, +// and the LogicalChannel is connected to the...L2DL, and the L2DL is connected to the...FACCHL2, +// and the FACCHL2 is connected to the...L2LapDm, and the L2LapDm is connected to the...L3FrameFIFO, +// and the L3FrameFIFO is connected to...LogicalChannel::recv, and thence to whatever procedure in the L3 layers that wants +// to receive these messages, eg MOCController, MTCController, although the L3rewrite will substitute all +// those with the L3 state machine. +class TCHFACCHL1Decoder : + public XCCHL1Decoder, + public TCHFRL1Decoder +{ + protected: + SoftVector mE[8]; ///< deinterleaving history, 8 blocks instead of 4 + SoftVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4 + AudioFrameFIFO mSpeechQ; ///< output queue for speech frames + unsigned stealBitsU[8], stealBitsL[8]; // (pat 1-16-2014) These are single bits; the upper and lower stealing bits found in incoming bursts. public: - TCHFACCHL1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC *wParent); ChannelType channelType() const { return FACCHType; } - /** TCH/FACCH has a special-case writeLowSide. */ void writeLowSideRx(const RxBurst& inBurst); @@ -1110,31 +1193,28 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder { void saveMi(); void restoreMi(); void decrypt(int B); - + /** Deinterleave i[] to c[]. */ - void deinterleave(int blockOffset ); + // (pat) This overrides the deinterleave in SharedL1Decoder. + // Note also that we use SharedL1Decoder::decode() for control frames, but TCHFRL1Decoder::decodeTCH() for TCH frames. + void deinterleaveTCH(int blockOffset ); - // (pat) Routine does not exist. - void replaceFACCH( int blockOffset ); - - /** - Decode a traffic frame from TCHI[] and enqueue it. - Return true if there's a good frame. - */ - bool decodeTCH(bool stolen); + void deinterleave() { assert(0); } // We must not use the inherited SharedL1Decoder::deinterleave(). /** Receive a traffic frame. Non-blocking. Returns NULL if queue is dry. Caller is responsible for deleting the returned array. */ - unsigned char *recvTCH() { return mSpeechQ.read(0); } + AudioFrame *recvTCH() { return mSpeechQ.read(0); } + void addToSpeechQ(AudioFrame *newFrame); // write the audio frame into the mSpeechQ /** Return count of internally-queued traffic frames. */ unsigned queueSize() const { return mSpeechQ.size(); } /** Return true if the uplink is dead. */ - bool uplinkLost() const; + // (pat) 3-29: Moved this higher in the hierarchy so it can be shared with SDCCH. + //bool uplinkLost() const; }; @@ -1190,17 +1270,19 @@ class SCHL1Encoder : public GeneratorL1Encoder { private: ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code Parity mBlockCoder; ///< block parity coder - BitVector mU; ///< u[], as per GSM 05.03 2.2 - BitVector mE; ///< e[], as per GSM 05.03 2.2 - BitVector mD; ///< d[], as per GSM 05.03 2.2 - BitVector mP; ///< p[], as per GSM 05.03 2.2 - BitVector mE1; ///< first half of e[] - BitVector mE2; ///< second half of e[] + BitVector2 mU; ///< u[], as per GSM 05.03 2.2 + BitVector2 mE; ///< e[], as per GSM 05.03 2.2 + BitVector2 mD; ///< d[], as per GSM 05.03 2.2 + BitVector2 mP; ///< p[], as per GSM 05.03 2.2 + BitVector2 mE1; ///< first half of e[] + BitVector2 mE2; ///< second half of e[] public: SCHL1Encoder(L1FEC* wParent); + const char* descriptiveString() const { return "SCH"; } + protected: void generate(); @@ -1220,6 +1302,7 @@ class FCCHL1Encoder : public GeneratorL1Encoder { public: FCCHL1Encoder(L1FEC *wParent); + const char* descriptiveString() const { return "FCCH"; } protected: @@ -1276,6 +1359,7 @@ class BCCHL1Encoder : public NDCCHL1Encoder { BCCHL1Encoder(L1FEC *wParent) :NDCCHL1Encoder(0,0,gBCCHMapping,wParent) {} + const char* descriptiveString() const { return "BCCH"; } private: @@ -1286,13 +1370,14 @@ class BCCHL1Encoder : public NDCCHL1Encoder { /** L1 decoder for the SACCH. Like any other control channel, but with hooks for power/timing control. - The SI5 and SI5 generation will be handled in higher layers. + The SI5 and SI6 generation will be handled in higher layers. */ class SACCHL1Encoder : public XCCHL1Encoder { private: SACCHL1FEC *mSACCHParent; + string mSacchDescriptiveString; // In L1Encoder /**@name Physical header, GSM 04.04 6, 7.1, 7.2 */ //@{ @@ -1304,11 +1389,13 @@ class SACCHL1Encoder : public XCCHL1Encoder { SACCHL1Encoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, SACCHL1FEC *wParent); - void orderedMSPower(int power) { mOrderedMSPower = power; } - void orderedMSTiming(int timing) { mOrderedMSTiming = timing; } + // (pat) commented out: never used. + //void orderedMSPower(int power) { mOrderedMSPower = power; } + //void orderedMSTiming(int timing) { mOrderedMSTiming = timing; } void setPhy(const SACCHL1Encoder&); void setPhy(float RSSI, float timingError); + virtual const char* descriptiveString() const; /** Override open() to initialize power and timing. */ void open(); @@ -1429,7 +1516,7 @@ public: } /** Send a traffic frame. */ - void sendTCH(const unsigned char * frame) + void sendTCH(AudioFrame * frame) { assert(mTCHEncoder); mTCHEncoder->sendTCH(frame); } /** @@ -1438,14 +1525,16 @@ public: Non-blocking. Returns NULL is no data available. */ - unsigned char* recvTCH() + //unsigned char* recvTCH() + AudioFrame* recvTCH() { assert(mTCHDecoder); return mTCHDecoder->recvTCH(); } unsigned queueSize() const { assert(mTCHDecoder); return mTCHDecoder->queueSize(); } - bool radioFailure() const - { assert(mTCHDecoder); return mTCHDecoder->uplinkLost(); } + // (pat) 3-29: Moved this higher in the hierarchy so it can be shared with SDCCH. + //bool radioFailure() const + //{ assert(mTCHDecoder); return mTCHDecoder->uplinkLost(); } }; @@ -1474,16 +1563,15 @@ class SACCHL1FEC : public L1FEC { SACCHL1Decoder *decoder() { return mSACCHDecoder; } SACCHL1Encoder *encoder() { return mSACCHEncoder; } + virtual const char* descriptiveString() const + { return mSACCHEncoder->descriptiveString(); } + /**@name Physical parameter access. */ //@{ - float RSSI() const { return mSACCHDecoder->RSSI(); } - float timingError() const { return mSACCHDecoder->timingError(); } - double timestamp() const { return mSACCHDecoder->timestamp(); } - int actualMSPower() const { return mSACCHDecoder->actualMSPower(); } - int actualMSTiming() const { return mSACCHDecoder->actualMSTiming(); } + MSPhysReportInfo *getPhysInfo() { return mSACCHDecoder->getPhysInfo(); } + void RSSIBumpDown(int dB) { mSACCHDecoder->RSSIBumpDown(dB); } void setPhy(const SACCHL1FEC&); virtual void setPhy(float RSSI, float timingError, double wTimestamp); - void RSSIBumpDown(int dB) { mSACCHDecoder->RSSIBumpDown(dB); } //@} }; diff --git a/GSM/GSML2LAPDm.cpp b/GSM/GSML2LAPDm.cpp index 59ad111..496d6da 100644 --- a/GSM/GSML2LAPDm.cpp +++ b/GSM/GSML2LAPDm.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for @@ -36,6 +36,7 @@ implementation, although no code is copied directly. #include "GSMSAPMux.h" #include #include +#include using namespace std; using namespace GSM; @@ -43,14 +44,14 @@ using namespace GSM; //#define NDEBUG -ostream& GSM::operator<<(ostream& os, L2LAPDm::LAPDState state) +ostream& GSM::operator<<(ostream& os, LAPDState state) { switch (state) { - case L2LAPDm::LinkReleased: os << "LinkReleased"; break; - case L2LAPDm::AwaitingEstablish: os << "AwaitingEstablish"; break; - case L2LAPDm::AwaitingRelease: os << "AwaitingRelease"; break; - case L2LAPDm::LinkEstablished: os << "LinkEstablished"; break; - case L2LAPDm::ContentionResolution: os << "ContentionResolution"; break; + case LinkReleased: os << "LinkReleased"; break; + case AwaitingEstablish: os << "AwaitingEstablish"; break; + case AwaitingRelease: os << "AwaitingRelease"; break; + case LinkEstablished: os << "LinkEstablished"; break; + case ContentionResolution: os << "ContentionResolution"; break; default: os << "?" << (int)state << "?"; } return os; @@ -59,27 +60,29 @@ ostream& GSM::operator<<(ostream& os, L2LAPDm::LAPDState state) -void CCCHL2::writeHighSide(const GSM::L3Frame& l3) +void CCCHL2::l2WriteHighSide(const GSM::L3Frame& l3) { OBJLOG(DEBUG) <<"CCCHL2::writeHighSide " << l3; - assert(mDownstream); + assert(mL2Downstream); assert(l3.primitive()==UNIT_DATA); L2Header header(L2Length(l3.L2Length())); - mDownstream->writeHighSide(L2Frame(header,l3,true)); + mL2Downstream->writeHighSide(L2Frame(header,l3,true)); } -L2LAPDm::L2LAPDm(unsigned wC, unsigned wSAPI) +L2LAPDm::L2LAPDm(unsigned wC, SAPI_t wSAPI) :mRunning(false), mC(wC),mR(1-wC),mSAPI(wSAPI), mMaster(NULL), + mState(LAPDStateUnused), mT200(T200ms), mIdleFrame(DATA) { // sanity checks assert(mC<2); - assert(mSAPI<4); + //assert(mSAPI<4); + assert(mSAPI == SAPI0 || mSAPI == SAPI3); // SAPI 1 and 2 are never used in the spec. clearState(); @@ -94,12 +97,33 @@ L2LAPDm::L2LAPDm(unsigned wC, unsigned wSAPI) void L2LAPDm::writeL1(const L2Frame& frame) { OBJLOG(DEBUG) <<"L2LAPDm::writeL1 " << frame; - //assert(mDownstream); - if (!mDownstream) return; + //assert(mL2Downstream); + if (!mL2Downstream) return; // It is tempting not to lock this, but if we don't, // the ::open operation can result in contention in L1. ScopedLock lock(mL1Lock); - mDownstream->writeHighSide(frame); + mL2Downstream->writeHighSide(frame); +} + + +// For FACCHL2 and SDCCHL2 we can dispense with the service loop and send L3 messages +// directly to the L3 state machine. Prior to l3rewrite this was done by DCCHDispatchMessage. +void L2LAPDm::writeL3(L3Frame *frame) +{ + OBJLOG(DEBUG) << LOGVAR(frame->size()); + // Attempt first to deliver all messages of any kind to the L3 state machine. + // (pat) Update: For GSM we are going to continue to use one queue per channel with one thread per channel. + //if (! Control::gCSL3StateMachine.csl3Write(new Control::GenericL3Msg(frame,mL2Upstream))) + // Well that didn't work. Fall back to the old code: + mL3Out.write(frame); +} + +// For SACCH we retain the L2 service loop to handle measurement reports and power levels. +// If SACCHLogicalChannel::serviceLoop() does not handle the message, then it will go to do CSL3StateMachine. +void SACCHL2::writeL3(L3Frame *frame) +{ + OBJLOG(DEBUG) << LOGVAR(frame->size()); + mL3Out.write(frame); } @@ -111,6 +135,7 @@ void L2LAPDm::writeL1NoAck(const L2Frame& frame) } +// This writes something that expects an ACK. It does not write an ACK. void L2LAPDm::writeL1Ack(const L2Frame& frame) { // Caller should hold mLock. @@ -128,14 +153,18 @@ void L2LAPDm::waitForAck() { // Block until any pending ack is received. // Caller should hold mLock. - OBJLOG(DEBUG) <<"L2LAPDm::waitForAck state=" << mState << " VS=" << mVS << " VA=" << mVA; + //OBJLOG(DEBUG) <<"L2LAPDm::waitForAck state=" << mState << " VS=" << mVS << " VA=" << mVA; + OBJLOG(DEBUG) <<"L2LAPDm::waitForAck "; // OBJLOG prints the entire LAPDm object now. while (true) { if (mState==LinkReleased) break; if ((mState==ContentionResolution) && (mVS==mVA)) break; if ((mState==LinkEstablished) && (mVS==mVA)) break; // HACK -- We should not need a timeout here. + // (pat) When we send a RELEASE the calling thread blocks FOREVER so this timeout + // is needed to prevent the channel from hanging. It will be gone for 30 secs. mAckSignal.wait(mLock,N200()*T200ms); - OBJLOG(DEBUG) <<"L2LAPDm::waitForAck state=" << mState << " VS=" << mVS << " VA=" << mVA; + //OBJLOG(DEBUG) <<"L2LAPDm::waitForAck state=" << mState << " VS=" << mVS << " VA=" << mVA; + OBJLOG(DEBUG) <<"L2LAPDm::waitForAck "; } } @@ -143,19 +172,26 @@ void L2LAPDm::waitForAck() void L2LAPDm::releaseLink(Primitive releaseType) { - OBJLOG(DEBUG) << "mState=" << mState; + OBJLOG(DEBUG);//<< "mState=" << mState; // Caller should hold mLock. mState = LinkReleased; mEstablishmentInProgress = false; mAckSignal.signal(); - if (mSAPI==0) writeL1(releaseType); - mL3Out.write(new L3Frame(releaseType)); + // (pat) This line of code is magic and loaded with assumed dependencies, which are dictated by GSM specs: + // The SAPMux has multiple uplink and just one downlink connection. + // From here the RELEASE or HARDRELEASE traverses the hierarchy downward and is finally processed + // by XCCHL1Encoder::writeHighSide(), where it releases the physical channel (starts sending idle fills.) + // For the connections we care about SAP0 is the RR/MM/CC message connection, and SAP3 is SMS. + // This means that the RELEASE/HARDRELEASE does not close the dowstream channel if arriving from an SMS connection, + // which is dictated in GSM 4.06 5.4 + if (mSAPI==0) writeL1(L2Frame(releaseType)); + writeL3(new L3Frame(mSAPI,releaseType)); } void L2LAPDm::clearCounters() { - OBJLOG(DEBUG) << "mState=" << mState; + OBJLOG(DEBUG);//<< "mState=" << mState; // Caller should hold mLock. // This is called upon establishment or re-establihment of ABM. mT200.reset(); @@ -171,11 +207,12 @@ void L2LAPDm::clearCounters() void L2LAPDm::clearState(Primitive releaseType) { - OBJLOG(DEBUG) << "mState=" << mState; + OBJLOG(DEBUG);//<< "mState=" << mState; // Caller should hold mLock. // Reset the state machine. clearCounters(); releaseLink(releaseType); + // print this at the end of this routine; during initialization the object is not yet inited. } @@ -187,7 +224,7 @@ void L2LAPDm::processAck(unsigned NR) // Equivalent to vISDN datalink.c:lapd_ack_frames, // but much simpler for LAPDm. // Caller should hold mLock. - OBJLOG(DEBUG) << "NR=" << NR << " VA=" << mVA << " VS=" << mVS; + OBJLOG(DEBUG);//<< "NR=" << NR << " VA=" << mVA << " VS=" << mVS; mVA=NR; if (mVA==mVS) { mRC=0; @@ -221,26 +258,27 @@ void L2LAPDm::bufferIFrameData(const L2Frame& frame) if (mRecvBuffer.size()==0) { // The only frame -- just send it up. OBJLOG(DEBUG) << "single frame message"; - mL3Out.write(new L3Frame(frame)); + writeL3(new L3Frame(mSAPI,frame)); return; } // The last of several -- concat and send it up. OBJLOG(DEBUG) << "last frame of message"; - mL3Out.write(new L3Frame(mRecvBuffer,frame.L3Part())); + writeL3(new L3Frame(mSAPI,mRecvBuffer,frame.L3Part())); mRecvBuffer.clear(); return; } // One segment of many -- concat. // This is inefficient but simple. - mRecvBuffer = L3Frame(mRecvBuffer,frame.L3Part()); + //mRecvBuffer = L3Frame(mRecvBuffer,frame.L3Part()); // RecvBuffer is a BitVector2!! + mRecvBuffer = BitVector2(mRecvBuffer,frame.L3Part()); OBJLOG(DEBUG) <<"buffering recvBuffer=" << mRecvBuffer; } void L2LAPDm::unexpectedMessage() { - OBJLOG(NOTICE) << "mState=" << mState; + OBJLOG(NOTICE);//<< "mState=" << mState; // vISDN datalink.c:unexpeced_message // For LAPD, vISDN just keeps trying. // For LAPDm, just terminate the link. @@ -252,13 +290,13 @@ void L2LAPDm::unexpectedMessage() void L2LAPDm::abnormalRelease() { // Caller should hold mLock. - OBJLOG(INFO) << "state=" << mState; + OBJLOG(INFO);//<< "state=" << mState; // GSM 04.06 5.6.4. // We're cutting a corner here that we'll // clean up when L3 is more stable. - mL3Out.write(new L3Frame(ERROR)); + writeL3(new L3Frame(mSAPI,ERROR)); sendUFrameDM(true); - writeL1(ERROR); + writeL1(L2Frame(ERROR)); clearState(); } @@ -269,7 +307,7 @@ void L2LAPDm::retransmissionProcedure() // Caller should hold mLock. // vISDN datalink.c:lapd_invoke_retransmission_procedure // GSM 04.08 5.5.7, bullet point (a) - OBJLOG(DEBUG) << "VS=" << mVS << " VA=" << mVA << " RC=" << mRC; + OBJLOG(DEBUG);//<< "VS=" << mVS << " VA=" << mVA << " RC=" << mRC; mRC++; writeL1(mSentFrame); mT200.set(T200()); @@ -279,8 +317,9 @@ void L2LAPDm::retransmissionProcedure() -void L2LAPDm::open() +void L2LAPDm::l2open(std::string wDescriptiveString) { + myid = wDescriptiveString; OBJLOG(DEBUG); { ScopedLock lock(mLock); @@ -315,7 +354,7 @@ void *GSM::LAPDmServiceLoopAdapter(L2LAPDm *lapdm) -void L2LAPDm::writeHighSide(const L3Frame& frame) +void L2LAPDm::l2WriteHighSide(const L3Frame& frame) { OBJLOG(DEBUG) << frame; switch (frame.primitive()) { @@ -329,12 +368,12 @@ void L2LAPDm::writeHighSide(const L3Frame& frame) sendMultiframeData(frame); mLock.unlock(); break; - case ESTABLISH: + case ESTABLISH: // (pat) Establish SABM mode. // GSM 04.06 5.4.1.2 // vISDN datalink.c:lapd_establish_datalink_procedure // The BTS side should never call this in SAP0. - // See note in GSM 04.06 5.4.1.1. - assert(mSAPI!=0 || mC==0); + // See note in GSM 04.06 5.4.1.1. (pat) And I quote: "For SAPI 0 the data link is always established by the MS." + devassert(mSAPI!=0 || mC==0); if (mState==LinkEstablished) break; mLock.lock(); clearCounters(); @@ -397,19 +436,23 @@ void L2LAPDm::serviceLoop() unsigned timeout = mT200.remaining() + 2; // If SAP0 is released, other SAPs need to release also. if (mMaster) { - if (mMaster->mState==LinkReleased) mState=LinkReleased; + if (mMaster->mState==LinkReleased) { + OBJLOG(DEBUG) << "master release"; + mState=LinkReleased; + } } if (!mT200.active()) { if (mState==LinkReleased) timeout=3600000; - else timeout = T200(); + else timeout = T200(); // currently 3.6sec } - OBJLOG(DEBUG) << "read blocking up to " << timeout << " ms, state=" << mState; + OBJLOG(DEBUG) << "read blocking up to " << timeout << " ms ";//, state=" << mState; mLock.unlock(); // FIXME -- If the link is released, there should be no timeout at all. L2Frame* frame = mL1In.read(timeout); mLock.lock(); if (frame!=NULL) { - OBJLOG(DEBUG) << "state=" << mState << " received " << *frame; + //OBJLOG(DEBUG) << "state=" << mState << " received " << *frame; + OBJLOG(DEBUG) << " received " << *frame; receiveFrame(*frame); delete frame; } @@ -486,7 +529,7 @@ void L2LAPDm::receiveFrame(const GSM::L2Frame& frame) } break; case HANDOVER_ACCESS: - mL3Out.write(new L3Frame(HANDOVER_ACCESS)); + writeL3(new L3Frame(mSAPI,HANDOVER_ACCESS)); break; default: OBJLOG(ERR) << "unhandled primitive in L1->L2 " << frame; @@ -521,7 +564,8 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) // GSM 04.06 3.8.2, 5.4.1 // Q.921 5.5.1.2. // Also borrows from vISDN datalink.c:lapd_socket_handle_uframe_sabm. - OBJLOG(INFO) << "state=" << mState << " " << frame; + //OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame; // Ignore frame if P!=1. // See GSM 04.06 5.4.1.2. if (!frame.PF()) return; @@ -534,13 +578,13 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) clearCounters(); mEstablishmentInProgress = true; // Tell L3 what happened. - mL3Out.write(new L3Frame(ESTABLISH)); + writeL3(new L3Frame(mSAPI,ESTABLISH)); if (frame.L()) { // Presence of an L3 payload indicates contention resolution. // GSM 04.06 5.4.1.4. mState=ContentionResolution; mContentionCheck = frame.sum(); - mL3Out.write(new L3Frame(frame.L3Part(),DATA)); + writeL3(new L3Frame(mSAPI,frame.L3Part(),DATA)); // Echo back payload. sendUFrameUA(frame); } else { @@ -596,7 +640,7 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) void L2LAPDm::receiveUFrameDISC(const L2Frame& frame) { // Caller should hold mLock. - OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame; mEstablishmentInProgress = false; switch (mState) { case AwaitingEstablish: @@ -632,8 +676,9 @@ void L2LAPDm::receiveUFrameUA(const L2Frame& frame) // GSM 04.06 3.8.8 // vISDN datalink.c:lapd_socket_handle_uframe_ua - OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame; if (!frame.PF()) { + // (pat) TODO: This looks wrong. GSM 4.06 5.4.1.2 quote: 'A UA response with the F bit set to "0" shall be ignored.' unexpectedMessage(); return; } @@ -644,7 +689,7 @@ void L2LAPDm::receiveUFrameUA(const L2Frame& frame) clearCounters(); mState = LinkEstablished; mAckSignal.signal(); - mL3Out.write(new L3Frame(ESTABLISH)); + writeL3(new L3Frame(mSAPI,ESTABLISH)); break; case AwaitingRelease: // We sent DISC and the peer responded. @@ -663,7 +708,7 @@ void L2LAPDm::receiveUFrameUA(const L2Frame& frame) void L2LAPDm::receiveUFrameDM(const L2Frame& frame) { // Caller should hold mLock. - OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame; // GSM 04.06 5.4.5 if (mState==LinkReleased) return; // GSM 04.06 5.4.6.3 @@ -685,8 +730,8 @@ void L2LAPDm::receiveUFrameUI(const L2Frame& frame) { // The zero-length frame is the idle frame. if (frame.L()==0) return; - OBJLOG(INFO) << "state=" << mState << " " << frame; - mL3Out.write(new L3Frame(frame.tail(24),UNIT_DATA)); + OBJLOG(INFO) << frame; + writeL3(new L3Frame(mSAPI,frame.alias().tail(24),UNIT_DATA)); } @@ -707,10 +752,10 @@ void L2LAPDm::receiveSFrame(const L2Frame& frame) } -void L2LAPDm::receiveSFrameRR(const L2Frame& frame) +void L2LAPDm::receiveSFrameRR(const L2Frame& frame) // RR == Receive Ready. { // Caller should hold mLock. - OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame << LOGVAR2("PF",frame.PF()); // GSM 04.06 3.8.5. // Q.921 3.6.6. // vISDN datalink.c:lapd_handle_sframe_rr @@ -723,9 +768,18 @@ void L2LAPDm::receiveSFrameRR(const L2Frame& frame) case LinkEstablished: // "inquiry response procedure" // Never actually seen that happen in GSM... + // (pat) It seems to be sent by the Blackberry after a channel change from SDCCH to TCH, + // and it is sent on FACCH every 1/2 second forever because we dont answer. + // PF is the Poll/Final bit 3.5.1. true means sender is polling receiver; receiver responds with PF=true. +#if ORIGINAL if ((frame.CR()!=mC) && (frame.PF())) { sendSFrameRR(true); } +#else + if ((frame.CR()==mC) && (frame.PF())) { // If it is a command 5.8.1 says return an S RR frame. + sendSFrameRR(true); // Must return a frame with P==1. + } +#endif processAck(frame.NR()); break; default: @@ -737,7 +791,7 @@ void L2LAPDm::receiveSFrameRR(const L2Frame& frame) void L2LAPDm::receiveSFrameREJ(const L2Frame& frame) { // Caller should hold mLock. - OBJLOG(INFO) << "state=" << mState << " " << frame; + OBJLOG(INFO) << frame; // GSM 04.06 3.8.6, 5.5.4 // Q.921 3.7.6, 5.6.4. // vISDN datalink.c:lapd_handle_s_frame_rej. @@ -775,7 +829,7 @@ void L2LAPDm::receiveIFrame(const L2Frame& frame) // Caller should hold mLock. // See GSM 04.06 5.4.1.4. mEstablishmentInProgress = false; - OBJLOG(INFO) << "state=" << mState << " NS=" << frame.NS() << " NR=" << frame.NR() << " " << frame; + OBJLOG(INFO) << " NS=" << frame.NS() << " NR=" << frame.NR() << " " << frame; // vISDN datalink.c:lapd_handle_iframe // GSM 04.06 5.5.2, 5.7.1 // Q.921 5.6.2, 5.8.1 @@ -809,12 +863,12 @@ void L2LAPDm::sendSFrameRR(bool FBit) { // GSM 04.06 3.8.5. // The caller should hold mLock. - OBJLOG(INFO) << "F=" << FBit << " state=" << mState << " VS=" << mVS << " VR=" << mVR; - L2Address address(mR,mSAPI); - L2Control control(L2Control::SFormat,FBit,0); + L2Address address(mR,mSAPI); // C/R == 0, ie, this is a response. + L2Control control(L2Control::SFormat,FBit,0); // (pat) 4.06 3.8.1: Sbits == 0 is supervisory "Receive Ready" static const L2Length length; L2Header header(address,control,length); header.control().NR(mVR); + OBJLOG(INFO) << "F=" << FBit <writeHighSide(outFrame); + mL2Downstream->writeHighSide(outFrame); } } +void L2LAPDm::text(std::ostream&os) const +{ + os <<" LAPDm(" <mState); + } + } + os <<")"; +} + +ostream& GSM::operator<<(ostream& os, L2LAPDm& thing) +{ + thing.text(os); + return os; +} + +ostream& GSM::operator<<(ostream& os, L2LAPDm* thing) +{ + thing->text(os); + return os; +} + // vim: ts=4 sw=4 diff --git a/GSM/GSML2LAPDm.h b/GSM/GSML2LAPDm.h index 937b54c..74db6ea 100644 --- a/GSM/GSML2LAPDm.h +++ b/GSM/GSML2LAPDm.h @@ -1,6 +1,6 @@ /* * Copyright 2008 Free Software Foundation, Inc. -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -26,6 +26,7 @@ implementation, although no code is copied directly. #include "GSMCommon.h" #include "GSMTransfer.h" +#include namespace GSM { @@ -36,14 +37,28 @@ class SAPMux; /**@name L2 Processing Errors */ //@{ /** L2 Read Error is thrown if there is an error in the data on the input side. */ -class L2ReadError : public GSMError { }; - #define L2_READ_ERROR {throw L2ReadError();} +// (pat) You could not use these unless you replace all the places where Mutex lock is called explicitly with ScopedLocks. +//unused: class L2ReadError : public GSMError { }; +//unused: #define L2_READ_ERROR {throw L2ReadError();} /** L2 Write Error is thrown if there is an error in the data on the output side. */ -class L2WriteError : public GSMError { }; -#define L2_WRITE_ERROR {throw L2WriteError();} +//unused: class L2WriteError : public GSMError { }; +//unused: #define L2_WRITE_ERROR {throw L2WriteError();} //@} +/** + LAPD states, Q.921 4.3. + We have few states than vISDN LAPD because LAPDm is simpler. +*/ +enum LAPDState { + LAPDStateUnused, + LinkReleased, + AwaitingEstablish, ///< note that the BTS should never be in this state (pat) Incorrect, state is used during link establishment. + AwaitingRelease, + LinkEstablished, + ContentionResolution ///< GMS 04.06 5.4.1.4 +}; + /** @@ -52,27 +67,30 @@ class L2WriteError : public GSMError { }; Many derived classes are "thin" and do not implement full LAPDm. This is especially true of the downlink-only classes which do not have equivalents in Q.921 and HDLC. + // (pat) The idea is that each radio channel has multiple uplink data paths (usually two: SAPI0 and SAPI3) + // each with their own LAPDm state machine. All are associated with the same L2LogicalChannel. */ class L2DL { protected: - SAPMux *mDownstream; ///< a pointer to the lower layer + SAPMux *mL2Downstream; ///< a pointer to the lower layer + L2LogicalChannel *mL2Upstream; ///< The logical channel containing the SAPMux containing us. public: L2DL() - :mDownstream(NULL) + :mL2Downstream(NULL), mL2Upstream(NULL) { } virtual ~L2DL() {} - void downstream(SAPMux *wDownstream) - { mDownstream = wDownstream; } + void l2Downstream(SAPMux *wDownstream) { mL2Downstream = wDownstream; } + void l2Upstream(L2LogicalChannel *wUpstream) { mL2Upstream = wUpstream; } - virtual void open() = 0; + virtual void l2open(std::string wDescriptiveString) = 0; /** N201 value for a given frame format on this channel, GSM 04.06 5.8.3. */ virtual unsigned N201(GSM::L2Control::ControlFormat) const = 0; @@ -92,16 +110,22 @@ class L2DL { all of the corresponding radio bursts have been enqueued for transmission. That can take up to 1/2 second. + (pat) Above comment is for channels without LAPDm; + channels with LAPDm (the DCCH channels, ie, SACCH, FACCH, and SDCCH) can block + until they receive an ACK from the MS. If the MS has wandered out of range + that will be until the N200*T200 LAPDm timeout, which is 30.6 secs for FACCH, 20.7 secs for SDCCH, 4.5s for SACCH. */ - virtual void writeHighSide(const GSM::L3Frame&) = 0; + virtual void l2WriteHighSide(const GSM::L3Frame&) = 0; /** The L1->L2 interface */ virtual void writeLowSide(const GSM::L2Frame&) = 0; /** The L2->L3 interface. */ - virtual L3Frame* readHighSide(unsigned timeout=3600000) = 0; + virtual L3Frame* l2ReadHighSide(unsigned timeout=3600000) = 0; + // (pat) Never called on non-LAPDm channels, but let's return 0 rather than crashing. + virtual LAPDState getLapdmState() const { return LAPDStateUnused; } }; @@ -123,13 +147,13 @@ class CCCHL2 : public L2DL { unsigned N200() const { return 0; } - void open() {} + void l2open(std::string) {} void writeLowSide(const GSM::L2Frame&) { assert(0); } - L3Frame* readHighSide(unsigned timeout=3600000) { assert(0); return NULL; } + L3Frame* l2ReadHighSide(unsigned timeout=3600000) { if (timeout) {} assert(0); return NULL; } // The 'if' shuts up gcc. - void writeHighSide(const GSM::L3Frame&); + void l2WriteHighSide(const GSM::L3Frame&); }; @@ -143,17 +167,17 @@ class CBCHL2 : public L2DL { public: - unsigned N201(GSM::L2Control::ControlFormat format) const { assert(0); } + unsigned N201(GSM::L2Control::ControlFormat format) const { if (format) {} assert(0); } // The 'if' shuts up gcc. unsigned N200() const { return 0; } - void open() {} + void l2open(std::string) {} void writeLowSide(const GSM::L2Frame&) { assert(0); } - L3Frame* readHighSide(unsigned timeout=3600000) { assert(0); return NULL; } + L3Frame* l2ReadHighSide(unsigned timeout=3600000) { if (timeout) {} assert(0); return NULL; } // The 'if' shuts up gcc. - void writeHighSide(const GSM::L3Frame&); + void l2WriteHighSide(const GSM::L3Frame&); }; @@ -184,39 +208,30 @@ class CBCHL2 : public L2DL { - just using independent L2s for each active SAP - just using independent L2s on each dedicated channel, which works with k=1 */ + class L2LAPDm : public L2DL { public: - - /** - LAPD states, Q.921 4.3. - We have few states than vISDN LAPD because LAPDm is simpler. - */ - enum LAPDState { - LinkReleased, - AwaitingEstablish, ///< note that the BTS should never be in this state - AwaitingRelease, - LinkEstablished, - ContentionResolution ///< GMS 04.06 5.4.1.4 - }; + std::string myid; // The descriptive string from the LogicalChannel, used only in user messages. - protected: + private: Thread mUpstreamThread; ///< a thread for upstream traffic and T200 timeouts bool mRunning; ///< true once the service loop starts + protected: L3FrameFIFO mL3Out; ///< we connect L2->L3 through a FIFO + private: L2FrameFIFO mL1In; ///< we connect L1->L2 through a FIFO unsigned mC; ///< the "C" bit for commands, 1 for BTS, 0 for MS + // (pat) C is ALWAYS 1 and R 0 for us, so why is it an argument to the constructor? For testing? unsigned mR; ///< this "R" bit for commands, 0 for BTS, 1 for MS - unsigned mSAPI; ///< the service access point indicator for this L2 + SAPI_t mSAPI; ///< the service access point indicator for this L2 L2LAPDm *mMaster; ///< This points to the SAP0 LAPDm on this channel. - - /**@name Mutex-protected state shared by uplink and downlink threads. */ //@{ mutable Mutex mLock; @@ -231,7 +246,7 @@ class L2LAPDm : public L2DL { bool mEstablishmentInProgress; ///< flag described in GSM 04.06 5.4.1.4 /**@name Segmentation and retransmission. */ //@{ - BitVector mRecvBuffer; ///< buffer to concatenate received I-frames, same role as sk_rcvbuf in vISDN + BitVector2 mRecvBuffer; ///< buffer to concatenate received I-frames, same role as sk_rcvbuf in vISDN L2Frame mSentFrame; ///< previous ack-able kept for retransmission, same role as sk_write_queue in vISDN bool mDiscardIQueue; ///< a flag used to abort I-frame sending unsigned mContentionCheck; ///< checksum used for contention resolution, GSM 04.06 5.4.1.4. @@ -262,7 +277,7 @@ class L2LAPDm : public L2DL { @param wSAPI Service access point indicatior, GSM 040.6 3.3.3. */ - L2LAPDm(unsigned wC=1, unsigned wSAPI=0); + L2LAPDm(unsigned wC=1, SAPI_t wSAPI=SAPI0); virtual ~L2LAPDm() {} @@ -274,7 +289,7 @@ class L2LAPDm : public L2DL { Read the L3 output, with a timeout. Caller is responsible for deleting returned object. */ - L3Frame* readHighSide(unsigned timeout=3600000) + L3Frame* l2ReadHighSide(unsigned timeout=3600000) { return mL3Out.read(timeout); } /** @@ -284,11 +299,11 @@ class L2LAPDm : public L2DL { enqueued for transmission. That can take up to 1/2 second. */ - void writeHighSide(const GSM::L3Frame&); + void l2WriteHighSide(const GSM::L3Frame&); /** Prepare the channel for a new transaction. */ - virtual void open(); + virtual void l2open(std::string wDescriptiveString); /** Set the "master" SAP, SAP0; should be called no more than once. */ void master(L2LAPDm* wMaster) @@ -306,6 +321,8 @@ class L2LAPDm : public L2DL { /** Send an L2Frame on the L2->L1 interface. */ void writeL1(const L2Frame&); + /** Send an L3Frame upstream on the L2->L# interface. */ + virtual void writeL3(L3Frame *f); // Over-ridden only by SACCHL2 void writeL1Ack(const L2Frame&); ///< send an ack-able frame on L2->L1 void writeL1NoAck(const L2Frame&); ///< send a non-acked frame on L2->L1 @@ -314,12 +331,13 @@ class L2LAPDm : public L2DL { void linkError(); /** Clear the state variables to released condition. */ - void clearState(Primitive relesaeType=RELEASE); + void clearState(Primitive releaseType=RELEASE); /** Clear the ABM-related state variables. */ void clearCounters(); /** Go to the "link released" state. */ + // (pat) This sends the releaseType upward toward L3 but not downward; downward we send a DISC message to the peer. void releaseLink(Primitive releaseType=RELEASE); /** We go here when something goes really wrong. */ @@ -380,7 +398,7 @@ class L2LAPDm : public L2DL { In OpenBTS, you just call sendUFrameDISC. */ void sendMultiframeData(const L3Frame&); ///< send an L3 frame in one or more I-frames - void sendIFrame(const BitVector&, bool); ///< GSM 04.06 3.8.1, 5.5.1, with payload and "M" flag + void sendIFrame(const BitVector2&, bool); ///< GSM 04.06 3.8.1, 5.5.1, with payload and "M" flag void sendUFrameSABM(); ///< GMS 04.06 3.8.2, 5.4.1 void sendUFrameDISC(); ///< GSM 04.06 3.8.3, 5.4.4.2 void sendUFrameUI(const L3Frame&); ///< GSM 04.06 3.8.4, 5.2.1 @@ -425,10 +443,16 @@ class L2LAPDm : public L2DL { void serviceLoop(); friend void *LAPDmServiceLoopAdapter(L2LAPDm*); + + public: + LAPDState getLapdmState() const { return mState; } + void text(std::ostream&os) const; }; -std::ostream& operator<<(std::ostream&, L2LAPDm::LAPDState); +std::ostream& operator<<(std::ostream&, LAPDState); +std::ostream& operator<<(std::ostream&, L2LAPDm&); +std::ostream& operator<<(std::ostream&, L2LAPDm*); // such a great language /** C-style adapter for LAPDm serice loop. */ @@ -456,7 +480,7 @@ class SDCCHL2 : public L2LAPDm { @param wC "Command" bit, "1" for BTS, "0" for MS. @param wSAPI Service access point indicatior. */ - SDCCHL2(unsigned wC=1, unsigned wSAPI=0) + SDCCHL2(unsigned wC=1, SAPI_t wSAPI=SAPI0) :L2LAPDm(wC,wSAPI) { } @@ -493,6 +517,8 @@ class SACCHL2 : public L2LAPDm { /** SACCH does not use idle frames. */ void sendIdle() {}; + void writeL3(L3Frame *f); // Over-ridden only by SACCHL2 + public: /** @@ -500,7 +526,7 @@ class SACCHL2 : public L2LAPDm { @param wC "Command" bit, "1" for BTS, "0" for MS. @param wSAPI Service access point indicatior. */ - SACCHL2(unsigned wC=1, unsigned wSAPI=0) + SACCHL2(unsigned wC=1, SAPI_t wSAPI=SAPI0) :L2LAPDm(wC,wSAPI) { } @@ -534,7 +560,7 @@ class FACCHL2 : public L2LAPDm { @param wC "Command" bit, "1" for BTS, "0" for MS. @param wSAPI Service access point indicatior. */ - FACCHL2(unsigned wC=1, unsigned wSAPI=0) + FACCHL2(unsigned wC=1, SAPI_t wSAPI=SAPI0) :L2LAPDm(wC,wSAPI) { } diff --git a/GSM/GSML3CCElements.cpp b/GSM/GSML3CCElements.cpp index 4dc408e..8a1bb44 100644 --- a/GSM/GSML3CCElements.cpp +++ b/GSM/GSML3CCElements.cpp @@ -2,7 +2,7 @@ @brief Call Control messages, GSM 04.08 9.3 */ /* -* Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * @@ -19,10 +19,13 @@ #include "GSML3CCElements.h" +#include #include +#define CASENAME(x) case x: return #x; using namespace std; -using namespace GSM; +using namespace Control; +namespace GSM { void L3BearerCapability::writeV( L3Frame &dest, size_t &wp ) const @@ -32,36 +35,158 @@ void L3BearerCapability::writeV( L3Frame &dest, size_t &wp ) const // But we're going to ignore a lot of it. // "octet 3" - // We hard code this octet for circuit switched speech. - dest.writeField(wp, 0x04, 8); + dest.writeField(wp, mOctet3, 8); - // "octet 3a" - // We hard code for full rate speech v1, the GSM 06.10 codec. - dest.writeField(wp,0x80,8); + // zero or more "octet 3a" + for (unsigned i = 0; i < mNumOctet3a; i++) { + dest.writeField(wp,mOctet3a[i],8); + } } void L3BearerCapability::parseV( const L3Frame& src, size_t &rp, size_t expectedLength ) { + mPresent = true; // See GSM 10.5.4.5. // This is a hell of a complex element, inherited from ISDN. - - // But we're just going to assume circuit-switched speech - // with a full rate codec, since every phone supports that. - // So we can just ignore this hideously complex element. + // (pat) If the bearer capability is not for speech, we dont save it. + // Bits 1-3 of octet3 == 0 are the indicator, but the caller already lopped off the IEI and length + // so it is in the first byte. + if (expectedLength == 0) {return;} // Bad IE. Toss it. + size_t end = rp + 8*expectedLength; + unsigned octet3 = src.readField(rp,8); + LOG(DEBUG) "BearerCapability"<= 3) { + int sysid = src.readField(rp,8); + int bitmapLength = src.readField(rp,8); + // (pat) If there are two bytes, the second is the high bits, so we cant just + // read them as a single field, we have to byte swap them. + int fixedLength = min((int)expectedLength-2,bitmapLength); // Correct for error: bitmaplength longer than IE length. + unsigned codeclist = 0; + if (fixedLength >= 1) codeclist = src.readField(rp,8); + if (fixedLength >= 2) codeclist |= (src.readField(rp,8) << 8); + switch (sysid) { + case SysIdGSM: + mGsmPresent = true; + mGsmCodecs.mCodecs = (CodecType)codeclist; + break; + case SysIdUMTS: + mUmtsPresent = true; + mUmtsCodecs.mCodecs = (CodecType)codeclist; + break; + default: // toss it. + break; + } + expectedLength -= 2 + bitmapLength; + } +} + +// Return the CodecSet for the radio access technology that is currently in use, ie, +// if OpenBTS product return gsm, if OpenNodeB product return umts. +CodecSet L3SupportedCodecList::getCodecSet() const +{ +#if RN_UMTS + return mUmtsPresent ? mUmtsCodecs : CodecSet(); +#else + return mGsmPresent ? mGsmCodecs : CodecSet(); +#endif +} + +void L3SupportedCodecList::text(std::ostream& os) const +{ + os << "SupportedCodecList=("; + if (mGsmPresent) { os << "gsm="; mGsmCodecs.text(os); } + if (mUmtsPresent) { if (mGsmPresent) {os<<",";} os << "umts="; mUmtsCodecs.text(os); } + os << ")"; +} + +Control::CodecSet L3CCCapabilities::getCodecSet() const +{ + // (pat) I'm going to return an OR of all the codecs we find anywhere. + Control::CodecSet result; + if (mBearerCapability.mPresent) { result.orSet(mBearerCapability.getCodecSet()); } + // This is supposedly only for UMTS but phones (Samsung Galaxy) may return it for GSM: + if (mSupportedCodecs.mPresent) { result.orSet(mSupportedCodecs.getCodecSet()); } + // If the phone doesnt report any capabilities, fall back to GSM_FR and hope. + if (result.isEmpty()) { result.orType(GSM_FR); } + return result; +} void L3BCDDigits::parse(const L3Frame& src, size_t &rp, size_t numOctets, bool international) { @@ -80,25 +205,32 @@ void L3BCDDigits::parse(const L3Frame& src, size_t &rp, size_t numOctets, bool i } -int encode(char c) +static int encode(char c, bool *invalid) { - return c == '*' ? 10 : c == '#' ? 11 : c-'0'; + //return c == '*' ? 10 : c == '#' ? 11 : c-'0'; + if (c == '*') return 10; + if (c == '#') return 11; + if (isdigit(c)) return c - '0'; + *invalid = true; + return 0; // Not sure what to do. } void L3BCDDigits::write(L3Frame& dest, size_t &wp) const { + bool invalid = false; unsigned index = 0; unsigned numDigits = strlen(mDigits); if (index < numDigits && mDigits[index] == '+') { index++; } while (index < numDigits) { - if ((index+1) < numDigits) dest.writeField(wp,encode(mDigits[index+1]),4); + if ((index+1) < numDigits) dest.writeField(wp,encode(mDigits[index+1],&invalid),4); else dest.writeField(wp,0x0f,4); - dest.writeField(wp,encode(mDigits[index]),4); + dest.writeField(wp,encode(mDigits[index],&invalid),4); index += 2; } + if (invalid) { LOG(ERR) << "Invalid BCD string: '" < +#include namespace GSM { @@ -28,14 +29,26 @@ namespace GSM { /** Bearer Capability, GSM 04.08 10.5.4.5 */ class L3BearerCapability : public L3ProtocolElement { + // Obsolete comment: // The spec for this is really intimidating. // But we're just going to assume circuit-switched speech // with a full rate codec, since every phone supports that. // So we can just ignore this hideously complex element. -public: + // (pat) There may be multiple BearerCapability IEs for speech and non-speech. + // We save only the speech one; the speech version of this IE includes only Octet3 + // and zero or more octet3a, one for each codec supported. + uint8_t mOctet3, mOctet3a[10]; + unsigned mNumOctet3a; // Number of elements in mOctet3a. - L3BearerCapability() : L3ProtocolElement() {} +public: + Bool_z mPresent; + + L3BearerCapability() { + mOctet3 = 0x0f; // We hard code this octet for circuit switched speech. + mNumOctet3a = 1; + mOctet3a[0] = 0x80; // We hard code for full rate speech v1, the GSM 06.10 codec. + } size_t lengthV() const { return 2; } void writeV( L3Frame& dest, size_t &wp ) const; @@ -43,10 +56,69 @@ public: void parseV(const L3Frame&, size_t&) { assert(0); } void text(std::ostream&) const; + // accessors + // Note: As defined in 26.103 and 48.008 + // Meaning of these bits is hard to find: It is in 48.008 3.2.2.11: + // GSM speech full rate version 1: GSM FR + // GSM speech full rate version 2: GSM EFR + // GSM speech full rate version 3: FR AMR + // GSM speech full rate version 4: OFR AMR-WB + // GSM speech full rate version 5: FR AMR-WB + // GSM speech half rate version 1: GSM HR + // GSM speech half rate version 2: not defined + // GSM speech half rate version 3: HR AMR + // GSM speech half rate version 4: OHR AMR-WB + // GSM speech half rate version 6: OHR AMR + //unsigned getSpeechVersion() { return mOctet3 & 0xf; } + // Return the CodecType for the speech version in octet n; + Control::CodecType getCodecType(unsigned n) const; + Control::CodecType getCodecSet() const; + unsigned getHalfRateSupport() { return mOctet3 & 0x40; } // Bit 7 is true if half-rate supported. +}; + +// (pat) Added 10-22-2012. +// 3GPP 24.008 10.5.4.32 and 3GPP 26.103 +// I added this IE before I read the fine print. This is only used for UMTS, and the BearerCapability +// is used for GSM radio networks, so we dont really need this, since any UMTS phone supports AMR_FR, +// which is all we care. But here it is. +class L3SupportedCodecList : public L3ProtocolElement +{ + Control::CodecSet mGsmCodecs, mUmtsCodecs; + enum { // SysID defined in 26.103 6.1 + SysIdGSM = 0, + SysIdUMTS = 4 + }; + public: + Bool_z mPresent; // Was the IE present? + Bool_z mGsmPresent, mUmtsPresent; // Were these sub-parts of the IE present? + L3SupportedCodecList() {} + Control::CodecSet getCodecSet() const; // Return codec set for gsm in OpenBTS or umts in OpenNodeB + // Each sub-section is 4 bytes. + size_t lengthV() const { return (mGsmPresent?4:0) + (mUmtsPresent?4:0); } // length excluding IEI and initial length byte. + void writeV( L3Frame& dest, size_t &wp ) const; + void parseV( const L3Frame& src, size_t &rp, size_t expectedLength ); + void parseV(const L3Frame&, size_t&) { assert(0); } // This IE must always include an initial length byte. + void text(std::ostream&) const; }; +class L3CCCapabilities +{ + public: + /// Bearer Capability IE + // (pat) BearerCapability is sent by GSM phone + //Bool_z mHaveBearerCapability; + L3BearerCapability mBearerCapability; + + // (pat) SupportedCodecList is sent by UMTS phone + //Bool_z mHaveSupportedCodecs; + L3SupportedCodecList mSupportedCodecs; // (pat) added 10-22-2012 + + // Return the CodecSet for the radio access capability we are in. + Control::CodecSet getCodecSet() const; +}; + /** A general class for BCD numbers as they normally appear in L3. */ class L3BCDDigits { @@ -59,8 +131,13 @@ class L3BCDDigits { L3BCDDigits() { mDigits[0]='\0'; } + // (pat) The -1 below and +1 above are mutually redundant. L3BCDDigits(const char* wDigits) { strncpy(mDigits,wDigits,sizeof(mDigits)-1); mDigits[sizeof(mDigits)-1]='\0'; } + L3BCDDigits(const L3BCDDigits &other) { + memcpy(mDigits,other.mDigits,sizeof(mDigits)); + } + void parse(const L3Frame& src, size_t &rp, size_t numOctets, bool international = false); void write(L3Frame& dest, size_t &wp) const; @@ -81,6 +158,7 @@ std::ostream& operator<<(std::ostream&, const L3BCDDigits&); /** Calling Party BCD Number, GSM 04.08 10.5.4.9 */ +// (pat) 24.018 10.5.4.9 quote: "This IE is not used in the MS to network direction." class L3CallingPartyBCDNumber : public L3ProtocolElement { private: @@ -92,24 +170,29 @@ private: /**@name Octet 3a */ //@{ - bool mHaveOctet3a; - int mPresentationIndicator; - int mScreeningIndicator; + Bool_z mHaveOctet3a; + int mPresentationIndicator; // uninited, but not used unless mHaveOctet3a + int mScreeningIndicator; // uninited, but not used unless mHaveOctet3a //@} public: L3CallingPartyBCDNumber() - :mType(UnknownTypeOfNumber),mPlan(UnknownPlan), - mHaveOctet3a(false) + :mType(UnknownTypeOfNumber),mPlan(UnknownPlan) { } L3CallingPartyBCDNumber( const char * wDigits ) - :mType(NationalNumber),mPlan(E164Plan),mDigits(wDigits), - mHaveOctet3a(false) + :mType(NationalNumber),mPlan(E164Plan),mDigits(wDigits) { } + L3CallingPartyBCDNumber(const L3CallingPartyBCDNumber &other) + :mType(other.mType),mPlan(other.mPlan),mDigits(other.mDigits), + mHaveOctet3a(other.mHaveOctet3a), + mPresentationIndicator(other.mPresentationIndicator), + mScreeningIndicator(other.mScreeningIndicator) + {} + NumberingPlan plan() const { return mPlan; } TypeOfNumber type() const { return mType; } @@ -144,10 +227,19 @@ public: :mType(NationalNumber),mPlan(E164Plan),mDigits(wDigits) { } - L3CalledPartyBCDNumber(const L3CallingPartyBCDNumber& other) + // (pat) This auto-conversion from CallingParty to CalledParty was used in the SMS code, + // however, it was creating a disaster during unintended auto-conversions of L3Messages, + // which are unintentionally sprinkled throughout the code base due to incomplete constructors. + // The fix would be to add 'explicit' keywords everywhere. + explicit L3CalledPartyBCDNumber(const L3CallingPartyBCDNumber& other) :mType(other.type()),mPlan(other.plan()),mDigits(other.digits()) { } + // (pat) We must have this constructor as a choice also. + L3CalledPartyBCDNumber(const L3CalledPartyBCDNumber& other) + :mType(other.mType),mPlan(other.mPlan),mDigits(other.mDigits) + { } + NumberingPlan plan() const { return mPlan; } TypeOfNumber type() const { return mType; } @@ -169,6 +261,7 @@ public: /** Cause, GSM 04.08 10.5.4.11 + Very poorly names, it is the Call Control cause. Read the spec closely: we only have to support coding standard 3 (GSM), and that format doesn't carry the "recommendation" field. */ @@ -188,17 +281,69 @@ public: BeyondInternetworking=10 }; + enum Cause { + UnassignedNumber = 1, // or unallocated number + NoRouteToDestination = 3, + ChannelUnacceptable = 6, + OperatorDeterminedBarring = 8, + NormalCallClearing = 16, + UserBusy = 17, + NoUserResponding = 18, + UserAlertingNoAnswer = 19, + CallRejected = 21, + NumberChanged = 22, + Preemption = 25, + NonSelectedUserClearing = 26, + DestinationOutOfOrder = 27, + InvalidNumberFormat = 28, // invalid or incomplete number + FacilityRejected = 29, + ResponseToSTATUSENQUIRY = 30, + NormalUnspecified = 31, + NoChannelAvailable = 34, + NetworkOutOfOrder = 38, + TemporaryFailure = 41, + SwitchingEquipmentCongestion = 42, + AccessInformationDiscarded = 43, + RequestedChannelNotAvailable = 44, + ResourcesUnavailable = 47, + QualityOfServiceUnavailable = 49, + RequestedFacilityNotSubscribed = 50, + IncomingCallsBarredWithinCUG = 55, + BearerCapabilityNotAuthorized = 57, + BearerCapabilityNotPresentlyAvailable = 58, + ServiceOrOptionNotAvailable = 63, + BearerServiceNotImplemented = 65, + ACMGEMax = 68, // ACM greater or equal to ACM max. Whatever that is. + RequestedFacilityNotImplemented = 69, + OnlyRestrictedDigitalInformationBearerCapabilityIsAvailable = 70, // If you ever use, go ahead and abbreviate it. + ServiceOrOptionNotImplemented = 79, + InvalidTransactionIdentifiervalue = 81, + UserNotMemberOfCUG = 87, + IncompatibleDestination = 88, + InvalidTransitNetworkSelection = 91, + SemanticallyIncorrectMessage = 95, + InvalidMandatoryInformation = 96, + MessageTypeNotImplemented = 97, + MessagetypeNotCompatibleWithProtocolState = 98, + IENotImplemented = 99, // Information Element non-existent or not implemented. + ConditionalIEError = 100, + MessageNotCompatibleWithProtocolState = 101, + RecoveryOnTimerExpiry = 102, + ProtocolErrorUnspecified = 111, + InterworkingUnspecified = 127, + }; + private: // FIXME -- This should include any supplied diagnostics. // See ticket GSM 04.08 10.5.4.11 and ticket #1139. Location mLocation; - unsigned mCause; + Cause mCause; // 7 bits of cause, consisting of 3 bit "class" and 4 bit "value". public: - L3Cause(unsigned wCause=0, Location wLocation=PrivateServingLocal) + L3Cause(Cause wCause=NormalCallClearing, Location wLocation=PrivateServingLocal) :L3ProtocolElement(), mLocation(wLocation),mCause(wCause) { } @@ -214,6 +359,7 @@ public: void parseV(const L3Frame&, size_t&) { assert(0); } void text(std::ostream&) const; }; +typedef L3Cause::Cause CCCause; /** Call State, GSM 04.08 10.5.4.6. */ @@ -312,7 +458,6 @@ class L3KeypadFacility : public L3ProtocolElement { void text(std::ostream&) const; }; - } // GSM #endif diff --git a/GSM/GSML3CCMessages.cpp b/GSM/GSML3CCMessages.cpp index 73fcf7a..ddf71aa 100644 --- a/GSM/GSML3CCMessages.cpp +++ b/GSM/GSML3CCMessages.cpp @@ -2,7 +2,7 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -36,41 +36,45 @@ ostream& GSM::operator<<(ostream& os, L3CCMessage::MessageType val) { switch (val) { case L3CCMessage::Alerting: - os << "Alerting"; break; - case L3CCMessage::Connect: - os << "Connect"; break; - case L3CCMessage::Disconnect: - os << "Disconnect"; break; - case L3CCMessage::ConnectAcknowledge: - os << "Connect Acknowledge"; break; - case L3CCMessage::Release: - os << "Release"; break; - case L3CCMessage::ReleaseComplete: - os << "Release Complete"; break; - case L3CCMessage::Setup: - os << "Setup"; break; - case L3CCMessage::CCStatus: - os << "Status"; break; + os << "Alerting"; return os; case L3CCMessage::CallConfirmed: - os <<"Call Confirmed"; break; + os <<"Call Confirmed"; return os; case L3CCMessage::CallProceeding: - os <<"Call Proceeding"; break; + os <<"Call Proceeding"; return os; + case L3CCMessage::Connect: + os << "Connect"; return os; + case L3CCMessage::Disconnect: + os << "Disconnect"; return os; + case L3CCMessage::ConnectAcknowledge: + os << "Connect Acknowledge"; return os; + case L3CCMessage::Progress: + os << "Progress"; return os; + case L3CCMessage::Release: + os << "Release"; return os; + case L3CCMessage::ReleaseComplete: + os << "Release Complete"; return os; + case L3CCMessage::Setup: + os << "Setup"; return os; + case L3CCMessage::EmergencySetup: + os << "Emergency Setup"; return os; + case L3CCMessage::CCStatus: + os << "Status"; return os; case L3CCMessage::StartDTMF: - os << "Start DTMF"; break; + os << "Start DTMF"; return os; case L3CCMessage::StartDTMFReject: - os << "Start DTMF Reject"; break; + os << "Start DTMF Reject"; return os; case L3CCMessage::StartDTMFAcknowledge: - os << "Start DTMF Acknowledge"; break; + os << "Start DTMF Acknowledge"; return os; case L3CCMessage::StopDTMF: - os << "Stop DTMF"; break; + os << "Stop DTMF"; return os; case L3CCMessage::StopDTMFAcknowledge: - os << "Stop DTMF Acknowledge"; break; + os << "Stop DTMF Acknowledge"; return os; case L3CCMessage::Hold: - os << "Hold"; break; + os << "Hold"; return os; case L3CCMessage::HoldReject: - os << "Hold Reject"; break; - default: os << hex << "0x" << (int)val << dec; + os << "Hold Reject"; return os; } + os << hex << "0x" << (int)val << dec; return os; } @@ -82,6 +86,7 @@ L3CCMessage * GSM::L3CCFactory(L3CCMessage::MessageType MTI) case L3CCMessage::Connect: return new L3Connect(); case L3CCMessage::Alerting: return new L3Alerting(); case L3CCMessage::Setup: return new L3Setup(); + case L3CCMessage::EmergencySetup: return new L3EmergencySetup(); case L3CCMessage::Disconnect: return new L3Disconnect(); case L3CCMessage::CallProceeding: return new L3CallProceeding(); case L3CCMessage::Release: return new L3Release(); @@ -142,22 +147,57 @@ void L3CCMessage::text(ostream& os) const } +void L3CCCommonIEs::ccCommonText(ostream&os) const +{ + if (mHaveFacility) os << "facility=(" < rp + 8) { + unsigned thisIEI = src.peekField(rp,8); + switch (thisIEI) { + case 0x1c: mHaveFacility = mFacility.parseTLV(0x1c,src,rp); continue; + case 0x7f: mHaveSSVersion = mSSVersion.parseTLV(0x7f,src,rp); continue; + default: return; + } + } +} + +void L3CCCommonIEs::ccCommonWrite(L3Frame &dest, size_t &wp) const +{ + if (mHaveFacility) mFacility.writeTLV(0x1c,dest,wp); + if (mHaveSSVersion) mSSVersion.writeTLV(0x7f,dest,wp); +} + size_t L3Alerting::l2BodyLength() const { size_t sum=0; if (mHaveProgress) sum += mProgress.lengthTLV(); + sum += ccCommonLength(); return sum; } void L3Alerting::writeBody(L3Frame &dest, size_t &wp) const { if (mHaveProgress) mProgress.writeTLV(0x1E,dest,wp); + ccCommonWrite(dest,wp); } void L3Alerting::parseBody(const L3Frame& src, size_t &rp) { - skipTLV(0x1C,src,rp); // skip facility + ccCommonParse(src,rp); mHaveProgress = mProgress.parseTLV(0x1E,src,rp); + ccCommonParse(src,rp); // ignore the rest } @@ -165,6 +205,7 @@ void L3Alerting::text(ostream& os) const { L3CCMessage::text(os); if (mHaveProgress) os << "progress=(" << mProgress << ")"; + ccCommonText(os); } @@ -172,14 +213,14 @@ size_t L3CallProceeding::l2BodyLength() const { size_t sum=0; if (mHaveProgress) sum += mProgress.lengthTLV(); - if( mHaveBearerCapability) sum += mBearerCapability.lengthTLV(); + if( mBearerCapability.mPresent) sum += mBearerCapability.lengthTLV(); return sum; } void L3CallProceeding::writeBody(L3Frame &dest, size_t &wp) const { - if( mHaveBearerCapability) mBearerCapability.writeTLV(0x04, dest, wp); + if( mBearerCapability.mPresent) mBearerCapability.writeTLV(0x04, dest, wp); if (mHaveProgress) mProgress.writeTLV(0x1E, dest, wp); } @@ -237,6 +278,7 @@ size_t L3Release::l2BodyLength() const { size_t sum = 0; if (mHaveCause) sum += mCause.lengthTLV(); + sum += ccCommonLength(); return sum; } @@ -244,12 +286,14 @@ size_t L3Release::l2BodyLength() const void L3Release::writeBody(L3Frame& dest, size_t &wp) const { if (mHaveCause) mCause.writeTLV(0x08,dest,wp); + ccCommonWrite(dest,wp); } void L3Release::parseBody(const L3Frame& src, size_t &rp) { mHaveCause = mCause.parseTLV(0x08,src,rp); + ccCommonParse(src,rp); // ignore the rest } @@ -258,6 +302,7 @@ void L3Release::text(ostream& os) const { L3CCMessage::text(os); if (mHaveCause) os << "cause=(" << mCause << ")"; + ccCommonText(os); } @@ -266,6 +311,7 @@ size_t L3ReleaseComplete::l2BodyLength() const { size_t sum = 0; if (mHaveCause) sum += mCause.lengthTLV(); + sum += ccCommonLength(); return sum; } @@ -273,12 +319,14 @@ size_t L3ReleaseComplete::l2BodyLength() const void L3ReleaseComplete::writeBody(L3Frame& dest, size_t &wp) const { if (mHaveCause) mCause.writeTLV(0x08,dest,wp); + ccCommonWrite(dest,wp); } void L3ReleaseComplete::parseBody(const L3Frame& src, size_t &rp) { mHaveCause = mCause.parseTLV(0x08,src,rp); + ccCommonParse(src,rp); // ignore the rest } @@ -286,42 +334,122 @@ void L3ReleaseComplete::parseBody(const L3Frame& src, size_t &rp) void L3ReleaseComplete::text(ostream& os) const { L3CCMessage::text(os); + ccCommonText(os); if (mHaveCause) os << "cause=(" << mCause << ")"; } - void L3Setup::writeBody( L3Frame &dest, size_t &wp ) const { - if (mHaveBearerCapability) mBearerCapability.writeTLV(0x04, dest, wp); + if (mBearerCapability.mPresent) mBearerCapability.writeTLV(0x04, dest, wp); if (mHaveCallingPartyBCDNumber) mCallingPartyBCDNumber.writeTLV(0x5C,dest, wp); if (mHaveCalledPartyBCDNumber) mCalledPartyBCDNumber.writeTLV(0x5E,dest, wp); + if (mSupportedCodecs.mPresent) mSupportedCodecs.writeTLV(0x40, dest, wp); + ccCommonWrite(dest,wp); } - +// (pat) old doc 4.08, new doc 24.08 +// (pat) There are two versions: we are parsing 9.3.23.2, however, the old code had some +// of the IEIs for 0.3.23.1 which is the other direction! Whatever, I am throwing all the IEIs in here +// so you could parse out the network->mobile message too. void L3Setup::parseBody( const L3Frame &src, size_t &rp ) { +#if 0 // (pat) 10-2012, Replaced so we do not assume the order of the IEs. skipTV(0x0D,4,src,rp); // skip Repeat Indicator. skipTLV(0x04,src,rp); // skip Bearer Capability 1. skipTLV(0x04,src,rp); // skip Bearer Capability 2. skipTLV(0x1C,src,rp); // skip Facility. - skipTLV(0x1E,src,rp); // skip Progress. - skipTLV(0x34,src,rp); // skip Signal. + skipTLV(0x1E,src,rp); // skip Progress. (pat) This is an irrelevant error in the original version. + skipTLV(0x34,src,rp); // skip Signal. (pat) This is an irrelevant error in the original version. mHaveCallingPartyBCDNumber = mCallingPartyBCDNumber.parseTLV(0x5C,src,rp); skipTLV(0x5D,src,rp); // skip Calling Party Subaddress mHaveCalledPartyBCDNumber = mCalledPartyBCDNumber.parseTLV(0x5E,src,rp); // ignore the rest +#else + while (rp < src.size()) { + unsigned iei = src.readField(rp,8); + LOG(DEBUG) << "L3Setup"< namespace GSM { @@ -57,14 +59,15 @@ class L3CCMessage : public L3Message { CallProceeding=0x02, Connect=0x07, Setup=0x05, + EmergencySetup=0x0e, ConnectAcknowledge=0x0f, Progress=0x03, //@} /**@name call clearing */ //@{ - Disconnect=0x25, + Disconnect=0x25, // 37 Release=0x2d, - ReleaseComplete=0x2a, + ReleaseComplete=0x2a, // 42 //@} /**@name DTMF */ //@{ @@ -104,6 +107,21 @@ class L3CCMessage : public L3Message { }; +class L3CCCommonIEs { + public: + Bool_z mHaveFacility; + L3SupServFacilityIE mFacility; + // The SS Version is variable length, which is nonsense, because it is one byte which can take the values 0 or 1. Woo hoo! + Bool_z mHaveSSVersion; + L3SupServVersionIndicator mSSVersion; + + void ccCommonText(std::ostream&) const; + void ccCommonParse( const L3Frame &src, size_t &rp ); + void ccCommonWrite( L3Frame &dest, size_t &wp ) const; + size_t ccCommonLength() const; +}; + + std::ostream& operator<<(std::ostream& os, L3CCMessage::MessageType MTI); @@ -122,7 +140,7 @@ L3CCMessage* L3CCFactory(L3CCMessage::MessageType MTI); /** GSM 04.08 9.3.19 */ -class L3Release : public L3CCMessage { +class L3Release : public L3CCMessage, public L3CCCommonIEs { private: @@ -185,7 +203,7 @@ public: /** GSM 04.08 9.3.19 */ -class L3ReleaseComplete : public L3CCMessage { +class L3ReleaseComplete : public L3CCMessage, public L3CCCommonIEs { private: @@ -217,58 +235,41 @@ class L3ReleaseComplete : public L3CCMessage { - /** GSM 04.08 9.3.23 This message can have different forms for uplink and downlink but the TLV format is flexiable enough to allow us to use one class for both. */ -class L3Setup : public L3CCMessage +class L3Setup : public L3CCMessage, public L3CCCapabilities, public L3CCCommonIEs { - // We fill in IEs one at a time as we need them. - /// Bearer Capability IE - bool mHaveBearerCapability; - L3BearerCapability mBearerCapability; - /// Calling Party BCD Number (0x5C O TLV 3-19 ). - bool mHaveCallingPartyBCDNumber; + Bool_z mHaveCallingPartyBCDNumber; L3CallingPartyBCDNumber mCallingPartyBCDNumber; /// Called Party BCD Number (0x5E O TLV 3-19). - bool mHaveCalledPartyBCDNumber; + Bool_z mHaveCalledPartyBCDNumber; L3CalledPartyBCDNumber mCalledPartyBCDNumber; - - - public: L3Setup(unsigned wTI=7) - :L3CCMessage(wTI), - mHaveBearerCapability(false), - mHaveCallingPartyBCDNumber(false), - mHaveCalledPartyBCDNumber(false) + :L3CCMessage(wTI) { } L3Setup(unsigned wTI, const L3CalledPartyBCDNumber& wCalledPartyBCDNumber) - :L3CCMessage(wTI), - mHaveBearerCapability(false), - mHaveCallingPartyBCDNumber(false), + :L3CCMessage(wTI) , mHaveCalledPartyBCDNumber(true),mCalledPartyBCDNumber(wCalledPartyBCDNumber) { } + // (pat) This is cleverly converting callingPartyBCDNumber to a char string and back. L3Setup(unsigned wTI, const L3CallingPartyBCDNumber& wCallingPartyBCDNumber) :L3CCMessage(wTI), - mHaveBearerCapability(false), - mHaveCallingPartyBCDNumber(true),mCallingPartyBCDNumber(wCallingPartyBCDNumber), - mHaveCalledPartyBCDNumber(false) + mHaveCallingPartyBCDNumber(true),mCallingPartyBCDNumber(wCallingPartyBCDNumber) { } - - /** Accessors */ //@{ bool haveCalledPartyBCDNumber() const { return mHaveCalledPartyBCDNumber; } @@ -287,6 +288,25 @@ public: }; +/** + GSM 04.08 9.3.8 +*/ +// (pat) 5-2013: This uses a subset of the IEs as L3Setup, so we can use that class to parse it. +//class L3EmergencySetup : public L3CCMessage +class L3EmergencySetup : public L3Setup +{ +public: + + //L3EmergencySetup(unsigned wTI=7) :L3CCMessage(wTI) { } + + int MTI() const { return EmergencySetup; } + //void parseBody(const L3Frame &src, size_t &rp) {} // parseBody supplied by L3Setup. + size_t l2BodyLength() const { return 0; } // We dont write this message, so we dont care about this. +}; + + + + /** GSM 04.08 9.3.3 */ class L3CallProceeding : public L3CCMessage { @@ -294,7 +314,8 @@ class L3CallProceeding : public L3CCMessage { // We'fill in IEs one at a time as we need them. - bool mHaveBearerCapability; + // (pat) Should be modified to use L3CCCapabilities. + //bool mHaveBearerCapability; L3BearerCapability mBearerCapability; bool mHaveProgress; @@ -304,7 +325,7 @@ public: L3CallProceeding(unsigned wTI=7) :L3CCMessage(wTI), - mHaveBearerCapability(false), + //mHaveBearerCapability(false), mHaveProgress(false) {} @@ -324,18 +345,18 @@ public: Even though uplink and downlink forms have different optional fields, we can use a single message for both sides. */ -class L3Alerting : public L3CCMessage +class L3Alerting : public L3CCMessage, public L3CCCommonIEs { private: - bool mHaveProgress; + Bool_z mHaveProgress; L3ProgressIndicator mProgress; ///< Progress appears in uplink only. public: L3Alerting(unsigned wTI=7) - :L3CCMessage(wTI), - mHaveProgress(false) + :L3CCMessage(wTI) + //,mHaveProgress(false) {} L3Alerting(unsigned wTI,const L3ProgressIndicator& wProgress) @@ -394,8 +415,8 @@ public: {} int MTI() const { return ConnectAcknowledge; } - void writeBody( L3Frame &dest, size_t &wp ) const {} - void parseBody( const L3Frame &src, size_t &rp ) {} + void writeBody( L3Frame &/*dest*/, size_t &/*wp*/ ) const {} + void parseBody( const L3Frame &/*src*/, size_t &/*rp*/ ) {} size_t l2BodyLength() const { return 0; } }; @@ -411,7 +432,7 @@ private: public: /** Initialize with default cause of 0x10 "normal call clearing". */ - L3Disconnect(unsigned wTI=7, const L3Cause& wCause = L3Cause(0x10)) + L3Disconnect(unsigned wTI=7, const L3Cause& wCause = L3Cause(L3Cause::NormalCallClearing)) :L3CCMessage(wTI), mCause(wCause) {} @@ -422,24 +443,31 @@ public: void parseBody( const L3Frame &src, size_t &rp ); size_t l2BodyLength() const { return mCause.lengthLV(); } void text(std::ostream&) const; - }; /** GSM 04.08 9.3.2 */ -class L3CallConfirmed : public L3CCMessage { +class L3CallConfirmed : public L3CCMessage, public L3CCCapabilities { private: - bool mHaveCause; + // (pat) BearerCapability is sent by GSM phone + //Bool_z mHaveBearerCapability; + //L3BearerCapability mBearerCapability; + Bool_z mHaveCause; L3Cause mCause; + // (pat) SupportedCodecList is sent by UMTS phone + //Bool_z mHaveSupportedCodecs; + //L3SupportedCodecList mSupportedCodecs; + public: L3CallConfirmed(unsigned wTI=7) - :L3CCMessage(wTI), - mHaveCause(false) + :L3CCMessage(wTI) + //mHaveCause(false), + //mHaveSupportedCodecs(false) {} int MTI() const { return CallConfirmed; } @@ -531,7 +559,7 @@ class L3StopDTMF : public L3CCMessage { {} int MTI() const { return StopDTMF; } - void parseBody(const L3Frame &src, size_t &rp) { } + void parseBody(const L3Frame &/*src*/, size_t &/*rp*/) { } size_t l2BodyLength() const { return 0; }; }; @@ -588,8 +616,8 @@ public: {} int MTI() const { return Hold; } - void writeBody( L3Frame &dest, size_t &wp ) const {} - void parseBody( const L3Frame &src, size_t &rp ) {} + void writeBody( L3Frame &/*dest*/, size_t &/*wp*/ ) const {} + void parseBody( const L3Frame &/*src*/, size_t &/*rp*/ ) {} size_t l2BodyLength() const { return 0; } }; diff --git a/GSM/GSML3CommonElements.h b/GSM/GSML3CommonElements.h index 3cb99cc..fa82249 100644 --- a/GSM/GSML3CommonElements.h +++ b/GSM/GSML3CommonElements.h @@ -101,9 +101,7 @@ class L3LocationAreaIdentity : public L3ProtocolElement { /** Mobile Identity, GSM 04.08, 10.5.1.4 */ class L3MobileIdentity : public L3ProtocolElement { - private: - MobileIDType mType; ///< IMSI, TMSI, or IMEI? char mDigits[16]; ///< GSM 03.03 2.2 limits the IMSI or IMEI to 15 digits. @@ -134,6 +132,8 @@ class L3MobileIdentity : public L3ProtocolElement { MobileIDType type() const { return mType; } const char* digits() const { assert(mType!=TMSIType); return mDigits; } unsigned int TMSI() const { assert(mType==TMSIType); return mTMSI; } + bool isIMSI() const { return mType==IMSIType; } + bool isTMSI() const { return mType==TMSIType; } //@} /** Comparison. */ diff --git a/GSM/GSML3MMElements.cpp b/GSM/GSML3MMElements.cpp index 4506035..6f0d4dd 100644 --- a/GSM/GSML3MMElements.cpp +++ b/GSM/GSML3MMElements.cpp @@ -3,7 +3,7 @@ */ /* * Copyright 2008 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2010, 2014 Kestrel Signal Processing, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -32,25 +32,31 @@ using namespace GSM; void L3CMServiceType::parseV(const L3Frame& src, size_t &rp) { - mType = (TypeCode)src.readField(rp,4); + mType = (CMServiceTypeCode)src.readField(rp,4); } -ostream& GSM::operator<<(ostream& os, L3CMServiceType::TypeCode code) +ostream& GSM::operator<<(ostream& os, CMServiceTypeCode code) { switch (code) { - case L3CMServiceType::MobileOriginatedCall: os << "MOC"; break; - case L3CMServiceType::ShortMessage: os << "SMS"; break; - case L3CMServiceType::SupplementaryService: os << "SS"; break; - case L3CMServiceType::VoiceCallGroup: os << "VGCS"; break; - case L3CMServiceType::VoiceBroadcast: os << "VBS"; break; - case L3CMServiceType::LocationService: os << "LCS"; break; - case L3CMServiceType::MobileTerminatedCall: os << "MTC"; break; - case L3CMServiceType::MobileTerminatedShortMessage: os << "MTSMS"; break; - case L3CMServiceType::TestCall: os << "Test"; break; - case L3CMServiceType::FuzzCall: os << "Fuzz"; break; - default: os << "?" << (int)code << "?"; + case L3CMServiceType::MobileOriginatedCall: os << "MOC"; return os; + case L3CMServiceType::EmergencyCall: os << "Emergency"; return os; + case L3CMServiceType::ShortMessage: os << "SMS"; return os; + case L3CMServiceType::SupplementaryService: os << "SS"; return os; + case L3CMServiceType::VoiceCallGroup: os << "VGCS"; return os; + case L3CMServiceType::VoiceBroadcast: os << "VBS"; return os; + case L3CMServiceType::LocationService: os << "LCS"; return os; + case L3CMServiceType::MobileTerminatedCall: os << "MTC"; return os; + case L3CMServiceType::MobileTerminatedShortMessage: os << "MTSMS"; return os; + case L3CMServiceType::TestCall: os << "Test"; return os; + case L3CMServiceType::FuzzCallTch: os << "FuzzCallTCH"; return os; + case L3CMServiceType::FuzzCallSdcch: os << "FuzzCallSDCCH"; return os; + case L3CMServiceType::LocationUpdateRequest: os << "LUR"; return os; + case L3CMServiceType::HandoverCall: os << "Handover"; return os; + case L3CMServiceType::UndefinedType: break; + //default: os << "?" << (int)code << "?"; } + os << "invalid:" << (int)code; return os; } @@ -65,6 +71,11 @@ void L3RejectCause::writeV( L3Frame& dest, size_t &wp ) const } +void L3RejectCause::parseV(const L3Frame& src, size_t &rp) +{ + mRejectCause = (RejectCause) src.readField(rp,8); +} + void L3RejectCause::text(ostream& os) const { os <<"0x"<< hex << mRejectCause << dec; @@ -100,7 +111,7 @@ void L3NetworkName::writeV(L3Frame& dest, size_t &wp) const dest.writeField(wp,mCI,1); // show country name? dest.writeField(wp,spareBits,3); // spare bits in last octet // Temporary vector "chars" so we can do LSB8MSB() after encoding. - BitVector chars(dest.segment(wp,msgBits)); + BitVector2 chars(dest.segment(wp,msgBits)); size_t twp = 0; // the characters: 7 bit, GSM 03.38 6.1.2.2, 6.2.1 for (unsigned i=0; i #include @@ -51,7 +54,7 @@ void L3Message::write(L3Frame& dest) const // write the body writeBody(dest,wp); // set the L2 length or pseudolength - dest.L2Length(L2Length()); + dest.L2Length(l2Length()); } @@ -70,6 +73,15 @@ void L3Message::text(ostream& os) const os << " MTI=" << MTI(); } +string L3Message::text() const +{ + // Its a wonderful C++ world. + if (!this) return string("(null L3Message)"); + ostringstream ss; + text(ss); + return ss.str(); +} + @@ -112,6 +124,29 @@ size_t GSM::skipTV(unsigned IEI, size_t numBits, const L3Frame& source, size_t& return rp-base; } +string GSM::mti2string(L3PD pd, unsigned mti) +{ + ostringstream result; + // (pat) Since the C++ output paradigm is not OOP, we resort to switch statements. + switch (pd) { + // case L3GroupCallControlPD: break; + // case L3BroadcastCallControlPD: break; + // case L3PDSS1PD: break; + case L3CallControlPD: operator<<(result,(L3CCMessage::MessageType) mti); return result.str(); + // case L3PDSS2PD: break; + case L3MobilityManagementPD: operator<<(result,(L3MMMessage::MessageType) mti); return result.str(); + case L3RadioResourcePD: operator<<(result,(L3RRMessage::MessageType) mti); return result.str(); + case L3GPRSMobilityManagementPD: operator<<(result, (SGSN::L3GmmMsg::MessageType) mti); return result.str(); + case L3SMSPD: operator<<(result, (SMS::CPMessage::MessageType) mti); return result.str(); + case L3GPRSSessionManagementPD: operator<<(result, (SGSN::L3SmMsg::MessageType) mti); return result.str(); + case L3NonCallSSPD: operator<<(result, (L3SupServMessage::MessageType) mti); return result.str(); + // case L3LocationPD: break; + // case L3ExtendedPD: break; + // case L3TestProcedurePD: break; + default: break; + } + return string("unknown"); +} ostream& GSM::operator<<(ostream& os, const L3Message& msg) @@ -120,6 +155,12 @@ ostream& GSM::operator<<(ostream& os, const L3Message& msg) return os; } +ostream& GSM::operator<<(ostream& os, const L3Message*msg) +{ + if (msg) {msg->text(os);} else { os<<"null"; } + return os; +} + @@ -137,12 +178,13 @@ GSM::L3Message* GSM::parseL3(const GSM::L3Frame& source) case L3MobilityManagementPD: retVal=parseL3MM(source); break; case L3CallControlPD: retVal=parseL3CC(source); break; case L3SMSPD: retVal=SMS::parseSMS(source); break; + case L3NonCallSSPD: retVal = parseL3SupServ(source); break; default: LOG(NOTICE) << "L3 parsing failed for unsupported protocol " << PD; return NULL; } } - catch (L3ReadError) { + catch (...) { // (pat) Used to just catch L3ReadError, but lets be safer. LOG(NOTICE) << "L3 parsing failed for " << source; return NULL; } @@ -162,7 +204,7 @@ void L3ProtocolElement::parseLV(const L3Frame& source, size_t &rp) size_t rpEnd = rp + 8*expectedLength; parseV(source, rp, expectedLength); if (rpEnd != rp) { - LOG(NOTICE) << "LV element does not match expected length"; + LOG(NOTICE) << "LV element does not match expected length "< #include "GSMCommon.h" #include "GSMTransfer.h" @@ -64,7 +65,8 @@ class L3Message { virtual size_t fullBodyLength() const =0; /** Return the expected message length in bytes, including L3 header, but not including rest octets. */ - size_t L2Length() const { return l2BodyLength()+2; } + // (pat) L2Length is also a class. This renaming: + size_t l2Length() const { return l2BodyLength()+2; } /** Length ((pat) in BYTES!!) including header and rest octets. */ size_t FullLength() const { return fullBodyLength()+2; } @@ -76,6 +78,7 @@ class L3Message { The parse() method reads and decodes L3 message bits. This method invokes parseBody, assuming that the L3 header has already been read. + // (pat) You probably want parseL3, not this. */ virtual void parse(const L3Frame& source); @@ -89,6 +92,7 @@ class L3Message { /** Generate an L3Frame for this message. The caller is responsible for deleting the memory. + (pat) TODO: This is called only from RRLPServer, and apparently unnecessarily. Get rid of this. */ L3Frame* frame(GSM::Primitive prim=DATA) const; @@ -98,6 +102,8 @@ class L3Message { /** Return the messag type indicator (MTI). */ virtual int MTI() const =0; + // pat added: TI is only valid for CC and SMS and SS messages, so we will hit this assertion if you try to use this for MM or RR messages. + virtual unsigned TI() const { devassert(0); return 8; } protected: @@ -120,6 +126,7 @@ class L3Message { /** Generate a human-readable representation of a message. */ virtual void text(std::ostream& os) const; + std::string text() const; // Return the string formed by the above. }; @@ -160,6 +167,7 @@ L3Message* parseL3(const L3Frame& source); std::ostream& operator<<(std::ostream& os, const GSM::L3Message& msg); +std::ostream& operator<<(std::ostream& os, const GSM::L3Message* msg); @@ -291,6 +299,24 @@ class L3ProtocolElement { }; +// (pat 9-2013) A generic LV or TLV element. +class L3OctetAlignedProtocolElement : public L3ProtocolElement { + public: + string mData; // Here it is. + Bool_z mExtant; // Does this IE exist in the message? Avoids having to use mHave... all over the place. + const unsigned char *peData() const { return (const unsigned char*)mData.data(); } + size_t lengthV() const { return mData.size(); } + void writeV(L3Frame&dest, size_t&wp) const; + // This parse just cracks the components out. + void parseV(const L3Frame&src, size_t&rp, size_t expectedLength); // This form must be used for TLV format. + void parseV(const L3Frame&, size_t&) { assert(0); } // This form illegal for T/TV format. + void text(std::ostream&) const; + L3OctetAlignedProtocolElement(string wData): mData(wData), mExtant(true) {} + L3OctetAlignedProtocolElement(): mExtant(false) {} +}; + +bool parseHasT(unsigned IEI, const L3Frame& source, size_t &rp); + std::ostream& operator<<(std::ostream& os, const L3ProtocolElement& elem); @@ -308,6 +334,7 @@ class GenericMessageElement { std::ostream& operator<<(std::ostream& os, const GenericMessageElement& elem); +string mti2string(L3PD pd, unsigned mti); }; // GSM diff --git a/GSM/GSML3RRElements.cpp b/GSM/GSML3RRElements.cpp index 614ebd2..294b519 100644 --- a/GSM/GSML3RRElements.cpp +++ b/GSM/GSML3RRElements.cpp @@ -409,7 +409,7 @@ void L3RRCause::writeV( L3Frame &dest, size_t &wp ) const void L3RRCause::parseV( const L3Frame &src, size_t &rp ) { - mCauseValue = src.readField(rp, 8); + mCauseValue = (RRCause) src.readField(rp, 8); } void L3RRCause::text(ostream& os) const @@ -426,6 +426,12 @@ void L3PowerCommand::writeV( L3Frame &dest, size_t &wp )const dest.writeField(wp, mCommand, 5); } +void L3PowerCommand::parseV(const L3Frame &src, size_t &rp) +{ + // Just put the whole thing in mCommand so we can see it. + mCommand = src.readField(rp,8); +} + void L3PowerCommand::text(ostream& os) const { @@ -446,6 +452,36 @@ void L3ChannelMode::parseV(const L3Frame& src, size_t& rp) mMode = (Mode)src.readField(rp,8); } +void L3MultiRateConfiguration::writeV( L3Frame& dest, size_t &wp) const +{ + // The length is written by the caller. + //dest.writeField(wp,3,8); // fixed length + dest.writeField(wp,mOptions,8); + dest.writeField(wp,mAmrCodecSet,8); +} + +void L3MultiRateConfiguration::text(std::ostream&os) const +{ + char name[20]; + switch (mAmrCodecSet) { + case codec_set_AMR_FR: strcpy(name,"AMR_FR"); break; + case codec_set_AMR_HR: strcpy(name,"AMR_HR"); break; + case codec_set_UMTS_AMR: strcpy(name,"UMTS_AMR"); break; + default: sprintf(name,"0x%x",mAmrCodecSet); break; + } + os << format("MultRate(options=0x%x,codec=%s)",mOptions,name); +} + +//void L3MultiRateConfiguration::parseV(const L3Frame& src, size_t& rp) +//{ +// size_t rpstart = rp; +// unsigned len = src.readField(rp,8); +// if (len != 3) { LOG(ERR) << "L3MultRateConfiguration IEI unsupported length: "< output( os, "" ); //std::copy( mData.begin(), mData.end(), output ); - for (size_t i = 0 ; i < mData.size() ; ++i) { - os << (mData[i] ? "1" : "0"); - } - + os <<"data(size="< Maximum 1 retransmission. + // 1 => Maximum 2 retransmission. + // 2 => Maximum 4 retransmission. + // 3 => Maximum 7 retransmission. unsigned mTxInteger; ///< code for 3-50 slots to spread transmission unsigned mCellBarAccess; ///< if true, phones cannot camp unsigned mRE; ///< if true, call reestablishment is not allowed @@ -331,7 +350,7 @@ class L3RACHControlParameters : public L3ProtocolElement { // Configurable values. mMaxRetrans = gConfig.getNum("GSM.RACH.MaxRetrans"); mTxInteger = gConfig.getNum("GSM.RACH.TxInteger"); - mAC = 0x0400; //NO EMERGENCY SERVICE - kurtis + mAC = gConfig.getNum("GSM.RACH.AC"); } size_t lengthV() const { return 3; } @@ -417,14 +436,14 @@ class L3ChannelDescription : public L3ProtocolElement { // Octet 2. TypeAndOffset mTypeAndOffset; // 5 bit - unsigned mTN; //3 bit + unsigned mTN; //3 bit Timeslot Number // Octet 3 & 4. - unsigned mTSC; // 3 bit - unsigned mHFlag; // 1 bit + unsigned mTSC; // 3 bit Training Sequence Number. + unsigned mHFlag; // 1 bit If true, it is a hopping channel. unsigned mARFCN; // 10 bit overflows - unsigned mMAIO; // 6 bit overflows - unsigned mHSN; // 6 bit + unsigned mMAIO; // 6 bit overflows Mobile Allocation Index Offset (for hopping channels) + unsigned mHSN; // 6 bit Hopping Sequence Number (for hopping channels) public: @@ -458,7 +477,7 @@ public: unsigned ARFCN() const { return mARFCN; } }; -/** GSM 040.08 10.5.2.5a */ +/** GSM 44.018 10.5.2.5a */ class L3ChannelDescription2 : public L3ChannelDescription { public: @@ -468,6 +487,8 @@ class L3ChannelDescription2 : public L3ChannelDescription { :L3ChannelDescription(wTypeAndOffset,wTN,wTSC,wARFCN) { } + L3ChannelDescription2(const L3ChannelDescription &other) : L3ChannelDescription(other) {} + L3ChannelDescription2() { } }; @@ -542,18 +563,43 @@ public: /** GSM 04.08 10.5.2.31 */ +// 44.018 10.5.2.31 +// (pat) The default RR cause 0 indicates "Normal Event" class L3RRCause : public L3ProtocolElement { - int mCauseValue; + public: + enum RRCause { + NormalEvent = 0, + Unspecified = 1, + ChannelUnacceptable = 2, + TimerExpired = 3, + NoActivityOnTheRadio = 4, + PreemptiveRelease = 5, + UTRANConfigurationUnknown = 6, + HandoverImpossible = 8, // (Timing out of range) + ChannelModeUnacceptable = 9, + FrequencyNotImplemented = 0xa, + LeavingGroupCallArea = 0xb, + LowerLayerFailure = 0xc, + CallAlreadyCleared = 0x41, + SemanticallyIncorrectMessage = 0x5f, + InvalidMandatoryInformation = 0x60, + MessageTypeInvalid = 0x61, // Not implemented or non-existent + MessageTypeNotCompapatibleWithProtocolState = 0x62, + ConditionalIEError = 0x64, + NoCellAvailable = 0x65, + ProtocolErrorUnspecified = 0x6f + }; + private: RRCause mCauseValue; public: /** Constructor cause defaults to "normal event". */ - L3RRCause(int wValue=0) + L3RRCause(RRCause wValue=NormalEvent) :L3ProtocolElement() { mCauseValue=wValue; } - int causeValue() const { return mCauseValue; } + RRCause causeValue() const { return mCauseValue; } size_t lengthV() const { return 1; } void writeV(L3Frame&, size_t&) const; @@ -562,6 +608,7 @@ class L3RRCause : public L3ProtocolElement void text(std::ostream&) const; }; +typedef L3RRCause::RRCause RRCause; @@ -581,7 +628,7 @@ public: size_t lengthV() const { return 1; } void writeV( L3Frame &dest, size_t &wp ) const; - void parseV( const L3Frame&, size_t&) { assert(0); } + void parseV( const L3Frame&, size_t&); void parseV(const L3Frame&, size_t& , size_t) { assert(0); } void text(std::ostream&) const; @@ -597,9 +644,9 @@ public: enum Mode { SignallingOnly=0, - SpeechV1=1, - SpeechV2=2, - SpeechV3=3 + SpeechV1=1, // GSM FR or HR + SpeechV2=2, // GSM EFR (half rate not defined in this version of the protocol.) + SpeechV3=3 // AMR FR or HR }; private: @@ -613,6 +660,8 @@ public: mMode(wMode) {} + bool isAMR() const { return mMode == SpeechV3; } + bool operator==(const L3ChannelMode& other) const { return mMode==other.mMode; } bool operator!=(const L3ChannelMode& other) const { return mMode!=other.mMode; } @@ -624,6 +673,45 @@ public: }; + +// 3GPP 44.018 10.5.2.21aa +// (pat) 10-2012: Multi-rate element needed for AMR codecs when L3ChannelMode is set to SpeechV3. +// Not to be confused with unrelated GPRS multi-slot. +// It is sent in the L3 Channel Mode Modify, possibly among others. +// It is a variable length element with room to specify multiple codecs, +// but we do not support AMR rate adaptation yet so we use it with just one codec. +class L3MultiRateConfiguration : public L3ProtocolElement { + // IE specifies a set of 4 among the many AMR codecs, but there are only 3 possible configurations + // that are ever of any interest to us: AMR_FR, AMR_HR, and if we ever support + // rate-adaptation, the recommended multi-rate set from 28.062 table 7.11.3.1.3-2, + // which is Config-NB-1 includes codecs: 12.2, 7.4, 5.9, 4.75 for FR_AMR and compatible with GSM AMR. + // For multi-rate we must also specify the threshold and hystersis to switch codecs, which are black + // magic and possibly dependent on MS manufacturer - gotta love that, and we also have to implement + // the TFO/TrFO (Tandem Free/Transcoder Free) Operation controller, not to mention implementing + // codec negotation through SIP. It is a lot of work. + + // The options byte is a bit field defined in 10.5.2.21aa.1. + // There are only 3 possible values that will ever by useful: + enum AmrCodecSet { + codec_set_AMR_FR = 0x80, + codec_set_AMR_HR = 0x10, + codec_set_UMTS_AMR = 0x80 + 0x10 + 4 + 1 // AMR multi-rate config, in case we ever use it. + }; + unsigned mOptions; // octet "3". + AmrCodecSet mAmrCodecSet; // Choose one of the above. + public: + L3MultiRateConfiguration(bool halfrate=0) { + mOptions = 0x20; // AMR version 1 (ie, not WB), no noise suppression, no initial codec. + mAmrCodecSet = halfrate ? codec_set_AMR_HR : codec_set_AMR_FR; + } + // excluding IEI and the length byte itself, the fixed length for a single non-adaptive codec is 2. + size_t lengthV() const { return 2; } + void parseV(const L3Frame&, size_t&) { assert(0); } + void parseV(const L3Frame&, size_t& , size_t) { assert(0); } // Use this one for TLV format + void writeV(L3Frame&, size_t&) const; + void text(std::ostream&) const; +}; + std::ostream& operator<<(std::ostream&, L3ChannelMode::Mode); @@ -714,7 +802,7 @@ class L3APDUData : public L3ProtocolElement { private: - BitVector mData; // will contain a RRLP message + BitVector2 mData; // will contain a RRLP message public: @@ -722,7 +810,7 @@ class L3APDUData : public L3ProtocolElement { /** Default is a zero length APDUData IE */ L3APDUData(); - L3APDUData(BitVector data); + L3APDUData(BitVector2 data); size_t lengthV() const { @@ -778,6 +866,7 @@ class L3MeasurementResults : public L3ProtocolElement { void parseV(const L3Frame&, size_t&); void parseV(const L3Frame&, size_t& , size_t) { assert(0); } void text(std::ostream& os) const; + string text() const; /**@name Accessors. */ //@{ @@ -844,7 +933,7 @@ class L3CellDescription : public L3ProtocolElement { size_t lengthV() const { return 2; } void writeV(L3Frame&, size_t&) const; - void parseV(const L3Frame&, size_t&) { assert(0); } + void parseV(const L3Frame&, size_t&); void parseV(const L3Frame&, size_t& , size_t) { assert(0); } void text(std::ostream&) const; }; @@ -855,7 +944,7 @@ class L3HandoverReference : public L3ProtocolElement { protected: - unsigned mValue; + unsigned mValue; // Range 0 to 255 public: @@ -870,7 +959,7 @@ class L3HandoverReference : public L3ProtocolElement size_t lengthV() const { return 1; } void writeV(L3Frame &, size_t &wp ) const; void parseV( const L3Frame&, size_t&, size_t) { abort(); } - void parseV(const L3Frame&, size_t&) { abort(); } + void parseV(const L3Frame&src, size_t&rp) { mValue = src.readField(rp,8); } void text(std::ostream&) const; unsigned value() const { return mValue; } @@ -924,9 +1013,9 @@ class L3SynchronizationIndication : public L3ProtocolElement { protected: - bool mNCI; - bool mROT; - int mSI; + bool mNCI; // Normal Cell Indication. 0 => ignore TA out of range. 1 => handover fail on TA out of range. + bool mROT; // Report Observed Time difference in Handover Complete. + int mSI; // Synchronization Indication. 0 = unsynchronized. public: @@ -942,7 +1031,7 @@ class L3SynchronizationIndication : public L3ProtocolElement size_t lengthV() const { return 1; } void writeV(L3Frame &, size_t &wp ) const; void parseV( const L3Frame&, size_t&, size_t) { abort(); } - void parseV(const L3Frame&, size_t&) { abort(); } + void parseV(const L3Frame&, size_t&); void text(std::ostream&) const; unsigned NCI() const { return mNCI; } @@ -951,6 +1040,7 @@ class L3SynchronizationIndication : public L3ProtocolElement /** GSM 04.08 10.5.2.28a */ +// (pat) Defaults to 0 which is maximum possible power. class L3PowerCommandAndAccessType : public L3PowerCommand { }; diff --git a/GSM/GSML3RRMessages.cpp b/GSM/GSML3RRMessages.cpp index 0933951..3d3d8d6 100644 --- a/GSM/GSML3RRMessages.cpp +++ b/GSM/GSML3RRMessages.cpp @@ -48,86 +48,97 @@ void L3Message::parseBody(const L3Frame&, size_t&) } +const char *L3RRMessage::name(MessageType mt) +{ + switch (mt) { + case L3RRMessage::SystemInformationType1: + return "System Information Type 1"; + case L3RRMessage::SystemInformationType2: + return "System Information Type 2"; + case L3RRMessage::SystemInformationType2bis: + return "System Information Type 2bis"; + case L3RRMessage::SystemInformationType2ter: + return "System Information Type 2ter"; + case L3RRMessage::SystemInformationType3: + return "System Information Type 3"; + case L3RRMessage::SystemInformationType4: + return "System Information Type 4"; + case L3RRMessage::SystemInformationType5: + return "System Information Type 5"; + case L3RRMessage::SystemInformationType5bis: + return "System Information Type 5bis"; + case L3RRMessage::SystemInformationType5ter: + return "System Information Type 5ter"; + case L3RRMessage::SystemInformationType6: + return "System Information Type 6"; + case L3RRMessage::SystemInformationType7: + return "System Information Type 7"; + case L3RRMessage::SystemInformationType8: + return "System Information Type 8"; + case L3RRMessage::SystemInformationType9: + return "System Information Type 9"; + case L3RRMessage::SystemInformationType13: + return "System Information Type 13"; + case L3RRMessage::SystemInformationType16: + return "System Information Type 16"; + case L3RRMessage::SystemInformationType17: + return "System Information Type 17"; + case L3RRMessage::PagingResponse: + return "Paging Response"; + case L3RRMessage::PagingRequestType1: + return "Paging Request Type 1"; + case L3RRMessage::MeasurementReport: + return "Measurement Report"; + case L3RRMessage::AssignmentComplete: + return "Assignment Complete"; + case L3RRMessage::ImmediateAssignment: + return "Immediate Assignment"; + case L3RRMessage::ImmediateAssignmentReject: + return "Immediate Assignment Reject"; + case L3RRMessage::AssignmentCommand: + return "Assignment Command"; + case L3RRMessage::AssignmentFailure: + return "Assignment Failure"; + case L3RRMessage::ChannelRelease: + return "Channel Release"; + case L3RRMessage::ChannelModeModify: + return "Channel Mode Modify"; + case L3RRMessage::ChannelModeModifyAcknowledge: + return "Channel Mode Modify Acknowledge"; + case L3RRMessage::GPRSSuspensionRequest: + return "GPRS Suspension Request"; + case L3RRMessage::ClassmarkEnquiry: + return "Classmark Enquiry"; + case L3RRMessage::ClassmarkChange: + return "Classmark Change"; + case L3RRMessage::RRStatus: + return "RR Status"; + case L3RRMessage::ApplicationInformation: + return "Application Information"; + case L3RRMessage::HandoverCommand: + return "Handover Command"; + case L3RRMessage::HandoverComplete: + return "Handover Complete"; + case L3RRMessage::HandoverFailure: + return "Handover Failure"; + case L3RRMessage::CipheringModeCommand: + return "Ciphering Mode Command"; + case L3RRMessage::CipheringModeComplete: + return "Ciphering Mode Complete"; + case L3RRMessage::PhysicalInformation: + return "Physical Information"; + default: + return "?"; + } +} + ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val) { - switch (val) { - case L3RRMessage::SystemInformationType1: - os << "System Information Type 1"; break; - case L3RRMessage::SystemInformationType2: - os << "System Information Type 2"; break; - case L3RRMessage::SystemInformationType2bis: - os << "System Information Type 2bis"; break; - case L3RRMessage::SystemInformationType2ter: - os << "System Information Type 2ter"; break; - case L3RRMessage::SystemInformationType3: - os << "System Information Type 3"; break; - case L3RRMessage::SystemInformationType4: - os << "System Information Type 4"; break; - case L3RRMessage::SystemInformationType5: - os << "System Information Type 5"; break; - case L3RRMessage::SystemInformationType5bis: - os << "System Information Type 5bis"; break; - case L3RRMessage::SystemInformationType5ter: - os << "System Information Type 5ter"; break; - case L3RRMessage::SystemInformationType6: - os << "System Information Type 6"; break; - case L3RRMessage::SystemInformationType7: - os << "System Information Type 7"; break; - case L3RRMessage::SystemInformationType8: - os << "System Information Type 8"; break; - case L3RRMessage::SystemInformationType9: - os << "System Information Type 9"; break; - case L3RRMessage::SystemInformationType13: - os << "System Information Type 13"; break; - case L3RRMessage::SystemInformationType16: - os << "System Information Type 16"; break; - case L3RRMessage::SystemInformationType17: - os << "System Information Type 17"; break; - case L3RRMessage::PagingResponse: - os << "Paging Response"; break; - case L3RRMessage::PagingRequestType1: - os << "Paging Request Type 1"; break; - case L3RRMessage::MeasurementReport: - os << "Measurement Report"; break; - case L3RRMessage::AssignmentComplete: - os << "Assignment Complete"; break; - case L3RRMessage::ImmediateAssignment: - os << "Immediate Assignment"; break; - case L3RRMessage::ImmediateAssignmentReject: - os << "Immediate Assignment Reject"; break; - case L3RRMessage::AssignmentCommand: - os << "Assignment Command"; break; - case L3RRMessage::AssignmentFailure: - os << "Assignment Failure"; break; - case L3RRMessage::ChannelRelease: - os << "Channel Release"; break; - case L3RRMessage::ChannelModeModify: - os << "Channel Mode Modify"; break; - case L3RRMessage::ChannelModeModifyAcknowledge: - os << "Channel Mode Modify Acknowledge"; break; - case L3RRMessage::GPRSSuspensionRequest: - os << "GPRS Suspension Request"; break; - case L3RRMessage::ClassmarkEnquiry: - os << "Classmark Enquiry"; break; - case L3RRMessage::ClassmarkChange: - os << "Classmark Change"; break; - case L3RRMessage::RRStatus: - os << "RR Status"; break; - case L3RRMessage::ApplicationInformation: - os << "Application Information"; break; - case L3RRMessage::HandoverCommand: - os << "Handover Command"; break; - case L3RRMessage::HandoverComplete: - os << "Handover Complete"; break; - case L3RRMessage::HandoverFailure: - os << "Handover Failure"; break; - case L3RRMessage::CipheringModeCommand: - os << "Ciphering Mode Command"; break; - case L3RRMessage::CipheringModeComplete: - os << "Ciphering Mode Complete"; break; - case L3RRMessage::PhysicalInformation: - os << "Physical Information"; break; - default: os << hex << "0x" << (int)val << dec; + const char *result = L3RRMessage::name(val); + if (result[0] == '?') { + os << hex << "0x" << (int)val << dec; + } else { + os << result; } return os; } @@ -139,7 +150,6 @@ void L3RRMessage::text(ostream& os) const } - L3RRMessage* GSM::L3RRFactory(L3RRMessage::MessageType MTI) { switch (MTI) { @@ -156,6 +166,7 @@ L3RRMessage* GSM::L3RRFactory(L3RRMessage::MessageType MTI) case L3RRMessage::HandoverComplete: return new L3HandoverComplete(); case L3RRMessage::HandoverFailure: return new L3HandoverFailure(); case L3RRMessage::CipheringModeComplete: return new L3CipheringModeComplete(); + case L3RRMessage::HandoverCommand: return new L3HandoverCommand(); // Partial support just to get along with some phones. case L3RRMessage::GPRSSuspensionRequest: return new L3GPRSSuspensionRequest(); default: @@ -573,6 +584,7 @@ void L3AssignmentCommand::writeBody( L3Frame &dest, size_t &wp ) const mChannelDescription.writeV(dest, wp); mPowerCommand.writeV(dest, wp); if (mHaveMode1) mMode1.writeTV(0x63,dest,wp); + if (isAMR()) mMultiRate.writeTLV(3,dest,wp); } size_t L3AssignmentCommand::l2BodyLength() const @@ -580,6 +592,7 @@ size_t L3AssignmentCommand::l2BodyLength() const size_t len = mChannelDescription.lengthV(); len += mPowerCommand.lengthV(); if (mHaveMode1) len += mMode1.lengthTV(); + if (isAMR()) len += mMultiRate.lengthTLV(); return len; } @@ -590,6 +603,7 @@ void L3AssignmentCommand::text(ostream& os) const os <<"channelDescription=("< +#include "GSML3SSMessages.h" +#include +#include + + +using namespace std; +namespace GSM { + +void L3SupServVersionIndicator::_define_vtable() {} // Force the class vtable into this module. +void L3SupServFacilityIE::_define_vtable() {} // Force the class vtable into this module. + +ostream& operator<<(ostream& os, const L3SupServVersionIndicator& ie) +{ + ie.text(os); + return os; +} + +ostream& operator<<(ostream& os, const L3SupServFacilityIE& ie) +{ + ie.text(os); + return os; +} + +ostream& operator<<(ostream& os, const L3SupServMessage& msg) +{ + msg.text(os); + return os; +} + +ostream& operator<<(ostream& os, const L3SupServMessage* msg) +{ + if (msg == NULL) os << "(null SS message)"; + else msg->text(os); + return os; +} + +ostream& operator<<(ostream& os, L3SupServMessage::MessageType val) +{ + switch (val) { + case L3SupServMessage::ReleaseComplete: + os << "ReleaseComplete"; break; + case L3SupServMessage::Facility: + os << "Facility"; break; + case L3SupServMessage::Register: + os << "Register"; break; + default: os << hex << "0x" << (int)val << dec; + } + return os; +} + +void L3OneByteProtocolElement::parseV(const L3Frame&src, size_t&rp, size_t expectedLength) +{ + if (expectedLength != 1) { LOG(ERR) << "Unexpected length="<=256) { mComponentSize = 255; } + mExtant = true; + memcpy(mComponents,wComponents,mComponentSize); + } + + L3SupServFacilityIE():L3ProtocolElement(),mComponentSize(0) { } + + size_t componentSize() const { return mComponentSize; } + const unsigned char* components() const { return mComponents; } + + size_t lengthV() const { return mComponentSize; } + void writeV(L3Frame&dest, size_t&wp) const; + // This parse just cracks the components out. + void parseV(const L3Frame&src, size_t&rp, size_t expectedLength); // This form must be used for TLV format. + void parseV(const L3Frame&, size_t&) { assert(0); } // This form illegal for T/TV format. +#endif +}; + +// 24.008 10.5.4.24 +class L3SupServVersionIndicator : public L3OctetAlignedProtocolElement { + virtual void _define_vtable(); + public: + L3SupServVersionIndicator(std::string wData) : L3OctetAlignedProtocolElement(wData) {} + L3SupServVersionIndicator() {} +}; + + +std::ostream& operator<<(std::ostream& os, const L3SupServVersionIndicator& msg); +std::ostream& operator<<(std::ostream& os, const L3SupServFacilityIE& msg); +std::ostream& operator<<(std::ostream& os, const GSM::L3SupServMessage& msg); +std::ostream& operator<<(std::ostream& os, const GSM::L3SupServMessage* msg); +std::ostream& operator<<(std::ostream& os, L3SupServMessage::MessageType mTranId); + + + +/** +Parse a complete L3 call independent supplementary service control message into its object type. +@param source The L3 bits. +@return A pointer to a new message or NULL on failure. +*/ +L3SupServMessage* parseL3SupServ(const L3Frame& source); + +/** +A Factory function to return a L3SupServMessage of the specified mTranId. +Returns NULL if the MTI is not supported. +*/ +L3SupServMessage* L3SupServFactory(L3SupServMessage::MessageType MTI); + +/** Facility Message GSM 04.80/24.080 2.3. */ +class L3SupServFacilityMessage : public L3SupServMessage { + L3SupServFacilityIE mFacility; + + public: + L3SupServFacilityMessage(unsigned wTranId, const L3SupServFacilityIE& wFacility) + :L3SupServMessage(wTranId), + mFacility(wFacility) + {} + L3SupServFacilityMessage() {} + + string getMapComponents() const { return mFacility.mData; } + + int MTI() const { return Facility; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t l2BodyLength() const {return mFacility.lengthLV();} + void text(std::ostream& os) const; + +}; + +/** Register Message GSM 04.80/24.080 2.4. */ +class L3SupServRegisterMessage : public L3SupServMessage { + + L3SupServFacilityIE mFacility; + L3OneByteProtocolElement mVersionIndicator; + + public: + L3SupServRegisterMessage(unsigned wTranId, const L3SupServFacilityIE& wFacility) + :L3SupServMessage(wTranId), + mFacility(wFacility) + { } + L3SupServRegisterMessage() { } + + bool haveVersionIndicator() const {return mVersionIndicator.mExtant;} + uint8_t versionIndicator() const {assert(haveVersionIndicator()); return mVersionIndicator.mValue;} + + string getMapComponents() const { return mFacility.mData; } + + int MTI() const { return Register; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t l2BodyLength() const; + void text(std::ostream&) const; + +}; + +/** Release Complete Message GSM 04.80/24.080 2.5. */ +struct L3SupServReleaseCompleteMessage : public L3SupServMessage { + + L3SupServFacilityIE mFacility; + + L3Cause mCause; // It is an L3 Cause as described in 24.008 10.5.4.11 + bool mHaveCause; + + L3SupServReleaseCompleteMessage() : mHaveCause(false) {} + L3SupServReleaseCompleteMessage(unsigned wTranId) : + L3SupServMessage(wTranId), mHaveCause(false) {} + L3SupServReleaseCompleteMessage(unsigned wTranId, L3Cause wCause) : + L3SupServMessage(wTranId), mCause(wCause), mHaveCause(true) {} + L3SupServReleaseCompleteMessage(unsigned wTranId, L3SupServFacilityIE &wFacility) : + L3SupServMessage(wTranId), mFacility(wFacility), mHaveCause(false) {} + + bool haveFacility() const {return mFacility.mExtant; } + + // This is an outgoing message. It does not need accessors. + //bool haveCause() const {return mHaveCause;} + //const L3Cause& cause() const {assert(mHaveCause); return mCause;} + + int MTI() const { return ReleaseComplete; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t l2BodyLength() const; + void text(std::ostream&) const; + +}; +L3SupServMessage * parseL3SS(const L3Frame& source); + +} +#endif diff --git a/GSM/GSMLogicalChannel.cpp b/GSM/GSMLogicalChannel.cpp index 5bcc1be..0eb9e85 100644 --- a/GSM/GSMLogicalChannel.cpp +++ b/GSM/GSMLogicalChannel.cpp @@ -27,9 +27,7 @@ #include "GSMLogicalChannel.h" #include "GSMConfig.h" -#include -#include -#include +#include #include "GPRSExport.h" #include @@ -38,8 +36,7 @@ using namespace std; using namespace GSM; - -void LogicalChannel::open() +void L2LogicalChannel::open() { LOG(INFO); LOG(DEBUG); @@ -48,35 +45,30 @@ void LogicalChannel::open() if (mL1) mL1->open(); // (pat) L1FEC::open() LOG(DEBUG); for (int s=0; s<4; s++) { - if (mL2[s]) mL2[s]->open(); + if (mL2[s]) mL2[s]->l2open(descriptiveString()); LOG(DEBUG) << "SAPI=" << s << " open complete"; } - // Empty any stray transactions in the FIFO from the SIP layer. - while (true) { - Control::TransactionEntry *trans = mTransactionFIFO.readNoBlock(); - if (!trans) break; - LOG(WARNING) << "flushing stray transaction " << *trans; - // FIXME -- Shouldn't we be deleting these? - } - LOG(DEBUG); } // (pat) This is connecting layer2, not layer1. -void LogicalChannel::connect() +void L2LogicalChannel::connect() { mMux.downstream(mL1); if (mL1) mL1->upstream(&mMux); for (int s=0; s<4; s++) { mMux.upstream(mL2[s],s); - if (mL2[s]) mL2[s]->downstream(&mMux); + if (mL2[s]) { + mL2[s]->l2Downstream(&mMux); + mL2[s]->l2Upstream(this); + } } } // (pat) This is only called during initialization, using the createCombination*() functions. // The L1FEC->downstream hooks the radio to this logical channel, permanently. -void LogicalChannel::downstream(ARFCNManager* radio) +void L2LogicalChannel::downstream(ARFCNManager* radio) { assert(mL1); // This is L1FEC mL1->downstream(radio); @@ -86,12 +78,13 @@ void LogicalChannel::downstream(ARFCNManager* radio) // Serialize and send an L3Message with a given primitive. -void LogicalChannel::send(const L3Message& msg, +// The msg is not deleted; its value is used before return. +void L2LogicalChannel::l2sendm(const L3Message& msg, const GSM::Primitive& prim, - unsigned SAPI) + SAPI_t SAPI) { - LOG(INFO) << "L3 SAP" << SAPI << " sending " << msg; - send(L3Frame(msg,prim), SAPI); + OBJLOG(INFO) << "L3" <second.mnCount) it->second.mnCount--; + } +} + +string NeighborCache::neighborText() +{ + string result; result.reserve(100); + result.append("Neighbors("); + for (NeighborMap::iterator it = mNeighborRSSI.begin(); it != mNeighborRSSI.end(); it++) { + LOG(DEBUG); + unsigned freqindex = it->first >> 6, bsic = it->first & 0x3f; + char buf[82]; + snprintf(buf,80,"(freqIndex=%u BSIC=%u count=%u AvgRSSI=%d)",freqindex,bsic,it->second.mnCount,it->second.mnAvgRSSI); + result.append(buf); + } + result.append(")"); + return result; +} + +int NeighborCache::neighborAddMeasurement(unsigned freqindex, unsigned BSIC, int RSSI) +{ + unsigned key = (freqindex<<6) + BSIC; + NeighborData &data = mNeighborRSSI[key]; + int result; + int startCount = data.mnCount, startAvg = data.mnAvgRSSI; + if (data.mnCount == 0) { + // Handsets sometimes send a spuriously low measurement report, + // so dont handover until we have seen at least two measurements from the same neighbor. + // We prevent handover by sending an impossibly low RSSI. + data.mnAvgRSSI = RSSI; + data.mnCount = cNumReports; + result = -200; // Impossibly low value. + } else { + data.mnCount = cNumReports; + result = data.mnAvgRSSI = RSSI/2 + data.mnAvgRSSI/2; + } + int endCount=data.mnCount; // ffing << botches this. + LOG(DEBUG) <segment(24, l3frame->size()-24); L3Message* message = parseL3(*l3frame); if (!message) { LOG(WARNING) << "SACCH recevied unparsable L3 frame " << *l3frame; + WATCHF("SACCH received unparsable L3 frame PD=%d MTI=%d",l3frame->PD(),l3frame->MTI()); } return message; } +// (pat) This is started when SACCH is opened, and runs forever. +// The SACCHLogicalChannel are created by the SDCCHLogicalChannel and TCHFACCHLogicalChannel constructors. void SACCHLogicalChannel::serviceLoop() { @@ -362,22 +426,47 @@ void SACCHLogicalChannel::serviceLoop() // Throttle back if not active. if (!active()) { //OBJLOG(DEBUG) << "SACCH sleeping"; - sleepFrames(51); - continue; + // pat 5-2013: Vastly reducing the delays here and in L2LAPDm to try to reduce + // random failures of handover and channel reassignment from SDCCH to TCHF. + // Update: The further this sleep is reduced, the more reliable handover becomes. + // I left it at 4 for a while but handover still failed sometimes. + //sleepFrames(51); +#define USE_SEMAPHORE 0 // This does not work well - there appear to be hundreds of interrupts per second. +#if USE_SEMAPHORE + // (pat) Update: Getting rid of the sleep entirely. We will use a semaphore instead. + // Note that the semaphore call may return on signal, which is ok here. + cout << descriptiveString() << " WAIT" <PD()) { + case L3RadioResourcePD: sapi = SAPI0; break; + case L3SMSPD: sapi = SAPI3; break; + default: + OBJLOG(ERR)<<"In SACCHLogicalChannel, unexpected"<PD()); + break; + } + L2LogicalChannel::l2sendm(*l3msg,GSM::DATA,sapi); + delete l3msg; + } else { + // Send alternating SI5/SI6. + // These L3Frames were created with the UNIT_DATA primivitive. + OBJLOG(DEBUG) << "sending SI5/6 on SACCH"; + if (count%2) L2LogicalChannel::l2sendf(gBTS.SI5Frame()); + else L2LogicalChannel::l2sendf(gBTS.SI6Frame()); + count++; } - else LogicalChannel::send(gBTS.SI6Frame()); - count++; // Receive inbound messages. // This read loop flushes stray reports quickly. @@ -387,9 +476,37 @@ void SACCHLogicalChannel::serviceLoop() bool nothing = true; // Process SAP0 -- RR Measurement reports - L3Frame *rrFrame = LogicalChannel::recv(0,0); - if (rrFrame) nothing=false; - L3Message* rrMessage = processSACCHMessage(rrFrame); + if (L3Frame *rrFrame = L2LogicalChannel::l2recv(0,0)) { + nothing=false; + bool isMeasurementReport = rrFrame->isData() + && rrFrame->PD() == L3RadioResourcePD && rrFrame->MTI() == L3RRMessage::MeasurementReport; + if (isMeasurementReport) { + // Neither of these 'ifs' should fail, but be safe. + if (const L3Message* rrMessage = parseSACCHMessage(rrFrame)) { + if (const L3MeasurementReport* measurement = dynamic_cast(rrMessage)) { + OBJLOG(DEBUG) << "SACCH measurement report " << mMeasurementResults; + mMeasurementResults = measurement->results(); + if (mMeasurementResults.MEAS_VALID() == 0) { + addSelfRxLev(mMeasurementResults.RXLEV_SUB_SERVING_CELL_dBm()); + } + // Add the measurement results to the table + // Note that the typeAndOffset of a SACCH match the host channel. + gPhysStatus.setPhysical(this, mMeasurementResults); + // Check for handover requirement. + // (pat) TODO: This may block while waiting for a reply from a Peer BTS. + Control::HandoverDetermination(mMeasurementResults,mAverageRXLEV_SUB_SERVICING_CELL,this); + } + delete rrMessage; + } + delete rrFrame; + } else { + // Send it off to Layer 3. Who knows what might show up here. + hostChan()->chanEnqueueFrame(rrFrame); + } + } + +#if 0 + L3Message* rrMessage = parseSACCHMessage(rrFrame); delete rrFrame; if (rrMessage) { L3MeasurementReport* measurement = dynamic_cast(rrMessage); @@ -400,51 +517,34 @@ void SACCHLogicalChannel::serviceLoop() // Note that the typeAndOffset of a SACCH match the host channel. gPhysStatus.setPhysical(this, mMeasurementResults); // Check for handover requirement. + // (pat) TODO: This may block while waiting for a reply from a Peer BTS. Control::HandoverDetermination(mMeasurementResults,this); + delete rrMessage; } else { - OBJLOG(NOTICE) << "SACCH SAP0 sent unaticipated message " << rrMessage; + if (Control::l3rewrite()) { + OBJLOG(DEBUG) << "chanEnqueuel3msg:"<chanEnqueuel3msg(rrMessage); + } else { + OBJLOG(NOTICE) << "SACCH SAP0 sent unaticipated message " << rrMessage; + delete rrMessage; + } } - delete rrMessage; } +#endif // Process SAP3 -- SMS - L3Frame *smsFrame = LogicalChannel::recv(0,3); - if (smsFrame) nothing=false; - L3Message* smsMessage = processSACCHMessage(smsFrame); - delete smsFrame; - if (smsMessage) { - const SMS::CPData* cpData = dynamic_cast(smsMessage); - if (cpData) { - OBJLOG(INFO) << "SMS CPDU " << *cpData; - Control::TransactionEntry *transaction = gTransactionTable.find(this); - try { - if (transaction) { - Control::InCallMOSMSController(cpData,transaction,this); - } else { - OBJLOG(WARNING) << "in-call MOSMS CP-DATA with no corresponding transaction"; - } - } catch (Control::ControlLayerException e) { - //LogicalChannel::send(RELEASE,3); - gTransactionTable.remove(e.transactionID()); - } - } else { - OBJLOG(NOTICE) << "SACCH SAP3 sent unaticipated message " << rrMessage; - } - delete smsMessage; - } + L3Frame *smsFrame = L2LogicalChannel::l2recv(0,3); + if (smsFrame) { + nothing=false; - // Anything from the SIP side? - // MTSMS (delivery from SIP to the MS) - Control::TransactionEntry *sipTransaction = mTransactionFIFO.readNoBlock(); - if (sipTransaction) { - OBJLOG(INFO) << "SIP-side transaction: " << sipTransaction; - assert(sipTransaction->service() == L3CMServiceType::MobileTerminatedShortMessage); - try { - Control::MTSMSController(sipTransaction,this); - } catch (Control::ControlLayerException e) { - //LogicalChannel::send(RELEASE,3); - gTransactionTable.remove(e.transactionID()); - } + OBJLOG(DEBUG) <<"received SMS frame:"<chanEnqueueFrame(smsFrame); } // Did we get anything from the phone? @@ -454,7 +554,6 @@ void SACCHLogicalChannel::serviceLoop() // Nothing happened? if (nothing) break; } - } } @@ -467,21 +566,14 @@ void *GSM::SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel* chan) // These have to go into the .cpp file to prevent an illegal forward reference. -void LogicalChannel::setPhy(float wRSSI, float wTimingError, double wTimestamp) +void L2LogicalChannel::setPhy(float wRSSI, float wTimingError, double wTimestamp) { assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError,wTimestamp); } -void LogicalChannel::setPhy(const LogicalChannel& other) +void L2LogicalChannel::setPhy(const L2LogicalChannel& other) { assert(mSACCH); mSACCH->setPhy(*other.SACCH()); } -float LogicalChannel::RSSI() const - { assert(mSACCH); return mSACCH->RSSI(); } -float LogicalChannel::timingError() const - { assert(mSACCH); return mSACCH->timingError(); } -double LogicalChannel::timestamp() const - { assert(mSACCH); return mSACCH->timestamp(); } -int LogicalChannel::actualMSPower() const - { assert(mSACCH); return mSACCH->actualMSPower(); } -int LogicalChannel::actualMSTiming() const - { assert(mSACCH); return mSACCH->actualMSTiming(); } -const L3MeasurementResults& LogicalChannel::measurementResults() const +MSPhysReportInfo * L2LogicalChannel::getPhysInfo() const { + assert(mSACCH); return mSACCH->getPhysInfo(); +} +const L3MeasurementResults& L2LogicalChannel::measurementResults() const { assert(mSACCH); return mSACCH->measurementResults(); } @@ -495,8 +587,8 @@ TCHFACCHLogicalChannel::TCHFACCHLogicalChannel( mL1 = mTCHL1; // SAP0 is RR/MM/CC, SAP3 is SMS // SAP1 and SAP2 are not used. - mL2[0] = new FACCHL2(1,0); - mL2[3] = new FACCHL2(1,3); + mL2[0] = new FACCHL2(1,SAPI0); + mL2[3] = new FACCHL2(1,SAPI3); mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this); connect(); } @@ -513,23 +605,24 @@ CBCHLogicalChannel::CBCHLogicalChannel(const CompleteMapping& wMapping) } -void CBCHLogicalChannel::send(const L3SMSCBMessage& msg) +void CBCHLogicalChannel::l2sendm(const L3SMSCBMessage& msg) { L3Frame frame(UNIT_DATA,88*8); msg.write(frame); - LogicalChannel::send(frame); + L2LogicalChannel::l2sendf(frame); } -bool LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms) +#if UNUSED +bool L2LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms) { bool waiting = true; while (waiting) { L3Frame *req = recv(timeout_ms); if (req==NULL) { - LOG(NOTICE) << "timeout at uptime " << gBTS.uptime() << " frame " << gBTS.time(); + OBJLOG(NOTICE) << "timeout at uptime " << gBTS.uptime() << " frame " << gBTS.time(); return false; } waiting = (req->primitive()!=primitive); @@ -539,7 +632,7 @@ bool LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms) } -void LogicalChannel::waitForPrimitive(Primitive primitive) +void L2LogicalChannel::waitForPrimitive(Primitive primitive) { bool waiting = true; while (waiting) { @@ -549,34 +642,29 @@ void LogicalChannel::waitForPrimitive(Primitive primitive) delete req; } } +#endif -L3Frame* LogicalChannel::waitForEstablishOrHandover() +// We only return state for SAPI0, although the state could be different in SAPI0 and SAPI3. +LAPDState L2LogicalChannel::getLapdmState() const { - while (true) { - L3Frame *req = recv(); - if (req==NULL) continue; - if (req->primitive()==ESTABLISH) return req; - if (req->primitive()==HANDOVER_ACCESS) return req; - LOG(INFO) << "LogicalChannel: Ignored primitive:"<primitive(); - delete req; - } - return NULL; // to keep the compiler happy + // The check for NULL is redundant - these objects are allocated at startup and are immortal. + if (mL2[0]) { return mL2[0]->getLapdmState(); } + return LAPDStateUnused; } - -ostream& GSM::operator<<(ostream& os, const LogicalChannel& chan) +ostream& GSM::operator<<(ostream& os, const L2LogicalChannel& chan) { os << chan.descriptiveString(); return os; } - - -void LogicalChannel::addTransaction(Control::TransactionEntry *transaction) +std::ostream& GSM::operator<<(std::ostream&os, const L2LogicalChannel*ch) { - assert(transaction->channel()==this); - mTransactionFIFO.write(transaction); + if (ch) { os <<*ch; } else { os << "(null L2Logicalchannel)"; } + return os; } + + // vim: ts=4 sw=4 diff --git a/GSM/GSMLogicalChannel.h b/GSM/GSMLogicalChannel.h index f1fb3c4..bbd4157 100644 --- a/GSM/GSMLogicalChannel.h +++ b/GSM/GSMLogicalChannel.h @@ -3,6 +3,7 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -25,15 +26,17 @@ #include #include +#include #include +#include #include "GSML1FEC.h" #include "GSMSAPMux.h" #include "GSML2LAPDm.h" #include "GSML3RRElements.h" #include "GSMTDMA.h" -#include +#include #include @@ -41,19 +44,13 @@ class ARFCNManager; class UDPSocket; -//namespace Control { -//class TransactionEntry; -//}; - - namespace GSM { -typedef InterthreadQueue TransactionFIFO; - class SACCHLogicalChannel; class L3Message; class L3RRMessage; class L3SMSCBMessage; +class L2LogicalChannel; /** @@ -62,10 +59,17 @@ class L3SMSCBMessage; The layered structure of GSM is defined in GSM 04.01 7, as well as many other places. The concept of the logical channel and the channel types are defined in GSM 04.03. This is virtual class; specific channel types are subclasses. + (pat) This class is used for both DCCH and SACCH. The DCCH can be TCH+FACCH or SDCCH. + SACCH is a slave LogicalChannel always associated with a DCCH LogicalChannel. + (pat) About Channel Establishment: See more comments at L3LogicalChannel. */ -// (pat) It would be nice to break this into two classes: one that has the base functionality -// that GPRS will not use, and one with all the RR specific channel stuff. -class LogicalChannel { + +// (pat) This class is a GSM-specific dedicated logical channel, meaning that at any given time the channel is connected to just one MS. +// GPRS and UMTS do not use dedicated logical channels, rather they use shared resources in layer 2, and in fact they dont +// even really have a logical channel type entity in layer 2. +// Therefore I split this class into L2 and L3 portions, where the L3LogicalChannel is common with UMTS and managed in the Control directory. +// This L2LogicalChannel is rarely referenced outside this directory; primarily just to send L3 level handover information to a Peer. +class L2LogicalChannel : public Control::L3LogicalChannel { protected: @@ -73,24 +77,19 @@ protected: //@{ L1FEC *mL1; ///< L1 forward error correction SAPMux mMux; ///< service access point multiplex + // (pat) mL2 is redundant with SAPMux mUpstream[]. L2DL *mL2[4]; ///< data link layer state machines, one per SAP //@} SACCHLogicalChannel *mSACCH; ///< The associated SACCH, if any. - - /** - A FIFO of inbound transactions intiated in the SIP layers on an already-active channel. - Unlike most interthread FIFOs, do *NOT* delete the pointers that come out of it. - */ - TransactionFIFO mTransactionFIFO; - + // (pat) The reverse pointer is SACCHLogicalChannel::mHost. public: /** Blank initializer just nulls the pointers. Specific sub-class initializers allocate new components as needed. */ - LogicalChannel() + L2LogicalChannel() :mL1(NULL),mSACCH(NULL) { for (int i=0; i<4; i++) mL2[i]=NULL; @@ -99,7 +98,7 @@ public: /** The destructor doesn't do anything since logical channels should not be destroyed. */ - virtual ~LogicalChannel() {}; + virtual ~L2LogicalChannel() {}; /**@name Accessors. */ @@ -120,13 +119,17 @@ public: virtual void setPhy(float wRSSI, float wTimingError, double wTimestamp); /* Set L1 physical parameters from an existing logical channel. */ - virtual void setPhy(const LogicalChannel&); + virtual void setPhy(const L2LogicalChannel&); virtual const L3MeasurementResults& measurementResults() const; /**@name L3 interfaces */ //@{ + // (pat) This function is only applicable on channels that use LAPDm. + // There is a fifo on LAPDm uplkink so this is only blocking if the fifo is empty. + // For the GSM version of l3rewrite we are going to add a SIP notification to this queue, + // so it should be moved from LAPDm to this class. /** Read an L3Frame from SAP0 uplink, blocking, with timeout. The caller is responsible for deleting the returned pointer. @@ -135,8 +138,13 @@ public: @param SAPI The service access point indicator from which to read. @return A pointer to an L3Frame, to be deleted by the caller, or NULL on timeout. */ - virtual L3Frame * recv(unsigned timeout_ms = 15000, unsigned SAPI=0) - { assert(mL2[SAPI]); return mL2[SAPI]->readHighSide(timeout_ms); } + virtual L3Frame * l2recv(unsigned timeout_ms = 15000, unsigned SAPI=0) + { + assert(mL2[SAPI]); + L3Frame *result = mL2[SAPI]->l2ReadHighSide(timeout_ms); + if (result) { LOG(DEBUG) <L2 methods to l2WriteHighSide. // - // For traffic channels: - // This function calls virtual L2DL::writeHighSide(L3Frame) which I think maps - // to L2LAPDm::writeHighSide() which interprets the primitive, and then + // For DCCH channels (FACCH, SACCH, SDCCH): + // This function calls virtual L2DL::l2WriteHighSide(L3Frame) which I think maps + // to L2LAPDm::l2WriteHighSide() which interprets the primitive, and then // sends traffic data through sendUFrameUI(L3Frame) which creates an L2Frame // and sends it through several irrelevant functions to L2LAPDm::writeL1 // which calls (SAPMux)mDownstream->SAPMux::writeHighSide(L2Frame), @@ -189,8 +198,8 @@ public: // burst time to L1Encoder::mNextWriteTime and // calls (ARFCNManager*)mDownStream->writeHighSideTx() which writes to the socket. assert(mL2[SAPI]); - LOG(DEBUG) << "SAP"<< SAPI << " " << frame; - mL2[SAPI]->writeHighSide(frame); + LOG(INFO) <l2WriteHighSide(frame); } /** @@ -198,32 +207,27 @@ public: @param prim The primitive to send. @pram SAPI The service access point on which to send. */ - virtual void send(const GSM::Primitive& prim, unsigned SAPI=0) - { assert(mL2[SAPI]); mL2[SAPI]->writeHighSide(L3Frame(prim)); } - - /** - Initiate a transaction from the SIP side on an already-active channel. - (*/ - virtual void addTransaction(Control::TransactionEntry* transaction); + // (pat) This is never over-ridden except for testing. + virtual void l2sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0) + { assert(mL2[SAPI]); mL2[SAPI]->l2WriteHighSide(L3Frame(SAPI,prim)); } /** Serialize and send an L3Message with a given primitive. @param msg The L3 message. @param prim The primitive to use. */ - virtual void send(const L3Message& msg, + // (pat) This is never over-ridden except for testing. + virtual void l2sendm(const L3Message& msg, const GSM::Primitive& prim=DATA, - unsigned SAPI=0); + SAPI_t SAPI=SAPI0); /** Block on a channel until a given primitive arrives. Any payload is discarded. Block indefinitely, no timeout. @param primitive The primitive to wait for. */ - void waitForPrimitive(GSM::Primitive primitive); - - /** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */ - L3Frame* waitForEstablishOrHandover(); + // unused + //void waitForPrimitive(GSM::Primitive primitive); /** Block on a channel until a given primitive arrives. @@ -232,7 +236,8 @@ public: @param timeout_ms The timeout in milliseconds. @return True on success, false on timeout. */ - bool waitForPrimitive(GSM::Primitive primitive, unsigned timeout_ms); + // unused + //bool waitForPrimitive(GSM::Primitive primitive, unsigned timeout_ms); @@ -242,7 +247,9 @@ public: //@{ /** Write a received radio burst into the "low" side of the channel. */ - virtual void writeLowSide(const RxBurst& burst) { assert(mL1); mL1->writeLowSideRx(burst); } + // (pat) What the heck? This method makes no sense and is not used anywhere. + // The operative virtual writeLowSide method is in class L2DL; + //virtual void writeLowSide(const RxBurst& burst) { assert(mL1); mL1->writeLowSideRx(burst); } /** Return true if the channel is safely abandoned (closed or orphaned). */ virtual bool recyclable() const { assert(mL1); return mL1->recyclable(); } @@ -250,6 +257,10 @@ public: /** Return true if the channel is active. */ virtual bool active() const { assert(mL1); return mL1->active(); } + // (pat 8-2013) Return the LAPDm state of the main SAPI0 for reporting in the CLI; + // on channels without LAPDm it would return an empty string, except it will never be called for such cases. + LAPDState getLapdmState() const; + /** The TDMA parameters for the transmit side. */ // (pat) This lovely function is unused. Use L1Encoder::mapping() const TDMAMapping& txMapping() const { assert(mL1); return mL1->txMapping(); } @@ -264,6 +275,8 @@ public: /** ARFCN */ /* TODO: Use this, or when obtaining the physical info use ARFCN from a diff location? */ unsigned ARFCN() const { assert(mL1); return mL1->ARFCN(); } + bool radioFailure() const { assert(mL1); return mL1->radioFailure(); } + /**@name Channel stats from the physical layer */ //@{ /** Carrier index. */ @@ -272,18 +285,11 @@ public: unsigned TN() const { assert(mL1); return mL1->TN(); } /** Receive FER. */ float FER() const { assert(mL1); return mL1->FER(); } - /** RSSI wrt full scale. */ - virtual float RSSI() const; - /** Uplink timing error. */ - virtual float timingError() const; - /** System timestamp of RSSI and TA */ - virtual double timestamp() const; - /** Actual MS uplink power. */ - virtual int actualMSPower() const; - /** Actual MS uplink timing advance. */ - virtual int actualMSTiming() const; + DecoderStats getDecoderStats() const { return mL1->decoder()->getDecoderStats(); } + // Obtains SACCH reporting info. + virtual MSPhysReportInfo *getPhysInfo() const; /** Control whether to accept a handover. */ - void handoverPending(bool flag) { assert(mL1); mL1->handoverPending(flag); } + HandoverRecord& handoverPending(bool flag, unsigned handoverRef) { assert(mL1); return mL1->handoverPending(flag, handoverRef); } //@} //@} // L1 @@ -303,11 +309,20 @@ public: void downstream(ARFCNManager* radio); /** Return the channel type. */ - virtual ChannelType type() const =0; + virtual ChannelType chtype() const =0; /** Make the channel ready for a new transaction. The channel is closed with primitives from L3. + (pat) LogicalChannel::open() calls: L1FEC::open(), L1Encoder::open(), L1Encoder::open(), none of which do much but reset the L1 layer classes. + If there is an associated SACCH, that is opened too. + On channels with LAPDm, which are: TCHFACCH, SDCCH and SACCH: + LogicalChannel::open() also calls L2LAPDm::l2open() on each SAP endpoint, which has a side effect of starting to send idle frames in downlink. + After open, an ESTABLISH primitive may be sent on the channel to indicate when SABM mode is established. + In downlink: only for MT-SMS, an ESTABLISH primitive is sent to establish LAPDm SABM mode, which is used only on SAP 3, which is used + only for SMS messages in OpenBTS. + In uplink: the MS always establishes SABM mode. After the open(), when the first good frame arrives, + an ESTABLISH primitive is sent upstream toward L3, which will notify the DCCHDispatcher to start looking for messages. */ virtual void open(); @@ -329,13 +344,13 @@ public: virtual void connect(); public: - bool inUseByGPRS() { return mL1->inUseByGPRS(); } - + bool inUseByGPRS() const { return mL1->inUseByGPRS(); } bool decryptUplink_maybe(string wIMSI, int wA5Alg) { return mL1->decoder()->decrypt_maybe(wIMSI, wA5Alg); } }; -std::ostream& operator<<(std::ostream&, const LogicalChannel&); +std::ostream& operator<<(std::ostream&, const L2LogicalChannel&); +std::ostream& operator<<(std::ostream&os, const L2LogicalChannel*ch); /** @@ -346,7 +361,7 @@ std::ostream& operator<<(std::ostream&, const LogicalChannel&); allocation of a TCH. The bit rate of a SDCCH is 598/765 kbit/s. " */ -class SDCCHLogicalChannel : public LogicalChannel { +class SDCCHLogicalChannel : public L2LogicalChannel { public: @@ -355,7 +370,7 @@ class SDCCHLogicalChannel : public LogicalChannel { unsigned wTN, const CompleteMapping& wMapping); - ChannelType type() const { return SDCCHType; } + ChannelType chtype() const { return SDCCHType; } }; @@ -367,22 +382,49 @@ class SDCCHLogicalChannel : public LogicalChannel { This is a virtual base class this is extended for CCCH & BCCH. See GSM 04.06 4.1.1, 4.1.3. */ -class NDCCHLogicalChannel : public LogicalChannel { +class NDCCHLogicalChannel : public L2LogicalChannel { public: /** This channel only sends RR protocol messages. */ - virtual void send(const L3RRMessage& msg) - { LogicalChannel::send((const L3Message&)msg,UNIT_DATA); } + virtual void l2sendm(const L3RRMessage& msg) + { L2LogicalChannel::l2sendm((const L3Message&)msg,UNIT_DATA); } /** This channel only sends RR protocol messages. */ - void send(const L3Message&) { assert(0); } + //void send(const L3Message&) { assert(0); } // old method name. + void l2sendm(const L3Message&) { assert(0); } }; +// (pat) We average the measurement reports from the best neighbors for handover purposes, so we dont +// cause a handover from one spuriously low measurement report. +// Note that there could be neighbors varying slightly but all much better than the current cell, +// so we save all the neighbor data, not just the best one. +// We dont have to worry about this growing without bounds because there will only be a few neighbors. +// (pat) At my house, using the Blackberry, I see a regular 9.5 second heart-beat, where the measurements drop about 8db. +// The serving cell RSSI drops first, then in the next measurement report the serving RSSI is back to normal +// and the neighbor RSSI drops. If it were just 2db more, it would be causing a spurious handover back and +// forth every 9.5 seconds. This cache alleviates that problem. +class NeighborCache { + struct NeighborData { + int16_t mnAvgRSSI; // Must be signed. + uint8_t mnCount; + NeighborData() : mnCount(0) {} + }; + typedef std::map NeighborMap; + NeighborMap mNeighborRSSI; + int cNumReports; // Neighbor must appear in 2 of last cNumReports measurement reports. + public: + // Argument is current RSSI, and return is the averaged RSSI to use for handover determination purposes. + int neighborAddMeasurement(unsigned freq, unsigned BSIC, int RSSI); + void neighborStartMeasurements(); // Call this at the start of each measurement report. + void neighborClearMeasurements(); // Call to clear everything. + string neighborText(); +}; + /** @@ -401,9 +443,12 @@ class NDCCHLogicalChannel : public LogicalChannel { The main role of the SACCH, for now, will be to send SI5 and SI6 messages and to accept uplink mesaurement reports. */ -class SACCHLogicalChannel : public LogicalChannel { +class SACCHLogicalChannel : public L2LogicalChannel, public NeighborCache { protected: + InterthreadQueue mTxQueue; // FIXME: not currently used. Queue of outbound messages from Layer3 for this SACCH. SAPI is determined from message PD. + + sem_t mOpenSignal; // (pat 7-25-2013) SACCHL1FEC *mSACCHL1; Thread mServiceThread; ///< a thread for the service loop @@ -412,7 +457,31 @@ class SACCHLogicalChannel : public LogicalChannel { /** MeasurementResults from the MS. They are caught in serviceLoop, accessed for recording along with GPS and other data in MobilityManagement.cpp */ L3MeasurementResults mMeasurementResults; - const LogicalChannel *mHost; + + // (pat 7-21-2013) This self RXLEV returned from the measurement reports has short-term variations of up to 23db + // on an iphone version 1, enough to trigger a spurious handover, so we are going to average this value. + // The short-term variation can last up to 3 consecutive reports, so we want to average over a long enough period + // to smooth that out. Reports come every 1/2 second so we can make the averaging period pretty large. + // Since this value is used only for handover, we dont have to worry about making the value correct during the first few, + // in fact, we dont want a handover to happen too soon after the channel is opened anyway, + // so we will just init it to 0 when the channel is opened and let it drift down. + // (pat 1-2014) GSM 5.08 A3.1 says how we are supposed to average this; we are supposed to throw out + // the best and worst measurements and average over a programmable period. + // Note that this averaging puts a constraint on the maximum speed of the handset through the overlap area between cells + // for a successful handover. To improve handover for quickly moving handsets we should also watch delta(RXLEV) + // and delta(TA) and if they together indicate quickly moving out of the cell, do the handover faster. + static const int cAveragePeriodRXLEV_SUB_SERVING_CELL = 8; // How many we measurement reports we average over. + float mAverageRXLEV_SUB_SERVICING_CELL; // Must be signed! + + // Add a measurement result data point to the averaged RXLEV_SUB_SERVING_CELL value. + void addSelfRxLev(int wDataPoint) { + int minus1 = cAveragePeriodRXLEV_SUB_SERVING_CELL - 1; + mAverageRXLEV_SUB_SERVICING_CELL = ((float) wDataPoint + minus1 * mAverageRXLEV_SUB_SERVICING_CELL) + / (float) cAveragePeriodRXLEV_SUB_SERVING_CELL; + } + + /*const*/ L2LogicalChannel *mHost; + void serviceSMS(L3Frame *smsFrame); // Original pre-l3rewrite SMS message handler. public: @@ -420,9 +489,9 @@ class SACCHLogicalChannel : public LogicalChannel { unsigned wCN, unsigned wTN, const MappingPair& wMapping, - const LogicalChannel* wHost); + /*const*/ L2LogicalChannel* wHost); - ChannelType type() const { return SACCHType; } + ChannelType chtype() const { return SACCHType; } void open(); @@ -430,11 +499,8 @@ class SACCHLogicalChannel : public LogicalChannel { /**@name Pass-through accoessors to L1. */ //@{ - float RSSI() const { return mSACCHL1->RSSI(); } - float timingError() const { return mSACCHL1->timingError(); } - double timestamp() const { return mSACCHL1->timestamp(); } - int actualMSPower() const { return mSACCHL1->actualMSPower(); } - int actualMSTiming() const { return mSACCHL1->actualMSTiming(); } + // Obtains SACCH reporting info. + MSPhysReportInfo *getPhysInfo() const { return mSACCHL1->getPhysInfo(); } void setPhy(float RSSI, float timingError, double wTimestamp) { mSACCHL1->setPhy(RSSI,timingError,wTimestamp); } void setPhy(const SACCHLogicalChannel& other) { mSACCHL1->setPhy(*other.mSACCHL1); } @@ -452,6 +518,7 @@ class SACCHLogicalChannel : public LogicalChannel { /** Get recyclable state from the host DCCH. */ bool recyclable() const { assert(mHost); return mHost->recyclable(); } + L2LogicalChannel *hostChan() const { return mHost; } protected: @@ -506,14 +573,14 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel { void open(); - void send(const L3RRMessage& msg) + void l2sendm(const L3RRMessage& msg) { // DEBUG: //LOG(WARNING) << "CCCHLogicalChannel2::write q"; mQ.write(new L3Frame((const L3Message&)msg,UNIT_DATA)); } - void send(const L3Message&) { assert(0); } + void l2sendm(const L3Message&) { assert(0); } /** This is a loop in its own thread that empties mQ. */ void serviceLoop(); @@ -542,7 +609,7 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel { // Note: Time wraps at gHyperFrame. Time getNextMsgSendTime(); - ChannelType type() const { return CCCHType; } + ChannelType chtype() const { return CCCHType; } friend void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*); @@ -553,7 +620,7 @@ void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*); -class TCHFACCHLogicalChannel : public LogicalChannel { +class TCHFACCHLogicalChannel : public L2LogicalChannel { protected: @@ -572,22 +639,24 @@ class TCHFACCHLogicalChannel : public LogicalChannel { unsigned wTN, const CompleteMapping& wMapping); - UDPSocket * RTPSocket() { return mRTPSocket; } - UDPSocket * RTCPSocket() { return mRTCPSocket; } + // unused: + //UDPSocket * RTPSocket() { return mRTPSocket; } + //UDPSocket * RTCPSocket() { return mRTCPSocket; } - ChannelType type() const { return FACCHType; } + ChannelType chtype() const { return FACCHType; } - void sendTCH(const unsigned char* frame) + void sendTCH(AudioFrame* frame) { assert(mTCHL1); mTCHL1->sendTCH(frame); } - unsigned char* recvTCH() + AudioFrame* recvTCH() { assert(mTCHL1); return mTCHL1->recvTCH(); } unsigned queueSize() const { assert(mTCHL1); return mTCHL1->queueSize(); } - bool radioFailure() const - { assert(mTCHL1); return mTCHL1->radioFailure(); } + // (pat) 3-28: Moved this higher in the hierarchy so we can use it on SDCCH as well. + //bool radioFailure() const + // { assert(mTCHL1); return mTCHL1->radioFailure(); } }; @@ -609,11 +678,11 @@ class CBCHLogicalChannel : public NDCCHLogicalChannel { CBCHLogicalChannel(const CompleteMapping& wMapping); - void send(const L3SMSCBMessage& msg); + void l2sendm(const L3SMSCBMessage& msg); - void send(const L3Message&) { assert(0); } + void l2sendm(const L3Message&) { assert(0); } - ChannelType type() const { return CBCHType; } + ChannelType chtype() const { return CBCHType; } }; @@ -628,7 +697,7 @@ class CBCHLogicalChannel : public NDCCHLogicalChannel { A logical channel that loops L3Frames from input to output. Use a pair of these for control layer testing. */ -class L3LoopbackLogicalChannel : public LogicalChannel { +class L3LoopbackLogicalChannel : public Control::L3LogicalChannel { private: @@ -639,18 +708,27 @@ class L3LoopbackLogicalChannel : public LogicalChannel { L3LoopbackLogicalChannel(); /** Fake the SDCCH channel type because that makes sense for most tests. */ - ChannelType type() const { return SDCCHType; } + ChannelType chtype() const { return SDCCHType; } /** L3 Loopback */ - void send(const L3Frame& frame, unsigned SAPI=0) + // (pat) I dont think this class is used, but keep the old 'send' method names anyway in case + // there is some test code somewhere that uses this class: + //void send(const L3Frame& frame, unsigned SAPI=0) + //{ l2sendf(frame,SAPI); } + + // (pat 7-25-2013) The 'new L3Frame' below was doing an auto-conversion through L3Message. + void l2sendf(const L3Frame& frame, unsigned SAPI=0) { mL3Q[SAPI].write(new L3Frame(frame)); } /** L3 Loopback */ - void send(const GSM::Primitive prim, unsigned SAPI=0) - { mL3Q[SAPI].write(new L3Frame(prim)); } + //void send(const GSM::Primitive prim, unsigned SAPI=0) + //{ l2sendp(prim,SAPI); } + + void l2sendp(const GSM::Primitive prim, SAPI_t SAPI=SAPI0) + { mL3Q[SAPI].write(new L3Frame(SAPI,prim)); } /** L3 Loopback */ - L3Frame* recv(unsigned timeout_ms = 15000, unsigned SAPI=0) + L3Frame* l2recv(unsigned timeout_ms = 15000, unsigned SAPI=0) { return mL3Q[SAPI].read(timeout_ms); } }; diff --git a/GSM/GSMSAPMux.cpp b/GSM/GSMSAPMux.cpp index 35bfc5c..392ee06 100644 --- a/GSM/GSMSAPMux.cpp +++ b/GSM/GSMSAPMux.cpp @@ -37,21 +37,39 @@ void SAPMux::writeHighSide(const L2Frame& frame) void SAPMux::writeLowSide(const L2Frame& frame) { - OBJLOG(DEBUG) << frame.SAPI() << " " << frame; - unsigned SAPI = frame.SAPI(); - bool data = frame.primitive()==DATA; - if (data && (!mUpstream[SAPI])) { - LOG(WARNING) << "received DATA for unsupported SAP " << SAPI; - return; + OBJLOG(DEBUG) << frame; + unsigned SAPI; + + // (pat) Add switch to validate upstream primitives. The upstream only generates a few primitives; + // the rest are created in L2LAPDm. + switch (frame.primitive()) { + case HANDOVER_ACCESS: // Only send this on SAPI 0. + SAPI = 0; + break; + case DATA: + SAPI = frame.SAPI(); + break; + case ESTABLISH: // (pat) Do we really want to send this on all SAPI? + case ERROR: + // If this is a non-data primitive, copy it out to every SAP. + for (int i=0; i<4; i++) { + if (mUpstream[i]) mUpstream[i]->writeLowSide(frame); + } + return; + default: + case RELEASE: // Sent downstream, but not upstream. + case UNIT_DATA: // Only used above L2. + case HARDRELEASE: // Sent downstream, but not upstream. + // If you get this assertion, make SURE you know what will happen upstream to that primitive. + assert(0); + return; // make g++ happy. } - if (data) { + if (mUpstream[SAPI]) { mUpstream[SAPI]->writeLowSide(frame); } else { - // If this is a non-data primitive, copy it out to every SAP. - for (int i=0; i<4; i++) { - if (mUpstream[i]) mUpstream[i]->writeLowSide(frame); - } + LOG(WARNING) << "received DATA for unsupported SAP " << SAPI; } + return; } diff --git a/GSM/GSMSAPMux.h b/GSM/GSMSAPMux.h index 9aa046c..b2ac382 100644 --- a/GSM/GSMSAPMux.h +++ b/GSM/GSMSAPMux.h @@ -39,7 +39,9 @@ class SAPMux { protected: mutable Mutex mLock; + // (pat) mUpstream[] is redundant with GSMLogicalChannel mL2[] L2DL * mUpstream[4]; ///< one L2 for each SAP, GSM 04.05 5.3 + // (pat) Only SAP 0 and 3 are used: 0 for RR/MM/CC messages and 3 for SMS. L1FEC * mDownstream; ///< a single L1 public: diff --git a/GSM/GSMTAPDump.cpp b/GSM/GSMTAPDump.cpp index 66a319c..6190353 100644 --- a/GSM/GSMTAPDump.cpp +++ b/GSM/GSMTAPDump.cpp @@ -23,7 +23,7 @@ UDPSocket GSMTAPSocket; void gWriteGSMTAP(unsigned ARFCN, unsigned TS, unsigned FN, GSM::TypeAndOffset to, bool is_saach, bool ul_dln, // (pat) This flag means uplink - const BitVector& frame, + const BitVector2& frame, unsigned wType) // Defaults to GSMTAP_TYPE_UM { char buffer[MAX_UDP_LENGTH]; diff --git a/GSM/GSMTAPDump.h b/GSM/GSMTAPDump.h index 62f5037..b3acdbc 100644 --- a/GSM/GSMTAPDump.h +++ b/GSM/GSMTAPDump.h @@ -24,7 +24,7 @@ void gWriteGSMTAP(unsigned ARFCN, unsigned TS, unsigned FN, GSM::TypeAndOffset to, bool is_sacch, bool ul_dln, - const BitVector& frame, + const BitVector2& frame, unsigned wType = GSMTAP_TYPE_UM); #endif diff --git a/GSM/GSMTransfer.cpp b/GSM/GSMTransfer.cpp index 6580b2a..5ed3597 100644 --- a/GSM/GSMTransfer.cpp +++ b/GSM/GSMTransfer.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008 Free Software Foundation, Inc. +* Copyright 2008, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * @@ -19,7 +19,7 @@ #include "GSMTransfer.h" #include "GSML3Message.h" -#include "Globals.h" +//#include "Globals.h" using namespace std; @@ -28,20 +28,44 @@ using namespace GSM; ostream& GSM::operator<<(ostream& os, const L2Frame& frame) { - os << "primitive=" << frame.primitive(); + os << LOGVAR2("primitive",frame.primitive()) < L2Frame::L2Frame(const L2Header& header, const BitVector& l3, bool noran) :BitVector(23*8),mPrimitive(DATA) { @@ -286,7 +309,9 @@ unsigned L2Frame::SAPI() const // assuming frame format A or B. // See GSM 04.06 2.1, 2.3, 3.2. // This assumes MSB-first field packing. - return peekField(3,3); + // (pat) If the frame is a primitive, the size is 0 and sapi is not saved in the L2Frame. + // The L3Frame will be rebound with the correct SAPI in L2LAPDm. + return size()>8 ? peekField(3,3) : SAPIUndefined; } @@ -311,17 +336,17 @@ L2Control::ControlFormat L2Frame::controlFormat() const L2Control::FrameType L2Frame::UFrameType() const { // Determine U-frame command type. - // GSM 04.06 Table 4. + // GSM 04.06 Table 4 (in section 3.8.1, obviously) // TODO -- This would be more efficient as an array. char upper = peekField(8+0,3); char lower = peekField(8+4,2); char uBits = upper<<2 | lower; switch (uBits) { case 0x07: return L2Control::SABMFrame; - case 0x03: return L2Control::DMFrame; - case 0x00: return L2Control::UIFrame; - case 0x08: return L2Control::DISCFrame; - case 0x0c: return L2Control::UAFrame; + case 0x03: return L2Control::DMFrame; // (disconnect mode) + case 0x00: return L2Control::UIFrame; // (unnumbered information) + case 0x08: return L2Control::DISCFrame; // (disconnect) + case 0x0c: return L2Control::UAFrame; // (unnumbered acknowledge) default: return L2Control::BogusFrame; } } @@ -389,6 +414,20 @@ RxBurst::RxBurst(const TxBurst& source, float wTimingError, int wRSSI) { } +// (pat 1-2014) Compute the SNR of an RxBurst that is a GSM "normal burst", ie, +// one used for TCH/FACCH rather than for RACCH or other. +// For this case we ignore the tail bits and training bits and consider only the data bits. +float RxBurst::getNormalSNR() const +{ + SoftVector chunk1(this->segment(3,58)); // 57 data bits + stealing bit. + float snr1 = chunk1.getSNR(); + SoftVector chunk2(this->segment(87,58)); // stealing bit + 57 data bits. + float snr2 = chunk2.getSNR(); + assert(! chunk1.isOwner()); // Make sure the stupid SoftVector class really returned an alias. + return (snr1 + snr2) / 2.0; +} + + // Methods for the L2 address field. @@ -499,12 +538,26 @@ ostream& GSM::operator<<(ostream& os, Primitive prim) return os; } +const char *GSM::SAPI2Text(SAPI_t sapi) +{ + switch (sapi) { + case SAPI0: return "SAPI0"; + case SAPI3: return "SAPI3"; + case SAPIUndefined: return "SAPIUndefined"; + default: return "SAP-Invalid!"; + } +} +ostream& GSM::operator<<(ostream& os, SAPI_t sapi) +{ + os << SAPI2Text(sapi); return os; +} -L3Frame::L3Frame(const L3Message& msg, Primitive wPrimitive) - :BitVector(msg.bitsNeeded()),mPrimitive(wPrimitive), - mL2Length(msg.L2Length()) + +L3Frame::L3Frame(const L3Message& msg, Primitive wPrimitive, SAPI_t wSapi) + :BitVector(msg.bitsNeeded()),mPrimitive(wPrimitive),mSapi(wSapi), + mL2Length(msg.l2Length()) { msg.write(*this); } @@ -512,7 +565,7 @@ L3Frame::L3Frame(const L3Message& msg, Primitive wPrimitive) L3Frame::L3Frame(const char* hexString) - :mPrimitive(DATA) + :mPrimitive(DATA),mSapi(SAPIUndefined) { size_t len = strlen(hexString); mL2Length = len/2; @@ -528,7 +581,7 @@ L3Frame::L3Frame(const char* hexString) L3Frame::L3Frame(const char* binary, size_t len) - :mPrimitive(DATA) + :mPrimitive(DATA),mSapi(SAPIUndefined) { mL2Length = len; resize(len*8); @@ -538,6 +591,24 @@ L3Frame::L3Frame(const char* binary, size_t len) } } +unsigned L3Frame::MTI() const +{ + if (!isData()) { + // If someone calls MTI() on a primitive return a guaranteed invalid MTI instead of crashing: + return (unsigned)-1; + } + int mti = peekField(8,8); + switch (PD()) { + case L3NonCallSSPD: + case L3CallControlPD: + case L3MobilityManagementPD: + // (pat) 5-2013: For these protocols only, mask out the unused bits of the raw MTI from the L3 Frame. See 3GPP 4.08 10.4 + return mti & 0xbf; + default: + return mti; + } +} + static const unsigned fillPattern[8] = {0,0,1,0,1,0,1,1}; @@ -556,5 +627,29 @@ void L3Frame::writeL(size_t &wp) } +AudioFrameRtp::AudioFrameRtp(AMRMode wMode) : ByteVector((headerSizeBits(wMode)+GSM::gAMRKd[wMode]+7)/8), mMode(wMode) +{ + setAppendP(0); + // Fill in the RTP and AMR headers. + if (wMode == TCH_FS) { + appendField(0xd,RtpHeaderSize); // RTP type of GSM codec. + } else { + appendField(wMode,RtpHeaderSize); // The CMR field is allegedly not used by anyone yet, but lets set it to the AMR mode we are using. + appendField(0,1); // The F bit must always be zero; we are directed to send one frame every 20ms. + appendField(wMode,4); // This is the important field that must be the AMR type index. + appendField(1,1); // All frames are "good" for now. + } +} + +void AudioFrameRtp::getPayload(BitVector *result) const +{ + // Cheating: set the BitVector directly. TODO: move this into the BitVector class. + char *rp = result->begin(); + for (int i = headerSizeBits(mMode), n = result->size(); n > 0; i++, n--) { + *rp++ = getBit(i); + } +} + + // vim: ts=4 sw=4 diff --git a/GSM/GSMTransfer.h b/GSM/GSMTransfer.h index d4b517a..eda6943 100644 --- a/GSM/GSMTransfer.h +++ b/GSM/GSMTransfer.h @@ -1,5 +1,5 @@ /* -* Copyright 2008 Free Software Foundation, Inc. +* Copyright 2008, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * @@ -20,8 +20,9 @@ #include "Defines.h" #include "Interthread.h" #include "BitVector.h" +#include "ByteVector.h" #include "GSMCommon.h" - +#include "GSM503Tables.h" /* Data transfer objects for the GSM core. */ @@ -32,6 +33,7 @@ namespace GSM { class TxBurst; class RxBurst; class L3Message; +class L2LogicalChannel; // Used as transparent pointer in Control directory. /**@name Positions of stealing bits within a normal burst, GSM 05.03 3.1.4. */ //@{ @@ -54,17 +56,36 @@ static const unsigned gSlotLen = 148; ///< number of symbols per slot, not count our primitive set is simple. */ enum Primitive { - ESTABLISH, ///< channel establihsment + // (pat) The ESTABLISH primitive is used on both the L1<->L2 and L2<->L3 interfaces, only on XCCH channels, + // which are SDCCH, TCHFACCH and SACCH; all use L2LAPDm and XCCHEncoder/XCCHDecoder. + // The use of ESTABLISH on these two layer interfaces is not coupled, and the ESTABLISH primitive does not directly penetrate through L2LAPDm. + // We could use different primitive names for these two purposes (as per above comment). + // On L1->L2LAPDm the ESTABLISH is sent on the first good frame, and does nothing but (possibly redundant?) re-init of L2LAPDm variables. + // On L2->L1 there is code in L1 to open the channel when an ESTABLISH primitive is seen, but I dont believe this code is used. + // So ESTABLISH is only important for L2LAPDm<->L3, where the ESTABLISH primitive starts SABM [reliable transport mode]. + // On SAP0 the MS always establishes SABM so in uplink the layer3 DCCHDispatch sits around and waits for it. + // On downlink, only for MT-SMS, the BTS must initiate SABM, which it does by sending an ESTABLISH to the high side of L2LAPDm. + ESTABLISH, ///< L2<->L3 SABM establishment, or L1->L2 notification of first good frame. + // (pat) The RELEASE primitive is sent on L2<->L3 both ways. On SAPI=0 the RELEASE primitive is only sent + // when the channel is released or lost. RELEASE, ///< normal channel release + // (pat) This is not a good idea, to have globals named "DATA" and "ERROR"; risks collisions with libraries. DATA, ///< multiframe data transfer UNIT_DATA, ///< datagram-type data transfer ERROR, ///< channel error HARDRELEASE, ///< forced release after an assignment HANDOVER_ACCESS ///< received inbound handover access burst }; +std::ostream& operator<<(std::ostream& os, Primitive prim); +enum SAPI_t { + SAPIUndefined = 4, // Any other value is fine. + SAPI0 = 0, + SAPI3 = 3 + }; +const char *SAPI2Text(SAPI_t sapi); +std::ostream& operator<<(std::ostream& os, SAPI_t sapi); -std::ostream& operator<<(std::ostream& os, Primitive); @@ -140,6 +161,15 @@ class TxBurstQueue : public InterthreadPriorityQueue { /** Class to represent one timeslot of channel bits with soft encoding. + // (pat) A "normal burst" looks like this: + 3 tail bits + 57 upper data bits + 1 upper stealing flag + 26 training sequence + 1 lower stealing flag + 57 lower data bits + 3 tail bits + 8.25 guard bits. */ class RxBurst : public SoftVector { @@ -172,11 +202,14 @@ class RxBurst : public SoftVector { float timingError() const { return mTimingError; } /** Return a SoftVector alias to the first data field. */ + // (pat) Actually, these are probably returning clones, not aliases, due to the conversion from Vector via copy-constructor. const SoftVector data1() const { return segment(3, 57); } /** Return a SoftVector alias to the second data field. */ const SoftVector data2() const { return segment(88, 57); } + float getNormalSNR() const; + /** Return upper stealing bit. */ bool Hu() const { return bit(gHuIndex); } @@ -227,6 +260,9 @@ class L2Address { /**@name Obvious accessors. */ //@{ unsigned SAPI() const { return mSAPI; } +#ifdef CR +#undef CR // This is defined in the sip or ortp include files somewhere. +#endif unsigned CR() const { return mCR; } unsigned LPD() const { return mLPD; } //@} @@ -473,16 +509,17 @@ class L2Frame : public BitVector { void idleFill(); /** Build an empty frame with a given primitive. */ - L2Frame(GSM::Primitive wPrimitive=UNIT_DATA) + explicit L2Frame(GSM::Primitive wPrimitive=UNIT_DATA) :BitVector(23*8), mPrimitive(wPrimitive) { idleFill(); } /** Make a new L2 frame by copying an existing one. */ - L2Frame(const L2Frame& other) - :BitVector((const BitVector&)other), - mPrimitive(other.mPrimitive) - { } + // (pat) This constructor is not needed - it is supplied by default. + //L2Frame(const L2Frame& other) + // :BitVector((const BitVector&)other), + // mPrimitive(other.mPrimitive) + //{ } /** Make an L2Frame from a block of bits. @@ -501,7 +538,7 @@ class L2Frame : public BitVector { Make an L2Frame from a header with no payload. The primitive is DATA. */ - L2Frame(const L2Header&); + explicit L2Frame(const L2Header&); /** Get the LPD from the L2 header. Assumes address byte is first. */ unsigned LPD() const; @@ -541,7 +578,7 @@ class L2Frame : public BitVector { bool M() const { return mStart[8*2+6] & 0x01; } /** Return the L3 payload part. Assumes A or B header format. */ - BitVector L3Part() const { return segment(8*3,8*L()); } + BitVector L3Part() const { return cloneSegment(8*3,8*L()); } /** Return NR sequence number, GSM 04.06 3.5.2.4. Assumes A or B header. */ unsigned NR() const { return peekField(8*1+0,3); } @@ -575,6 +612,7 @@ class L2Frame : public BitVector { const L2Frame& L2IdleFrame(); std::ostream& operator<<(std::ostream& os, const L2Frame& msg); +std::ostream& operator<<(std::ostream& os, const L2Frame* msg); typedef InterthreadQueueWithWait L2FrameFIFO; @@ -585,42 +623,75 @@ typedef InterthreadQueueWithWait L2FrameFIFO; Bit ordering is MSB-first in each octet. NOTE: This is for the GSM message bits, not the message content. See L3Message. */ -class L3Frame : public BitVector { +class L3Frame : public BitVector { // (pat) This is in Layer3, common to UMTS and GSM and someday should move to some other directory. private: Primitive mPrimitive; + SAPI_t mSapi; // (pat) 5-2013: added SAPI this frame was received on or sent to. + // Not relevant for non-DATA L3Frame, for example, frames sent on CCCH. + // In other words, only relevant if primitive is DATA. + // This is only used in one place in Layer3, but it is interesting debugging information always. size_t mL2Length; ///< length, or L2 pseudo-length, as appropriate + // (pat) FIXME: Apparently l2length is sometimes in bits and sometimes in bytes? (Just look at the constructors.) public: + L3Frame(const L3Frame &other) : BitVector(other), mPrimitive(other.mPrimitive), mSapi(other.mSapi), mL2Length(other.mL2Length) { } + +#if ORIGINAL /** Empty frame with a primitive. */ L3Frame(Primitive wPrimitive=DATA, size_t len=0) - :BitVector(len),mPrimitive(wPrimitive),mL2Length(len) + :BitVector(len),mPrimitive(wPrimitive),mSapi(SAPIUndefined),mL2Length(len) + { } +#endif +#if 0 + L3Frame(Primitive wPrimitive, SAPI_t sapi) + :BitVector((size_t)0),mPrimitive(wPrimitive),mSapi(SAPIUndefined),mL2Length(0) + { } +#endif + // Dont do this. A Primitive can be converted to a size_t, so it creates ambiguities in pre-existing code. + //explicit L3Frame(size_t bitsNeeded) :BitVector(bitsNeeded),mPrimitive(DATA),mSapi(SAPIUndefined),mL2Length(bitsNeeded/8) { } + + explicit L3Frame(Primitive wPrimitive) :BitVector((size_t)0),mPrimitive(wPrimitive),mSapi(SAPIUndefined),mL2Length(0) { } + L3Frame(SAPI_t wSapi, Primitive wPrimitive) :BitVector((size_t)0),mPrimitive(wPrimitive),mSapi(wSapi),mL2Length(0) { } + + L3Frame(Primitive wPrimitive, size_t len) + :BitVector(len),mPrimitive(wPrimitive),mSapi(SAPIUndefined),mL2Length(len) { } /** Put raw bits into the frame. */ - L3Frame(const BitVector& source, Primitive wPrimitive=DATA) - :BitVector(source),mPrimitive(wPrimitive),mL2Length(source.size()/8) - { if (source.size()%8) mL2Length++; } + // (pat 11-2013) The old BitVector automatically cloned because the BitVector is declared const; now we must be explicit. + explicit L3Frame(SAPI_t wSapi,const BitVector& source, Primitive wPrimitive=DATA) + :mPrimitive(wPrimitive),mSapi(wSapi),mL2Length(source.size()/8) + { clone(source); if (source.size()%8) mL2Length++; } /** Concatenate 2 L3Frames */ - L3Frame(const L3Frame& f1, const L3Frame& f2) - :BitVector(f1,f2),mPrimitive(DATA), - mL2Length(f1.mL2Length + f2.mL2Length) + // (pat) This was previously used only to concatenate BitVectors. With lots of unneeded conversions. Oops. So I removed it. + //L3Frame(const L3Frame& f1, const L3Frame& f2) + // :BitVector(f1,f2),mPrimitive(DATA),mSapi(SAPIUndefined), + // mL2Length(f1.mL2Length + f2.mL2Length) + //{ } + + // (pat) This is used only in L2LAPDm::bufferIFrameData to avoid one extra copy in the final concat. + // TODO: Make a better assembly buffer there, then get rid of this constructor. + L3Frame(SAPI_t wSapi, const BitVector& f1, const BitVector& f2) // (pat) added to replace above. + :BitVector(f1,f2),mPrimitive(DATA),mSapi(wSapi), + mL2Length((f1.size() + f2.size())/8) { } /** Build from an L2Frame. */ - L3Frame(const L2Frame& source) - :BitVector(source.L3Part()),mPrimitive(DATA), - mL2Length(source.L()) - { } + // (pat 11-2013) The old BitVector automatically cloned because the BitVector is declared const; now we must be explicit. + explicit L3Frame(SAPI_t wSapi, const L2Frame& source) + :mPrimitive(DATA), mSapi(wSapi),mL2Length(source.L()) + { clone(source.L3Part()); } /** Serialize a message into the frame. */ - L3Frame(const L3Message& msg, Primitive wPrimitive=DATA); + // (pat) Note: This previously caused unanticipated auto-conversion from L3Message to L3Frame throughout the code base. + explicit L3Frame(const L3Message& msg, Primitive wPrimitive=DATA, SAPI_t sapi=SAPIUndefined); /** Get a frame from a hex string. */ - L3Frame(const char*); + explicit L3Frame(const char*); /** Get a frame from raw binary. */ L3Frame(const char*, size_t len); @@ -629,13 +700,16 @@ class L3Frame : public BitVector { L3PD PD() const { return (L3PD)peekField(4,4); } /** Message Type Indicator, GSM 04.08 10.4. */ - unsigned MTI() const { return peekField(8,8); } + // Note: must AND with 0xbf for MM and CC messages. (And it doesnt hurt the other PDs either.) + unsigned MTI() const; /** TI (transaction Identifier) value, GSM 04.07 11.2.3.1.3. */ + // (pat) Only valid for certain types of messages, notably call control and SMS. unsigned TI() const { return peekField(0,4); } /** Return the associated primitive. */ GSM::Primitive primitive() const { return mPrimitive; } + bool isData() const { return mPrimitive == DATA || mPrimitive == UNIT_DATA; } /** Return frame length in BYTES. */ size_t length() const { return size()/8; } @@ -648,46 +722,74 @@ class L3Frame : public BitVector { // Methods for writing H/L bits into rest octets. void writeH(size_t& wp); void writeL(size_t& wp); + SAPI_t getSAPI() const { return mSapi; } }; std::ostream& operator<<(std::ostream& os, const L3Frame&); +std::ostream& operator<<(std::ostream& os, const L3Frame*); typedef InterthreadQueue L3FrameFIFO; - +// Audio frames are bytes communicated between the RTP-layer [Real-time Transport Protocol] and the L1-layer FECs. +// The RTP frames come/go upstream directly on internet ports for communication with another BTS or elsewhere. +// The Audio frame is encoded inside an RTP frame transferred on the wire, but cracked out by the RTP layer, +// so we see only the Audio frame. The Audio format is communicated via SIP using SDP [Session Description Protocol] +// which is the "m=" and etc fields in the SIP invide. +// The simplest RTP/AVP audio payload type overview is wikipedia "RTP audio video profile". +// Formerly Audio Frames were fixed at 33 bytes for GSM Audio format, but now the size is variable, +// and when we support silence the AudioFrame size will vary with each frame. +typedef ByteVector AudioFrame; +typedef InterthreadQueue AudioFrameFIFO; /** + (pat) This is the old comment for the GSM Vocoder frame, which has been replaced by AudioFrameRtp. A vocoder frame for use in GSM/SIP contexts. This is based on RFC-3551 Section 4.5.8.1. Note the 4-bit pad at the start of the frame, filled with b1101 (0xd). + (pat) We are creating an RTP stream frame. + RFC-3551 specifies a 4 bit signature consisting of a one bit marker bit (which cant really mark much, can it) + followed by the payload type, which is RTP stream type 3, which is GSM Full Rate Audio 13kbit/s, + ie, 0xd followed by 260 bits of payload. (260+4)/8 == 33 bytes. */ -class VocoderFrame : public BitVector { + +class AudioFrameRtp : public AudioFrame { + + // For AMR mode: + // The 3 fields are bit-aligned (closest packing) in "bandwidth-efficient" mode: + // | payload header (4 bits) | table of contents (6 bits) | + // followed by speech data (number of bits depends on type) | followed by padding to byte boundary. + // Payload Header: + // 4 bits; Codec Mode Request. + // Table of Contents: + // Single bit; F bit is 1 to indicate this frame is followed by another, so always 0 for us. + // 4 bits; Frame type index. + // Single Bit; Quality indicator: 0 means the frame is "severely damaged". + // Everything else is payload. public: + static const int RtpHeaderSize = 4; + static const int RtpPlusAmrHeaderSize = 4 + 6; + AMRMode mMode; - VocoderFrame() - :BitVector(264) - { fillField(0,0x0d,4); } + static int headerSizeBits(AMRMode wMode) { + return (wMode == TCH_FS) ? RtpHeaderSize : RtpPlusAmrHeaderSize; + } - /** Construct by unpacking a char[33]. */ - VocoderFrame(const unsigned char *src) - :BitVector(264) - { unpack(src); } + // Create an empty RTP frame of specified mode and fill in the RTP header. + // Leave the ByteVector append pointer at the payload location so the caller can simply append the payload. + AudioFrameRtp(AMRMode wMode); - /** Non-const form returns an alias. */ - BitVector payload() { return tail(4); } - - /** Const form returns a copy. */ - const BitVector payload() const { return tail(4); } + // Load the generic data from a ByteVector (aka AudioFrame) into this object and set the AMRMode so it the RTP data can be decoded. + AudioFrameRtp(AMRMode wMode, const AudioFrame *genericFrame) : ByteVector(*genericFrame), mMode(wMode) {} + // Put the payload from this RTP frame into the specified BitVector, which must be the correct size. + void getPayload(BitVector *result) const; }; -typedef InterthreadQueue VocoderFrameFIFO; - }; // namespace GSM diff --git a/GSM/Makefile.am b/GSM/Makefile.am index 4b8d1f8..aac0a2c 100644 --- a/GSM/Makefile.am +++ b/GSM/Makefile.am @@ -26,6 +26,7 @@ AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) noinst_LTLIBRARIES = libGSM.la libGSM_la_SOURCES = \ + GSML3SSMessages.cpp \ GSM610Tables.cpp \ GSMCommon.cpp \ GSMConfig.cpp \ @@ -50,6 +51,7 @@ libGSM_la_SOURCES = \ PhysicalStatus.cpp noinst_HEADERS = \ + GSML3SSMessages.h \ GSM610Tables.h \ GSMCommon.h \ GSMConfig.h \ diff --git a/GSM/PhysicalStatus.cpp b/GSM/PhysicalStatus.cpp index 222edf5..c2f4679 100644 --- a/GSM/PhysicalStatus.cpp +++ b/GSM/PhysicalStatus.cpp @@ -2,7 +2,7 @@ /* * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -87,7 +87,7 @@ PhysicalStatus::~PhysicalStatus() if (mDB) sqlite3_close(mDB); } -bool PhysicalStatus::createEntry(const LogicalChannel* chan) +bool PhysicalStatus::createEntry(const L2LogicalChannel* chan) { assert(mDB); assert(chan); @@ -110,7 +110,7 @@ bool PhysicalStatus::createEntry(const LogicalChannel* chan) return false; } -bool PhysicalStatus::setPhysical(const LogicalChannel* chan, +bool PhysicalStatus::setPhysical(const L2LogicalChannel* chan, const L3MeasurementResults& measResults) { // TODO -- It would be better if the argument what just the channel @@ -134,7 +134,7 @@ bool PhysicalStatus::setPhysical(const LogicalChannel* chan, std::vector ARFCNList = gNeighborTable.ARFCNList(); size_t sz = ARFCNList.size(); if (sz!=0) { - if (CNgetPhysInfo(); if (ARFCN<0) { sprintf(query, @@ -162,8 +163,8 @@ bool PhysicalStatus::setPhysical(const LogicalChannel* chan, measResults.RXLEV_SUB_SERVING_CELL_dBm(), measResults.RXQUAL_FULL_SERVING_CELL_BER(), measResults.RXQUAL_SUB_SERVING_CELL_BER(), - chan->RSSI(), chan->timingError(), - chan->actualMSPower(), chan->actualMSTiming(), + phys->RSSI(), phys->timingError(), + phys->actualMSPower(), phys->actualMSTiming(), chan->FER(), (unsigned)time(NULL), chan->ARFCN(), @@ -189,8 +190,8 @@ bool PhysicalStatus::setPhysical(const LogicalChannel* chan, measResults.RXLEV_SUB_SERVING_CELL_dBm(), measResults.RXQUAL_FULL_SERVING_CELL_BER(), measResults.RXQUAL_SUB_SERVING_CELL_BER(), - chan->RSSI(), chan->timingError(), - chan->actualMSPower(), chan->actualMSTiming(), + phys->RSSI(), phys->timingError(), + phys->actualMSPower(), phys->actualMSTiming(), chan->FER(), (unsigned)time(NULL), chan->ARFCN(), diff --git a/GSM/PhysicalStatus.h b/GSM/PhysicalStatus.h index 57312e6..c41059f 100644 --- a/GSM/PhysicalStatus.h +++ b/GSM/PhysicalStatus.h @@ -33,7 +33,7 @@ struct sqlite3; namespace GSM { class L3MeasurementResults; -class LogicalChannel; +class L2LogicalChannel; /** A table for tracking the state of channels. @@ -62,7 +62,7 @@ public: @param measResults The measurement report. @return The result of the SQLite query: true for the query being executed successfully, false otherwise. */ - bool setPhysical(const LogicalChannel* chan, const L3MeasurementResults& measResults); + bool setPhysical(const L2LogicalChannel* chan, const L3MeasurementResults& measResults); /** Dump the physical status table to the output stream. @@ -77,7 +77,7 @@ public: @param chan The channel to create an entry for. @return The result of the SQLite query: true for the query being executed successfully, false otherwise. */ - bool createEntry(const LogicalChannel* chan); + bool createEntry(const L2LogicalChannel* chan); }; diff --git a/GSM/PowerManager.cpp b/GSM/PowerManager.cpp index 06f4595..05bcdb1 100644 --- a/GSM/PowerManager.cpp +++ b/GSM/PowerManager.cpp @@ -29,13 +29,13 @@ void PowerManager::increasePower() { int maxAtten = gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB"); int minAtten = gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB"); - if (mAtten==minAtten) { + if ((int)mAtten==minAtten) { LOG(DEBUG) << "power already at maximum"; return; } mAtten--; // raise power by reducing attenuation - if (mAttenmaxAtten) mAtten=maxAtten; + if ((int)mAttenmaxAtten) mAtten=maxAtten; LOG(INFO) << "power increased to -" << mAtten << " dB"; mRadio->setPower(mAtten); } @@ -44,13 +44,13 @@ void PowerManager::reducePower() { int maxAtten = gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB"); int minAtten = gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB"); - if (mAtten==maxAtten) { + if ((int)mAtten==maxAtten) { LOG(DEBUG) << "power already at minimum"; return; } mAtten++; // reduce power be increasing attenuation - if (mAttenmaxAtten) mAtten=maxAtten; + if ((int)mAttenmaxAtten) mAtten=maxAtten; LOG(INFO) << "power decreased to -" << mAtten << " dB"; mRadio->setPower(mAtten); } diff --git a/GSMShare/A51.cpp b/GSMShare/A51.cpp new file mode 100644 index 0000000..f5217fd --- /dev/null +++ b/GSMShare/A51.cpp @@ -0,0 +1,228 @@ +/* + * A pedagogical implementation of A5/1. + * + * Copyright (C) 1998-1999: Marc Briceno, Ian Goldberg, and David Wagner + * + * The source code below is optimized for instructional value and clarity. + * Performance will be terrible, but that's not the point. + * The algorithm is written in the C programming language to avoid ambiguities + * inherent to the English language. Complain to the 9th Circuit of Appeals + * if you have a problem with that. + * + * This software may be export-controlled by US law. + * + * This software is free for commercial and non-commercial use as long as + * the following conditions are aheared to. + * Copyright remains the authors' and as such any Copyright notices in + * the code are not to be removed. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The license and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution license + * [including the GNU Public License.] + * + * Background: The Global System for Mobile communications is the most widely + * deployed cellular telephony system in the world. GSM makes use of + * four core cryptographic algorithms, neither of which has been published by + * the GSM MOU. This failure to subject the algorithms to public review is all + * the more puzzling given that over 100 million GSM + * subscribers are expected to rely on the claimed security of the system. + * + * The four core GSM algorithms are: + * A3 authentication algorithm + * A5/1 "strong" over-the-air voice-privacy algorithm + * A5/2 "weak" over-the-air voice-privacy algorithm + * A8 voice-privacy key generation algorithm + * + * In April of 1998, our group showed that COMP128, the algorithm used by the + * overwhelming majority of GSM providers for both A3 and A8 + * functionality was fatally flawed and allowed for cloning of GSM mobile + * phones. + * Furthermore, we demonstrated that all A8 implementations we could locate, + * including the few that did not use COMP128 for key generation, had been + * deliberately weakened by reducing the keyspace from 64 bits to 54 bits. + * The remaining 10 bits are simply set to zero! + * + * See http://www.scard.org/gsm for additional information. + * + * The question so far unanswered is if A5/1, the "stronger" of the two + * widely deployed voice-privacy algorithm is at least as strong as the + * key. Meaning: "Does A5/1 have a work factor of at least 54 bits"? + * Absent a publicly available A5/1 reference implementation, this question + * could not be answered. We hope that our reference implementation below, + * which has been verified against official A5/1 test vectors, will provide + * the cryptographic community with the base on which to construct the + * answer to this important question. + * + * Initial indications about the strength of A5/1 are not encouraging. + * A variant of A5, while not A5/1 itself, has been estimated to have a + * work factor of well below 54 bits. See http://jya.com/crack-a5.htm for + * background information and references. + * + * With COMP128 broken and A5/1 published below, we will now turn our attention + * to A5/2. The latter has been acknowledged by the GSM community to have + * been specifically designed by intelligence agencies for lack of security. + * + */ + + +#include +#include +#include "A51.h" + +/* Masks for the three shift registers */ +#define R1MASK 0x07FFFF /* 19 bits, numbered 0..18 */ +#define R2MASK 0x3FFFFF /* 22 bits, numbered 0..21 */ +#define R3MASK 0x7FFFFF /* 23 bits, numbered 0..22 */ + +/* Middle bit of each of the three shift registers, for clock control */ +#define R1MID 0x000100 /* bit 8 */ +#define R2MID 0x000400 /* bit 10 */ +#define R3MID 0x000400 /* bit 10 */ + +/* The three shift registers. They're in global variables to make the code + * easier to understand. + * A better implementation would not use global variables. */ +word R1, R2, R3; + +/* Look at the middle bits of R1,R2,R3, take a vote, and + * return the majority value of those 3 bits. */ +bit majority() { + int sum; + sum = ((R1>>8)&1) + ((R2>>10)&1) + ((R3>>10)&1); + if (sum >= 2) + return 1; + else + return 0; +} + +/* Clock two or three of R1,R2,R3, with clock control + * according to their middle bits. + * Specifically, we clock Ri whenever Ri's middle bit + * agrees with the majority value of the three middle bits.*/ +void clock() { + bit maj = majority(); + if (((R1&R1MID)!=0) == maj) + R1 = ((R1<<1) & R1MASK) | (1 & (R1>>18 ^ R1>>17 ^ R1>>16 ^ R1>>13)); + if (((R2&R2MID)!=0) == maj) + R2 = ((R2<<1) & R2MASK) | (1 & (R2>>21 ^ R2>>20)); + if (((R3&R3MID)!=0) == maj) + R3 = ((R3<<1) & R3MASK) | (1 & (R3>>22 ^ R3>>21 ^ R3>>20 ^ R3>>7)); +} + +/* Clock all three of R1,R2,R3, ignoring their middle bits. + * This is only used for key setup. */ +void clockallthree() { + R1 = ((R1<<1) & R1MASK) | (1 & (R1>>18 ^ R1>>17 ^ R1>>16 ^ R1>>13)); + R2 = ((R2<<1) & R2MASK) | (1 & (R2>>21 ^ R2>>20)); + R3 = ((R3<<1) & R3MASK) | (1 & (R3>>22 ^ R3>>21 ^ R3>>20 ^ R3>>7)); +} + +/* Generate an output bit from the current state. + * You grab a bit from each register via the output generation taps; + * then you XOR the resulting three bits. */ +bit getbit() { + return ((R1>>18)^(R2>>21)^(R3>>22))&1; +} + +/* Do the A5/1 key setup. This routine accepts a 64-bit key and + * a 22-bit frame number. */ +void keysetup(byte key[8], word frame) { + int i; + bit keybit, framebit; + + /* Zero out the shift registers. */ + R1 = R2 = R3 = 0; + + /* Load the key into the shift registers, + * LSB of first byte of key array first, + * clocking each register once for every + * key bit loaded. (The usual clock + * control rule is temporarily disabled.) */ + for (i=0; i<64; i++) { + clockallthree(); /* always clock */ + keybit = (key[7-i/8] >> (i&7)) & 1; /* The i-th bit of the +key */ + R1 ^= keybit; R2 ^= keybit; R3 ^= keybit; + } + + /* Load the frame number into the shift + * registers, LSB first, + * clocking each register once for every + * key bit loaded. (The usual clock + * control rule is still disabled.) */ + for (i=0; i<22; i++) { + clockallthree(); /* always clock */ + framebit = (frame >> i) & 1; /* The i-th bit of the frame # +*/ + R1 ^= framebit; R2 ^= framebit; R3 ^= framebit; + } + + /* Run the shift registers for 100 clocks + * to mix the keying material and frame number + * together with output generation disabled, + * so that there is sufficient avalanche. + * We re-enable the majority-based clock control + * rule from now on. */ + for (i=0; i<100; i++) { + clock(); + } + + /* Now the key is properly set up. */ +} + +/* Generate output. We generate 228 bits of + * keystream output. The first 114 bits is for + * the A->B frame; the next 114 bits is for the + * B->A frame. You allocate a 15-byte buffer + * for each direction, and this function fills + * it in. */ +void run(byte AtoBkeystream[], byte BtoAkeystream[]) { + int i; + + /* Zero out the output buffers. */ + for (i=0; i<=113/8; i++) + AtoBkeystream[i] = BtoAkeystream[i] = 0; + + /* Generate 114 bits of keystream for the + * A->B direction. Store it, MSB first. */ + for (i=0; i<114; i++) { + clock(); + AtoBkeystream[i/8] |= getbit() << (7-(i&7)); + } + + /* Generate 114 bits of keystream for the + * B->A direction. Store it, MSB first. */ + for (i=0; i<114; i++) { + clock(); + BtoAkeystream[i/8] |= getbit() << (7-(i&7)); + } +} + +void A51_GSM( byte *key, int klen, int count, byte *block1, byte *block2 ) +{ + assert(klen == 64); + keysetup(key, count); // TODO - frame and count are not the same + run(block1, block2); +} diff --git a/GSMShare/A51.h b/GSMShare/A51.h new file mode 100644 index 0000000..d1337de --- /dev/null +++ b/GSMShare/A51.h @@ -0,0 +1,11 @@ + + +#include +#include + +typedef unsigned char byte; +typedef unsigned long word; +typedef word bit; + +void A51_GSM( byte *key, int klen, int count, byte *block1, byte *block2 ); + diff --git a/GSMShare/A51Test.cpp b/GSMShare/A51Test.cpp new file mode 100644 index 0000000..e34b3f4 --- /dev/null +++ b/GSMShare/A51Test.cpp @@ -0,0 +1,162 @@ +/* + * A pedagogical implementation of A5/1. + * + * Copyright (C) 1998-1999: Marc Briceno, Ian Goldberg, and David Wagner + * + * The source code below is optimized for instructional value and clarity. + * Performance will be terrible, but that's not the point. + * The algorithm is written in the C programming language to avoid ambiguities + * inherent to the English language. Complain to the 9th Circuit of Appeals + * if you have a problem with that. + * + * This software may be export-controlled by US law. + * + * This software is free for commercial and non-commercial use as long as + * the following conditions are aheared to. + * Copyright remains the authors' and as such any Copyright notices in + * the code are not to be removed. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The license and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution license + * [including the GNU Public License.] + * + * Background: The Global System for Mobile communications is the most widely + * deployed cellular telephony system in the world. GSM makes use of + * four core cryptographic algorithms, neither of which has been published by + * the GSM MOU. This failure to subject the algorithms to public review is all + * the more puzzling given that over 100 million GSM + * subscribers are expected to rely on the claimed security of the system. + * + * The four core GSM algorithms are: + * A3 authentication algorithm + * A5/1 "strong" over-the-air voice-privacy algorithm + * A5/2 "weak" over-the-air voice-privacy algorithm + * A8 voice-privacy key generation algorithm + * + * In April of 1998, our group showed that COMP128, the algorithm used by the + * overwhelming majority of GSM providers for both A3 and A8 + * functionality was fatally flawed and allowed for cloning of GSM mobile + * phones. + * Furthermore, we demonstrated that all A8 implementations we could locate, + * including the few that did not use COMP128 for key generation, had been + * deliberately weakened by reducing the keyspace from 64 bits to 54 bits. + * The remaining 10 bits are simply set to zero! + * + * See http://www.scard.org/gsm for additional information. + * + * The question so far unanswered is if A5/1, the "stronger" of the two + * widely deployed voice-privacy algorithm is at least as strong as the + * key. Meaning: "Does A5/1 have a work factor of at least 54 bits"? + * Absent a publicly available A5/1 reference implementation, this question + * could not be answered. We hope that our reference implementation below, + * which has been verified against official A5/1 test vectors, will provide + * the cryptographic community with the base on which to construct the + * answer to this important question. + * + * Initial indications about the strength of A5/1 are not encouraging. + * A variant of A5, while not A5/1 itself, has been estimated to have a + * work factor of well below 54 bits. See http://jya.com/crack-a5.htm for + * background information and references. + * + * With COMP128 broken and A5/1 published below, we will now turn our attention + * to A5/2. The latter has been acknowledged by the GSM community to have + * been specifically designed by intelligence agencies for lack of security. + * + */ + + +#include +#include +#include +#include "./A51.h" + + +/* Test the code by comparing it against + * a known-good test vector. */ +void test() { + byte key[8] = {0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x12}; + word frame = 0x134; + byte goodAtoB[15] = { 0x53, 0x4E, 0xAA, 0x58, 0x2F, 0xE8, 0x15, + 0x1A, 0xB6, 0xE1, 0x85, 0x5A, 0x72, 0x8C, 0x00 }; + byte goodBtoA[15] = { 0x24, 0xFD, 0x35, 0xA3, 0x5D, 0x5F, 0xB6, + 0x52, 0x6D, 0x32, 0xF9, 0x06, 0xDF, 0x1A, 0xC0 }; + byte AtoB[15], BtoA[15]; + int i, failed=0; + + A51_GSM(key, 64, frame, AtoB, BtoA); + + /* Compare against the test vector. */ + for (i=0; i<15; i++) + if (AtoB[i] != goodAtoB[i]) + failed = 1; + for (i=0; i<15; i++) + if (BtoA[i] != goodBtoA[i]) + failed = 1; + + /* Print some debugging output. */ + printf("key: 0x"); + for (i=0; i<8; i++) + printf("%02X", key[i]); + printf("\n"); + printf("frame number: 0x%06X\n", (unsigned int)frame); + printf("known good output:\n"); + printf(" A->B: 0x"); + for (i=0; i<15; i++) + printf("%02X", goodAtoB[i]); + printf(" B->A: 0x"); + for (i=0; i<15; i++) + printf("%02X", goodBtoA[i]); + printf("\n"); + printf("observed output:\n"); + printf(" A->B: 0x"); + for (i=0; i<15; i++) + printf("%02X", AtoB[i]); + printf(" B->A: 0x"); + for (i=0; i<15; i++) + printf("%02X", BtoA[i]); + printf("\n"); + + if (!failed) { + printf("Self-check succeeded: everything looks ok.\n"); + } else { + /* Problems! The test vectors didn't compare*/ + printf("\nI don't know why this broke; contact the authors.\n"); + exit(1); + } + + printf("time test\n"); + int n = 10000; + float t = clock(); + for (i = 0; i < n; i++) { + A51_GSM(key, 64, frame, AtoB, BtoA); + } + t = (clock() - t) / (CLOCKS_PER_SEC * (float)n); + printf("A51_GSM takes %g seconds per iteration\n", t); +} + +int main(void) { + test(); + return 0; +} diff --git a/GSMShare/AMRTest.cpp b/GSMShare/AMRTest.cpp new file mode 100644 index 0000000..8105690 --- /dev/null +++ b/GSMShare/AMRTest.cpp @@ -0,0 +1,167 @@ +/* +* Copyright 2012 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* +* 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. + +*/ + +#include "BitVector.h" +#include "AmrCoder.h" +#include +#include +#include +#include "GSM503Tables.cpp" + +using namespace std; + +BitVector randomBitVector(int n) +{ + BitVector t(n); + for (int i = 0; i < n; i++) t[i] = random()%2; + return t; +} + +#if OLD_TEST +// Doug wrote this AMR test code +void testEncodeDecode(const char *label, void (*encoder)(BitVector&,BitVector&), void (*decoder)(SoftVector&,BitVector&), unsigned frameSize, unsigned iRate, unsigned order, bool debug) +#else +// Pat modified to use the Viterbi base class. +void testEncodeDecode(const char *label, ViterbiBase *coder, unsigned frameSize, unsigned iRate, unsigned order, bool debug) +#endif +{ + const unsigned trials = 10; + cout << endl; + if (debug) cout << label << endl; + BitVector v1 = randomBitVector(frameSize); + if (debug) cout << "v1 " << v1 << endl; + BitVector v2(iRate*frameSize+iRate*order); +#if OLD_TEST + (*encoder)(v1, v2); +#else + coder->encode(v1,v2); +#endif + if (debug) cout << "v2 " << v2 << endl; + for (int perr = 0; perr <= 50; perr += 10) { + int errs = 0; + // multiple trials smooths out the weirdnesses due to unfortunate positioning of noisy bits + for (unsigned trial = 0; trial < trials; trial++) { + SoftVector sv2(v2); + for (unsigned j = 0; j < sv2.size(); j++) { + if (random() % 100 < perr) sv2[j] = 0.5; + } + if (debug) cout << "n2 " << sv2 << endl; + BitVector v3(frameSize); +#if OLD_TEST + decoder(sv2, v3); +#else + coder->decode(sv2,v3); +#endif + if (debug) cout << "v3 " << v3 << endl; + for (unsigned j = 0; j < frameSize; j++) { + if (v1.bit(j) != v3.bit(j)) errs++; + } + } + int pbbo = 100 * errs / (frameSize * trials); + cout << label << ", " << "% ambiguous bits in : % bad bits out = " << perr << " : " << pbbo << endl; + } +} + +#if OLD_TEST +//void encoder12_2(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS12_2 vCoder; v1.encode(vCoder,v2); } +//void decoder12_2(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS12_2 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder10_2(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS10_2 vCoder; v1.encode(vCoder,v2); } +//void decoder10_2(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS10_2 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder7_95(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS7_95 vCoder; v1.encode(vCoder,v2); } +//void decoder7_95(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS7_95 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder7_4(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS7_4 vCoder; v1.encode(vCoder,v2); } +//void decoder7_4(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS7_4 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder6_7(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS6_7 vCoder; v1.encode(vCoder,v2); } +//void decoder6_7(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS6_7 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder5_9(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS5_9 vCoder; v1.encode(vCoder,v2); } +//void decoder5_9(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS5_9 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder5_15(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS5_15 vCoder; v1.encode(vCoder,v2); } +//void decoder5_15(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS5_15 vCoder; sv2.decode(vCoder,v3); } +// +//void encoder4_75(BitVector &v1, BitVector &v2) { ViterbiTCH_AFS4_75 vCoder; v1.encode(vCoder,v2); } +//void decoder4_75(SoftVector &sv2, BitVector &v3) { ViterbiTCH_AFS4_75 vCoder; sv2.decode(vCoder,v3); } +#endif + +void testPunctureUnpuncture(const char *label, const unsigned int *punk, size_t plth) +{ + bool ok = true; + // Things could go wrong and be missed due to we're just wrangling bit vectors, + // so run each speed a few times to reduce the probability of missing an error. + for (int i = 0; i < 5; i++) { + // generate orig + BitVector orig = randomBitVector(448+plth); + // puncture + BitVector punctured(orig.size() - plth); + orig.copyPunctured(punctured, punk, plth); + SoftVector softPunctured(punctured); + // unpuncture + SoftVector unpunctured(orig.size()); + softPunctured.copyUnPunctured(unpunctured, punk, plth); + // verify + const unsigned *p = punk; + for (unsigned i = 0; i < orig.size(); i++) { + if (unpunctured[i] == 0.5) { + if (*p++ != i) ok = false; + } else { + if (orig.bit(i) != unpunctured[i]) ok = false; + } + } + ok = ok && (p == punk + plth); + } + cout << "puncture->unpuncture " << label << " " << (ok ? "ok" : "NOT ok") << endl; +} + + +void testAMR() +{ +#if OLD_TEST + testEncodeDecode("12_2", encoder12_2, decoder12_2, 250, 2, 4, false); + testEncodeDecode("10_2", encoder10_2, decoder10_2, 210, 3, 4, false); + testEncodeDecode("7_95", encoder7_95, decoder7_95, 165, 3, 6, false); + testEncodeDecode("7_4", encoder7_4, decoder7_4, 154, 3, 4, false); + testEncodeDecode("6_7", encoder6_7, decoder6_7, 140, 4, 4, false); + testEncodeDecode("5_9", encoder5_9, decoder5_9, 124, 4, 6, false); + testEncodeDecode("5_15", encoder5_15, decoder5_15, 109, 5, 4, false); + testEncodeDecode("4_75", encoder4_75, decoder4_75, 101, 5, 6, false); +#else + testEncodeDecode("12_2", new ViterbiTCH_AFS12_2(), 250, 2, 4, false); + testEncodeDecode("10_2", new ViterbiTCH_AFS10_2(), 210, 3, 4, false); + testEncodeDecode("7_95", new ViterbiTCH_AFS7_95(), 165, 3, 6, false); + testEncodeDecode("7_4", new ViterbiTCH_AFS7_4(), 154, 3, 4, false); + testEncodeDecode("6_7", new ViterbiTCH_AFS6_7(), 140, 4, 4, false); + testEncodeDecode("5_9", new ViterbiTCH_AFS5_9(), 124, 4, 6, false); + testEncodeDecode("5_15", new ViterbiTCH_AFS5_15(), 109, 5, 4, false); + testEncodeDecode("4_75", new ViterbiTCH_AFS4_75(), 101, 5, 6, false); +#endif + + testPunctureUnpuncture("12.2", GSM::gAMRPuncturedTCH_AFS12_2, 60); + testPunctureUnpuncture("10.2", GSM::gAMRPuncturedTCH_AFS10_2, 194); + testPunctureUnpuncture("7.95", GSM::gAMRPuncturedTCH_AFS7_95, 65); + testPunctureUnpuncture("7.4", GSM::gAMRPuncturedTCH_AFS7_4, 26); + testPunctureUnpuncture("6.7", GSM::gAMRPuncturedTCH_AFS6_7, 128); + testPunctureUnpuncture("5.9", GSM::gAMRPuncturedTCH_AFS5_9, 72); + testPunctureUnpuncture("5.15", GSM::gAMRPuncturedTCH_AFS5_15, 117); + testPunctureUnpuncture("4.75", GSM::gAMRPuncturedTCH_AFS4_75, 87); +} + +int main(int argc, char *argv[]) +{ + testAMR(); +} diff --git a/GSMShare/AmrCoder.cpp b/GSMShare/AmrCoder.cpp new file mode 100644 index 0000000..20fbced --- /dev/null +++ b/GSMShare/AmrCoder.cpp @@ -0,0 +1,1887 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ + + +#include "BitVector.h" +#include "AmrCoder.h" +#include +#include +#include + +using namespace std; + + + +ViterbiTCH_AFS12_2::ViterbiTCH_AFS12_2() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x019; + mCoeffsFB[0] = 0x019; + mCoeffs[1] = 0x01b; + mCoeffsFB[1] = 0x019; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + +//void BitVector::encode(const ViterbiTCH_AFS12_2& coder, BitVector& target) const +void ViterbiTCH_AFS12_2::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 250); + assert(target.size() == 508); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 4; + BitVector r(254+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 249; k++) { + r[k+H] = u[k] ^ r[k-3+H] ^ r[k-4+H]; + C[2*k] = u[k]; + C[2*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + } + // termination + for (unsigned k = 250; k <= 253; k++) { + r[k+H] = 0; + C[2*k] = r[k-3+H] ^ r[k-4+H]; + C[2*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + } +} + + + +//void BitVector::encode(const ViterbiTCH_AFS10_2& coder, BitVector& target) +void ViterbiTCH_AFS10_2::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 210); + assert(target.size() == 642); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 4; + BitVector r(214+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 209; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[3*k+2] = u[k]; + } + // termination + for (unsigned k = 210; k <= 213; k++) { + r[k+H] = 0; + C[3*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[3*k+2] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + } +} + + + +//void BitVector::encode(const ViterbiTCH_AFS7_95& coder, BitVector& target) +void ViterbiTCH_AFS7_95::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 165); + assert(target.size() == 513); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 6; + BitVector r(171+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 164; k++) { + r[k+H] = u[k] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[3*k] = u[k]; + C[3*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[3*k+2] = r[k+H] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + } + // termination + for (unsigned k = 165; k <= 170; k++) { + r[k+H] = 0; + C[3*k] = r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[3*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[3*k+2] = r[k+H] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + } +} + + + +void ViterbiTCH_AFS7_4::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 154); + assert(target.size() == 474); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 4; + BitVector r(158+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 153; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[3*k+2] = u[k]; + } + // termination + for (unsigned k = 154; k <= 157; k++) { + r[k+H] = 0; + C[3*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[3*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[3*k+2] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + } +} + + + +void ViterbiTCH_AFS6_7::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 140); + assert(target.size() == 576); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 4; + BitVector r(144+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 139; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[4*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[4*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[4*k+2] = u[k]; + C[4*k+3] = u[k]; + } + // termination + for (unsigned k = 140; k <= 143; k++) { + r[k+H] = 0; + C[4*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[4*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[4*k+2] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[4*k+3] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + } +} + + + +void ViterbiTCH_AFS5_9::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 124); + assert(target.size() == 520); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 6; + BitVector r(130+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 123; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + C[4*k] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[4*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[4*k+2] = u[k]; + C[4*k+3] = u[k]; + } + // termination + for (unsigned k = 124; k <= 129; k++) { + r[k+H] = 0; + C[4*k] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[4*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[4*k+2] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + C[4*k+3] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + } +} + + + +void ViterbiTCH_AFS5_15::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 109); + assert(target.size() == 565); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 4; + BitVector r(113+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 108; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k+2] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[5*k+3] = u[k]; + C[5*k+4] = u[k]; + } + // termination + for (unsigned k = 109; k <= 112; k++) { + r[k+H] = 0; + C[5*k] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k+1] = r[k+H] ^ r[k-1+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k+2] = r[k+H] ^ r[k-2+H] ^ r[k-4+H]; + C[5*k+3] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + C[5*k+4] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H]; + } +} + + + +void ViterbiTCH_AFS4_75::encode(const BitVector& in, BitVector& target) const +{ + assert(in.size() == 101); + assert(target.size() == 535); + const char *u = in.begin(); + char *C = target.begin(); + const unsigned H = 6; + BitVector r(107+H); + for (int k = -H; k <= -1; k++) r[k+H] = 0; + for (unsigned k = 0; k <= 100; k++) { + r[k+H] = u[k] ^ r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + C[5*k] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[5*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[5*k+2] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[5*k+3] = u[k]; + C[5*k+4] = u[k]; + } + // termination + for (unsigned k = 101; k <= 106; k++) { + r[k+H] = 0; + C[5*k] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[5*k+1] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-5+H] ^ r[k-6+H]; + C[5*k+2] = r[k+H] ^ r[k-1+H] ^ r[k-4+H] ^ r[k-6+H]; + C[5*k+3] = r[k+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + C[5*k+4] = r[k-1+H] ^ r[k-2+H] ^ r[k-3+H] ^ r[k-4+H] ^ r[k-6+H]; + } +} + + +void ViterbiTCH_AFS12_2::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS12_2::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS12_2::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS12_2::vCand& ViterbiTCH_AFS12_2::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS12_2::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS12_2 &decoder = *this; + const size_t sz = in.size() - 8; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS10_2::ViterbiTCH_AFS10_2() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x01b; + mCoeffsFB[0] = 0x01f; + mCoeffs[1] = 0x015; + mCoeffsFB[1] = 0x01f; + mCoeffs[2] = 0x01f; + mCoeffsFB[2] = 0x01f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS10_2::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS10_2::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS10_2::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS10_2::vCand& ViterbiTCH_AFS10_2::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS10_2::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS10_2 &decoder = *this; + const size_t sz = in.size() - 12; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS7_95::ViterbiTCH_AFS7_95() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x06d; + mCoeffsFB[0] = 0x06d; + mCoeffs[1] = 0x053; + mCoeffsFB[1] = 0x06d; + mCoeffs[2] = 0x05f; + mCoeffsFB[2] = 0x06d; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS7_95::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS7_95::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS7_95::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS7_95::vCand& ViterbiTCH_AFS7_95::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS7_95::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS7_95 &decoder = *this; + const size_t sz = in.size() - 18; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS7_4::ViterbiTCH_AFS7_4() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x01b; + mCoeffsFB[0] = 0x01f; + mCoeffs[1] = 0x015; + mCoeffsFB[1] = 0x01f; + mCoeffs[2] = 0x01f; + mCoeffsFB[2] = 0x01f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS7_4::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS7_4::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS7_4::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS7_4::vCand& ViterbiTCH_AFS7_4::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS7_4::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS7_4 &decoder = *this; + const size_t sz = in.size() - 12; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS6_7::ViterbiTCH_AFS6_7() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x01b; + mCoeffsFB[0] = 0x01f; + mCoeffs[1] = 0x015; + mCoeffsFB[1] = 0x01f; + mCoeffs[2] = 0x01f; + mCoeffsFB[2] = 0x01f; + mCoeffs[3] = 0x01f; + mCoeffsFB[3] = 0x01f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS6_7::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS6_7::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS6_7::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS6_7::vCand& ViterbiTCH_AFS6_7::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS6_7::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS6_7 &decoder = *this; + const size_t sz = in.size() - 16; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS5_9::ViterbiTCH_AFS5_9() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x06d; + mCoeffsFB[0] = 0x05f; + mCoeffs[1] = 0x053; + mCoeffsFB[1] = 0x05f; + mCoeffs[2] = 0x05f; + mCoeffsFB[2] = 0x05f; + mCoeffs[3] = 0x05f; + mCoeffsFB[3] = 0x05f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS5_9::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS5_9::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS5_9::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS5_9::vCand& ViterbiTCH_AFS5_9::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS5_9::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS5_9 &decoder = *this; + const size_t sz = in.size() - 24; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS5_15::ViterbiTCH_AFS5_15() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x01b; + mCoeffsFB[0] = 0x01f; + mCoeffs[1] = 0x01b; + mCoeffsFB[1] = 0x01f; + mCoeffs[2] = 0x015; + mCoeffsFB[2] = 0x01f; + mCoeffs[3] = 0x01f; + mCoeffsFB[3] = 0x01f; + mCoeffs[4] = 0x01f; + mCoeffsFB[4] = 0x01f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS5_15::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS5_15::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS5_15::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS5_15::vCand& ViterbiTCH_AFS5_15::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS5_15::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS5_15 &decoder = *this; + const size_t sz = in.size() - 20; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + +ViterbiTCH_AFS4_75::ViterbiTCH_AFS4_75() +{ + assert(mDeferral < 32); + mCoeffs[0] = 0x06d; + mCoeffsFB[0] = 0x05f; + mCoeffs[1] = 0x06d; + mCoeffsFB[1] = 0x05f; + mCoeffs[2] = 0x053; + mCoeffsFB[2] = 0x05f; + mCoeffs[3] = 0x05f; + mCoeffsFB[3] = 0x05f; + mCoeffs[4] = 0x05f; + mCoeffsFB[4] = 0x05f; + for (unsigned i = 0; i < mIRate; i++) { + computeStateTables(i); + } + computeGeneratorTable(); +} + + + + +void ViterbiTCH_AFS4_75::initializeStates() +{ + for (unsigned i=0; ioState) << mIRate; + for (unsigned in = 0; in <= 1; in++) { + mCandidates[cand+in].iState = ((sp->iState) << 1) | in; + mCandidates[cand+in].cost = sp->cost; + uint32_t outputs = oStateShifted; + for (unsigned out = 0; out < mIRate; out++) { + char feedback = applyPoly(sp->rState[out], mCoeffsFB[out] ^ 1, mOrder+1); + char rState = (((sp->rState[out]) ^ feedback) << 1) | in; + mCandidates[cand+in].rState[out] = rState; + outputs |= (mGeneratorTable[rState & mCMask] & (1 << (mIRate - out - 1))); + } + mCandidates[cand+in].oState = outputs; + } + sp++; + } +} + + +void ViterbiTCH_AFS4_75::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>i)&0x01][mIRate-i-1]; + } + } +} + + +void ViterbiTCH_AFS4_75::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiTCH_AFS4_75::vCand& ViterbiTCH_AFS4_75::step(uint32_t inSample, const float *probs, const float *iprobs) +{ + branchCandidates(); + getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return minCost(); +} + + + +void ViterbiTCH_AFS4_75::decode(const SoftVector &in, BitVector& target) +{ + ViterbiTCH_AFS4_75 &decoder = *this; + const size_t sz = in.size() - 30; + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz == decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + for (size_t i=sz; i=deferral) *op++ = (minCost.iState >> deferral)&0x01; + oCount++; + } + } +} + + + diff --git a/GSMShare/AmrCoder.h b/GSMShare/AmrCoder.h new file mode 100644 index 0000000..29e93cf --- /dev/null +++ b/GSMShare/AmrCoder.h @@ -0,0 +1,936 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. + +*/ +#ifndef _AMRCODER_H_ +#define _AMRCODER_H_ +#include +#include "BitVector.h" +#include "Viterbi.h" + + + +/** + Class to represent recursive systematic convolutional coders/decoders of rate 1/2, memory length 4. +*/ +class ViterbiTCH_AFS12_2 : public ViterbiBase { + + private: + /**name Lots of precomputed elements so the compiler can optimize like hell. */ + //@{ + /**@name Core values. */ + //@{ + static const unsigned mIRate = 2; ///< reciprocal of rate + static const unsigned mOrder = 4; ///< memory length of generators + //@} + /**@name Derived values. */ + //@{ + static const unsigned mIStates = 0x01 << mOrder; ///< number of states, number of survivors + static const uint32_t mSMask = mIStates-1; ///< survivor mask + static const uint32_t mCMask = (mSMask<<1) | 0x01; ///< candidate mask + static const uint32_t mOMask = (0x01<. +# + +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) +AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread + +noinst_LTLIBRARIES = libGSMShare.la + +libGSMShare_la_SOURCES = \ + AmrCoder.cpp \ + GSM503Tables.cpp \ + ViterbiR204.cpp \ + A51.cpp + +noinst_PROGRAMS = \ + ViterbiTest \ + AMRTest \ + A51Test + +# ReportingTest + +noinst_HEADERS = \ + Viterbi.h \ + ViterbiR204.h \ + AmrCoder.h \ + ViterbiR204.h \ + GSM503Tables.h \ + A51.h + +# (pat 8-2013) I dont know how to properly include the CommonLibs directory using autofoo. You are welcome to correct this reference. +ViterbiTest_SOURCES = AMRTest.cpp +ViterbiTest_LDADD = $(noinst_LTLIBRARIES) ../CommonLibs/libcommon.la + +AMRTest_SOURCES = AMRTest.cpp +AMRTest_LDADD = $(noinst_LTLIBRARIES) ../CommonLibs/libcommon.la + +A51Test_SOURCES = A51Test.cpp +A51Test_LDADD = $(noinst_LTLIBRARIES) ../CommonLibs/libcommon.la + diff --git a/GSMShare/Viterbi.h b/GSMShare/Viterbi.h new file mode 100644 index 0000000..de1155c --- /dev/null +++ b/GSMShare/Viterbi.h @@ -0,0 +1,35 @@ +/* +* 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 distribuion. +* +* 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. + +*/ + + +#ifndef _VITERBI_H_ +#define _VITERBI_H_ 1 + +// (pat) Virtual base class for Viterbi and Turbo coder/decoders. +class ViterbiBase { + public: + virtual void encode(const BitVector &in, BitVector& target) const = 0; + virtual void decode(const SoftVector &in, BitVector& target) = 0; + // (pat) Return error count from most recent decoder run. + // If you get -1 from this, the method is not defined in the Viterbi class. + virtual int getBEC() { return -1; } + //virtual ~ViterbiBase(); Currently None of these have destructors. + + // These functions are logically part of the Viterbi functionality, even though they do not use any class variables. + unsigned applyPoly(uint64_t val, uint64_t poly); + unsigned applyPoly(uint64_t val, uint64_t poly, unsigned order); +}; +#endif diff --git a/GSMShare/ViterbiR204.cpp b/GSMShare/ViterbiR204.cpp new file mode 100644 index 0000000..71fe13d --- /dev/null +++ b/GSMShare/ViterbiR204.cpp @@ -0,0 +1,305 @@ +/* +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + + + +#include "BitVector.h" +#include "ViterbiR204.h" +#include +#include +#include +#include + +using namespace std; + + +/** + Apply a Galois polymonial to a binary seqeunce. + @param val The input sequence. + @param poly The polynomial. + @param order The order of the polynomial. + @return Single-bit result. +*/ +unsigned ViterbiBase::applyPoly(uint64_t val, uint64_t poly, unsigned order) +{ + uint64_t prod = val & poly; + unsigned sum = prod; + for (unsigned i=1; i>i; + return sum & 0x01; +} + +unsigned ViterbiBase::applyPoly(uint64_t val, uint64_t poly) +{ + uint64_t prod = val & poly; + prod = (prod ^ (prod >> 32)); + prod = (prod ^ (prod >> 16)); + prod = (prod ^ (prod >> 8)); + prod = (prod ^ (prod >> 4)); + prod = (prod ^ (prod >> 2)); + prod = (prod ^ (prod >> 1)); + return prod & 0x01; +} + + + +//void BitVector::encode(const ViterbiR2O4& coder, BitVector& target) +void ViterbiR2O4::encode(const BitVector& in, BitVector& target) const +{ + const ViterbiR2O4& coder = *this; + size_t sz = in.size(); + + assert(sz*coder.iRate() == target.size()); + + // Build a "history" array where each element contains the full history. + uint32_t history[sz]; + uint32_t accum = 0; + for (size_t i=0; iiState) << 1; // input state for 0 + const uint32_t iState1 = iState0 | 0x01; // input state for 1 + const uint32_t oStateShifted = (sp->oState) << mIRate; // shifted output (by 2) + const float cost = sp->cost; + int bec = sp->bitErrorCnt; + sp++; + // 0 input extension + mCandidates[i].cost = cost; + // mCMask is the low 5 bits, ie, full width of mGeneratorTable. + mCandidates[i].oState = oStateShifted | mGeneratorTable[iState0 & mCMask]; + mCandidates[i].iState = iState0; + mCandidates[i].bitErrorCnt = bec; + // 1 input extension + mCandidates[i+1].cost = cost; + mCandidates[i+1].oState = oStateShifted | mGeneratorTable[iState1 & mCMask]; + mCandidates[i+1].iState = iState1; + mCandidates[i+1].bitErrorCnt = bec; + } +} + + +void ViterbiR2O4::getSoftCostMetrics(const uint32_t inSample, const float *matchCost, const float *mismatchCost) +{ + const float *cTab[2] = {matchCost,mismatchCost}; + for (unsigned i=0; i>1)&0x01][0]; + if (mismatched & 1) { thisCand.bitErrorCnt++; } + if (mismatched & 2) { thisCand.bitErrorCnt++; } + } +} + + +void ViterbiR2O4::pruneCandidates() +{ + const vCand* c1 = mCandidates; // 0-prefix + const vCand* c2 = mCandidates + mIStates; // 1-prefix + for (unsigned i=0; i=minCost) continue; + minCost = thisCost; + minIndex=i; + } + return mSurvivors[minIndex]; +} + + +const ViterbiR2O4::vCand* ViterbiR2O4::vstep(uint32_t inSample, const float *probs, const float *iprobs, bool isNotTailBits) +{ + branchCandidates(); + // (pat) tail bits do not affect cost or error bit count of any branch. + if (isNotTailBits) getSoftCostMetrics(inSample,probs,iprobs); + pruneCandidates(); + return &minCost(); +} + + +void ViterbiR2O4::decode(const SoftVector &in, BitVector& target) +{ + ViterbiR2O4& decoder = *this; + const size_t sz = in.size(); + const unsigned oSize = in.size() / decoder.iRate(); + const unsigned deferral = decoder.deferral(); + const size_t ctsz = sz + deferral*decoder.iRate(); + assert(sz <= decoder.iRate()*target.size()); + + // Build a "history" array where each element contains the full history. + // (pat) We only use every other history element, so why are we setting them? + uint32_t history[ctsz]; + { + BitVector bits = in.sliced(); + uint32_t accum = 0; + for (size_t i=0; i0.5F) pVal = 1.0F-pVal; + float ipVal = 1.0F-pVal; + // This is a cheap approximation to an ideal cost function. + if (pVal<0.01F) pVal = 0.01; + if (ipVal<0.01F) ipVal = 0.01; + matchCostTable[i] = 0.25F/ipVal; + mismatchCostTable[i] = 0.25F/pVal; + } + + // pad end of table with unknowns + // Note that these bits should not contribute to Bit Error Count. + for (size_t i=sz; i=deferral) *op++ = (minCost->iState >> deferral)&0x01; + oCount++; + } + // Dont think minCost == NULL can happen. + mBitErrorCnt = minCost ? minCost->bitErrorCnt : 0; + } +} + +// vim: ts=4 sw=4 diff --git a/GSMShare/ViterbiR204.h b/GSMShare/ViterbiR204.h new file mode 100644 index 0000000..88eeae3 --- /dev/null +++ b/GSMShare/ViterbiR204.h @@ -0,0 +1,148 @@ +/* +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +#ifndef _VITERBIR204_H_ +#define _VITERBIR204_H_ 1 + +#include "Viterbi.h" + + +/** + Class to represent convolutional coders/decoders of rate 1/2, memory length 4. + This is the "workhorse" coder for most GSM channels. +*/ +class ViterbiR2O4 : public ViterbiBase { + + private: + /**name Lots of precomputed elements so the compiler can optimize like hell. */ + //@{ + /**@name Core values. */ + //@{ + static const unsigned mIRate = 2; ///< reciprocal of rate + static const unsigned mOrder = 4; ///< memory length of generators + //@} + /**@name Derived values. */ + //@{ + static const unsigned mIStates = 0x01 << mOrder; ///< (16) number of states, number of survivors + static const uint32_t mSMask = mIStates-1; ///< survivor mask + static const uint32_t mCMask = (mSMask<<1) | 0x01; ///< candidate mask + static const uint32_t mOMask = (0x01<. + +*/ + + + + +#include "BitVector.h" +#include "ViterbiR204.h" +#include +#include +#include +#include +#include "GSM503Tables.cpp" +#ifndef LOGVAR +#define LOGVAR(var) (" " #var "=") << var +#define LOGVAR2(name,val) " " << name << "=" << (val) +#endif + +using namespace std; + +// We must have a gConfig now to include BitVector. +//#include "Configuration.h" +//ConfigurationTable gConfig; + + +void origTest() +{ + BitVector v0("0000111100111100101011110000"); + cout << v0 << endl; + // (pat) The conversion from a string was inserting garbage into the result BitVector. + // Fixed now so only 0 or 1 are inserted, but lets check: + for (char *cp = v0.begin(); cp < v0.end(); cp++) cout << (int)*cp<<" "; + cout << endl; + + BitVector v1(v0); + v1.LSB8MSB(); + cout <encode(v1,v2); + if (debug) cout << "v2 " << v2 << endl; + int bec; + unsigned bitErrors; + + for (float badness = 0.0; badness <= 1.0; badness += 0.1) + for (int perr = 0; perr <= 50; perr += 10) { + int errsOut = 0; + int errsIn = 0; + // multiple trials smooths out the weirdnesses due to unfortunate positioning of noisy bits + for (unsigned trial = 0; trial < trials; trial++) { + SoftVector sv2(v2); + for (unsigned j = 0; j < sv2.size(); j++) { + if (random() % 100 < perr) { + // (pat) old test, this was not very good because 0.5 always maps to 1. + // sv2[j] = 0.5; + //sv2[j] = 1.0 - sv2[j]; + sv2[j] = sv2.bit(j) ? 1.0 - badness : badness; + errsIn++; + } + } + if (debug) cout << "n2 " << sv2 << endl; + BitVector v3(frameSize); + coder->decode(sv2,v3); + bec = coder->getBEC(); + if (debug) cout << "v3 " << v3 << endl; + for (unsigned j = 0; j < frameSize; j++) { + if (v1.bit(j) != v3.bit(j)) errsOut++; + } + + // Lets try re-encoding it and see what happens. + BitVector try2(resultsize); + coder->encode(v3,try2); + // The try should be equivalent to sv2? + bitErrors = 0; + for (unsigned k = 0; k < try2.size(); k++) { + if (try2[k] != sv2.bit(k)) bitErrors++; + } + } + int pbbo = 100 * errsOut / (frameSize * trials); + //cout << label << ", " << "% ambiguous bits in : % bad bits out = " << perr << " : " << pbbo <unpuncture " << label << " " << (ok ? "ok" : "NOT ok") << endl; +} + +int main(int argc, char *argv[]) +{ + struct timeval tv; + gettimeofday(&tv,NULL); + srandom(tv.tv_usec); + origTest(); + testEncodeDecode("ViterbiR204", new ViterbiR2O4(), 378, 2, 4, false); +} diff --git a/Globals/Defines.h b/Globals/Defines.h deleted file mode 100644 index ed2795d..0000000 --- a/Globals/Defines.h +++ /dev/null @@ -1,44 +0,0 @@ -// Pat added this file. -// We need an include file that is included before any other include files. -// Might I suggest that Range Networks specific global #defines be prefixed with RN_ - -#ifndef DEFINES_H -#define DEFINES_H - -// GPRS_1 turns on the SharedEncoder. It is the thing that keeps the modem from registering. -#define GPRS_ENCODER 1 // Use SharedL1Encoder and SharedL1Decoder -#define GPRS_TESTSI4 1 -#define GPRS_TEST 1 // Compile in other GPRS stuff. -#define GPRS_PAT 1 // Compile in GPRS code. Turn this off to get previous non-GRPS code, - // although I am not maintaining it so you may have to fix compile - // problems to use it. - -// __GNUG__ is true for g++ and __GNUC__ for gcc. -#if __GNUC__&0==__GNUG__ - -#define RN_UNUSED __attribute__((unused)) - -#define RN_UNUSED_PARAM(var) RN_UNUSED var - -// Pack structs onto byte boundaries. -// Note that if structs are nested, this must appear on all of them. -#define RN_PACKED __attribute__((packed)) - -#else - -// Suppress warning message about a variable or function being unused. -// In C++ you can leave out the variable name to suppress the 'unused variable' warning. -#define RN_UNUSED_PARAM(var) /*nothing*/ -#define RN_UNUSED /*not defined*/ -#define RN_PACKED /*not defined*/ -#endif - -// Bound value between min and max values. -#define RN_BOUND(value,min,max) ( (value)<(min) ? (min) : (value)>(max) ? (max) : (value) ) - -#define RN_PRETTY_TEXT(name) (" " #name "=(") << name << ")" -#define RN_PRETTY_TEXT1(name) (" " #name "=") << name -#define RN_WRITE_TEXT(name) os << RN_PRETTY_TEXT(name) -#define RN_WRITE_OPT_TEXT(name,flag) if (flag) { os << RN_WRITE_TEXT(name); } - -#endif diff --git a/Globals/Globals.cpp b/Globals/Globals.cpp index 79bc5e8..0852f95 100644 --- a/Globals/Globals.cpp +++ b/Globals/Globals.cpp @@ -2,7 +2,7 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -34,13 +34,13 @@ const char* gOpenBTSWelcome = "OpenBTS\n" "Copyright 2008, 2009, 2010 Free Software Foundation, Inc.\n" "Copyright 2010 Kestrel Signal Processing, Inc.\n" - "Copyright 2011, 2012, 2013 Range Networks, Inc.\n" + "Copyright 2011, 2012, 2013, 2014 Range Networks, Inc.\n" "Release " VERSION " " PROD_CAT " formal build date " __DATE__ " rev" SVN_REV "\n" "\"OpenBTS\" is a registered trademark of Range Networks, Inc.\n" "\nContributors:\n" " Range Networks, Inc.:\n" " David Burgess, Harvind Samra, Donald Kirker, Doug Brown,\n" - " Pat Thompson, Kurtis Heimerl\n" + " Pat Thompson, Kurtis Heimerl, Michael Iedema, Dave Gotwisner\n" " Kestrel Signal Processing, Inc.:\n" " David Burgess, Harvind Samra, Raffi Sevlian, Roshan Baliga\n" " GNU Radio:\n" @@ -49,11 +49,16 @@ const char* gOpenBTSWelcome = " Anne Kwong, Jacob Appelbaum, Joshua Lackey, Alon Levy\n" " Alexander Chemeris, Alberto Escudero-Pascual\n" "Incorporated L/GPL libraries and components:\n" - " libosip2, LGPL, 2.1 Copyright 2001-2007 Aymeric MOIZARD jack@atosc.org\n" " libortp, LGPL, 2.1 Copyright 2001 Simon MORLAT simon.morlat@linphone.org\n" " libusb, LGPL 2.1, various copyright holders, www.libusb.org\n" + " libzmq, LGPL 3:\n" + " Copyright (c) 2009-2011 250bpm s.r.o.\n" + " Copyright (c) 2011 Botond Ballo\n" + " Copyright (c) 2007-2009 iMatix Corporation\n" + "Incorporated BSD/MIT-style libraries and components:\n" " A5/1 Pedagogical Implementation, Simplified BSD License, Copyright 1998-1999 Marc Briceno, Ian Goldberg, and David Wagner\n" + " JsonBox, Copyright 2011 Anhero Inc.\n" "Incorporated public domain libraries and components:\n" " sqlite3, released to public domain 15 Sept 2001, www.sqlite.org\n" "\n" @@ -64,19 +69,34 @@ const char* gOpenBTSWelcome = "regulations and laws. See the LEGAL file in the source code for\n" "more information.\n" "\n" + "Note to US Government Users:\n" + " The OpenBTS software applications and associated documentation are \"Commercial\n" + " Item(s),\" as that term is defined at 48 C.F.R. Section 2.101, consisting of\n" + " \"Commercial Computer Software\" and \"Commercial Computer Software Documentation,\"\n" + " as such terms are used in 48 C.F.R. 12.212 or 48 C.F.R. 227.7202, as\n" + " applicable. Consistent with 48 C.F.R. 12.212 or 48 C.F.R. Sections 227.7202-1\n" + " through 227.7202-4, as applicable, the Commercial Computer Software and\n" + " Commercial Computer Software Documentation are being licensed to U.S. Government\n" + " end users (a) only as Commercial Items and (b) with only those rights as are\n" + " granted to all other end users pursuant to the terms and conditions of\n" + " Range Networks' software licenses and master customer agreement.\n" + + ; CommandLine::Parser gParser; -GSM::Z100Timer watchdog; -Mutex watchdogLock; +static Timeval watchdog; +static bool watchdogActive = false; +static Mutex watchdogLock; void gResetWatchdog() { - watchdogLock.lock(); - watchdog.set(gConfig.getNum("Control.WatchdogMinutes")*60*1000); - watchdogLock.unlock(); + ScopedLock lock(watchdogLock); + int value = gConfig.getNum("Control.WatchdogMinutes"); + if (value <= 0) { watchdogActive = false; } // The stupid timer core-dumps if you call reset with a 0 value. + else { watchdog.future(value*60*1000); } LOG(DEBUG) << "reset watchdog timer, expires in " << watchdog.remaining() / 1000 << " seconds"; } @@ -84,11 +104,11 @@ size_t gWatchdogRemaining() { // Returns remaning time in seconds. ScopedLock lock(watchdogLock); - return watchdog.remaining() / 1000; + return watchdogActive ? watchdog.remaining() / 1000 : 0; } bool gWatchdogExpired() { ScopedLock lock(watchdogLock); - return watchdog.expired(); + return watchdogActive ? watchdog.passed() : false; } diff --git a/Globals/Globals.h b/Globals/Globals.h index 048c86e..d2c0352 100644 --- a/Globals/Globals.h +++ b/Globals/Globals.h @@ -1,8 +1,7 @@ -//#define RN_DEVELOPER_MODE 1 /**@file Global system parameters. */ /* * Copyright 2008, 2009 Free Software Foundation, Inc. -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -28,11 +27,15 @@ #include #include #include -#include -#include +//#include #include #include +#ifndef RN_DEVERLOPER_MODE +#define RN_DEVERLOPER_MODE 0 +#endif + +namespace GPRS { extern unsigned GPRSDebug; } /** Date-and-time string, defined in OpenBTS.cpp. */ extern const char* gDateTime; @@ -53,14 +56,11 @@ extern const char *gVersionString; extern CommandLine::Parser gParser; /** The global TMSI table. */ -extern Control::TMSITable gTMSITable; +//extern Control::TMSITable gTMSITable; /** The physical status reporting table */ extern GSM::PhysicalStatus gPhysStatus; -/** The subscriber registry and authenticator */ -extern SubscriberRegistry gSubscriberRegistry; - /** The global transceiver interface. */ extern TransceiverManager gTRX; diff --git a/Globals/Makefile.am b/Globals/Makefile.am index 37d355d..f987b33 100644 --- a/Globals/Makefile.am +++ b/Globals/Makefile.am @@ -30,5 +30,4 @@ libglobals_la_SOURCES = Globals.cpp noinst_PROGRAMS = noinst_HEADERS = \ - Globals.h \ - Defines.h + Globals.h diff --git a/INSTALLATION b/INSTALLATION index 3e35d7e..8406235 100644 --- a/INSTALLATION +++ b/INSTALLATION @@ -12,8 +12,6 @@ To run OpenBTS, the following should be installed: Asterisk (http://www.asterisk.org), running SIP on port 5060. - libosip2 (http://www.gnu.org/software/osip/) - libortp (http://freshmeat.net/projects/ortp/) libusrp (http://gnuradio.org). diff --git a/LEGAL b/LEGAL index 9280626..561b969 100644 --- a/LEGAL +++ b/LEGAL @@ -69,5 +69,5 @@ GPL releases of OpenBTS use the following GPL components that must be licensed, removed or replaced for non-GPL distributions: readline. GPL releases of OpenBTS use the following LGPL components that must be -licensed or linked dynamically for non-GPL distributions: libosip2, liportp2. +licensed or linked dynamically for non-GPL distributions: liportp2. diff --git a/Makefile.am b/Makefile.am index 0f8fdca..b126135 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,9 +20,8 @@ include $(top_srcdir)/Makefile.common -DESTDIR := +DESTDIR := -ACLOCAL_AMFLAGS = -I config AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES) AM_CXXFLAGS = -Wall -pthread -ldl #AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread -ldl @@ -34,20 +33,21 @@ _SUBDIRS = \ sqlite3 \ CommonLibs \ Globals \ - SR \ - CLI \ SIP \ GSM \ + GSMShare \ SMS \ + Scanning \ TRXManager \ Control \ Peering \ + NodeManager \ GPRS \ SGSNGGSN \ + CLI \ apps \ doc - #if UHD or USRP1 defined, build Transceiver52M as well if UHD SUBDIRS = $(_SUBDIRS) \ diff --git a/Makefile.common b/Makefile.common index 4cb6e80..f6b688b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -24,6 +24,7 @@ AM_CXXFLAGS = -Wall -pthread -ldl $(OPENBTS_CXXFLAGS) COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs CONTROL_INCLUDEDIR = $(top_srcdir)/Control GSM_INCLUDEDIR = $(top_srcdir)/GSM +GSMSHARE_INCLUDEDIR = $(top_srcdir)/GSMShare GPRS_INCLUDEDIR = $(top_srcdir)/GPRS SGSNGGSN_INCLUDEDIR = $(top_srcdir)/SGSNGGSN SIP_INCLUDEDIR = $(top_srcdir)/SIP @@ -32,17 +33,19 @@ TRX_INCLUDEDIR = $(top_srcdir)/TRXManager GLOBALS_INCLUDEDIR = $(top_srcdir)/Globals CLI_INCLUDEDIR = $(top_srcdir)/CLI SQLITE_INCLUDEDIR = $(top_srcdir)/sqlite3 -SR_INCLUDEDIR = $(top_srcdir)/SR PEERING_INCLUDEDIR = $(top_srcdir)/Peering +NODEMANAGER_INCLUDEDIR = $(top_srcdir)/NodeManager +JSONBOX_INCLUDEDIR = $(top_srcdir)/NodeManager/JsonBox-0.4.3/include +SCANNING_INCLUDEDIR = $(top_srcdir)/Scanning -SVNDEV = -D'SVN_REV="$(shell svnversion -n $(top_builddir))"' +SVNDEV = -D'SVN_REV="$(shell svn info $(top_builddir) | grep "Last Changed Rev:" | cut -d " " -f 4) CommonLibs:rev$(shell svn info $(top_builddir)/CommonLibs | grep "Last Changed Rev:" | cut -d " " -f 4)"' STD_DEFINES_AND_INCLUDES = \ $(SVNDEV) \ - $(ACTIVEFEATURES) \ -I$(COMMON_INCLUDEDIR) \ -I$(CONTROL_INCLUDEDIR) \ -I$(GSM_INCLUDEDIR) \ + -I$(GSMSHARE_INCLUDEDIR) \ -I$(GPRS_INCLUDEDIR) \ -I$(SGSNGGSN_INCLUDEDIR) \ -I$(SIP_INCLUDEDIR) \ @@ -50,13 +53,16 @@ STD_DEFINES_AND_INCLUDES = \ -I$(TRX_INCLUDEDIR) \ -I$(GLOBALS_INCLUDEDIR) \ -I$(CLI_INCLUDEDIR) \ - -I$(SR_INCLUDEDIR) \ -I$(PEERING_INCLUDEDIR) \ + -I$(NODEMANAGER_INCLUDEDIR) \ + -I$(JSONBOX_INCLUDEDIR) \ + -I$(SCANNING_INCLUDEDIR) \ -I$(SQLITE_INCLUDEDIR) # These macros are referenced in apps/Makefile.am, which must be changed in sync with these. COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la GSM_LA = $(top_builddir)/GSM/libGSM.la +GSMSHARE_LA = $(top_builddir)/GSMShare/libGSMShare.la GPRS_LA = $(top_builddir)/GPRS/libGPRS.la SGSNGGSN_LA = $(top_builddir)/SGSNGGSN/libSGSNGGSN.la SIP_LA = $(top_builddir)/SIP/libSIP.la @@ -65,8 +71,9 @@ TRX_LA = $(top_builddir)/TRXManager/libtrxmanager.la CONTROL_LA = $(top_builddir)/Control/libcontrol.la GLOBALS_LA = $(top_builddir)/Globals/libglobals.la CLI_LA = $(top_builddir)/CLI/libcli.la -SR_LA = $(top_builddir)/SR/libSR.la PEERING_LA = $(top_builddir)/Peering/libpeering.la +NODEMANAGER_LA = $(top_builddir)/NodeManager/libnodemanager.la -lzmq +SCANNING_LA = $(top_builddir)/Scanning/libscanning.la SQLITE_LA = $(top_builddir)/sqlite3/libsqlite.la -ldl MOSTLYCLEANFILES = *~ diff --git a/NodeManager b/NodeManager new file mode 160000 index 0000000..c154368 --- /dev/null +++ b/NodeManager @@ -0,0 +1 @@ +Subproject commit c154368a55d081d7be241a14825e9a009dee093a diff --git a/Peering/NeighborTable.cpp b/Peering/NeighborTable.cpp index a3b6b84..4a8828c 100644 --- a/Peering/NeighborTable.cpp +++ b/Peering/NeighborTable.cpp @@ -26,7 +26,7 @@ using namespace std; static const char* createNeighborTable = { "CREATE TABLE IF NOT EXISTS NEIGHBOR_TABLE (" - "IPADDRESS TEXT UNIQUE NOT NULL, " // IP address of peer BTS + "IPADDRESS TEXT UNIQUE NOT NULL, " // IP address of peer BTS (pat) it includes the port. "UPDATED INTEGER DEFAULT 0, " // timestamp of last update "HOLDOFF INTEGER DEFAULT 0, " // hold off until after this time "C0 INTEGER DEFAULT NULL, " // peer BTS C0 ARFCN @@ -61,10 +61,23 @@ void NeighborTable::NeighborTableInit(const char* wPath) } +// Does the ipaddr include a port specification? +// This will fail miserably with ipv6. +bool includesPort(const char *ipaddr) +{ + const char *colon = strrchr(ipaddr,':'); + const char *dot = strrchr(ipaddr,'.'); + return colon && colon > dot; // Works if dot is NULL too. +} + +// (pat) 5-25: Allow the config neighbor list to optionally include a port. +// (pat) Neighbor discovery should be moved into SR. Each BTS should register, +// then when they want to look up a LAC reported by the MS they could just ask SR. void NeighborTable::fill() { mConfigured.clear(); + if (mDB == NULL) { return; } // we already threw an ALERT. // Stuff the neighbor ip addresses into the table without any other info. // Let existing information persist for current neighbors. // NeighborTable::refresh() will get updated infomation when it's available. @@ -73,9 +86,21 @@ void NeighborTable::fill() unsigned short port = gConfig.getNum("Peering.Port"); for (unsigned int i = 0; i < neighbors.size(); i++) { struct sockaddr_in address; + // (pat) The measurement report allows only 32 ARFCN slots, the last of which we reserve for 3G to avoid confusion. + // However, we cannot check that here, because some of the neighbors may share ARFCNs, and we cannot tell + // that until they report in. + // Do not check this here: if (i == 31) ... const char *host = neighbors[i].c_str(); LOG(DEBUG) << "resolving host name for " << host; - if (!resolveAddress(&address, host, port)) { + bool validAddr; + if (includesPort(host)) { + LOG(DEBUG) << "resolving host name for " << host; + validAddr = resolveAddress(&address, host); + } else { + LOG(DEBUG) << "resolving host name for " << host <<" + port:" <sin_port) << " in neighbor table"; char query[200]; - unsigned int dummy; // C0 is arbitrary integer column. just want to know if ipaddress is in table. + unsigned oldC0; // C0 is arbitrary integer column. just want to know if ipaddress is in table. + unsigned oldBSIC; sprintf(query, "%s:%d", addrString,(int)ntohs(address->sin_port)); - if (!sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", query, "C0", dummy)) { + if (!sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", query, "C0", oldC0) || + !sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", query, "BSIC", oldBSIC)) { LOG(NOTICE) << "Ignoring unsolicited 'RSP NEIGHBOR_PARAMS' from " << query; return false; } + LOG(DEBUG) << "updating " << addrString << ":" << ntohs(address->sin_port) << " in neighbor table" < NeighborTable::getARFCNs() const { char query[500]; vector bcchChannelList; - sprintf(query,"SELECT C0 FROM NEIGHBOR_TABLE WHERE BSIC > -1 ORDER BY UPDATED DESC LIMIT %u", gConfig.getNum("GSM.Neighbors.NumToSend")); + sprintf(query,"SELECT C0 FROM NEIGHBOR_TABLE WHERE BSIC > -1 ORDER BY UPDATED DESC LIMIT %lu", gConfig.getNum("GSM.Neighbors.NumToSend")); sqlite3_stmt *stmt; int prc = sqlite3_prepare_statement(mDB,&stmt,query); if (prc) { diff --git a/Peering/NeighborTable.h b/Peering/NeighborTable.h index 3520a95..71842f4 100644 --- a/Peering/NeighborTable.h +++ b/Peering/NeighborTable.h @@ -58,7 +58,7 @@ class NeighborTable { unsigned paramAge(const char* address); /** Returns a C-string that must be free'd by the caller. */ - char* getAddress(unsigned BCCH_FREQ_NCELL, unsigned BSIC); + string getAddress(unsigned BCCH_FREQ_NCELL, unsigned BSIC); /** Return the ARFCN given its position in the BCCH channel list (GSM 04.08 10.5.2.20). */ int getARFCN(unsigned BCCH_FREQ_NCELL); diff --git a/Peering/Peering.cpp b/Peering/Peering.cpp index 338d65d..6c4f6b5 100644 --- a/Peering/Peering.cpp +++ b/Peering/Peering.cpp @@ -1,6 +1,6 @@ /**@file Messages for peer-to-peer protocol */ /* - * Copright 2011 Range Networks, Inc. + * Copright 2011, 2014 Range Networks, Inc. * All rights reserved. */ @@ -13,13 +13,50 @@ #include #include #include -#include +#include +//#include #undef WARNING -using namespace Peering; +namespace Peering { +using namespace Control; +// Original Diagram courtesy Doug: +// MS BS1 BS2 switch +// +// A ------------- REQ HANDOVER -----------> a +// (BS1tranid, L3TI, IMSI, called/caller, SIP REFER message) +// C <------------ RSP HANDOVER ------------ a +// (BS1tranid, cause, L3HandoverCommand) +// <------------- L3HandoverCommand ---------- D +// -------------------------------- handover access -----------------------------------> b +// <------------------------------- physical information ------------------------------- c +// -------------------------------- handover complete ---------------------------------> d +// e ----- re-INVITE ------> +// f <-------- OK ---------- +// g --------- ACK --------> +// E <------- IND HANDOVER_COMPLETE -------- h (Note: These were reversed in +// (BS2tranid) +// E -------- ACK HANDOVER_COMPLETE -------> h original diagram) +// (BS2tranid) +// +// +// +// A = handover determination ac = stuff transaction entry +// We could use a REFER for this message, with a content including both SDP and MSC params. +// B = build string for handover determination ah = transaction entry definition +// BH = SIPEngine.h +// A = processHandoverRequest in BS2 +// If resources are available: +// calls TranEntry::newHandover(peer,horef,params,chan,oldTransID) to squirrel away: +// IMSI, either called (for MOC) or calling (for MTC) number, L3TI and the SIP REFER message. +// Note that everything but L3TI is also available in the SIP REFER message. +// C = processHandoverResponse in BS1 +// d when we receive the handover complete the InboundHandoverMachine calls newSipDialogHandover, which sends the re-INVITE. +// then when the dialog becomas active, we send the IND HANDOVER_COMPLETE. +// E = processHandoverComplete in BS1 + void PeerMessageFIFOMap::addFIFO(unsigned transactionID) @@ -94,7 +131,7 @@ void* PeerInterface::serviceLoop1(void*) sleep(8); while (1) { gNeighborTable.refresh(); - sleep(1); + sleep(1); // (pat) Every second? Give me a break. } return NULL; } @@ -129,6 +166,9 @@ void PeerInterface::drive() void PeerInterface::process(const struct sockaddr_in* peer, const char* message) { + LOG(DEBUG) << message; + // The REQ HANDOVER is only watched if it is the first. + if (strstr(message,"HANDOVER") && ! strstr(message,"REQ HANDOVER")) WATCH(Utils::timestr() << " Peering recv:"< mSuppressedAlerts; + map::iterator it = mSuppressedAlerts.find(message); + if (it == mSuppressedAlerts.end()) { + LOG(ALERT) << message; + mSuppressedAlerts[message] = time(NULL); + } else { + time_t when = it->second; + if ((signed)(time(NULL) - when) > (signed)alertRepeatTime) { + // One ALERT every five minutes. The rest are demoted to ERR. + LOG(ALERT) << message; + mSuppressedAlerts[message] = time(NULL); + } else { + LOG(ERR) << message; + } + } +} +// pats TODO: We should check for conflicts when we start up. +// pats TODO: Check for more than 31 ARFCNs and report. +// pats TODO: We should check for ARFCN+BSIC conflicts among the neighbors. That implies sucking the whole neighbor table in, +// but dont worry about now it because the whole thing should move to SR imho. void PeerInterface::processNeighborParams(const struct sockaddr_in* peer, const char* message) { - static const char rspFormat[] = "RSP NEIGHBOR_PARAMS %d %d"; + static const char rspFormat[] = "RSP NEIGHBOR_PARAMS %u %u"; LOG(DEBUG) << "got message " << message; if (0 == strncmp(message,"REQ ",4)) { @@ -174,27 +239,42 @@ void PeerInterface::processNeighborParams(const struct sockaddr_in* peer, const if (0 == strncmp(message,"RSP ",4)) { // RSP? Digest it. - unsigned C0, BSIC; - int r = sscanf(message, rspFormat, &C0, &BSIC); + // (pat) ARFCN-C0 (the one carrying BCCH) and BSIC consisting of NCC (Network Color Code) and BCC. (Base station Color Code) + unsigned neighborC0, neighborBSIC; + int r = sscanf(message, rspFormat, &neighborC0, &neighborBSIC); if (r!=2) { - LOG(ALERT) << "badly formatted peering message: " << message; + logAlert(format("badly formatted peering message: %s",message)); return; } // Did the neighbor list change? - bool change = gNeighborTable.addInfo(peer,(unsigned)time(NULL),C0,BSIC); + bool change = gNeighborTable.addInfo(peer,(unsigned)time(NULL),neighborC0,neighborBSIC); // no change includes unsolicited RSP NEIGHBOR_PARAMS. drop it. if (!change) return; // It there a BCC conflict? - int ourBSIC = gBTS.BSIC(); - int BCC = BSIC & 0x07; - int ourBCC = ourBSIC & 0x07; - if (BCC == ourBCC) { LOG(ALERT) << "neighbor with matching BCC " << ourBCC; } + unsigned ourBSIC = gBTS.BSIC(); + //int neighborBCC = neighborBSIC & 0x07; + //int ourBCC = ourBSIC & 0x07; + // (pat) 5-2013: This message was incorrect, because the BTS uniquifying information is not just the BCC, + // it is the full C0+BSIC, so fixed it. + // Note that this message also comes out over and over again. + // if (BCC == ourBCC) { LOG(ALERT) << "neighbor with matching BCC " << ourBCC; } + if (neighborC0 == gTRX.C0()) { + if (neighborBSIC == ourBSIC) { + logAlert(format("neighbor with matching ARFCN.C0 + BSIC [Base Station Identifier] codes: C0=%u BSIC=%u",neighborC0,neighborBSIC)); + } else { + // (pat) This seems like a bad idea too, with two BTS on the same ARFCN close enough to be neighbors, + // but I dont know if it is worth an ALERT? + LOG(WARNING) << format("neighbor with matching ARFCN.C0 but different BSIC [Base Station Identifier] code: C0=%u, BSIC=%u, my BSIC=%u",neighborC0,neighborBSIC,gTRX.C0()); + } + } // Is there an NCC conflict? - int NCC = BSIC >> 3; - int NCCMaskBit = 1 << NCC; + int neighborNCC = neighborBSIC >> 3; + int NCCMaskBit = 1 << neighborNCC; int ourNCCMask = gConfig.getNum("GSM.CellSelection.NCCsPermitted"); + ourNCCMask |= 1 << gConfig.getNum("GSM.Identity.BSIC.NCC"); if ((NCCMaskBit & ourNCCMask) == 0) { - LOG(ALERT) << "neighbor with NCC " << NCC << " not in NCCsPermitted"; + //LOG(ALERT) << "neighbor with NCC " << neighborNCC << " not in NCCsPermitted"; + logAlert(format("neighbor with NCC=%u not in NCCsPermitted",neighborNCC)); } // There was a change, so regenerate the beacon gBTS.regenerateBeacon(); @@ -219,37 +299,35 @@ void PeerInterface::sendNeighborParamsRequest(const struct sockaddr_in* peer) } +// (pat) This is BS2 which has received a request from BS1 to transfer the MS from BS1 to BS2. +// Manufacture a TransactionEntry and SIPEngine from the peering message. void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const char* message) { // This is "Handover Request" in the ladder diagram; we are "BS2" accepting it. assert(message); - unsigned oldTransID; + + unsigned oldTransID; // (pat) tran on BS1 that wants to come to BS2. if (!sscanf(message,"REQ HANDOVER %u ", &oldTransID)) { LOG(ALERT) << "cannot parse peering message " << message; return; } - LOG(DEBUG) << message; // Break message into space-delimited tokens, stuff into a SimpleKeyValue and then unpack it. SimpleKeyValue params; params.addItems(message); const char* IMSI = params.get("IMSI"); GSM::L3MobileIdentity mobileID = GSM::L3MobileIdentity(IMSI); - //const char *callID = params.get("CallID"); - const char *proxy = params.get("Proxy"); // find existing transaction record if this is a duplicate REQ HANDOVER - Control::TransactionEntry* transaction = gTransactionTable.find(mobileID, oldTransID); + Control::TranEntry* transaction = gNewTransactionTable.ttFindHandoverOther(mobileID, oldTransID); // and the channel that goes with it - GSM::LogicalChannel* chan = NULL; - if (transaction) { - chan = transaction->channel(); - LOG(DEBUG) << *transaction; - } + GSM::L2LogicalChannel* chan = NULL; + unsigned horef; // if this is the first REQ HANDOVER if (!transaction) { + WATCH(Utils::timestr() << " Peering recv:"<SACCH()->debugGetL1()->decoder()->debug3101remaining() < 1000) { LOG(NOTICE) << "handover TCH allocation took too long; risk of T3101 timeout; trying again"; + chan->l2sendp(HARDRELEASE); // (pat) added 9-6-2013 return; } // If there's no channel available, send failure. if (!chan) { - LOG(CRIT) << "congestion"; + LOG(CRIT) << "congestion, incoming handover request denied"; char rsp[50]; // RR Cause 101 "cell allocation not available" // GSM 04.08 10.5.2.31 @@ -280,32 +359,57 @@ void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const return; } - // build a new transaction record - transaction = new Control::TransactionEntry(peer,mReferenceCounter++,params,proxy,chan,oldTransID); - mFIFOMap.addFIFO(transaction->ID()); - gTransactionTable.add(transaction); + // build a new transaction record. + // Allocate a new inbound handover reference. It is placed in the L3HandoverCommand and then used + // in Layer1 as a really cheap validation on an inbound handover access + // to make sure the incoming handset is the one we want. + horef = 0xff & (++mReferenceCounter); + transaction = Control::TranEntry::newHandover(peer,horef,params,chan,oldTransID); + mFIFOMap.addFIFO(transaction->tranID()); LOG(INFO) "creating new transaction " << *transaction; // Set the channel state. - chan->handoverPending(true); + // This starts T3103. + chan->handoverPending(true,horef); + } else { + chan = transaction->getL2Channel(); + horef = transaction->getHandoverEntry(true)->mInboundReference; + LOG(DEBUG) << *transaction; } // Send accept. + // FIXME TODO_NOW: Get rid of this sleepFrames... + sleepFrames(30); // Pat added delay to let SACCH get started. + const GSM::L3ChannelDescription desc = chan->channelDescription(); +#if 1 + // Build the L3 HandoverCommand that BS1 will send to the phone to tell it to come to us, BS2. + L3HandoverCommand handoverMsg( + GSM::L3CellDescription(gTRX.C0(),gBTS.NCC(),gBTS.BCC()), + GSM::L3ChannelDescription2(desc), + GSM::L3HandoverReference(horef), + GSM::L3PowerCommandAndAccessType(), + GSM::L3SynchronizationIndication(true, true)); + + L3Frame handoverFrame(handoverMsg); + string handoverHex = handoverFrame.hexstr(); + char rsp[50]; + sprintf(rsp,"RSP HANDOVER %u 0 0x%s",oldTransID,handoverHex.c_str()); +#else char rsp[50]; sprintf(rsp,"RSP HANDOVER %u 0 %u %u %u %u %u %u %u %u", oldTransID, - transaction->inboundReference(), + horef, gTRX.C0(), gBTS.NCC(), gBTS.BCC(), desc.typeAndOffset(), desc.TN(), desc.TSC(), desc.ARFCN() ); +#endif sendMessage(peer,rsp); return; } void PeerInterface::processHandoverComplete(const struct sockaddr_in* peer, const char* message) { - // This is "IND HANDOVER" in the ladder diagram; we are "BS1" receiving it. unsigned transactionID; if (!sscanf(message,"IND HANDOVER_COMPLETE %u", &transactionID)) { @@ -333,7 +437,6 @@ void PeerInterface::processHandoverComplete(const struct sockaddr_in* peer, cons void PeerInterface::processHandoverFailure(const struct sockaddr_in* peer, const char* message) { - // This indication means that inbound handover processing failed // on the other BTS. This BTS cannot handover this call right now. unsigned transactionID; @@ -363,11 +466,39 @@ void PeerInterface::processHandoverFailure(const struct sockaddr_in* peer, const - +// (pat) BS1 receives this message from BS2 to allow transferring an MS to BS2. +// We set a state in the transaction that causes the serviceloop to send an L3HandoverCommand to the MS. void PeerInterface::processHandoverResponse(const struct sockaddr_in* peer, const char* message) { unsigned cause; unsigned transactionID; + LOG(DEBUG) <getHandoverEntry(true); + hop->mHexEncodedL3HandoverCommand = string(handoverCommandBuffer); + transaction->setGSMState(CCState::HandoverOutbound); +#else unsigned reference; unsigned C0, NCC, BCC; unsigned typeAndOffset, TN, TSC, ARFCN; @@ -386,7 +517,7 @@ void PeerInterface::processHandoverResponse(const struct sockaddr_in* peer, cons return; } - Control::TransactionEntry *transaction = gTransactionTable.find(transactionID); + Control::TranEntry *transaction = gNewTransactionTable.ttFindById(transactionID); if (!transaction) { LOG(NOTICE) << "received handover response with no matching transaction " << transactionID; return; @@ -402,6 +533,7 @@ void PeerInterface::processHandoverResponse(const struct sockaddr_in* peer, cons GSM::L3PowerCommandAndAccessType(), GSM::L3SynchronizationIndication(true, true) ); +#endif } @@ -410,32 +542,40 @@ void PeerInterface::processHandoverResponse(const struct sockaddr_in* peer, cons void PeerInterface::sendMessage(const struct sockaddr_in* peer, const char *message) { LOG(DEBUG) << "sending message: " << message; + const char *eol = strchr(message,'\n'); + if (!strstr(message,"REQ NEI")) { + WATCH(Utils::timestr() << " Peering send:"<inboundPeer(); + const struct sockaddr_in* peer = &hop->mInboundPeer; char *ack = NULL; unsigned timeout = gConfig.getNum("Peering.ResendTimeout"); unsigned count = gConfig.getNum("Peering.ResendCount"); while (!ack && count>0) { sendMessage(peer,message); - ack = mFIFOMap.readFIFO(transaction->ID(),timeout); + ack = mFIFOMap.readFIFO(hop->tranID(),timeout); count--; } // Timed out? if (!ack) { - LOG(ALERT) << "lost contact with peer: " << *transaction; + LOG(ALERT) << "lost contact with peer: ";// TODO: << *hop; return false; } LOG(DEBUG) << "ack message: " << ack; // FIXME -- Check to be sure it's the right host acking the right message. // See #832. + // (pat) Any such above problems will go away when we switch peering to the sip interface. if (strncmp(ack,"ACK ",4)==0) { free(ack); return true; @@ -447,5 +587,32 @@ bool PeerInterface::sendUntilAck(const Control::TransactionEntry* transaction, c return false; } +// This is sent by BS2. +void PeerInterface::sendHandoverComplete(const Control::HandoverEntry* hop) +{ + char ind[100]; + sprintf(ind,"IND HANDOVER_COMPLETE %u", hop->tranID()); + gPeerInterface.sendUntilAck(hop,ind); +} +void PeerInterface::sendHandoverFailure(const Control::HandoverEntry *hop, GSM::RRCause cause,unsigned holdoff) +{ + char ind[100]; + sprintf(ind,"IND HANDOVER_FAILURE %u %u %u", hop->tranID(),cause,holdoff); + gPeerInterface.sendUntilAck(hop,ind); +} +bool PeerInterface::sendHandoverRequest(string peer, const RefCntPointer tran) +{ + string msg = string("REQ HANDOVER ") + tran->handoverString(peer); + struct sockaddr_in peerAddr; + if (!resolveAddress(&peerAddr,peer.c_str())) { + LOG(ALERT) << "cannot resolve peer address " << peer; + return false; + } + LOG(DEBUG) < #include #include +#include +//#include +#include namespace Control { -class TransactionEntry; -} +class TranEntry; +class HandoverEntry; +}; @@ -78,6 +82,21 @@ class PeerInterface { Thread mServer1; Thread mServer2; + /** + Send a message repeatedly until the ACK arrives. + @param transaction Carries the peer address and transaction ID. + @param message The IND message to send. + @erturn true on ack, false on timeout + */ + // (pat) Made this private and put the methods that use it in Peering.cpp + bool sendUntilAck(const Control::HandoverEntry*, const char* message); + /** + Send a message on the peering interface. + @param IP The IP address of the remote peer. + @param The message to send. + */ + void sendMessage(const struct ::sockaddr_in* peer, const char* message); + public: /** Initialize the interface. */ @@ -92,26 +111,10 @@ class PeerInterface { void sendNeighborParamsRequest(const struct ::sockaddr_in* peer); - /** - Send a message on the peering interface. - @param IP The IP address of the remote peer. - @param The message to send. - */ - void sendMessage(const struct ::sockaddr_in* peer, const char* message); - - /** - Send a message repeatedly until the ACK arrives. - @param transaction Carries the peer address and transaction ID. - @param message The IND message to send. - @erturn true on ack, false on timeout - */ - bool sendUntilAck(const Control::TransactionEntry*, const char* message); - /** Remove a FIFO with the given transaction ID. */ void removeFIFO(unsigned transactionID) { mFIFOMap.removeFIFO(transactionID); } - private: void drive(); @@ -145,12 +148,23 @@ class PeerInterface { /** Process IND HANDOVER_FAILURE */ void processHandoverFailure(const struct sockaddr_in* peer, const char* message); + public: + + /** Send IND HANDOVER_COMPLETE */ + void sendHandoverComplete(const Control::HandoverEntry* hop); + + /** Send IND HANDOVER_FAILURE */ + void sendHandoverFailure(const Control::HandoverEntry *hop,GSM::RRCause cause,unsigned holdoff); + + /** Send REQ HANDOVER */ + bool sendHandoverRequest(string peer, const RefCntPointer tran); + //@} }; -} //namespace +}; //namespace extern Peering::PeerInterface gPeerInterface; diff --git a/SGSNGGSN/GPRSL3Messages.cpp b/SGSNGGSN/GPRSL3Messages.cpp index 96ea870..a3653d7 100644 --- a/SGSNGGSN/GPRSL3Messages.cpp +++ b/SGSNGGSN/GPRSL3Messages.cpp @@ -1182,7 +1182,7 @@ void L3GmmMsgAuthentication::gmmWriteBody(ByteVector &msg) void L3GmmMsgAuthenticationResponse::gmmParseBody(L3GmmFrame &src, size_t &rp) { // 9.4.10 of 24.008 - unsigned char ACrefnum = src.readByte(rp); // ignore for now + //unsigned char ACrefnum = src.readByte(rp); // ignore for now ByteVector SRES(4); // optional ieis: diff --git a/SGSNGGSN/Ggsn.cpp b/SGSNGGSN/Ggsn.cpp index 9214f54..aa3d8dd 100644 --- a/SGSNGGSN/Ggsn.cpp +++ b/SGSNGGSN/Ggsn.cpp @@ -171,7 +171,7 @@ void addShellRequest(const char *wCmd,const char *arg1) void *miniGgsnShellServiceLoop(void *arg) { Ggsn *ggsn = (Ggsn*)arg; - std::string shname = gConfig.getStr("GGSN.ShellScript"); + std::string shname = gConfig.getStr(SQL_SHELLSCRIPT); while (ggsn->active()) { ShellRequest *req = ggsn->mShellQ.read(ggsn->mStopTimeout); if (! req) continue; @@ -189,7 +189,7 @@ bool Ggsn::start() if (!miniggsn_init()) { return false; } gGgsn.mGgsnRecvThread.start(miniGgsnReadServiceLoop,&gGgsn); gGgsn.mGgsnSendThread.start(miniGgsnWriteServiceLoop,&gGgsn); - if (gConfig.getStr("GGSN.ShellScript").size() > 1) { + if (gConfig.getStr(SQL_SHELLSCRIPT).size() > 1) { gGgsn.mGgsnShellThread.start(miniGgsnShellServiceLoop,&gGgsn); gGgsn.mShellThreadActive = true; } diff --git a/SGSNGGSN/Sgsn.cpp b/SGSNGGSN/Sgsn.cpp index 2c7f011..f44bd38 100644 --- a/SGSNGGSN/Sgsn.cpp +++ b/SGSNGGSN/Sgsn.cpp @@ -15,11 +15,13 @@ */ #include +#if RN_UMTS #include #include #include #include -#include +using namespace SIP; +#endif //#include "RList.h" #include "LLC.h" //#include "MSInfo.h" @@ -34,7 +36,6 @@ using namespace Utils; #define CASENAME(x) case x: return #x; #define SRB3 3 -using namespace SIP; namespace SGSN { typedef std::list SgsnInfoList_t; @@ -53,13 +54,13 @@ static int getNMO(); bool sgsnDebug() { - return gConfig.getBool("SGSN.Debug") || gConfig.getBool("GPRS.Debug"); + return (gConfig.getBool("SGSN.Debug") || gConfig.getBool("GPRS.Debug")); } bool enableMultislot() { - return gConfig.getNum("GPRS.Multislot.Max.Downlink") > 1 || - gConfig.getNum("GPRS.Multislot.Max.Uplink") > 1; + return gConfig.getNum(SQL_MULTISLOTMAXDOWNLINK) > 1 || + gConfig.getNum(SQL_MULTISLOTMAXUPLINK) > 1; } const char *GmmCause::name(unsigned mt, bool ornull) @@ -557,6 +558,7 @@ static void sendAuthenticationRequest(SgsnInfo *si, string IMSI) static void handleAuthenticationResponse(SgsnInfo *si, L3GmmMsgAuthenticationResponse &armsg) { +#if RN_UMTS if (Sgsn::isUmts()) { GmmInfo *gmm = si->getGmm(); if (!gmm) { @@ -590,10 +592,9 @@ static void handleAuthenticationResponse(SgsnInfo *si, L3GmmMsgAuthenticationRes //sendAuthenticationRequest(si); } -#if RN_UMTS SgsnAdapter::startIntegrityProtection(si->mMsHandle,Kcs); -#endif } +#endif } static void handleIdentityResponse(SgsnInfo *si, L3GmmMsgIdentityResponse &irmsg) diff --git a/SGSNGGSN/Sgsn.h b/SGSNGGSN/Sgsn.h index c05a47a..74d0426 100644 --- a/SGSNGGSN/Sgsn.h +++ b/SGSNGGSN/Sgsn.h @@ -36,6 +36,9 @@ namespace SGSN { +#define SQL_MULTISLOTMAXDOWNLINK "GPRS.Multislot.Max.Downlink" +#define SQL_MULTISLOTMAXUPLINK "GPRS.Multislot.Max.Uplink" +#define SQL_SHELLSCRIPT "GGSN.ShellScript" extern bool enableMultislot(); extern void sendImplicitlyDetached(SgsnInfo *si); diff --git a/SGSNGGSN/SgsnCli.cpp b/SGSNGGSN/SgsnCli.cpp index 42f863f..ae8a908 100644 --- a/SGSNGGSN/SgsnCli.cpp +++ b/SGSNGGSN/SgsnCli.cpp @@ -16,8 +16,9 @@ #include #include "Sgsn.h" -#include "Utils.h" -#include "Globals.h" +#include +#include +#include using namespace Utils; struct CliError { @@ -141,9 +142,9 @@ static void sgsnCliHelp(int argc, char **argv, int argi, ostream&os) // For now, just do a list. -int sgsnCLI(int argc, char **argv, std::ostream &os) +CommandLine::CLIStatus sgsnCLI(int argc, char **argv, std::ostream &os) { - if (argc <= 1) { sgsnCliHelp(0,0,0,os); return 0; } + if (argc <= 1) { sgsnCliHelp(0,0,0,os); return CommandLine::SUCCESS; } int argi = 1; // The number of arguments consumed so far; argv[0] was "sgsn" char *subcmd = argv[argi++]; @@ -154,14 +155,14 @@ int sgsnCLI(int argc, char **argv, std::ostream &os) gscp->subcmd(argc,argv,argi,os); } catch (CliError &e) { os << "sgsn:"< 254) { - MGERROR("GGSN.MS.IP.MaxCount specifies too many connections (%d) specifed, using 254", - ggConfig.mgMaxConnections); + MGERROR("%s specifies too many connections (%d) specifed, using 254", + SQL_PDP_MAX_COUNT,ggConfig.mgMaxConnections); ggConfig.mgMaxConnections = 254; } @@ -516,47 +529,48 @@ bool miniggsn_init() // All three can be derived from the ipRoute, if specfied. // But conceivably the user might want to start their base ip address elsewhere. - const char *ip_base_str = gConfig.getStr("GGSN.MS.IP.Base").c_str(); + const char *ip_base_str = gConfig.getStr(SQL_IP_BASE).c_str(); uint32_t mgIpBasenl = inet_addr(ip_base_str); if (mgIpBasenl == INADDR_NONE) { - MGERROR("miniggn: GGSN.MS.IP.Base address invalid:%s",ip_base_str); + MGERROR("miniggsn: %s address invalid:%s",SQL_IP_BASE,ip_base_str); return false; } if ((ntohl(mgIpBasenl) & 0xff) == 0) { - MGERROR("miniggn: GGSN.MS.IP.Base address should not end in .0 but proceeding anyway: %s",ip_base_str); + MGERROR("miniggsn: %s address should not end in .0 but proceeding anyway: %s",SQL_IP_BASE,ip_base_str); } + //const char *route_str = DEFAULT_IP_ROUTE; const char *route_str = 0; char route_buf[40]; string route_save; - if (gConfig.defines("GGSN.MS.IP.Route")) { - route_save = gConfig.getStr("GGSN.MS.IP.Route"); + if (gConfig.defines(SQL_IP_ROUTE)) { + route_save = gConfig.getStr(SQL_IP_ROUTE); route_str = route_save.c_str(); } uint32_t route_basenl, route_masknl; if (route_str && *route_str && *route_str != ' ') { if (strlen(route_str) > strlen("aaa.bbb.ccc.ddd/yy") + 2) { // add some slop. - MGWARN("miniggn: GGSN.MS.IP.Route address is too long:%s",route_str); + MGWARN("miniggsn: %s address is too long:%s",SQL_IP_ROUTE,route_str); // but use it anyway. } if (! ip_addr_crack(route_str,&route_basenl,&route_masknl) || route_basenl == INADDR_NONE) { - MGWARN("miniggsn: GGSN.MS.IP.Route is not a valid ip address: %s",route_str); + MGWARN("miniggsn: %s is not a valid ip address: %s",SQL_IP_ROUTE,route_str); // but use it anyway. } if (route_masknl == 0) { - MGWARN("miniggsn: GGSN.MS.IP.Route is not a valid route, /mask part missing or invalid: %sn", - route_str); + MGWARN("miniggsn: %s is not a valid route, /mask part missing or invalid: %sn", + SQL_IP_ROUTE,route_str); // but use it anyway. } // We would like to check that the base ip is within the ip route range, // which is tricky, but check the most common case: if ((route_basenl&route_masknl) != (mgIpBasenl&route_masknl)) { - MGWARN("miniggsn: GGSN.MS.IP.Base = %s ip address does not appear to be in range of GGSN.MS.IP.Route = %s", - ip_base_str, route_str); + MGWARN("miniggsn: %s = %s ip address does not appear to be in range of %s = %s", + SQL_IP_BASE, ip_base_str, SQL_IP_ROUTE,route_str); // but use it anyway. } } else { @@ -570,7 +584,7 @@ bool miniggsn_init() // Firewall rules: bool firewall_enable; - if ((firewall_enable = gConfig.getNum("GGSN.Firewall.Enable"))) { + if ((firewall_enable = gConfig.getNum(SQL_FIREWALL_ENABLE))) { // Block anything in the routed range: addFirewallRule(route_basenl,route_masknl); // Block local loopback: @@ -601,13 +615,13 @@ bool miniggsn_init() } MGINFO("GGSN Configuration:"); - MGINFO(" GGSN.MS.IP.Base=%s", ip_ntoa(mgIpBasenl,NULL)); - MGINFO(" GGSN.MS.IP.MaxCount=%d", ggConfig.mgMaxConnections); - MGINFO(" GGSN.MS.IP.Route=%s", route_str); - MGINFO(" GGSN.IP.MaxPacketSize=%d", ggConfig.mgMaxPduSize); - MGINFO(" GGSN.IP.ReuseTimeout=%d", ggConfig.mgIpTimeout); - MGINFO(" GGSN.Firewall.Enable=%d", firewall_enable); - MGINFO(" GGSN.IP.TossDuplicatePackets=%d", ggConfig.mgIpTossDup); + MGINFO(" %s=%s", SQL_IP_BASE, ip_ntoa(mgIpBasenl,NULL)); + MGINFO(" %s=%d", SQL_PDP_MAX_COUNT, ggConfig.mgMaxConnections); + MGINFO(" %s=%s", SQL_IP_ROUTE, route_str); + MGINFO(" %s=%d", SQL_PDU_MAX_SIZE, ggConfig.mgMaxPduSize); + MGINFO(" %s=%d", SQL_IP_TIMEOUT, ggConfig.mgIpTimeout); + MGINFO(" %s=%d", SQL_FIREWALL_ENABLE, firewall_enable); + MGINFO(" %s=%d", SQL_IP_TOSS_DUP, ggConfig.mgIpTossDup); if (firewall_enable) { MGINFO("GGSN Firewall Rules:"); for (GgsnFirewallRule *rp = gFirewallRules; rp; rp = rp->next) { @@ -618,7 +632,7 @@ bool miniggsn_init() uint32_t dns[2]; // We dont use the result, we just want to print out the DNS servers now. ip_finddns(dns); // The dns servers are polled again later. - const char *tun_if_name = gConfig.getStr("GGSN.TunName").c_str(); + const char *tun_if_name = gConfig.getStr(SQL_TUN_IF_NAME).c_str(); if (tun_fd == -1) { ip_init(); diff --git a/SIP/Makefile.am b/SIP/Makefile.am index 0ce482f..1ebb1f0 100644 --- a/SIP/Makefile.am +++ b/SIP/Makefile.am @@ -21,20 +21,26 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) \ - -I$(OSIP_INCLUDEDIR) $(OSIP_CPPFLAGS) \ -I$(ORTP_INCLUDEDIR) $(ORTP_CPPFLAGS) AM_CXXFLAGS = -Wall -Wextra noinst_LTLIBRARIES = libSIP.la libSIP_la_SOURCES = \ - SIPEngine.cpp \ - SIPInterface.cpp \ + SIPParse.cpp \ SIPMessage.cpp \ - SIPUtility.cpp + SIPDialog.cpp \ + SIP2Interface.cpp \ + SIPBase.cpp \ + SIPUtility.cpp \ + SIPTransaction.cpp noinst_HEADERS = \ - SIPEngine.h \ - SIPInterface.h \ + SIPParse.h \ + SIPBase.h \ + SIPDialog.h \ + SIPTransaction.h \ + SIP2Interface.h \ SIPMessage.h \ - SIPUtility.h + SIPUtility.h \ + SIPExport.h diff --git a/SIP/SIP2Interface.cpp b/SIP/SIP2Interface.cpp new file mode 100644 index 0000000..38dc056 --- /dev/null +++ b/SIP/SIP2Interface.cpp @@ -0,0 +1,755 @@ +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2011, 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 distribuion. +* +* 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. + +*/ + + +// Comments by pat: +// Documents: RFC-3261: The SIP bible. +// 3GPP-24-228: ladder diagrams for SIP flows. +// 3GPP-24-229: sec 7 has timer values, extensions to SIP headers for authentication, etc. +// SIP = Session Initiation Procotol +// UAC = User Agent Client. +// UAS = User Agent Server. +// TU = Transaction User. +// Transaction: a request and all related responses. +// Dialog: "A peer-to-peer SIP relationship between two user agents that persists for some time". Established by INVITE. +// Session: "A collection of participants and the media streams between them" Established by INVITE, using SDP. +// The idea is that a dialog may inolve multiple transactions to do in-session modifications like call-hold or transfer. +// RFS-3261 sec 5: The SIP layer protocols are: 1. syntax and encoding; 2. Transport; 3. Transaction; 4. Transaction User (TU) +// Transport Layer: +// The Transport Layer is responsible for selecting TCP/UDP/other and handling via requests, which alter where +// the responses would go. sec 18.2.1: TransportLayer is supposed to filter messages and silently throw away invalid ones. +// TransportLayer is supposed to be able to auto-switch between TCP & UDP, but that may not apply to us +// because our messages are always tiny - voice data packets. +// TransportLayer is supposed to process the Via field to provide redirection. +// Transaction Layer: +// A Transaction = a request along with all responses to that request. +// TransactionLayer matches responses to requests (SIPInterface.cpp) handles retransmissions and timeouts (SIPEngine) +// TransactionLayer is supposed to one of the simple state machines in sec 17. State machine type is chosen by message type. +// Transaction User: +// Sends a request and expects a response. It can send a CANCEL, which the TransactionLayer state machine must deal with. + +#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging + +#include + +#include +#include +//#include +#include +#include + +#include + +#include "SIPUtility.h" +#include "SIP2Interface.h" +#include "SIPMessage.h" + +#include + +#undef WARNING + + + +namespace SIP { +using namespace std; +using namespace GSM; +using namespace Control; + + +//static bool sipGetDirectMode() { return gConfig.getStr("SIP.Proxy.Mode") == string("direct"); } + + +// SIPInterface method definitions. + + +// CallId should include the host, but currently the remotehost is goofed up in sipSetUser, +// so for now continue to use just the sip user number. +//static string callIdGetNumber(string callid) +//{ +// //unsigned at = callid.find_first_of('@'); +// // The test is not necessary but may avoid an extra malloc. +// //return (at == string::npos) ? callid : callid.substr(0,at); +//} + +string SipDialogMap::makeTagKey(string callid, string localTag) +{ + // We are not using the remote-tag. The remote-tag (aka the to-tag) is not known at dialog creation time. + // The local tag is sufficient to disambiguate the two dialogs with the same callid for direct bts-to-bts communication. + // Note that whether the local tag is in the "from" or "to" depends on the message direction. + return format("%s-%s",callid,localTag); +} + + +// Find a dialog from an incoming Message. +// An outgoing INVITE has a local tag immediately, which is returned by the peer. +// An incoming INVITE does not have a local tag yet until the ACK is received, so ACK must be handled specially. +SipDialog *SipDialogMap::findDialogByMsg(SipMessage *msg) +{ + // This is an incoming message, so if there is a code then it is a reply so the Dialog was outbound. + string callid = msg->smGetCallId(), localtag = msg->smGetLocalTag(); + string key = makeTagKey(callid,localtag); + SipDialog *dialog = mDialogMap.readNoBlock(key); + if (! dialog && msg->isACK()) { + // For ACK try without the local tag. + // The ACK and all subsequent messages from the peer include our local tag that it regurgitates. + key = makeTagKey(callid,""); + dialog = mDialogMap.readNoBlock(key); + } + return dialog; +} + +// Add the local tag to the lookup key for this dialog. +void SipDialogMap::dmAddLocalTag(SipDialog *dialog) +{ + string callid = dialog->callId(), localtag = dialog->dsLocalTag(); + devassert(! localtag.empty()); + ScopedLock lock(mDialogMap.qGetLock()); + SipDialog *fnd = mDialogMap.getNoBlock(makeTagKey(callid,"")); + string newkey = makeTagKey(callid, localtag); + devassert(fnd == dialog); + if (fnd) { + mDialogMap.write(newkey,dialog); + } else { + // If it is a duplicate ACK it will already be moved. + if (! mDialogMap.readNoBlock(newkey)) { + LOG(ERR) << "Could not find dialog"<callId(), localtag = dialog->dsLocalTag(); + SipDialog *dialog1 = mDialogMap.getNoBlock(makeTagKey(callid,localtag)); // Removes the element. + if (dialog1) { + gSipInterface.mDeadDialogs.push_back(dialog1); + } + SipDialog *dialog2 = mDialogMap.getNoBlock(makeTagKey(callid,"")); + if (dialog2) { + gSipInterface.mDeadDialogs.push_back(dialog2); + } + LOG(DEBUG) << LOGVAR(callid) <callId(); + string key = makeTagKey(callid, dialog->dgIsServer() ? string("") : dialog->dsLocalTag()); + //SipDialog *previous = mDialogMap.read(key,2000); // why was this blocking? + SipDialog *previous = mDialogMap.readNoBlock(key); + LOG(DEBUG) <mIsHandover) { + // What happened is the dialog went to another BTS and is now coming back before + // the previous dialog has completely timed out. We must destroy the old dialog immediately. + } else { + LOG(ERR) << "Adding duplicate dialog."<second; + for (SipDialogList_t::iterator it2 = dialogs.begin(); it2 != dialogs.end(); it2++) { + SipDialog *dialog = *it2; + (dialog->*method)(); + } + } +} +#endif + +void SipDialogMap::dmPeriodicService() +{ + try { +#if USE_SCOPED_ITERATORS + DialogMap_t::ScopedIterator it(mDialogMap); +#else + ScopedLock lock(mDialogMap.qGetLock()); + DialogMap_t::iterator it; +#endif + for (it = mDialogMap.begin(); it != mDialogMap.end(); ) { + SipDialog *dialog = it->second; + it++; + if (dialog->dialogPeriodicService()) { + gSipInterface.dmRemoveDialog(dialog); + } + } + } catch(exception &e) { + // We dont expect any throws; just being ultra cautious. + LOG(ERR) << "SIP processing exception "<stBranch() + <::ScopedIterator sit(mTUMap); + for (TUMap_t::iterator sit = mTUMap.begin(); sit != mTUMap.end();) { + SipTransaction *me = sit->second; + sit++; + bool deleteme = me->TLPeriodicServiceV(); + LOG(DEBUG) <stGetMethodNameV()); + tup->TLWriteHighSide(msg); + return true; +} + +// Look at all the dead dialogs and delete any that can be deleted safely. +// They can be deleted if their SIP timers have expired and no TranEntry still points to them. +void SipInterface::purgeDeadDialogs() +{ +#if USE_SCOPED_ITERATORS + DeadDialogListType::ScopedIterator sit(mDeadDialogs); +#else + ScopedLock lock(mDeadDialogs.getLock()); + DeadDialogListType::iterator sit; +#endif + for (sit = mDeadDialogs.begin(); sit != mDeadDialogs.end();) { + SipDialog *dialog = *sit; + LOG(DEBUG) << "purgeDeadDialogs"<dgIsDeletable()); + if (dialog->dgIsDeletable()) { + sit = mDeadDialogs.erase(sit); + delete dialog; + } else { + sit++; + } + } +} + +SipBase *SipDialogMap::dmFindDialogByRtp(RtpSession *session) +{ +#if USE_SCOPED_ITERATORS + DialogMap_t::ScopedIterator sit(mDialogMap); +#else + ScopedLock lock(mDialogMap.qGetLock()); + DialogMap_t::iterator sit; +#endif + for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { + SipDialog *dialog = sit->second; + if (dialog->mSession == session) { + return (SipBase*)dialog; + } + } + return NULL; +} + +SipBase *SipDialogMap::dmFindDialogById(unsigned id) +{ +#if USE_SCOPED_ITERATORS + DialogMap_t::ScopedIterator sit(mDialogMap); +#else + ScopedLock lock(mDialogMap.qGetLock()); + DialogMap_t::iterator sit; +#endif + for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { + SipDialog *dialog = sit->second; + if (dialog->mDialogId == id) { + return (SipBase*)dialog; + } + } + return NULL; +} + +void SipDialogMap::printDialogs(ostream&os) +{ +#if USE_SCOPED_ITERATORS + DialogMap_t::ScopedIterator sit(mDialogMap); +#else + ScopedLock lock(mDialogMap.qGetLock()); + DialogMap_t::iterator sit; +#endif + for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { + SipDialog *dialog = sit->second; + os << dialog->dialogText(false) << "\n"; + } +// ScopedLock lock(mDialogMapLock); +// for (DialogListMap_t::iterator it1 = mDialogMap.begin(); it1 != mDialogMap.end(); it1++) { +// SipDialogList_t &dialogs = it1->second; +// for (SipDialogList_t::iterator it2 = dialogs.begin(); it2 != dialogs.end(); it2++) { +// SipDialog *existing = *it2; +// os << existing->dialogText(false) << "\n"; +// } +// } +} + +void printDialogs(ostream &os) +{ + gSipInterface.printDialogs(os); +} + + + +// This does NOT delete the msg. +void SipInterface::siWrite(const struct sockaddr_in* dest, SipMessage *msg) +{ + string msgstr = msg->smGenerate(); + string firstLine = msgstr.substr(0,msgstr.find('\n')); + + // For debug purposes dump the address assuming IPv4. + //uint32_t addr = ntohl(dest->sin_addr.s_addr); + char netbuf[102]; + inet_ntop(AF_INET,&(dest->sin_addr),netbuf,100); + uint16_t port = ntohs(dest->sin_port); + + //WATCHF("SIP write %s:%d %s to=%s\n",netbuf,port,firstLine.c_str(),msg->msmToValue.c_str()); + WATCH("SIP write "<smGetPrecis()); + LOG(INFO) << "write " << firstLine; + LOG(DEBUG) << "write " < MAX_UDP_LENGTH) { + // LOG(NOTICE) << "SIP Message length exceeds UDP limit, dropped; message:"<send((const struct sockaddr*)dest,msgstr.c_str(),msgstr.size()); + mSocketLock.unlock(); +} + +// If this message is handled by an existing TransactonUser return true; +// We only create client TUs [Transaction Users] so only replies are sent to TUs. +bool SipInterface::checkTU(SipMessage *msg) +{ + if (msg->msmCode == 0) { return false; } // This is a request and only replies go directly to TUs. + // 7-23-2013 Dont touch the via-branch. Neither sipauthserve nor smqueue are compliant so in defiance + // of RFC3261 17.1.3 we cannot use the via-branch. + //string branch = msg->smGetBranch(); + //if (branch.empty()) { + // // This may indicate a non-compliant peer. + // LOG(ERR) << "Reply with no via branch:"<smGetPrecis()); + + try { + if (newCheckInvite(msg)) { delete msg; return; } + + const char *methodname = msg->smGetMethodName(); // May be empty, but never NULL + if (*methodname) { LOG(DEBUG) << "non-initiating SIP method " << methodname; } + + if (checkTU(msg)) { delete msg; return; } + + // Send message to the appropriate SipDialog. + SipDialog *dialog = findDialogByMsg(msg); + if (dialog) { + if (msg->smGetCode()) { + // All replies go to the TUs, so if we did not find one above, this is an error. + LOG(NOTICE) << "SIP reply to non-existent SIP transaction "<dialogWriteDownlink(msg); + } else { + string callid = msg->smGetCallId(); + LOG(NOTICE) << "unrecognized"<smGetCode() != 0) { return; } // Ignore responses. + + IPAddressSpec peer; + if (! peer.ipSet(cause->smGetProxy(),"incoming SIP message")) { + return; // If the peer address is invalid, not much else we can do about it. + } + SipMessageReply err(cause,code,string(reason),NULL); + siWrite(&peer.mipSockAddr,&err); +} + + + +// Return true if the message was handled here. +void SipInterface::handleInvite(SipMessage *msg, bool isINVITE) +{ + // Get request username (IMSI) from invite request-uri, which is in the first line. + string toIMSIDigits = msg->smGetInviteImsi(); // This is just the numbers. + if (toIMSIDigits.length() == 0) { + // FIXME -- Send appropriate error (404) on SIP interface. + LOG(WARNING) << "Incoming INVITE/MESSAGE with no IMSI:"<smGetCallId(); + if (outboundCallIdNum.empty()) { + // FIXME -- Send appropriate error on SIP interface. + LOG(WARNING) << "Incoming INVITE/MESSAGE with no call ID"; + newSendEarlyError(msg,400,"Bad Request"); + return; // Message has been handled as much as it ever will be. + } + + SipPreposition from = msg->msmFrom; // from(msg->msmFromValue); + + // Get the caller ID if it's available. + string callerId = from.uriUsername(), callerHost = from.uriHostAndPort(); + LOG(DEBUG) << "callerId " << callerId << "@" << callerHost; + + + // Check SIP map. Repeated entry? Page again. + //string inboundCallIdNum = outboundCallIdNum; + // (pat) TODO: This must be locked so we can delete dialogs sometime. + SipDialog *existing = findDialogByMsg(msg); +#if PAT_TEST_SIP_DIRECT + // This is no longer needed... + //const char *callIdHost = osip_call_id_get_host(msg->omsg()->call_id); + //if (isINVITE && sipGetDirectMode() && existing) { + // // TODO: Check the invite to see if it comes directly from another Range BTS. + // // If it does, the MOC and MTC may be on the same BTS, in which case we need to create + // // an extra fifo for the MTC. The INVITE and the ACK will use this separate fifo. + // // TODO: Temporarily assume same BTS: + // const char *callerIMSI = extractIMSI(callerId.c_str()); + // if (callerIMSI && *callerIMSI) { + // LOG(DEBUG) << "Calling L3MobileIdentity " << callerIMSI; + // L3MobileIdentity callerMobileID(callerIMSI); + // LOG(DEBUG) << "after"; + // //if (TranEntry* transactionOC = gNewTransactionTable.ttFindBySIPCallId(callerMobileID,outboundCallIdNum)) + // if (TranEntry* transactionOC = existing->findTranEntry()) { + // inboundCallIdNum = outboundCallIdNum + string("_TC"); + // LOG(DEBUG) << "Changing"<getDialog()->setOutboundCallid(callIdHost,inboundCallIdNum.c_str()); + // } + // } + //} +#endif + + // (pat) Looks for a pending invite still in the queue. I didnt write this. + // Check for repeat INVITE, MESSAGE or re-INVITE. Respond to re-INVITE saying we don't support it. + if (existing) { + WATCH("SIP Message is repeat dialog"<getSipState())); + // sameINVITE checks if it is a duplicate INVITE. If it is not a duplicate, it is a RE-INVITE. + if (existing->sameInviteOrMessage(msg)) { + // This is a duplicate identical INVITE. + // If the via branch is the same, it is from an impatient SIP server. + // TODO: If the via branch is different, the INVITE arrived by two different proxy paths, + // and we are supposed to send "answered elsewhere". + + + // Send the duplicate message to the TL, which may resend the current response. + LOG(DEBUG) << "Sending same invite to existing"; + existing->MTWriteHighSide(msg); + + // If the Dialog is still pending, we want to repage. For that it is easier to look at the + // the Transaction side - if there is no transaction the dialog was terminated previously. + + // We want to keep paging this TransactionEntry. We cant just send a message through the Dialog because + // there is not yet any L3Procedure started on the TransactionEntry. + + TranEntry* transaction1= existing->findTranEntry(); + if (!transaction1) { + LOG(DEBUG) << "repeated INVITE/MESSAGE with no transaction record"; + } + //LOG(INFO) << "pre-existing transaction record: " << transaction1; // uggh. This could crash if tran freed here. + + // And if no channel is established yet, page again. The check for that is handled over in the Control directory. + // (pat) We could be blocked for several reasons, including paging, waiting for LUR to complete, waiting for channel + // to change, etc. But if we are paging, reset the paging timer so we keep paging. + gMMLayer.mmMTRepage(toIMSIDigits); + + return; // Message has been handled as much as it ever will be. + } else { + // This is a re-invite. + // (pat) TODO: Make a better error message handler in SipDialog. + /* don't cancel the call */ + LOG(CRIT) << "got reinvite on" <MODSendERROR(msg, 488, "Not Acceptable Here", false); + newSendEarlyError(msg,488,"Not Acceptable Here"); + return; // true because the message has been handled as much as it ever will be. + } + } + + // Create the transaction. + // (pat) Comments: + // mCallId is in all messages both directions. mCallIdHeader is the header built from mCallId. + // For MT, callid is decided by the peer; it is set here from the incoming INVITE. + // For MO, callid is decided by us, and set by calling SIPEngine::user() from the imsi, called by the TranEntry constructor. + // In both cases mCallIdHeader is set from saveInviteOrMessage. + // The inbound/outbound callid distinction is for the special case where the same BTS is both. + // newMT above calls SIPEngine(...,imsi from mobileID) calls sipSetUser(imsi) which sets the mCallId for an MO transaction. + // Then this SIPUser invocation overwrites mCallId with the callid for an MT transaction. + //FullMobileId msid; + //msid.mImsi = toIMSIDigits; + + // Doesnt matter if functions succeed or fail below. We return true from this function to indicate the SIP messages was handled. + + LOG(INFO) << msg->smGetPrecis() <MTCEarlyError(486,"Busy Here"); + dialog->dialogCancel(CancelCauseBusy); + return; + } + // Queue on MM for this IMSI. + // TODO: createMTTransaction still does messages too. + tran = dialog->createMTTransaction(msg); + } else { + + // Create an incipient TranEntry. It does not have a TI yet. + FullMobileId msid; + msid.mImsi = toIMSIDigits; + string smsBody = msg->smGetMessageBody(); + if (smsBody.empty()) { + LOG(NOTICE) << "MT-SMS discarded incoming MESSAGE method with no message body for " << msid.mImsi; + newSendEarlyError(msg,606,"not acceptable"); + return; // true because we handled the message as much as possible. + } + string smsContentType = msg->smGetMessageContentType(); + if (smsContentType == "") { + LOG(NOTICE) << "MT-SMS incoming MESSAGE method with no content type (or memory error) for " << msid.mImsi; + // TODO: Should this be fatal? + } + SipDialog *dialog = SipDialog::newSipDialogMT(SIPDTMTSMS, msg); + tran = TranEntry::newMTSMS(dialog,msid,callerId.c_str(),smsBody,smsContentType); + } + gMMLayer.mmAddMT(tran); +} + +bool SipInterface::newCheckInvite(SipMessage *msg) +{ + // Check for INVITE or MESSAGE methods. + // Check channel availability now, too, + // even if we are not actually assigning the channel yet. + if (msg->isINVITE()) { + gReports.incr("OpenBTS.SIP.INVITE.In"); + handleInvite(msg,true); + return true; + } else if (msg->isMESSAGE()) { + gReports.incr("OpenBTS.SIP.MESSAGE.In"); + handleInvite(msg,false); + return true; + } else { + + // Is this a message for an existing INVITE transaction, ie, that part of the INVITE before ACK? + // The findDialogMsg also finds a matching MESSAGE dialog or REGISTER dialog, but we are subsequently + // testing against the INVITE via-branch, which will find only INVITE transactions. + if (SipDialog *existing = findDialogByMsg(msg)) { + // The ACK and CANCEL message are sent to the INVITE server transaction. + // Other messages (BYE, INFO, etc) would be sent to the TU created for them. + if (msg->smGetCode() ? msg->smGetBranch() == existing->mInviteViaBranch : msg->isACK() || msg->isCANCEL()) { + WATCH("SIP Sending message to existing dialog"<getSipState())); + LOG(DEBUG) << "Sending message to existing dialog"<smGetCode()) <smGetBranch()) << LOGVAR(existing->mInviteViaBranch) << LOGVAR(msg->smGetMethodName()) << existing->dialogText(); + if (! existing->dgWriteHighSide(msg)) { + LOG(ERR) << "Confusing SIP message not handled, to dialog:" <dialogText(false)<< " message was:"<read(mReadBuffer); + if (numRead<0) { + LOG(ALERT) << "cannot read SIP socket."; + return; + } + if (numRead<10) { + LOG(WARNING) << "malformed packet (" << numRead << " bytes) on SIP socket"; + return; + } + mReadBuffer[numRead] = '\0'; + if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss")) { + LOG(NOTICE) << "simulating dropped inbound SIP packet: " << mReadBuffer; + return; + } + + newDriveIncoming(mReadBuffer); +} + +static void driveLoop2( SipInterface * si) +{ + while (true) { + si->siDrive2(); + } +} + +// (pat) Every now and then check every SipDialog engine for SIP timer expiration. +static void periodicServiceLoop(SipInterface *si) +{ + while (true) { + si->tuMapPeriodicService(); + si->dmPeriodicService(); + si->purgeDeadDialogs(); + // This timing is entirely non-critical, so dont bother to compute the exact next timeout, + // just delay a while and retry. + // Implicit assumption that Timer.E < Timer.F + unsigned howlong = gConfig.getNum("SIP.Timer.E")/2; + if (howlong < 250) { howlong = 250; } // Dont eat all the cpu cycles if someone accidently sets this too low. + msleep(howlong); + } +} + +SipInterface gSipInterface; // Here it is. + +// Pat added to hook messages from the ORTP library. See ortp_set_log_handler in ortp.c. +extern "C" { + static void ortpLogFunc(OrtpLogLevel lev, const char *fmt, va_list args) + { + // This floods the system with error messages, so regulate output to the console. + static time_t lasttime = 0; // No more than one message per minute. + char buf[202]; + vsnprintf(buf,200,fmt,args); + time_t now = time(NULL); + if (now - lasttime > 60) { + lasttime = now; + // This used to have a higher priority message. + // I demoted them both to NOTICE because this code seems to be working fine, and it is invoked + // every time the MS gets an in-call SMS. + LOG(NOTICE) << "RTP library:"< +#include +#include + +#include +#include "SIPMessage.h" +#include "SIPDialog.h" +#include "SIPTransaction.h" + +#define PAT_TEST_SIP_DIRECT 0 + + + +namespace SIP { +using namespace std; + +static const string cInviteStr("INVITE"); + +extern void SIPInterfaceStart(); +extern void printDialogs(std::ostream&os); + +typedef std::map TUMap_t; + +// We need two separate maps: one for requests and one for replies. +// For request matching we use SipDialogMap. We do not need to recognize in-dialog request repeats because +// we always just generate an immediate response for those anyway. So it only needs to map INVITEs. +// For reply matching we use SipTUMap, cf. +class SipDialogMap { + //Mutex mDialogMapLock; + typedef InterthreadMap DialogMap_t; + DialogMap_t mDialogMap; + string makeTagKey(string callid, string localTag); + public: + SipDialog *findDialogByMsg(SipMessage *msg); + void dmAddCallDialog(SipDialog*dialog); + void dmAddLocalTag(SipDialog*dialog); + void printDialogs(ostream&os); + void dmPeriodicService(); + bool dmRemoveDialog(SipBase *dialog); + SipBase *dmFindDialogById(unsigned id); + SipBase *dmFindDialogByRtp(RtpSession *session); +}; + +// Match incoming replies to the TU that made the outgoing request. +// The things saved here are SIP 'Transaction Users' as defined in RFC3261. +// The TransactionUsers are the layer that sends/receives original messages and has some behavior related to our application. +// RFC3261 17.1.3: When a response is received we are supposed find the client transaction using the branch in the top via. +// Unfortunately, as of 6/2013 our SR is non-compliant. It adds an extra via with a branch of "1". Gotta love that. +// An alternate way to find the transaction is by the CSeq. So why is there a branch at all? For reasons +// that do not apply to us, namely 1. The branch each proxy to have its own private ids +// 2. On the SIP Transaction Server side, the same request may arrive multiple times by multiple +// paths through the intermediate SIP proxies, and those requests can be distinguished by branch, but in our case we +// would want to view those as multiple identical requests and respond to all but the first with a repeated request error. +class SipTUMap { + InterthreadMap mTUMap; + // (pat 7-23-2013) We are supposed to use the via-branch to identify the transaction, but unfortunately + // sipauthserve is non-compliant and does not return it. + string tuMakeKey(string callid, string method, int seqnum) { + if (method == "ACK") { method = cInviteStr; } // Irrelevant, since we dont use TUs for ACK. + return format("%s %s %d",callid,method,seqnum); + } + string tuMakeKey(SipMessage *msg) { return tuMakeKey(msg->msmCallId, msg->msmCSeqMethod, msg->msmCSeqNum); } + string tuMakeKey(SipTransaction*tup) { return tuMakeKey(tup->mstCallId,tup->mstMethod,tup->mstSeqNum); } + + public: + void tuMapAdd(SipTransaction*tup); + void tuMapRemove(SipTransaction*tup, bool /*whine*/=true); + void tuMapPeriodicService(); + // Attempt to dispatch an incoming SIP message to a TU; return true if a transaction wanted this message. + // This has to be locked so someone doesnt delete the TU between the time we get its pointer + // and send it the message. + bool tuMapDispatch(SipMessage*msg); +}; + + +class SipInterface : public SipDialogMap, public SipTUMap +{ +private: + + char mReadBuffer[MAX_UDP_LENGTH+500]; ///< buffer for UDP reads. The +500 is way overkill. + + UDPSocket *mSIPSocket; + Mutex mSocketLock; + + // SIP Message CSeq numbers for initial out-of-dialog transcations, which includes the initial INVITE: + // We want these to be unique, and the easiest way is to increment a counter. + unsigned mMessageCSeqNum; + unsigned mInfoCSeqNum; // This is for INFO messages outside a dialog. Inside a dialog they must use dsNextCSeq() + unsigned mInviteCSeqNum; // For the initial invite, then in-invite numbers advance from there. + +public: + Thread mDriveThread; + Thread mPeriodicServiceThread; + + // (pat) Formerly all downlink SIP messages were added to the FIFO from which they were read via polling. + // Now messages are delivered to the SipDialog state machine, which sends SIP responses immediately, and + // queues any L3 bound messages as DialogMessages into a queue destined for L3 processing, + // whence the messages will be sent to the TransactionEntries. + // How many SIPEngine/SipDialog objects are there? + // Formerly (GSM only, prior to UMTS and L3 rewrite) there could be only one SIPEngine per LogicalChannel; + // each TCH might have a SIPEngine in the TransactionEntry used for voice traffic, and each SDDCH/FACCH might have a SIPEngine + // used for registration. Each LogicalChannel was driven by a separate thread to make the polling scheme work. + // Now for GSM there can be at least two TransactionEntries per MS or UE (for call-hold/waiting), + // and in UMTS the maximum number of UEs is dependent on the spreading factor used for voice calls, maybe 128. + // Update: There is really no limit on the number of simultaneous SIPEngines and TransactionEntries, because + // we start a new one for each inbound SMS or voice call; if there are too many they will get destroyed + // in the upcoming connection-management layer. + + // DialogMap maps the SIP callid to the SipDialog state machine that runs the state machine. + // The SIPEngine class doesnt contain a state machine, but SipDialog does. + typedef ThreadSafeList DeadDialogListType; + DeadDialogListType mDeadDialogs; + + /** + Create the SIP interface to watch for incoming SIP messages. + */ + SipInterface() + // (pat) Dont do this! There is a constructor race between SIPInterface and ConfigurationTable needed by gConfig. + // :mSIPSocket(gConfig.getNum("SIP.Local.Port")) + { + // sipauthserve appears to have a bug that it does not properly differentiate messages + // from different BTS, possibly only if they have the same IP address, but in any case, + // using random numbers to init makes encountering that bug quite rare. + mMessageCSeqNum = random() & 0xfffff; + mInfoCSeqNum = random() & 0xfffff; // Any old number will do. + mInviteCSeqNum = random() & 0xfffff; // Any old number will do. + } + + + void siInit(); + /** Start the SIP drive loop. */ + void siDrive2(); + + /** Receive, parse and dispatch a single SIP message. */ + void newDriveIncoming(char *readBuffer); + void purgeDeadDialogs(); + + /** + Look for incoming INVITE messages to start MTC. + @param msg The SIP message to check. + @return true if the message is a new INVITE + */ + void handleInvite(SipMessage *msg,bool isINVITE); + bool newCheckInvite(SipMessage *msg); + bool checkTU(SipMessage *msg); + + /** + Send an error response before a transaction is even created. + */ + void newSendEarlyError(SipMessage *cause, int code, const char * reason); + + void siWrite(const struct sockaddr_in*, SipMessage *); // new + + unsigned nextMessageCSeqNum() { return ++mMessageCSeqNum; } + unsigned nextInfoCSeqNum() { return ++mInfoCSeqNum; } + unsigned nextInviteCSeqNum() { return ++mInviteCSeqNum; } +}; + + +/*@addtogroup Globals */ +//@{ +/** A single global SIPInterface in the global namespace. */ +extern SipInterface gSipInterface; +//@} + +}; // namespace SIP. + +#endif // SIP2INTERFACE_H +// vim: ts=4 sw=4 diff --git a/SIP/SIPBNF.txt b/SIP/SIPBNF.txt new file mode 100644 index 0000000..8e5e073 --- /dev/null +++ b/SIP/SIPBNF.txt @@ -0,0 +1,735 @@ +25.1 Basic Rules + + The following rules are used throughout this specification to + describe basic parsing constructs. The US-ASCII coded character set + is defined by ANSI X3.4-1986. + + alphanum = ALPHA / DIGIT + + + + + + + + + + + + + + + + +Rosenberg, et. al. Standards Track [Page 219] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + + Several rules are incorporated from RFC 2396 [5] but are updated to + make them compliant with RFC 2234 [10]. These include: + + reserved = ";" / "/" / "?" / ":" / "@" / "&" / "=" / "+" + / "$" / "," + unreserved = alphanum / mark + mark = "-" / "_" / "." / "!" / "~" / "*" / "'" + / "(" / ")" + escaped = "%" HEXDIG HEXDIG + + SIP header field values can be folded onto multiple lines if the + continuation line begins with a space or horizontal tab. All linear + white space, including folding, has the same semantics as SP. A + recipient MAY replace any linear white space with a single SP before + interpreting the field value or forwarding the message downstream. + This is intended to behave exactly as HTTP/1.1 as described in RFC + 2616 [8]. The SWS construct is used when linear white space is + optional, generally between tokens and separators. + + LWS = [*WSP CRLF] 1*WSP ; linear whitespace + SWS = [LWS] ; sep whitespace + + To separate the header name from the rest of value, a colon is used, + which, by the above rule, allows whitespace before, but no line + break, and whitespace after, including a linebreak. The HCOLON + defines this construct. + + HCOLON = *( SP / HTAB ) ":" SWS + + The TEXT-UTF8 rule is only used for descriptive field contents and + values that are not intended to be interpreted by the message parser. + Words of *TEXT-UTF8 contain characters from the UTF-8 charset (RFC + 2279 [7]). The TEXT-UTF8-TRIM rule is used for descriptive field + contents that are n t quoted strings, where leading and trailing LWS + is not meaningful. In this regard, SIP differs from HTTP, which uses + the ISO 8859-1 character set. + + TEXT-UTF8-TRIM = 1*TEXT-UTF8char *(*LWS TEXT-UTF8char) + TEXT-UTF8char = %x21-7E / UTF8-NONASCII + UTF8-NONASCII = %xC0-DF 1UTF8-CONT + / %xE0-EF 2UTF8-CONT + / %xF0-F7 3UTF8-CONT + / %xF8-Fb 4UTF8-CONT + / %xFC-FD 5UTF8-CONT + UTF8-CONT = %x80-BF + + + + + + +Rosenberg, et. al. Standards Track [Page 220] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + + A CRLF is allowed in the definition of TEXT-UTF8-TRIM only as part of + a header field continuation. It is expected that the folding LWS + will be replaced with a single SP before interpretation of the TEXT- + UTF8-TRIM value. + + Hexadecimal numeric characters are used in several protocol elements. + Some elements (authentication) force hex alphas to be lower case. + + LHEX = DIGIT / %x61-66 ;lowercase a-f + + Many SIP header field values consist of words separated by LWS or + special characters. Unless otherwise stated, tokens are case- + insensitive. These special characters MUST be in a quoted string to + be used within a parameter value. The word construct is used in + Call-ID to allow most separators to be used. + + token = 1*(alphanum / "-" / "." / "!" / "%" / "*" + / "_" / "+" / "`" / "'" / "~" ) + separators = "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / DQUOTE / + "/" / "[" / "]" / "?" / "=" / + "{" / "}" / SP / HTAB + word = 1*(alphanum / "-" / "." / "!" / "%" / "*" / + "_" / "+" / "`" / "'" / "~" / + "(" / ")" / "<" / ">" / + ":" / "\" / DQUOTE / + "/" / "[" / "]" / "?" / + "{" / "}" ) + + When tokens are used or separators are used between elements, + whitespace is often allowed before or after these characters: + + STAR = SWS "*" SWS ; asterisk + SLASH = SWS "/" SWS ; slash + EQUAL = SWS "=" SWS ; equal + LPAREN = SWS "(" SWS ; left parenthesis + RPAREN = SWS ")" SWS ; right parenthesis + RAQUOT = ">" SWS ; right angle quote + LAQUOT = SWS "<"; left angle quote + COMMA = SWS "," SWS ; comma + SEMI = SWS ";" SWS ; semicolon + COLON = SWS ":" SWS ; colon + LDQUOT = SWS DQUOTE; open double quotation mark + RDQUOT = DQUOTE SWS ; close double quotation mark + + + + + + + +Rosenberg, et. al. Standards Track [Page 221] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + + Comments can be included in some SIP header fields by surrounding the + comment text with parentheses. Comments are only allowed in fields + containing "comment" as part of their field value definition. In all + other fields, parentheses are considered part of the field value. + + comment = LPAREN *(ctext / quoted-pair / comment) RPAREN + ctext = %x21-27 / %x2A-5B / %x5D-7E / UTF8-NONASCII + / LWS + + ctext includes all chars except left and right parens and backslash. + A string of text is parsed as a single word if it is quoted using + double-quote marks. In quoted strings, quotation marks (") and + backslashes (\) need to be escaped. + + quoted-string = SWS DQUOTE *(qdtext / quoted-pair ) DQUOTE + qdtext = LWS / %x21 / %x23-5B / %x5D-7E + / UTF8-NONASCII + + The backslash character ("\") MAY be used as a single-character + quoting mechanism only within quoted-string and comment constructs. + Unlike HTTP/1.1, the characters CR and LF cannot be escaped by this + mechanism to avoid conflict with line folding and header separation. + +quoted-pair = "\" (%x00-09 / %x0B-0C + / %x0E-7F) + +SIP-URI = "sip:" [ userinfo ] hostport + uri-parameters [ headers ] +SIPS-URI = "sips:" [ userinfo ] hostport + uri-parameters [ headers ] +userinfo = ( user / telephone-subscriber ) [ ":" password ] "@" +user = 1*( unreserved / escaped / user-unreserved ) +user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" +password = *( unreserved / escaped / + "&" / "=" / "+" / "$" / "," ) +hostport = host [ ":" port ] +host = hostname / IPv4address / IPv6reference +hostname = *( domainlabel "." ) toplabel [ "." ] +domainlabel = alphanum + / alphanum *( alphanum / "-" ) alphanum +toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum + + + + + + + + + + +Rosenberg, et. al. Standards Track [Page 222] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT +IPv6reference = "[" IPv6address "]" +IPv6address = hexpart [ ":" IPv4address ] +hexpart = hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ] +hexseq = hex4 *( ":" hex4) +hex4 = 1*4HEXDIG +port = 1*DIGIT + + The BNF for telephone-subscriber can be found in RFC 2806 [9]. Note, + however, that any characters allowed there that are not allowed in + the user part of the SIP URI MUST be escaped. + +uri-parameters = *( ";" uri-parameter) +uri-parameter = transport-param / user-param / method-param + / ttl-param / maddr-param / lr-param / other-param +transport-param = "transport=" + ( "udp" / "tcp" / "sctp" / "tls" + / other-transport) +other-transport = token +user-param = "user=" ( "phone" / "ip" / other-user) +other-user = token +method-param = "method=" Method +ttl-param = "ttl=" ttl +maddr-param = "maddr=" host +lr-param = "lr" +other-param = pname [ "=" pvalue ] +pname = 1*paramchar +pvalue = 1*paramchar +paramchar = param-unreserved / unreserved / escaped +param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$" + +headers = "?" header *( "&" header ) +header = hname "=" hvalue +hname = 1*( hnv-unreserved / unreserved / escaped ) +hvalue = *( hnv-unreserved / unreserved / escaped ) +hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$" + +SIP-message = Request / Response +Request = Request-Line + *( message-header ) + CRLF + [ message-body ] +Request-Line = Method SP Request-URI SP SIP-Version CRLF +Request-URI = SIP-URI / SIPS-URI / absoluteURI +absoluteURI = scheme ":" ( hier-part / opaque-part ) +hier-part = ( net-path / abs-path ) [ "?" query ] +net-path = "//" authority [ abs-path ] +abs-path = "/" path-segments + + + +Rosenberg, et. al. Standards Track [Page 223] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +opaque-part = uric-no-slash *uric +uric = reserved / unreserved / escaped +uric-no-slash = unreserved / escaped / ";" / "?" / ":" / "@" + / "&" / "=" / "+" / "$" / "," +path-segments = segment *( "/" segment ) +segment = *pchar *( ";" param ) +param = *pchar +pchar = unreserved / escaped / + ":" / "@" / "&" / "=" / "+" / "$" / "," +scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +authority = srvr / reg-name +srvr = [ [ userinfo "@" ] hostport ] +reg-name = 1*( unreserved / escaped / "$" / "," + / ";" / ":" / "@" / "&" / "=" / "+" ) +query = *uric +SIP-Version = "SIP" "/" 1*DIGIT "." 1*DIGIT + +message-header = (Accept + / Accept-Encoding + / Accept-Language + / Alert-Info + / Allow + / Authentication-Info + / Authorization + / Call-ID + / Call-Info + / Contact + / Content-Disposition + / Content-Encoding + / Content-Language + / Content-Length + / Content-Type + / CSeq + / Date + / Error-Info + / Expires + / From + / In-Reply-To + / Max-Forwards + / MIME-Version + / Min-Expires + / Organization + / Priority + / Proxy-Authenticate + / Proxy-Authorization + / Proxy-Require + / Record-Route + / Reply-To + + + +Rosenberg, et. al. Standards Track [Page 224] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + + / Require + / Retry-After + / Route + / Server + / Subject + / Supported + / Timestamp + / To + / Unsupported + / User-Agent + / Via + / Warning + / WWW-Authenticate + / extension-header) CRLF + +INVITEm = %x49.4E.56.49.54.45 ; INVITE in caps +ACKm = %x41.43.4B ; ACK in caps +OPTIONSm = %x4F.50.54.49.4F.4E.53 ; OPTIONS in caps +BYEm = %x42.59.45 ; BYE in caps +CANCELm = %x43.41.4E.43.45.4C ; CANCEL in caps +REGISTERm = %x52.45.47.49.53.54.45.52 ; REGISTER in caps +Method = INVITEm / ACKm / OPTIONSm / BYEm + / CANCELm / REGISTERm + / extension-method +extension-method = token +Response = Status-Line + *( message-header ) + CRLF + [ message-body ] + +Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF +Status-Code = Informational + / Redirection + / Success + / Client-Error + / Server-Error + / Global-Failure + / extension-code +extension-code = 3DIGIT +Reason-Phrase = *(reserved / unreserved / escaped + / UTF8-NONASCII / UTF8-CONT / SP / HTAB) + +Informational = "100" ; Trying + / "180" ; Ringing + / "181" ; Call Is Being Forwarded + / "182" ; Queued + / "183" ; Session Progress + + + + +Rosenberg, et. al. Standards Track [Page 225] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +Success = "200" ; OK + +Redirection = "300" ; Multiple Choices + / "301" ; Moved Permanently + / "302" ; Moved Temporarily + / "305" ; Use Proxy + / "380" ; Alternative Service + +Client-Error = "400" ; Bad Request + / "401" ; Unauthorized + / "402" ; Payment Required + / "403" ; Forbidden + / "404" ; Not Found + / "405" ; Method Not Allowed + / "406" ; Not Acceptable + / "407" ; Proxy Authentication Required + / "408" ; Request Timeout + / "410" ; Gone + / "413" ; Request Entity Too Large + / "414" ; Request-URI Too Large + / "415" ; Unsupported Media Type + / "416" ; Unsupported URI Scheme + / "420" ; Bad Extension + / "421" ; Extension Required + / "423" ; Interval Too Brief + / "480" ; Temporarily not available + / "481" ; Call Leg/Transaction Does Not Exist + / "482" ; Loop Detected + / "483" ; Too Many Hops + / "484" ; Address Incomplete + / "485" ; Ambiguous + / "486" ; Busy Here + / "487" ; Request Terminated + / "488" ; Not Acceptable Here + / "491" ; Request Pending + / "493" ; Undecipherable + +Server-Error = "500" ; Internal Server Error + / "501" ; Not Implemented + / "502" ; Bad Gateway + / "503" ; Service Unavailable + / "504" ; Server Time-out + / "505" ; SIP Version not supported + / "513" ; Message Too Large + + + + + + + +Rosenberg, et. al. Standards Track [Page 226] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +Global-Failure = "600" ; Busy Everywhere + / "603" ; Decline + / "604" ; Does not exist anywhere + / "606" ; Not Acceptable + +Accept = "Accept" HCOLON + [ accept-range *(COMMA accept-range) ] +accept-range = media-range *(SEMI accept-param) +media-range = ( "*/*" + / ( m-type SLASH "*" ) + / ( m-type SLASH m-subtype ) + ) *( SEMI m-parameter ) +accept-param = ("q" EQUAL qvalue) / generic-param +qvalue = ( "0" [ "." 0*3DIGIT ] ) + / ( "1" [ "." 0*3("0") ] ) +generic-param = token [ EQUAL gen-value ] +gen-value = token / host / quoted-string + +Accept-Encoding = "Accept-Encoding" HCOLON + [ encoding *(COMMA encoding) ] +encoding = codings *(SEMI accept-param) +codings = content-coding / "*" +content-coding = token + +Accept-Language = "Accept-Language" HCOLON + [ language *(COMMA language) ] +language = language-range *(SEMI accept-param) +language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) / "*" ) + +Alert-Info = "Alert-Info" HCOLON alert-param *(COMMA alert-param) +alert-param = LAQUOT absoluteURI RAQUOT *( SEMI generic-param ) + +Allow = "Allow" HCOLON [Method *(COMMA Method)] + +Authorization = "Authorization" HCOLON credentials +credentials = ("Digest" LWS digest-response) + / other-response +digest-response = dig-resp *(COMMA dig-resp) +dig-resp = username / realm / nonce / digest-uri + / dresponse / algorithm / cnonce + / opaque / message-qop + / nonce-count / auth-param +username = "username" EQUAL username-value +username-value = quoted-string +digest-uri = "uri" EQUAL LDQUOT digest-uri-value RDQUOT +digest-uri-value = rquest-uri ; Equal to request-uri as specified + by HTTP/1.1 +message-qop = "qop" EQUAL qop-value + + + +Rosenberg, et. al. Standards Track [Page 227] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +cnonce = "cnonce" EQUAL cnonce-value +cnonce-value = nonce-value +nonce-count = "nc" EQUAL nc-value +nc-value = 8LHEX +dresponse = "response" EQUAL request-digest +request-digest = LDQUOT 32LHEX RDQUOT +auth-param = auth-param-name EQUAL + ( token / quoted-string ) +auth-param-name = token +other-response = auth-scheme LWS auth-param + *(COMMA auth-param) +auth-scheme = token + +Authentication-Info = "Authentication-Info" HCOLON ainfo + *(COMMA ainfo) +ainfo = nextnonce / message-qop + / response-auth / cnonce + / nonce-count +nextnonce = "nextnonce" EQUAL nonce-value +response-auth = "rspauth" EQUAL response-digest +response-digest = LDQUOT *LHEX RDQUOT + +Call-ID = ( "Call-ID" / "i" ) HCOLON callid +callid = word [ "@" word ] + +Call-Info = "Call-Info" HCOLON info *(COMMA info) +info = LAQUOT absoluteURI RAQUOT *( SEMI info-param) +info-param = ( "purpose" EQUAL ( "icon" / "info" + / "card" / token ) ) / generic-param + +Contact = ("Contact" / "m" ) HCOLON + ( STAR / (contact-param *(COMMA contact-param))) +contact-param = (name-addr / addr-spec) *(SEMI contact-params) +name-addr = [ display-name ] LAQUOT addr-spec RAQUOT +addr-spec = SIP-URI / SIPS-URI / absoluteURI +display-name = *(token LWS)/ quoted-string + +contact-params = c-p-q / c-p-expires + / contact-extension +c-p-q = "q" EQUAL qvalue +c-p-expires = "expires" EQUAL delta-seconds +contact-extension = generic-param +delta-seconds = 1*DIGIT + +Content-Disposition = "Content-Disposition" HCOLON + disp-type *( SEMI disp-param ) +disp-type = "render" / "session" / "icon" / "alert" + / disp-extension-token + + + +Rosenberg, et. al. Standards Track [Page 228] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +disp-param = handling-param / generic-param +handling-param = "handling" EQUAL + ( "optional" / "required" + / other-handling ) +other-handling = token +disp-extension-token = token + +Content-Encoding = ( "Content-Encoding" / "e" ) HCOLON + content-coding *(COMMA content-coding) + +Content-Language = "Content-Language" HCOLON + language-tag *(COMMA language-tag) +language-tag = primary-tag *( "-" subtag ) +primary-tag = 1*8ALPHA +subtag = 1*8ALPHA + +Content-Length = ( "Content-Length" / "l" ) HCOLON 1*DIGIT +Content-Type = ( "Content-Type" / "c" ) HCOLON media-type +media-type = m-type SLASH m-subtype *(SEMI m-parameter) +m-type = discrete-type / composite-type +discrete-type = "text" / "image" / "audio" / "video" + / "application" / extension-token +composite-type = "message" / "multipart" / extension-token +extension-token = ietf-token / x-token +ietf-token = token +x-token = "x-" token +m-subtype = extension-token / iana-token +iana-token = token +m-parameter = m-attribute EQUAL m-value +m-attribute = token +m-value = token / quoted-string + +CSeq = "CSeq" HCOLON 1*DIGIT LWS Method + +Date = "Date" HCOLON SIP-date +SIP-date = rfc1123-date +rfc1123-date = wkday "," SP date1 SP time SP "GMT" +date1 = 2DIGIT SP month SP 4DIGIT + ; day month year (e.g., 02 Jun 1982) +time = 2DIGIT ":" 2DIGIT ":" 2DIGIT + ; 00:00:00 - 23:59:59 +wkday = "Mon" / "Tue" / "Wed" + / "Thu" / "Fri" / "Sat" / "Sun" +month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + +Error-Info = "Error-Info" HCOLON error-uri *(COMMA error-uri) + + + +Rosenberg, et. al. Standards Track [Page 229] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +error-uri = LAQUOT absoluteURI RAQUOT *( SEMI generic-param ) + +Expires = "Expires" HCOLON delta-seconds +From = ( "From" / "f" ) HCOLON from-spec +from-spec = ( name-addr / addr-spec ) + *( SEMI from-param ) +from-param = tag-param / generic-param +tag-param = "tag" EQUAL token + +In-Reply-To = "In-Reply-To" HCOLON callid *(COMMA callid) + +Max-Forwards = "Max-Forwards" HCOLON 1*DIGIT + +MIME-Version = "MIME-Version" HCOLON 1*DIGIT "." 1*DIGIT + +Min-Expires = "Min-Expires" HCOLON delta-seconds + +Organization = "Organization" HCOLON [TEXT-UTF8-TRIM] + +Priority = "Priority" HCOLON priority-value +priority-value = "emergency" / "urgent" / "normal" + / "non-urgent" / other-priority +other-priority = token + +Proxy-Authenticate = "Proxy-Authenticate" HCOLON challenge +challenge = ("Digest" LWS digest-cln *(COMMA digest-cln)) + / other-challenge +other-challenge = auth-scheme LWS auth-param + *(COMMA auth-param) +digest-cln = realm / domain / nonce + / opaque / stale / algorithm + / qop-options / auth-param +realm = "realm" EQUAL realm-value +realm-value = quoted-string +domain = "domain" EQUAL LDQUOT URI + *( 1*SP URI ) RDQUOT +URI = absoluteURI / abs-path +nonce = "nonce" EQUAL nonce-value +nonce-value = quoted-string +opaque = "opaque" EQUAL quoted-string +stale = "stale" EQUAL ( "true" / "false" ) +algorithm = "algorithm" EQUAL ( "MD5" / "MD5-sess" + / token ) +qop-options = "qop" EQUAL LDQUOT qop-value + *("," qop-value) RDQUOT +qop-value = "auth" / "auth-int" / token + +Proxy-Authorization = "Proxy-Authorization" HCOLON credentials + + + +Rosenberg, et. al. Standards Track [Page 230] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +Proxy-Require = "Proxy-Require" HCOLON option-tag + *(COMMA option-tag) +option-tag = token + +Record-Route = "Record-Route" HCOLON rec-route *(COMMA rec-route) +rec-route = name-addr *( SEMI rr-param ) +rr-param = generic-param + +Reply-To = "Reply-To" HCOLON rplyto-spec +rplyto-spec = ( name-addr / addr-spec ) + *( SEMI rplyto-param ) +rplyto-param = generic-param +Require = "Require" HCOLON option-tag *(COMMA option-tag) + +Retry-After = "Retry-After" HCOLON delta-seconds + [ comment ] *( SEMI retry-param ) + +retry-param = ("duration" EQUAL delta-seconds) + / generic-param + +Route = "Route" HCOLON route-param *(COMMA route-param) +route-param = name-addr *( SEMI rr-param ) + +Server = "Server" HCOLON server-val *(LWS server-val) +server-val = product / comment +product = token [SLASH product-version] +product-version = token + +Subject = ( "Subject" / "s" ) HCOLON [TEXT-UTF8-TRIM] + +Supported = ( "Supported" / "k" ) HCOLON + [option-tag *(COMMA option-tag)] + +Timestamp = "Timestamp" HCOLON 1*(DIGIT) + [ "." *(DIGIT) ] [ LWS delay ] +delay = *(DIGIT) [ "." *(DIGIT) ] + +To = ( "To" / "t" ) HCOLON ( name-addr + / addr-spec ) *( SEMI to-param ) +to-param = tag-param / generic-param + +Unsupported = "Unsupported" HCOLON option-tag *(COMMA option-tag) +User-Agent = "User-Agent" HCOLON server-val *(LWS server-val) + + + + + + + + +Rosenberg, et. al. Standards Track [Page 231] + +RFC 3261 SIP: Session Initiation Protocol June 2002 + + +Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm) +via-parm = sent-protocol LWS sent-by *( SEMI via-params ) +via-params = via-ttl / via-maddr + / via-received / via-branch + / via-extension +via-ttl = "ttl" EQUAL ttl +via-maddr = "maddr" EQUAL host +via-received = "received" EQUAL (IPv4address / IPv6address) +via-branch = "branch" EQUAL token +via-extension = generic-param +sent-protocol = protocol-name SLASH protocol-version + SLASH transport +protocol-name = "SIP" / token +protocol-version = token +transport = "UDP" / "TCP" / "TLS" / "SCTP" + / other-transport +sent-by = host [ COLON port ] +ttl = 1*3DIGIT ; 0 to 255 + +Warning = "Warning" HCOLON warning-value *(COMMA warning-value) +warning-value = warn-code SP warn-agent SP warn-text +warn-code = 3DIGIT +warn-agent = hostport / pseudonym + ; the name or pseudonym of the server adding + ; the Warning header, for use in debugging +warn-text = quoted-string +pseudonym = token + +WWW-Authenticate = "WWW-Authenticate" HCOLON challenge + +extension-header = header-name HCOLON header-value +header-name = token +header-value = *(TEXT-UTF8char / UTF8-CONT / LWS) +message-body = *OCTET + diff --git a/SIP/SIPBase.cpp b/SIP/SIPBase.cpp new file mode 100644 index 0000000..216d008 --- /dev/null +++ b/SIP/SIPBase.cpp @@ -0,0 +1,1511 @@ +/**@file SIP Base -- SIP IETF RFC-3261, RTP IETF RFC-3550. */ +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2011, 2012, 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 distribuion. +* +* 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. + +*/ + + + +#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include // For HandoverEntry + +#include "SIPMessage.h" +#include "SIPParse.h" // For SipParam +#include "SIPBase.h" +#include "SIPDialog.h" +#include "SIP2Interface.h" +#include "SIPUtility.h" + +#undef WARNING + +int gCountRtpSessions = 0; +int gCountRtpSockets = 0; +int gCountSipDialogs = 0; + +namespace SIP { +using namespace std; +using namespace Control; + +const bool rtpUseRealTime = true; // Enables a bug fix for the RTP library. + +// TEMPORARY WORKAROUND : until smqueue can be brought up to speed, this needs enabling by default +bool gPeerIsBuggySmqueue = true; + +// These need to be declared here instead of in the header because of interaction with InterthreadQueue.h. +SipEngine::~SipEngine() {} +SipEngine::SipEngine() { mTranId = 0; } + +void SipBaseProtected::_define_vtable() {} +void DialogMessage::_define_vtable() {} + + +string makeUriWithTag(string username, string ip, string tag) +{ + return format(";tag=%s",username,ip,tag); +} +string makeUri(string username, string ip, unsigned port) +{ + if (port) { + return format("sip:%s@%s:%u",username,ip,port); + } else { + return format("sip:%s@%s",username,ip); + } +} + +static int get_rtp_tev_type(char dtmf){ + switch (dtmf){ + case '1': return TEV_DTMF_1; + case '2': return TEV_DTMF_2; + case '3': return TEV_DTMF_3; + case '4': return TEV_DTMF_4; + case '5': return TEV_DTMF_5; + case '6': return TEV_DTMF_6; + case '7': return TEV_DTMF_7; + case '8': return TEV_DTMF_8; + case '9': return TEV_DTMF_9; + case '0': return TEV_DTMF_0; + case '*': return TEV_DTMF_STAR; + case '#': return TEV_DTMF_POUND; + case 'a': + case 'A': return TEV_DTMF_A; + case 'B': + case 'b': return TEV_DTMF_B; + case 'C': + case 'c': return TEV_DTMF_C; + case 'D': + case 'd': return TEV_DTMF_D; + case '!': return TEV_FLASH; + default: + LOG(WARNING) << "Bad dtmf: " << dtmf; + return -1; + } +} + + +const char* SipStateString(SipState s) +{ + switch(s) + { + case SSNullState: return "NullState"; + case SSTimeout: return "Timeout"; + case Starting: return "Starting"; + case Proceeding: return "Proceeding"; + case Ringing: return "Ringing"; + case Connecting: return "Connecting"; + case Active: return "Active"; + case SSFail: return "SSFail"; + case MOCBusy: return "MOCBusy"; + case MODClearing: return "MODClearing"; + case MODCanceling: return "MODCanceling"; + case MODError: return "MODError"; + case MTDClearing: return "MTDClearing"; + case MTDCanceling: return "MTDCanceling"; + case Canceled: return "Canceled"; + case Cleared: return "Cleared"; + //case SipRegister: return "SipRegister"; + //case SipUnregister: return "SipUnregister"; + case MOSMSSubmit: return "SMS-Submit"; + case HandoverInbound: return "HandoverInbound"; + //case HandoverInboundReferred: return "HandoverInboundReferred"; + case HandoverOutbound: return "HandoverOutbound"; + //default: return NULL; + } + return NULL; +} + +ostream& operator<<(ostream& os, SipState s) +{ + const char* str = SipStateString(s); + if (str) os << str; + else os << "?" << ((int)s) << "?"; + return os; +} + +// Should we cancel a transaction based on the SIP state? +bool SipBaseProtected::sipIsStuck() const +{ + switch (getDialogType()) { + case SIPDTUndefined: + LOG(ERR) << "undefined dialog type?"; return false; + case SIPDTRegister: + case SIPDTUnregister: + return false; // The registrar dialogs are immortal. + default: + break; // Call and Message dialogs should either proceed to Active or eventually die on their own. + } + unsigned age = mStateAge.elapsed(); + // These ages were copied from the old pre-l3-rewrite code in TransactionTable.cpp + switch (getSipState()) { + case Active: + return false; // Things are copascetic. Let this SIP dialog run. + case SSFail: + case HandoverInbound: + case Proceeding: + case Canceled: + case Cleared: + // Stuck in these states longer than 30 seconds? Grounds for terminating the transaction. + return age > 30*1000; + default: + // Stuck in any other state longer than 180 seconds? Grounds for terminating the transaction. + return age > 180*1000; + } +} + +void SipRtp::rtpStop() +{ + if (mSession) { + RtpSession *save = mSession; + mSession = NULL; // Prevent rxFrame and txFrame from using it, which is overkill because we dont call this until the state is not Active. + rtp_session_destroy(save); + gCountRtpSessions--; + gCountRtpSockets--; + } +} + +void SipRtp::rtpInit() +{ + mSession = NULL; + mTxTime = 0; + mRxTime = 0; + mRxRealTime = 0; + mTxRealTime = 0; + mDTMF = 0; + mDTMFDuration = 0; + mDTMFEnding = 0; + mRTPPort = 0; //to make sure noise doesn't magically equal a valid RTP port +} + +DialogStateVars::DialogStateVars() +{ + // generate a tag now. + // (pat) It identifies our side of the SIP dialog to the peer. + // It is placed in the to-tag for both responses and requests. + // For responses we munge the saved INVITE immediately so all responses use this to-tag. + //mLocalTag=make_tag(); + + // set our CSeq in case we need one + mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600; +} + +string DialogStateVars::dsToString() const +{ + ostringstream ss; + ss << LOGVARM(mCallId); + ss << LOGVARM(mLocalHeader.value())<msmContentType == "application/sdp") { + mSdpAnswer = response->msmBody; + } + + // The To: is the remote party and might have a new tag. + // (pat) But only for a response to one of our requests. + dsSetRemoteHeader(&response->msmTo); + if (response->msmCSeqMethod == "INVITE") { + // Handle the Contact. + // If the INVITE is canceled early we get a 487 error which does not have a Contact. + // TODO: Handle the routeset. + string uristr; + if (! response->msmContactValue.empty() && crackUri(response->msmContactValue,NULL,&uristr,NULL)) { + SipUri uri(uristr); + mProxy.ipSet(uri.uriHostAndPort(), "INVITE Contact"); + } + } +} + + +void DialogStateVars::dsSetLocalMO(const FullMobileId &msid, bool addTag) +{ + // We dont really have to check for collisions; our ids were vanishingly unlikely to collide + // before and now the time is encoded into it as well. + //string callid; + //do { callid = globallyUniqueId(""); } while (gSipInterface.findDialogsByCallId(callid,false)); + dsSetCallId(globallyUniqueId("")); + + string username; + { + // IMSI gets prefixed with "IMSI" to form a SIP username + username = msid.fmidUsername(); + } + dsSetLocalUri(makeUri(username,localIPAndPort())); + //mSipUsername = username; + LOG(DEBUG) << "set user MO"< TE= UpRSSI= TxPwr= DnRSSIdBm= + // Get the values. + if (l3chan) { + char phy_info[200]; + // (pat) TODO: This is really cheating. + const GSM::L2LogicalChannel *chan = l3chan->getL2Channel(); + MSPhysReportInfo *phys = chan->getPhysInfo(); + snprintf(phy_info,200,"OpenBTS; TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d time=%9.3lf", + phys->actualMSTiming(), phys->timingError(), + phys->RSSI(), phys->actualMSPower(), + chan->measurementResults().RXLEV_FULL_SERVING_CELL_dBm(), + phys->timestamp()); + static const string cPhyInfoString("P-PHY-Info"); + msg->smAddHeader(cPhyInfoString,phy_info); + } + + // P-Access-Network-Info + // See 3GPP 24.229 7.2. This is a genuine specified header. + char cgi_3gpp[256]; + snprintf(cgi_3gpp,256,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x", + gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(), + (unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI")); + static const string cAccessNetworkInfoString("P-Access-Network-Info"); + msg->smAddHeader(cAccessNetworkInfoString, cgi_3gpp); + + // FIXME -- Use the subscriber registry to look up the E.164 + // and make a second P-Preferred-Identity header. +} + + +// Like this: Note no via branch, but registrar added one. +// REGISTER sip:127.0.0.1 SIP/2.0^M // this is the proxy IP +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M +// From: IMSI001010002220002 ;tag=vzmikpkffdomsjvb^M +// To: IMSI001010002220002 ^M +// Call-ID: 196117285@127.0.0.1^M +// CSeq: 226 REGISTER^M +// Contact: ;expires=5400^M +// User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M +// Max-Forwards: 70^M +// P-PHY-Info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M +// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-Preferred-Identity: ^M +// Content-Length: 0^M +// +// SIP/2.0 401 Unauthorized^M +// Via: SIP/2.0/UDP localhost:5064;branch=1;received=string_address@foo.bar^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M +// From: IMSI001010002220002 ;tag=vzmikpkffdomsjvb^M +// To: IMSI001010002220002 ^M +// Call-ID: 196117285@127.0.0.1^M +// CSeq: 226 REGISTER^M +// Contact: ;expires=5400^M +// WWW-Authenticate: Digest nonce=972ed867f7284fca8670e44a71f97040^M +// User-agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M +// Max-forwards: 70^M +// P-phy-info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M +// P-access-network-info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-preferred-identity: ^M +// Content-Length: 0^M +// +// REGISTER sip:127.0.0.1 SIP/2.0^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M +// From: IMSI001010002220002 ;tag=yyourwrzymenfffa^M +// To: IMSI001010002220002 ^M +// Call-ID: 2140400952@127.0.0.1^M +// CSeq: 447 REGISTER^M +// Contact: ;expires=5400^M +// Authorization: Digest, nonce=83775658971b9dc469ad88c3c3d5250b, uri=001010002220002, response=4f9eb260^M +// User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M +// Max-Forwards: 70^M +// P-PHY-Info: OpenBTS; TA=1 TE=0.423828 UpRSSI=-63.000000 TxPwr=13 DnRSSIdBm=-48^M +// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-Preferred-Identity: ^M +// Content-Length: 0^M + + + +// We wrap our REGISTER messages inside a dialog object, even though it is technically not a dialog. +SipMessage *SipBase::makeRegisterMsg(DialogType wMethod, const L3LogicalChannel* chan, string RAND, const FullMobileId &msid, const char *SRES) +{ + // TODO: We need to make a transaction here... + static const string registerStr("REGISTER"); + // The request URI is special for REGISTER; + string reqUri = string("sip:") + proxyIP(); + // We formerly allocated a new Dialog for each REGISTER message so the IMSI was stashed in the dialog, and localSipUri() worked. + //SipPreposition myUri(localSipUri()); + string username = msid.fmidUsername(); + // RFC3261 is somewhat contradictory on the From-tag and To-tag. + // The documentation for 'from' says the from-tag is always included. + // The examples in 24.1 show a From-tag but no To-tag. + // The To-tag includes the optional <>, and Paul at null team incorrectly thought the <> were required, + // so we will include them as that appears to be common practice. + string myUriString = makeUri(username,dsPeer()->mipName,0); // The port, if any, is already in mipName. + //string fromUriString = makeUriWithTag(username,dsPeer()->mipName,make_tag()); // The port, if any, is already in mipName. + SipPreposition toHeader("",myUriString,""); + SipPreposition fromHeader("",myUriString,make_tag()); + dsNextCSeq(); // Advance CSeqNum. + SipMessage *msg = makeRequest(registerStr,reqUri,username,&toHeader,&fromHeader,make_branch()); + + // Replace the Contact header so we can set the expires option. What a botched up spec. + // Replace the P-Preferred-Identity + unsigned expires; + if (wMethod == SIPDTRegister ) { + expires = 60*gConfig.getNum("SIP.RegistrationPeriod"); + if (SRES && strlen(SRES)) { + msg->msmAuthorizationValue = format("Digest, nonce=%s, uri=%s, response=%s",RAND.c_str(),msid.mImsi.c_str(),SRES); + } + } else if (wMethod == SIPDTUnregister ) { + expires = 0; + } else { assert(0); } + // We use myURI instead of localContact because the SIPDialog for REGISTER is shared by all REGISTER + // users and does not contain the personal info for this user. + //msg->msmContactValue = format("<%s>;expires=%u",myUriString.c_str(),expires); + msg->msmContactValue = localContact(username,expires); + writePrivateHeaders(msg,chan); + return msg; +} + + + + +string SipBase::makeSDPOffer() +{ + SdpInfo sdp; + sdp.sdpInitOffer(this); + return sdp.sdpValue(); + //return makeSDP("0","0"); +} + +// mCodec is an implicit parameter, consisting of the chosen codec. +string SipBase::makeSDPAnswer() +{ + SdpInfo answer; + answer.sdpInitOffer(this); + mSdpAnswer = answer.sdpValue(); + return mSdpAnswer; +} + + +// A request inside an invite is ACK, CANCEL, BYE, INFO, or re-INVITE. +// The only target-refresh-request is re-INVITE, which can change the "Dialog State". +// For ACK sec 17.1.1.3 the To must equal the To of the response being acked, which specifically contains a tag. +// ACK contains only one via == top via of original request with matching branch. +// TODO: ACK must copy route header fields from INVITE. +// sec 9.1 For CANCEL, all fields must == INVITE except the method. Note CSeq must match too. Must have single via +// matching the [invite] request being cancelled, with matching branch. TODO: Must copy route header from request. +// 15.1.1: BYE is a new request within a dialog as per section 12.2. +// transaction constructed as per with a new tag, branch, etc. +// It should not include CONTACT because it is non-target-refresh. +// Implicit parameters from SipBase: mCallId, mRemoteUsername, mRemoteDomain, mCSeq, etc. +SipMessage *SipBase::makeRequest(string method,string requestUri, string whoami, SipPreposition*toHeader, SipPreposition*fromHeader, string branch) +{ + SipMessage *invite = new SipMessage(); + invite->msmReqMethod = method; + invite->msmCallId = this->mCallId; + //string toContact = makeUri(mRemoteUsername,mRemoteDomain); // dialed_number@remote_ip + //string fromContact = makeUri(mSipUsername,localIP()); + //invite->msmReqUri = makeUri(mRemoteUsername,mRemoteDomain); + invite->msmReqUri = requestUri; + invite->smAddViaBranch(this,branch); + invite->msmCSeqMethod = invite->msmReqMethod; + invite->msmCSeqNum = mLocalCSeq; // We dont need to advance for an initial request; caller advances if necessary. + invite->msmTo = *toHeader; + invite->msmFrom = *fromHeader; + invite->msmContactValue = localContact(whoami); + string prefid = this->preferredIdentity(whoami); + static const string cPreferredIdentityString("P-Preferred-Identity"); + invite->smAddHeader(cPreferredIdentityString,prefid); + + // The caller has not added the content yet: saveInviteOrMessage(invite,true); + return invite; +} + +// This is an *initial* request only, for INVITE or MESSAGE. +// This version generates the request from the values in the DialogStateVars. +SipMessage *SipBase::makeInitialRequest(string method) +{ + string requestUri = dsRemoteURI(); + this->mInviteViaBranch = make_branch(); + return makeRequest(method,requestUri,sipLocalUsername(),&mRemoteHeader,&mLocalHeader,this->mInviteViaBranch); +} + +void SipMOInviteClientTransactionLayer::MOUssdSendINVITE(string ussd, const L3LogicalChannel *chan) +{ + static const char* xmlUssdTemplate = + "\n" + "\n" + " en\n" + " %s\n" + "\n"; + + LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() <smAddBody(string("application/sdp"),makeSDPOffer()); + + // Add RFC-4119 geolocation XML to content area, if available. + // TODO: This makes it a multipart message, needs to be tested. + string xml = format(xmlUssdTemplate,ussd); + invite->smAddBody(string("application/vnd.3gpp.ussd+xml"),xml); + + writePrivateHeaders(invite,chan); + moWriteLowSide(invite); + LOG(DEBUG) <\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%s\n" + "\n" + "\n" + "\n" + "\n" + "no\n" + "\n" + "\n" + "\n" + "\n" + "\n"; + + LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() < _X.,1,Set(Name=${ODBC_SQL(select dial from dialdata_table where exten=\"${EXTEN}\")}) + // exten => _X.,n,GotoIf($["${Name}"=""] ?other-lines,${EXTEN},1) + // exten => _X.,n,Set(IPAddr=${ODBC_SQL(select ipaddr from sip_buddies where username=\"${Name}\")}) + // exten => _X.,n,GotoIf($["${IPAddr}"=""] ?other-lines,${EXTEN},1) + // exten => _X.,n,Set(Port=${ODBC_SQL(select port from sip_buddies where username=\"${Name}\")}) + // exten => _X.,n,GotoIf($["${Port}"!=""] ?dialNum) + // exten => _X.,n,Set(Port=5062) ; Port was not set, so set to default. Gets around bug in subscriberRegistry + // exten => _X.,n(dialNum),Dial(SIP/${Name}@${IPAddr}:${Port}) + if (gConfig.getStr("SIP.Proxy.Mode") == string("direct")) { + // Is this IMSI registered directly on a BTS? + string remoteIMSI = gSubscriberRegistry.getIMSI(wCalledUsername); + string remoteIPStr, remotePortStr; + if (remoteIMSI != "") { + remoteIPStr = gSubscriberRegistry.imsiGet(remoteIMSI,"ipaddr"); + remotePortStr = gSubscriberRegistry.imsiGet(remoteIMSI,"port"); + unsigned remotePort = (remotePortStr != "") ? atoi(remotePortStr.c_str()) : 0; + LOG(DEBUG) << "BTS direct test: "< SIP/%s@%s:%s",remoteIMSI.c_str(),remoteIPStr.c_str(),remotePortStr.c_str()) < SIP/%s@%s:%u",mRemoteUsername.c_str(),mProxyIP.c_str(),mProxyPort) <smAddBody(string("application/sdp"),makeSDPOffer()); + + + string username = sipLocalUsername(); + WATCH("MOC imsi="<smAddHeader("P-Associated-URI",pAssociatedUri); + } + if (pAssertedIdentity.size()) { + invite->smAddHeader("P-Asserted-Identity",pAssertedIdentity); + } + } + + writePrivateHeaders(invite,chan); + moWriteLowSide(invite); + delete invite; + setSipState(Starting); +}; + + +void SipMOInviteClientTransactionLayer::handleSMSResponse(SipMessage *sipmsg) +{ + // There are only three cases, and here they are: + stopTimers(); + int code = sipmsg->smGetCode(); + if (code < 200) { + // (pat) 11-26 This was wrong: mTimerBF.set(64*T1); + return; // Ignore 100 Trying. + } + // Smqueue incorrectly requires us to add a from-tag, and sends a 400 error if we dont. + // So if we get a 400 error, see if the user agent is blank (which is what smqueue does, yate adds a correct user-agent) + // then make an attempt to redeliver the message with a from-tag. + // What a botch-up. + if (code == 400) { + string useragent = sipmsg->msmHeaders.paramFind("User-Agent"); + if (useragent == "") { + LOG(INFO) << "Message delivery failed; no user-agent specified. Assuming smqueue and resending MESSAGE"; + gPeerIsBuggySmqueue = true; + // Resend the message, and this time it will add the from-tag. + if (dgGetDialog()->MOSMSRetry()) { return; } + } + } + dialogPushState((code/100)*100 == 200 ? Cleared : SSFail,code); + mTimerK.setOnce(T4); // 17.1.2.2: Timer K Controls when the dialog is destroyed. +} + +void SipInviteServerTransactionLayerBase::SipMTCancel(SipMessage *sipmsg) +{ + assert(sipmsg->isCANCEL()); + SipMessageReply cancelok(sipmsg,200,string("OK"),this); + sipWrite(&cancelok); + if (dsPeer()->ipIsReliableTransport()) { + // It no longer matters whether we use Canceled or MTDCanceling state, and we should get rid of some states. + dialogPushState(Canceled,0); + } else { + dialogPushState(MTDCanceling,0); + setTimerJ(); + } +} + +void SipInviteServerTransactionLayerBase::SipMTBye(SipMessage *sipmsg) +{ + assert(sipmsg->isBYE()); + gReports.incr("OpenBTS.SIP.BYE.In"); + SipMessageReply byeok(sipmsg,200,string("OK"),this); + sipWrite(&byeok); + if (dsPeer()->ipIsReliableTransport()) { + dialogPushState(Cleared,0); + } else { + dialogPushState(MTDClearing,0); + setTimerJ(); + } +} + +// Incoming message from SIPInterface. +void SipMOInviteClientTransactionLayer::MOWriteHighSide(SipMessage *sipmsg) +{ + int code = sipmsg->smGetCode(); + LOG(DEBUG) <isCANCEL()) { + mTimerBF.stop(); + // I dont think the peer is supposed to do this, but lets cancel it: + SipMTCancel(sipmsg); + } else if (sipmsg->isBYE()) { + // A BYE should not be sent by the peer until dialog established, and we are supposed to send 405 error. + // but instead we are going to quietly terminate the dialog anyway. + SipMTBye(sipmsg); + } else { + // Not expecting any others. + // Must send 405 error. + LOG(WARNING)<<"SIP Message ignored:"<isINVITE() || sipmsg->isMESSAGE()) { // It cant be anything else. + if (!dsPeer()->ipIsReliableTransport()) { mTimerAE.set(2*T1); } + mTimerBF.setOnce(64*T1); // RFC3261 17.1.2.2.2 Timer F + // This assert is not true in the weird case where we resend an SMS message. + // We should not be using this code for MESSAGE in the first place - it should be a TU. + //assert(mInvite == 0); + saveInviteOrMessage(sipmsg,true); + } + sipWrite(sipmsg); +} + +// (pat) Counter-intuitively, the "ACK" is a SIP Request, not a SIP Response. +// Therefore its first line includes a Request-URI, and the request-uri is also placed in the "To" field. +// RFC2234 13.1: "The procedure for sending this ACK depends on the type of response. +// For final responses between 300 and 699, the ACK processing is done in the transaction layer and follows +// one set of rules (See Section 17). For 2xx responses, the ACK is generated by the UAC core. (section 13)" +// 17.1.1.3: "The ACK request constructed by the client transaction MUST contain +// values for the Call-ID, From, and Request-URI that are equal to the +// values of those header fields in the request passed to the transport +// by the client transaction (call this the "original request"). The To +// header field in the ACK MUST equal the To header field in the +// response being acknowledged, and therefore will usually differ from +// the To header field in the original request by the addition of the +// tag parameter. The ACK MUST contain a single Via header field, and +// this MUST be equal to the top Via header field of the original +// request. The CSeq header field in the ACK MUST contain the same +// value for the sequence number as was present in the original request, +// but the method parameter MUST be equal to "ACK". +// +void SipMOInviteClientTransactionLayer::MOCSendACK() +{ + assert(! mTimerAE.isActive() && ! mTimerBF.isActive()); + LOG(INFO) << sbText(); + //LOG(INFO) << "user " << mSipUsername << " state " << getSipState() <smAddBody(contentType,messageText); + moWriteLowSide(msg); + delete msg; + setSipState(MOSMSSubmit); +} + + +// This is a temporary routine to work around bugs in smqueue. +// Resend the message with changes (gPeerIsBuggySmqueue, set/reset by the caller) to see if it works any better. +bool SipMOInviteClientTransactionLayer::MOSMSRetry() +{ + LOG(INFO); + SipDialog *oldDialog = dgGetDialog(); + FullMobileId fromMsId(oldDialog->sipLocalUsername()); + SipDialog *newDialog = SipDialog::newSipDialogMOSMS( + mTranId, + fromMsId, // caller imsi + oldDialog->sipRemoteUsername(), + oldDialog->smsBody, + oldDialog->smsContentType); + gNewTransactionTable.ttSetDialog(oldDialog->mTranId,newDialog); + return true; // success + //dialog->mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600; + //dialog->dsSetCallId(globallyUniqueId("")); + //dialog->mLocalHeader.setTag(gPeerIsBuggySmqueue ? make_tag() : ""); + //dialog->MOSMSSendMESSAGE(mInvite->msmBody,mInvite->msmContentType); +} + +// Return TRUE to remove the dialog. +bool SipMOInviteClientTransactionLayer::moPeriodicService() +{ + if (mTimerAE.expired()) { // Resend timer. + // The HANDOVER is inbound, but the invite is outbound like MOC. + if (getSipState() == Starting || getSipState() == HandoverInbound || getSipState() == MOSMSSubmit) { + sipWrite(getInvite()); + mTimerAE.setDouble(); + } else { + mTimerAE.stop(); + } + } else if (mTimerBF.expired() || mTimerD.expired()) { // Dialog killer timer. + // Whoops. No response from peer. + stopTimers(); + dialogPushState(SSFail,0); + return true; + } else if (mTimerK.expired()) { // Normal exit delay to absorb resends. + stopTimers(); + if (! dgIsInvite()) { return true; } // It is a SIP MESSAGE. + } else if (sipIsFinished()) { + // If one of the kill timers is active, wait for it to expire, otherwise kill now. + return (mTimerBF.isActive() || mTimerD.isActive() || mTimerK.isActive()) ? false : true; + } + return false; +} + +string SipMOInviteClientTransactionLayer::motlText() const +{ + ostringstream os; + os <(this)); + cancelTU->sctStart(); + return getSipState(); +} + + + +// Type is RtpCallback, but args determined by rtp_signal_table_emit2 +extern "C" { + void ourRtpTimestampJumpCallback(RtpSession *session, unsigned long timestamp,unsigned long dialogid) + { + SipBase *dialog = gSipInterface.dmFindDialogByRtp(session); + if (dialog) { + LOG(NOTICE) << "RTP timestamp jump"<mRxTime) { + rtp_session_resync(session); + dialog->mRxTime = 0; + dialog->mRxRealTime = 0; + } + } else { + LOG(ALERT) << "RTP timestamp jump, but no dialog"<= 63200) { + mDTMFEnding = 1; + } + //volume 10 for some magic reason, arg 3 is true to send an end packet. + // (pat) The magic reason is that the spec says DTMF tones below a certain dB should be ignored by the receiver, which is dumber than snot. + int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),!!mDTMFEnding,10,mDTMFDuration); + int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime); + LOG(DEBUG) <= 3) { + mDTMFEnding = 0; + mDTMF = 0; + } + } + return (!code && bytes > 0); +} + +// Return true if ok, false on failure. +bool SipRtp::startDTMF(char key) +{ + LOG (DEBUG) << key <clock_rate in rtp_session_ts_to_time to yield 20ms. +void SipRtp::txFrame(GSM::AudioFrame* frame, unsigned numFlushed) +{ + if(getSipState()!=Active) return; + ScopedLock lock(mRtpLock); + + // HACK -- Hardcoded for GSM/8000. + // FIXME: Make this work for multiple vocoder types. (pat) fixed, but codec is still hard-coded in initRTP1. + int nbytes = frame->sizeBytes(); + // (pat 8-2013) Our send stream is discontinous. After a discontinuity, the sound degrades. + // I think this is caused by bugs in the RTP library. + + mTxTime += (numFlushed+1)*160; + int result = rtp_session_send_with_ts(mSession, frame->begin(), nbytes, mTxTime); + LOG(DEBUG) << LOGVAR(mTxTime) <begin(), maxsize, mRxTime, &more); + // (pat) You MUST increase rxTime even if rtp_session_recv... returns 0. + // This looks like a bug in the RTP lib to me, specifically, here: + // Excerpt from rtpsession.c: rtp_session_recvm_with_ts(): + // // prevent reading from the sockets when two consecutives calls for a same timestamp*/ + // if (user_ts==session->rtp.rcv_last_app_ts) + // read_socket=FALSE; + // The bug is the above should also check the qempty() flag. + // It should only manifest when blocking mode is off but we had it on when I thought I saw troubles. + // I tried incrementing by just 1 when ret is 0, but that resulted in no sound at all. + + if (!rtpUseRealTime) { mRxTime += 160; } + + //LOG(DEBUG) << "rtp_session_recv returns("<setSizeBits(ret * 8); + // (pat) Added warning; you could get ALOT of these: + // Update: It is not that the frame is over-sized, it is that there is another frame already in the queue. + //if (more) { LOG(WARNING) << "Incoming RTP frame over-sized, extra ignored." <isINVITE(); +} + +// Are these the same dialogs? +bool SipBase::sameDialog(SipDialog *other) +{ + if (this->callId() != other->callId()) { return false; } + if (this->dsLocalTag() != other->dsLocalTag()) { return false; } + if (this->dsRemoteTag() != other->dsRemoteTag()) { return false; } + // Good enough. + return true; +} + +// Does this incoming message want to be processed by this dialog? +// NOTE: This is temporary until we fully support SipTransactions, in which case this will be +// used only for in-dialog requests, and in that case both to- and from-tags must match. +// This may not be correct for group calls - we may want to search each of several possible +// matching dialogs for the transaction matching the via-branch. +bool SipBase::matchMessage(SipMessage *msg) +{ + // The caller already checked the callid, but lets do it again in case someone modifies the code later. + if (msg->smGetCallId() != this->callId()) { return false; } + // This code could be simplified by combining logic, but I left it verbose for clarity + if (msg->isRequest()) { + // If it is a request sent by the peer, the remote tag must match. Both empty is ok. + if (msg->smGetRemoteTag() != this->dsRemoteTag()) { return false; } + // The local tag in the message is either empty (meaning the peer has not received it yet) or matches. + string msgLocalTag = msg->smGetLocalTag(); + if (! msgLocalTag.empty()) { + if (msgLocalTag != this->dsLocalTag()) { return false; } + } + } else { + // If it is a reply, it means the original request was sent by us. The local tags must match. Both empty is ok. + if (msg->smGetLocalTag() != this->dsLocalTag()) { return false; } + // The remote tag in the dialog is either empty (has not been set yet, will probably be set by this message), or matches. + string remoteTag = dsRemoteTag(); + if (! remoteTag.empty()) { + if (remoteTag != msg->smGetRemoteTag()) { return false; } + } + } + return true; // Good enough. +} + + +// 17.2.3 tells how to match requests to server transactions, but that does not apply to this. + +/* return true if this is exactly the same invite (not a re-invite) as the one we have stored */ +bool SipBase::sameInviteOrMessage(SipMessage * msg) +{ + ScopedLock lock(mDialogLock,__FILE__,__LINE__); // probably unnecessary. + assert(getInvite()); + if (NULL == msg){ + LOG(NOTICE) << "trying to compare empty message" <smGenerate(); + //LOG(DEBUG) <(this); +} + +void SipMTInviteServerTransactionLayer::MTCSendTrying() +{ + SipMessage *invite = getInvite(); + if (invite==NULL) { + setSipState(SSFail); + gReports.incr("OpenBTS.SIP.Failed.Local"); + } + LOG(INFO) << sbText(); + if (getSipState()==SSFail) return; + SipMessageReply trying(invite,100,string("Trying"),this); + mtWriteLowSide(&trying); + setSipState(Proceeding); +} + +void SipMTInviteServerTransactionLayer::MTSMSSendTrying() +{ + MTCSendTrying(); +} + + +void SipMTInviteServerTransactionLayer::MTCSendRinging() +{ + if (getSipState()==SSFail) return; + SipMessage *invite = getInvite(); + LOG(INFO) <smGetCode() == 0) { + if (sipmsg->isINVITE()) { + if (mtLastResponse.smIsEmpty()) { MTCSendTrying(); } + else { sipWrite(&mtLastResponse); } + } else if (sipmsg->isACK()) { + if (state == SSNullState || state == Proceeding || state == Connecting) { + dialogPushState(Active,0); + gSipInterface.dmAddLocalTag(dgGetDialog()); + } else { + // We could be failed or canceled, so ignore the ACK. + } + stopTimers(); // happiness + // The spec shows a short Timer I being started here, but all it does is specify a time + // when the dialog will stop absorbing additional ACKS, thus suppressing error messages. + // Well, how about if we just never throw an error for that? Done. + } else if (sipmsg->isCANCEL()) { + SipMTCancel(sipmsg); + } else if (sipmsg->isBYE()) { // TODO: This should not happen. + SipMTBye(sipmsg); + } else if (sipmsg->isMESSAGE()) { + // It is a duplicate MESSAGE. Resend the current response. + if (mtLastResponse.smIsEmpty()) { + if (! gConfig.getBool("SIP.RFC3428.NoTrying")) { MTSMSSendTrying(); } // Otherwise just ignore the duplicate MESSAGE. + } else { + sipWrite(&mtLastResponse); + } + } else { + // Not expecting any others. Must send 405 error. + LOG(WARNING)<<"SIP Message ignored:"< +#include +#include +#include + + +#include +#undef WARNING // The nimrods defined this to "warning" +#undef CR // This too + + +#include +#include +#include +#include +#include +#include "SIPUtility.h" +#include "SIPMessage.h" + +extern int gCountRtpSessions; +extern int gCountRtpSockets; +extern int gCountSipDialogs; + +namespace SIP { +class SipDialog; +using namespace Control; +using namespace std; + +extern bool gPeerIsBuggySmqueue; +extern bool gPeerTestedForSmqueue; + +extern void writePrivateHeaders(SipMessage *msg, const L3LogicalChannel *chan); + +// These could be global. +inline string localIP() { // Replaces mSIPIP. + return gConfig.getStr("SIP.Local.IP"); +} +inline string localIPAndPort() { // Replaces mSIPIP and mSIPPort. + return format("%s:%u", localIP(), (unsigned) gConfig.getNum("SIP.Local.Port")); +} + +// These are the overall dialog states. +enum SipState { + SSNullState, + SSTimeout, + Starting, // (pat) MOC or MOSMS or inboundHandover sent INVITE; MTC not used. + Proceeding, // (pat) MOC received Trying, Queued, BeingForwarded; MTC sent Trying + Ringing, // (pat) MOC received Ringing, notably not used for MTC sent Ringing, which is probably a bug of no import. + MOCBusy, // (pat) MOC received Busy; MTC not used. + Connecting, // (pat) MTC sent OK. + Active, // (pat) MOC received OK; MTC sent ACK + MODClearing, // (pat) MOD sent BYE + MODCanceling, // (pat) MOD sent a cancel, see forceSIPClearing. + MODError, // (pat) MOD sent an error response, see forceSIPClearing. + MTDClearing, // (pat) MTD received BYE. + MTDCanceling, // (pat) MTD received CANCEL + Canceled, // (pat) received OK to CANCEL. + Cleared, // (pat) MTD sent OK to BYE, or MTD internal error loss of FIFO, or MOSMS received OK, or MTSMS sent OK. + + //SipRegister, // (pat) This SIPEngine is being used for registration, none of the other stuff applies. + //SipUnregister, // (pat) This SIPEngine is being used for registration, none of the other stuff applies. + SSFail, + + MOSMSSubmit, // (pat) SMS message submitted, "MESSAGE" method. Set but never used. Message success indicated by Cleared. + // (pat) The handover states are invalid SIPEngine states, set during initial state for handover, + // used only to check if SIPEngine is dead because this state was never changed to a valid SIP state. + HandoverInbound, + //HandoverInboundReferred, // pat removed, totally unused. + HandoverOutbound // (pat) Another fine state that we dont ever use. +}; +std::ostream& operator<<(std::ostream& os, SipState s); +extern const char* SipStateString(SipState s); + +enum DialogType { SIPDTUndefined, SIPDTRegister, SIPDTUnregister, SIPDTMOC, SIPDTMTC, SIPDTMOSMS, SIPDTMTSMS, SIPDTMOUssd }; + +class DialogMessage; +class SipTransaction; + +// This is a simple attachment point for a TranEntry. +//typedef InterthreadQueue DialogMessageFIFO; +//typedef list SipTransactionList; +class SipEngine { + + public: + Control::TranEntryId mTranId; // So we can find the TransactionEntry that owns this SIPEngine. + + //DialogMessageFIFO mDownlinkFifo; // Messages from the SIP layer to Control Layer3. Layer3 will read them from here. + //DialogMessage *dialogRead(); + void dialogQueueMessage(DialogMessage *dmsg); + + //SipTransactionList mTransactionList; + void setTranId(Control::TranEntryId wTranId) { mTranId = wTranId; } + + // Dont delete these empty constructor/destructors. They are needed because InterthreadQueue calls + // delete DialogMessage and it causes weird error messages without these. + SipEngine(); + virtual ~SipEngine(); +}; + +// This class is for variables that not even SipBase is allowed to change directly. +class SipBaseProtected +{ + virtual void _define_vtable(); + // You must not change mState without going through setSipState to make sure the state age is updated. + SipState mState; ///< current SIP call state + Timeval mStateAge; + virtual DialogType getDialogType() const = 0; + + public: + /** Return the current SIP call state. */ + SipState getSipState() const { return mState; } + /** Set the state. Must be called by for all internal and external users. */ + void setSipState(SipState wState) { mState=wState; mStateAge.now(); } + + /** Return TRUE if it looks like the SIP session is stuck indicating some internal error. */ + bool sipIsStuck() const; + + /** Return true if the call has finished, successfully or not. */ + bool sipIsFinished() const { + switch (mState) { + case Cleared: case MODClearing: case MTDClearing: + case Canceled: case MODCanceling: case MTDCanceling: + case SSFail: case MODError: + case SSTimeout: + return true; + case SSNullState: + case Starting: + case Proceeding: + case Ringing: + case MOCBusy: + case Connecting: + case Active: + case MOSMSSubmit: + case HandoverInbound: + case HandoverOutbound: + return false; + } + } + + SipBaseProtected() : mState(SSNullState) { mStateAge.now(); } +}; + + +// The 'Dialog State' is a defined term in RFC3261. It is established by the INVITE and 2xx reply, +// and can only be modified by re-INVITE. +// Be careful with the name - we also use a DialogState to mean the integral dialog state, so we call this a context. +class DialogStateVars { + protected: + friend class SipTransaction; + // RFC3261 uses the terms 'remote URI' and 'remote target URI' interchangably. + // The To and From headers optionally include a Display Name as well as the URI. + // We are going to save the whole contact here, including the display name, + // even though the Display Names are technically not part of the DialogStateVars. + SipPreposition mLocalHeader; // Used in From: + SipPreposition mRemoteHeader; // Used in To: and sometimes in request-URI. Updated from Contact: header. + string mRouteSet; // Complicated. See RFC-3261 12.2.1.1 Immutable once set. + std::string mCallId; // (pat) dialog-id for inbound sip messages but also used for some outbound messages. + IPAddressSpec mProxy; // The remote URL. It starts out as the proxy address, but is set from the Contact for established dialogs. + public: + unsigned mLocalCSeq; // The local CSeq for in-dialog requests. We dont bother tracking the remote CSeq. + // 14.1: The Cseq for re-INVITE follows same rules for in-dialog requests, namely, + // CSeq num must be incremented by one. + + DialogStateVars(); + string dsLocalTag() { return mLocalHeader.mTag; } + string dsRemoteTag() { return mRemoteHeader.mTag; } + string sipLocalUsername() const { return mLocalHeader.mUri.uriUsername(); } // This is the IMSI or IMEI. + string sipRemoteUsername() const { return mRemoteHeader.mUri.uriUsername(); } + string sipRemoteDisplayname() const { return mRemoteHeader.mDisplayName.empty() ? sipRemoteUsername() : dequote(mRemoteHeader.mDisplayName); } + + // Various ways to set local and remote headers: + //void dsSetLocalNameAndHost(string username, string host, unsigned port = 0) { + // mLocalHeader.prepSetUri(makeUri(username,host,port)); + //} + // This sets the local URI without a tag. It is used for non-dialogs + private: + void dsSetLocalUri(string uri) { mLocalHeader.prepSetUri(uri); } + public: + void dsSetRemoteUri(string uri) { mRemoteHeader.prepSetUri(uri); } + //void dsSetRemoteNameAndHost(string username, string host, unsigned port = 0) { + // mRemoteHeader.prepSetUri(makeUri(username,host,port)); + //} + void dsSetLocalHeaderMT(SipPreposition *toheader, bool addTag); + void dsSetLocalMO(const FullMobileId &msid, bool addTag); + // Set local header directly, used for handover: + void dsSetLocalHeader(const SipPreposition *header) { LOG(DEBUG); mLocalHeader = *header; } + void dsSetRemoteHeader(const SipPreposition *header) { LOG(DEBUG); mRemoteHeader = *header; } + void dsSetCallId(const std::string wCallId) { mCallId = wCallId; } + + // RFC3261 12.2.1.1: "The URI in the To field of the request MUST be set to the remote URI + // from the dialog state. The tag in the To header field of the request + // MUST be set to the remote tag of the dialog ID. The From URI of the + // request MUST be set to the local URI from the dialog state. The tag + // in the From header field of the request MUST be set to the local tag + // of the dialog ID. If the value of the remote or local tags is null, + // the tag parameter MUST be omitted from the To or From header fields, respectively. + + // Accessors: + // We set the remote-tag when we create the URI for a dialog. + // We set the local-tag the first time we hear from the peer, which for an inbound INVITE is + // in that invite, or for outbound INVITE is the first response. + //string dsRequestToHeader() const { return mRemoteHeader.toFromValue(); } + //string dsRequestFromHeader() const { return mLocalHeader.toFromValue(); } + // This does alot of copying, but at least they are strings so it is just refcnt incrementing. + const SipPreposition *dsRequestToHeader() const { return &mRemoteHeader; } + const SipPreposition *dsRequestFromHeader() const { return &mLocalHeader; } + //string dsReplyToHeader() const { return mLocalHeader.toFromValue(); } + //string dsReplyFromHeader() const { return mRemoteHeader.toFromValue(); } + const SipPreposition *dsReplyToHeader() const { return &mLocalHeader; } + const SipPreposition *dsReplyFromHeader() const { return &mRemoteHeader; } + //string dsRemoteURI() const { return mRemoteHeader.mUri.uriValue(); } + string dsRemoteURI() const { return format("sip:%s@%s",sipRemoteUsername(),mProxy.mipName); } + // TODO_NOW: THIS IS WRONG! See SipMessageRequestWithinDialog + //string dsInDialogRequestURI() const { return mRemoteHeader.mUri.uriAddress(); } + string dsInDialogRequestURI() const { return dsRemoteURI(); } + unsigned dsNextCSeq() { return ++mLocalCSeq; } + string dsToString() const; + const std::string& callId() const { return mCallId; } + const IPAddressSpec *dsPeer() const { return &mProxy; } + string localIP() const { return SIP::localIP(); } + string localIPAndPort() const { return SIP::localIPAndPort(); } + void updateProxy(const char *sqlOption) { + string proxy = gConfig.getStr(sqlOption); + if (! proxy.empty() && ! mProxy.ipSet(proxy,sqlOption)) { + LOG(ALERT) << "cannot resolve IP address for"< INVITE -> them +// us <- 1xx whatever <- them +// us <- 200 OK <- them +// us -> ACK -> them +// We dont use server transactions for requests within an INVITE (RFC3261 section 17.2.2 Figure 8) +// - the only thing the server transaction has to do is resend the reply if a new request arrives, +// so that is just handled by the dialog. +// For the INVITE server transaction (figure 7), the transaction layer is required to resend the 2xx +// MT, uses SIP Server transaction: +// us <- INVITE <- them duplicate INVITE handled by SIP2Interface, not SipServerTransaction +// us -> 1xx whatever -> them +// us -> 200 OK -> them +// us <- ACK <- them +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipBase,MemCheckSipBase) +class SipBase : public MemCheckSipBase, + public RefCntBase, public SipEngine, public DialogStateVars, public SipRtp, public SipBaseProtected +{ + friend class SipInterface; +protected: + mutable Mutex mDialogLock; + /**@name General SIP tags and ids. */ + + SipMessage* mInvite; ///< the INVITE or MESSAGE message for this transaction +public: + SipMessage *getInvite() const { return mInvite; } + + /**@name SIP UDP parameters */ + //@{ + SipState getSipState() const { return SipBaseProtected::getSipState(); } // stupid language. + + + string localSipUri() { + return format("sip:%s@%s", sipLocalUsername(), localIPAndPort()); + } + // Normally the localUserName would just be sipLocalUserName from the SipDialog, but + // REGISTER is special because the on-going dialog for all REGISTER messages does not contain + // the user info for any particular REGISTER, so we need to pass the user name in for that. + string preferredIdentity(string localUsername) { + return format("",localUsername,localIP()); + } + string localContact(string localUsername, int expires=3600) { + return format(";expires=%u",localUsername,localIPAndPort(),expires); + } + string proxyIP() const { return mProxy.mipIP; } + string transportName() { return string("UDP"); } // TODO + bool isReliableTransport() { return transportName() == "TCP"; } // TODO + //@} + + //bool mInstigator; ///< true if this side initiated the call + DialogType mDialogType; + unsigned mDialogId; // A numeric id in case we need it. It may be used for the RTP call back. + + /**@name Saved SIP messages. */ + //@{ + // The SDP we received from the peer, in the MTC invite or the MOC 200 OK. We need it for the IP and port number. + // Note that in the MTC case the peer info is the offer, while in the MOC case it is the final answer. + //string mSdpPeerInfo; + string mSdpOffer, mSdpAnswer; + string getSdpRemote() const { return mDialogType == SIPDTMTC ? mSdpOffer : mSdpAnswer; } + SipMessage *mLastResponse; // used only for the 200 OK to INVITE to retrive the sdp stuff. + // Return the response code, only for MO invites. + int getLastResponseCode() { return mLastResponse ? mLastResponse->smGetCode() : 0; } + //@} + + // These go in the Transaction: + std::string mInviteViaBranch; // (pat) This is part of the individual Transaction. + + // Make an initial request: INVITE MESSAGE REGISTER + SipMessage *makeRequest(string method,string requri, string whoami, SipPreposition* toContact, SipPreposition* fromContact, string branch); + // Make a standard initial request with info from the DialogState. + SipMessage *makeInitialRequest(string method); // with default URIs +public: + Bool_z mIsHandover; // TRUE if this dialog was established by an inbound handover. + + // Warning: sipWrite is only for non-invite transactions. + // Instead use moWriteLowSide or mtWriteLowSide for INVITE or MESSAGE transactions. + // However, sipWrite is called from L3MobilityManagement.cpp for the register message in the pre-transaction code + void sipWrite(SipMessage *sipmsg); + + /** + Default constructor. Initialize the object. + // (pat) New way is to call SipDialogMT or SipDialogMO + @param proxy : + */ + void SipBaseInit(); + void SipBaseInit(DialogType wDialogType, string proxy, const char *proxyProvenance); // , TranEntryId wTranId) + + // This constructor is just to count them. Initialization is via SipBaseInit(). + SipBase() { gCountSipDialogs++; } + /** Destroy held message copies. */ + virtual ~SipBase(); + + bool dgIsInvite() const; // As opposed to a MESSAGE. + + // Return if we are the server, ie, return true if communication was started by peer, not us. + DialogType getDialogType() const { return mDialogType; } + bool dgIsServer() const { return mDialogType == SIPDTMTC || mDialogType == SIPDTMTSMS; } + + /**@name Messages for SIP registration. */ + //@{ + /** + Send sip register and look at return msg. + Can throw SIPTimeout(). + @return True on success. + */ + SipMessage *makeRegisterMsg(DialogType wMethod, const L3LogicalChannel* chan, string RAND, const FullMobileId &msid, const char *SRES = NULL); + + /** + Send sip unregister and look at return msg. + Can throw SIPTimeout(). + @return True on success. + */ + //bool unregister() { return (Register(SIPDTUnregister)); }; + + //@} + + // unused string makeSDP(string rtpSessionId, string rtpVersionId); + string makeSDPOffer(); + string makeSDPAnswer(); + + + /**@name Messages for MOD procedure. */ + //@{ + SipState MODSendCANCEL(); + //@} + + /** Save a copy of an INVITE or MESSAGE message in the engine. */ + void saveInviteOrMessage(const SipMessage *INVITE, bool mine); + + /** Save a copy of a response message to an MO request in the engine. */ + void saveMOResponse(SipMessage *respsonse); + + /** Determine if this invite matches the saved one */ + bool sameInviteOrMessage(SipMessage * msg); + + bool sameDialog(SipDialog *other); + bool matchMessage(SipMessage *msg); + SipDialog *dgGetDialog(); + + public: + void sbText(std::ostringstream&os, bool verbose=false) const; + string sbText() const; + + /** + Initialize the RTP session. + @param msg A SIP INVITE or 200 OK wth RTP parameters + */ + public: + void initRTP(); + void MOCInitRTP(); + void MTCInitRTP(); + + /** + Generate a standard set of private headers on initiating messages. + */ + string dsHandoverMessage(string peer) const; + //bool dsDecapsulate(string msgstr); +}; + + +// Typical OpenBTS message stream for MOC to MTC on same BTS: +// MOC invite-> +// MOC <-100 +// MTC <-invite +// MTC 100-> +// MTC 180-> +// MOC <-180 +// MTC 200-> +// MTC <-ACK +// MOC <-200 +// CC Connect + +class SipInviteServerTransactionLayerBase: public SipTimers, public virtual SipBase +{ + public: + virtual void dialogPushState(SipState newState, int code, char letter=0) = 0; + // This is used by inbound BYE or CANCEL. We dont care which because both kill off the dialog. + SipTimer mTimerJ; + void setTimerJ() { if (!dsPeer()->ipIsReliableTransport()) { mTimerJ.setOnce(64 * T1); } } + void SipMTBye(SipMessage *sipmsg); + void SipMTCancel(SipMessage *sipmsg); +}; + +// The SipStates used here and their correspondence to RFC3261 server transaction states are: +// Our SipState | RFC3261 INVITE server | RFC3261 non-INVITE server +// Starting | N/A | Trying +// Proceeding | Proceeding | Proceeding +// Connecting | TU sent OK, TL goes to state Terminated, but we go to Completed | N/A +// Active | Confirmed | Completed +// SSFail, various cancel/bye states +class SipMTInviteServerTransactionLayer : public virtual SipInviteServerTransactionLayerBase +{ + SipTimer mTimerG; // Resend response for unreliable transport. + + // TimerHJ is how long we wait for a response from the peer before declaring failure. + // For non-INVITE it is TimerJ, and for INVITE it is timerH. + // For INVITE it is the wait for additional requests to be answered with the previous response. + // In RFC3261 the 200 OK reply is passed to the TU which needs a similar delay; but we are going to use the same + // state machine to send the 200 OK response, which makes it look more similar to the non-INVITE server transaction (figure 8.) + // After an ACK is received, TimerI is how long we will soak up additional ACKs, but who cares? We will soak + // them up as long as the dialog is extant. + SipTimer mTimerH; + + // There is no SIP specified timer for the 2xx response to an INVITE. + // Eventually the MS will stop waiting and we will be canceled from that side. + + SipMessage mtLastResponse; + + void stopTimers() { mTimerG.stop(); mTimerH.stop(); /*mTimerI.stop();*/ } + void setTimerG() { if (!dsPeer()->ipIsReliableTransport()) { mTimerG.setOnce(T1); } } + void setTimerH() { if (!dsPeer()->ipIsReliableTransport()) { mTimerH.setOnce(64 * T1); } } + + protected: + void mtWriteLowSide(SipMessage *sipmsg) { // Outgoing message. + mtLastResponse = *sipmsg; + sipWrite(sipmsg); + } + + public: + + void MTCSendTrying(); + void MTCSendRinging(); + void MTCSendOK(CodecSet wCodec, const L3LogicalChannel *chan); + + // Doesnt seem like messages need the private headers. + void MTSMSReply(int code, const char *explanation); // , const L3LogicalChannel *chan) + void MTSMSSendTrying(); + + // This can only be used for early errors before we get the ACK. + void MTCEarlyError(int code, const char*reason); // The message must be 300-699. + + // This is called for the second and subsequent received INVITEs as well as the ACK. + // We send the current response, whatever it is. + void MTWriteHighSide(SipMessage *sipmsg); + + // Return TRUE to remove the dialog. + bool mtPeriodicService(); + string mttlText() const; // MT Transaction Layer Text +}; + + +class SipMOInviteClientTransactionLayer : public virtual SipInviteServerTransactionLayerBase +{ + SipTimer mTimerAE; // Time to resend initial INVITE for non-reliable transport. + SipTimer mTimerBF; // Timeout during INVITE phase. + virtual void handleInviteResponse(int status, bool sendAck) = 0; + void stopTimers() { mTimerAE.stop(); mTimerBF.stop(); mTimerK.stop(); mTimerD.stop(); } + + protected: + // Timers K and D are for non-invite client transactions, MO BYE and MO CANCEL. + SipTimer mTimerK; // Timeout destroys dialog. + SipTimer mTimerD; // Timeout destroys dialog. + void MOCSendINVITE(const L3LogicalChannel *chan = NULL); + void MOUssdSendINVITE(string ussd, const L3LogicalChannel *chan = NULL); + void handleSMSResponse(SipMessage *sipmsg); + void MOWriteHighSide(SipMessage *sipmsg); // Incoming message from outside, called from SIPInterface. + void moWriteLowSide(SipMessage *sipmsg); // Outgoing message from us, called only by SIPBase descendents. + + public: + void MOCSendACK(); + void MOSMSSendMESSAGE(const string &messageText, const string &contentType); + + // This is a temporary routine to work around bugs in smqueue. + // Resend the message with changes (made by the caller) to see if it works any better. + bool MOSMSRetry(); + bool moPeriodicService(); // Return TRUE to remove the dialog. + string motlText() const; // MO Transaction Layer Text +}; + + +}; +#endif diff --git a/SIP/SIPDialog.cpp b/SIP/SIPDialog.cpp new file mode 100644 index 0000000..4225bac --- /dev/null +++ b/SIP/SIPDialog.cpp @@ -0,0 +1,919 @@ +/* +* 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 distribuion. +* +* 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::SIP // Can set Log.Level.SIP for debugging + +#include "SIPBase.h" +#include "SIPDialog.h" +#include "SIP2Interface.h" +#include "SIPTransaction.h" +//#include +#include +#include +#include // for L3CMServiceType + +namespace SIP { +using namespace Control; +SipDialog *gRegisterDialog = NULL; + +SipDialog *getRegistrar() +{ + if (gRegisterDialog == NULL) { + gRegisterDialog = SipDialog::newSipDialogRegister1(); + } else { + // This allows the user to change SIP.Proxy.Registration from the CLI. + gRegisterDialog->updateProxy("SIP.Proxy.Registration"); + } + return gRegisterDialog; +} + +void SipDialog::dgReset() +{ + mPrevDialogState = DialogState::dialogUndefined; sipStopTimers(); + //mDownlinkFifo.clear(); +} + + +void SipDialog::MODSendBYE() +{ + LOG(INFO) <sctStart(); +} + +void SipDialog::sendInfoDtmf(unsigned bcdkey) +{ + // Has a previous DTMF not finished yet? + + // Start a new Sip INFO Transaction to send the key off. + SipDtmfTU *dtmfTU = new SipDtmfTU(this,bcdkey); + dtmfTU->sctStart(); +} + +// (pat) This is the post-l3-rewrite way, most initialization during construction. +SipDialog *SipDialog::newSipDialogMT(DialogType dtype, SipMessage *req) +{ + LOG(DEBUG); + assert(dtype == SIPDTMTC || dtype == SIPDTMTSMS); + string proxy = req->smGetProxy(); // Get it from the top via. + if (proxy.empty()) { + LOG(ERR) << "Missing proxy (from top via) in MT SIP message:"<dsSetLocalHeaderMT(&req->msmTo,true); //dtype == SIPDTMTC); + */ + dialog->dsSetLocalHeaderMT(&req->msmTo,dtype == SIPDTMTC); + dialog->dsSetRemoteHeader(&req->msmFrom); + //dialog->mSipUsername = req->smUriUsername(); // IMSI/TMSI is in both the URI and the To: header. + // TODO: Validate username - must be valid IMSI or TMSI. + ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // probably unnecessary. + dialog->dsSetCallId(req->msmCallId); + dialog->mSdpOffer = req->msmBody; // Only useful for MTC, a no-op for MTSMS. + dialog->saveInviteOrMessage(req,false); + gSipInterface.dmAddCallDialog(dialog); + return dialog; +} + +// There is just one SipDialog that handles all REGISTER requests. +SipDialog *SipDialog::newSipDialogRegister1() // caller imsi +{ + LOG(DEBUG); + SipDialog *dialog = new SipDialog(SIPDTRegister,gConfig.getStr("SIP.Proxy.Registration"),"SIP.Proxy.Registration"); + // RFC3261 10.2: REGISTER fields are different from normal requests. + // The Request URL is the IP address (only) of the Registrar. + // The To: is the 'address of record' formatted as a SIP URI. + // The From: is the 'responsible party' and is equal to To: unless it is a third-party registration. + // What about tags? I dont think it needs them because it is not a dialog creating request, but we add them + // anyway and it hasn't hurt anything. + dialog->dsSetCallId(globallyUniqueId("")); + gSipInterface.dmAddCallDialog(dialog); + return dialog; +} + +// Open an MOSMS [Mobile Originated Short Message Service] SIP Transaction and send the invite. +// We use a dialog for this even though it is just a message because it was easier to interface +// to the Control directory without changing anything. +SipDialog *SipDialog::newSipDialogMOSMS( + TranEntryId tranid, + const FullMobileId &fromMsId, // caller imsi + const string &calledDigits, // number being called, or it may be config option SIP.SMSC + const string &body, + const string &contentType) +{ + LOG(DEBUG) <dsSetLocalMO(fromMsId,gPeerIsBuggySmqueue ? true : false); + string calledDomain = dialog->localIP(); + dialog->dsSetRemoteUri(makeUri(calledDigits,calledDomain)); + + dialog->smsBody = body; // Temporary until smqueue is fixed. + dialog->smsContentType = contentType; // Temporary until smqueue is fixed. + + // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog + // while we finish construction. + ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); + gSipInterface.dmAddCallDialog(dialog); + //dialog->MOSMSSendMESSAGE(calledDigits,calledDomain,body,contentType); + gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. + dialog->MOSMSSendMESSAGE(body,contentType); + return dialog; +} + +SipDialog *SipDialog::newSipDialogMOUssd( + TranEntryId tranid, + const FullMobileId &fromMsId, // caller imsi + const string &wUssd, // USSD string entered by user to send to network. + L3LogicalChannel *chan + ) +{ + LOG(DEBUG) << "MOUssd (INVITE)"< 259) { // TODO: This should be in the config checker, if anywhere. + LOG(ALERT) << "Configured " <dsSetLocalMO(fromMsId,true); + gReports.incr("OpenBTS.SIP.INVITE.Out"); + // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog + // while we finish construction. + ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // Must lock before dmAddCallDialog. + + if (proxy == "testmode") { + gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. + DialogUssdMessage *dmsg = new DialogUssdMessage(tranid,DialogState::dialogBye,0); + dmsg->dmMsgPayload = "Hello from OpenBTS. You entered:"+wUssd; + LOG(DEBUG) << "USSD test mode"<dmMsgPayload; + dialog->dialogQueueMessage(dmsg); + return dialog; + } + dialog->dsSetRemoteUri(makeUri(wUssd,dialog->localIP())); + // TODO: What about codecs? The example in 24.390 annex A has them. + + gSipInterface.dmAddCallDialog(dialog); + gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. + dialog->MOUssdSendINVITE(wUssd,chan); + return dialog; +} + +// Open an MOC [Mobile Originated Call] dialog and send the invite. +SipDialog *SipDialog::newSipDialogMOC( + TranEntryId tranid, + const FullMobileId &fromMsId, // caller imsi + const string &wCalledDigits, // number being called, or empty for an emergency call. + CodecSet wCodecs, // phone capabilities + L3LogicalChannel *chan + ) +{ + +#ifdef C_CRELEASE + LOG(DEBUG) << "MOC SIP (INVITE)"< 259) { // TODO: This should be in the config checker, if anywhere. + LOG(ALERT) << "Configured " <dsSetLocalMO(fromMsId,true); + + { + gReports.incr("OpenBTS.SIP.INVITE.Out"); + dialog->dsSetRemoteUri(makeUri(wCalledDigits,dialog->localIP())); + } + + dialog->mRTPPort = Control::allocateRTPPorts(); + dialog->mCodec = wCodecs; + + // Must lock once we do dmAddCallDialog to prevent the SIPInterface threads from accessing this dialog + // while we finish construction. + ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); // Must lock before dmAddCallDialog. + gSipInterface.dmAddCallDialog(dialog); + gNewTransactionTable.ttSetDialog(tranid,dialog); // Must do this before the dialog receives any messages. + dialog->MOCSendINVITE(chan); + return dialog; +} + +// This is called in BS2 after a handover complete is received. It is an inbound handover, but an outoing MO re-INVITE. +// We take the SIP REFER message created by BS1 and send it to the SIP server as a re-INVITE. +// Note that the MS may go from BS1 to BS2 and back to BS1, in which case there may +// already be an existing dialog in some non-Active state. +SipDialog *SipDialog::newSipDialogHandover(TranEntry *tran, string sipReferStr) +{ + LOG(DEBUG)<msmHeaders.paramFind("Refer-To")); + string proxy = referto.uriHostAndPort(); + // 7-23 wrong: SipDialog *dialog = new SipDialog(SIPDTMTC,proxy); + SipDialog *dialog = new SipDialog(SIPDTMOC,proxy,"REFER message"); + dialog->mIsHandover = true; + dialog->dsSetRemoteHeader(&msg->msmTo); + dialog->dsSetLocalHeader(&msg->msmFrom); + dialog->dsSetCallId(msg->msmCallId); + // TODO: If any other intervening messages were sent by BTS1 between the REFER and now the CSeqNum will not be correct. + dialog->mLocalCSeq = msg->msmCSeqNum + 1; + // We copied the peer SDP we got from the SIP server into the handover message passed from BS1 to BS2; + // I dont think we need to save sdpResponse here - we are going to use it for the last time immediately below. + dialog->mCodec = tran->getCodecs(); // TODO: We need to renegotiate this, or set it from SDP. There is no point even setting this here. + + // Get remote RTP from SIP REFER message, init RTP, create new SDP offer from previous SDP response. + // The incoming SDP has the codec previously negotiated, so it should still be ok. + dialog->mRTPPort = Control::allocateRTPPorts(); + SdpInfo sdpRemote; + sdpRemote.sdpParse(msg->msmBody); + SdpInfo sdpLocal = sdpRemote; // In particular, we are copying the sessionId and versionId. + // Send our local RTP port to the SIP server. + sdpLocal.sdpRtpPort = dialog->mRTPPort; + sdpLocal.sdpHost = dialog->localIP(); + dialog->mSdpOffer = sdpLocal.sdpValue(); + + // Make the re-INVITE + SipMessage *invite = dialog->makeInitialRequest(inviteStr); + invite->smAddBody(string("application/sdp"),dialog->mSdpOffer); + + // Send it off. + ScopedLock lock(dialog->mDialogLock,__FILE__,__LINE__); + gSipInterface.dmAddCallDialog(dialog); + dialog->moWriteLowSide(invite); + delete invite; // moWriteLowSide saved a copy of this. + dialog->setSipState(HandoverInbound); + + return dialog; +} + + +SipDialog::~SipDialog() +{ + // nothing +} + +TranEntry *SipDialog::findTranEntry() +{ + if (this->mTranId == 0) { + // No attached transaction. Can happen if we jumped the gun (the dialog is created before the transaction + // and there could be a race with an incoming message) or if we responded with an early error + // to a dialog and never created a transaction for it, for example, 486 Busy Here. + return NULL; + } + return gNewTransactionTable.ttFindById(this->mTranId); +} + + +TranEntry *SipDialog::createMTTransaction(SipMessage *invite) +{ + // Create an incipient TranEntry. It does not have a TI yet. + TranEntry *tran = NULL; + //string& callerId = this->mRemoteUsername; + string callerId = sipRemoteDisplayname(); + FullMobileId msid; + msid.mImsi = invite->smGetInviteImsi(); + if (invite->isINVITE()) { + tran = TranEntry::newMTC(this,msid,GSM::L3CMServiceType::MobileTerminatedCall,callerId); + // Tell the sender we are trying. + this->MTCSendTrying(); + } else { + assert(0); + } + return tran; +} + +// If the cause is handoverOutbound, kill the dialog now: dont send a BYE, dont wait for any other incoming messsages. +// Used for outbound handover, where the SIP session was transferred to another BTS. +void SipDialog::dialogCancel(CancelCause cause) +{ + WATCH("dialogCancel"<getSipState(); + LOG(INFO) << dialogText(); // "SIP state " << state; + + switch (cause) { + case CancelCauseHandoverOutbound: + case CancelCauseSipInternalError: + // Terminate the dialog instantly. Dont send anything on the SIP interface. + sipStopTimers(); + // We need to remove the callid of the terminated outbound dialog queue from SIPInterface in case + // the same call is handerovered back, it would then be a duplicate. + gSipInterface.dmRemoveDialog(this); + return; + default: + break; + } + + //why aren't we checking for failed here? -kurtis ; we are now. -david + if (this->sipIsFinished()) return; + switch (mDialogType) { + case SIPDTRegister: + case SIPDTUnregister: + // The Register is not a full dialog so we dont send anything when we cancel. + break; + case SIPDTMOSMS: + case SIPDTMTSMS: + case SIPDTMOUssd: + setSipState(Cleared); + break; + case SIPDTMTC: + case SIPDTMOC: + switch (state) { + case SSTimeout: + case MOSMSSubmit: // Should never see a message state in an INVITE dialog. + LOG(ERR) "Unexpected SIP State:"<MODSendBYE(); + //then cleared + sipStartTimers(); // formerly: MODWaitForBYEOK(); + break; + case SSNullState: // (pat) MTC initial state - nothing sent yet. MOC not used because sends INVITE on construction. + case Starting: // (pat) MOC or MOSMS or inboundHandover sent INVITE; MTC not used. + case Proceeding: // (pat) MOC received Trying, Queued, BeingForwarded; MTC sent Trying + case Ringing: // (pat) MOC received Ringing, notably not used for MTC sent Ringing. + case MOCBusy: // (pat) MOC received Busy; MTC not used. + case Connecting: // (pat) MTC sent OK. + case HandoverInbound: + if (mDialogType == SIPDTMOC) { + // To cancel the invite before the ACK is received we must send CANCEL instead of BYE. + this->MODSendCANCEL(); //Changes state to MODCanceling + } else { + // We are the INVITE recipient server and have not received the ACK yet, so we must send an error response. + // Yes this was formerly used for MTC also. TODO: Make sure it works! + // RFC3261 (SIP) is internally inconsistent describing the error codes - the 4xxx and 5xx generic + // descriptions are contracted by specific error code descriptions. + // This is from Paul Chitescu at Null Team: + // "A 504 Server Timeout seems the most adequate response [to MS not responding to page.] + // 408 is reserved for SIP protocol timeouts (no answer to SIP message) + // 504 indicates some other timeout beyond SIP (interworking) + // 480 indicates some temporary form of resource unavailability or congestion but + // resource is accessible and can be checked" + // 486 "Busy Here" implies that we found the MS but it really is busy. + // 503 indicates the service is unavailable but does not imply for how long + // TODO: We should probably send different codes for different reasons. + // Note: We previously sent 480. + //this->MTCEarlyError(480, "Temporarily Unavailable"); // The message must be 300-699. + + int sipcode = 500; const char *reason = "Server Internal Error"; + switch (cause) { + case CancelCauseHandoverOutbound: + case CancelCauseSipInternalError: + assert(0); // handled above + case CancelCauseBusy: // MS is here and unavailable. + case CancelCauseUnknown: // Loss of contact with MS or an error. + case CancelCauseCongestion: // MS is here but no channel avail or other congestion. + // The MS is here but we cannot get at it for some reason. + sipcode = 486; reason = "Busy Here"; + break; + case CancelCauseNoAnswerToPage: // We dont have any clue if the MS is in this area or not. + // The MS is not here or turned off. + sipcode = 504; reason = "Temporarily Unavailable"; + break; + case CancelCauseOperatorIntervention: + sipcode = 487; reason = "Request Terminated Operator Intervention"; + break; + } + this->MTCEarlyError(sipcode,reason); // The message must be 300-699. + } + break; + case MODClearing: // (pat) MOD sent BYE + case MODCanceling: // (pat) MOD sent a cancel, see forceSIPClearing. + case MODError: // (pat) MOD sent an error response, see forceSIPClearing. + case MTDClearing: // (pat) MTD received BYE. + case MTDCanceling: // (pat) MTD received CANCEL + case Canceled: // (pat) received OK to CANCEL. + case Cleared: // (pat) MTD sent OK to BYE, or MTD internal error loss of FIFO, or MOSMS received OK, or MTSMS sent OK. + case SSFail: + // Some kind of clearing already in progress. Do not repeat. + break; + case HandoverOutbound: // We never used this state. + // Not sure what to do with these. + break; + } + break; + default: + assert(0); + break; + } +} + +void SipEngine::dialogQueueMessage(DialogMessage *dmsg) +{ + // This was used when there was just one layer3 thread: + // TODO: We may still use this for UMTS. + //Control::gCSL3StateMachine.csl3Write(new Control::GenericL3Msg(dmsg,callID())); + // Now we enqueue dialog messages in a queue in their dialog, and let L3 fish it out from there. + // We dont enqueue on the GSM LogicalChannel because that may change from, eg, SDCCH to FACCH before this message is processed. + LOG(DEBUG) << "sending DialogMessage to L3 " /*< oldState) { return true; } // That was easy! + if (newState == oldState) { + // Allow multiple proceeding/ringing notifications: + if (newState == DialogState::dialogProceeding || newState == DialogState::dialogRinging) { return true; } + } + return false; +} + +void SipDialog::dialogPushState( + SipState newSipState, // The new sip state. + int code, // The SIP message code that caused the state change, or 0 for ACK or total failures. + char timer) +{ + SipState oldSipState = getSipState(); + DialogState::msgState oldDialogState = getDialogState(); + setSipState(newSipState); + + // If it is a new state, inform L3. + DialogState::msgState nextDialogState = getDialogState(); // based on the newSipState we just set. + LOG(DEBUG) <ipIsReliableTransport()) { + mTimerD.setOnce(32000); + } + break; + case 'K': + mTimerK.setOnce(T4); + break; + default: assert(0); + } +} + + +void SipDialog::dialogChangeState( + SipMessage *sipmsg) // The message that caused the state change, or NULL for total failures. +{ + dialogPushState(getSipState(),sipmsg?sipmsg->smGetCode():0); + //LOG(DEBUG) <smGetCode() : 0; + // DialogMessage *dmsg = new DialogMessage(mTranId,nextDialogState,code); + // // done by the register TU + // dialogQueueMessage(dmsg); + //} else { + // LOG(DEBUG) << "no dialog state change"; + //} + //mPrevDialogState = nextDialogState; +} + +// Only a small subset of SIP states are passed to the L3 Control layer as dialog states. +DialogState::msgState SipDialog::getDialogState() const +{ + // Do not add a default case so that if someone adds a new SipState they will get a warning here. + // Therefore we define every state including the impossible ones. + switch (getSipState()) { + case SSNullState: + return DialogState::dialogUndefined; + case Starting: // (pat) MOC or MOSMS or inboundHandover sent INVITE; MTC not used. + return DialogState::dialogStarted; + case Proceeding: // (pat) MOC received Trying, Queued, BeingForwarded; MTC sent Trying + case Connecting: // (pat) MTC sent OK. + // TODO: Is this correct for MTC Connecting? + return DialogState::dialogProceeding; + case Ringing: // (pat) MOC received Ringing, notably not used for MTC sent Ringing, which is probably a bug of no import. + return DialogState::dialogRinging; + case Active: // (pat) MOC received OK; MTC sent ACK + return DialogState::dialogActive; + + case MODClearing: // (pat) MOD sent BYE + case MODCanceling: // (pat) MOD sent a cancel, see forceSIPClearing. + case MTDClearing: // (pat) MTD received BYE. + case MTDCanceling: // (pat) received CANCEL + case Canceled: // (pat) received OK to CANCEL. + case Cleared: // (pat) MTD sent OK to BYE, or MTD internal error loss of FIFO, or MOSMS received OK, or MTSMS sent OK. + return DialogState::dialogBye; + + case MOCBusy: // (pat) MOC received Busy; MTC not used. + case SSTimeout: + case MODError: // (pat) MOD sent a cancel, see forceSIPClearing. + case SSFail: + return DialogState::dialogFail; + + //case SipRegister: // (pat) This SIPEngine is being used for registration, none of the other stuff applies. + //case SipUnregister: // (pat) This SIPEngine is being used for registration, none of the other stuff applies. + case MOSMSSubmit: // (pat) SMS message submitted, "MESSAGE" method. Set but never used. + case HandoverInbound: + case HandoverOutbound: + return DialogState::dialogUndefined; + } + devassert(0); + return DialogState::dialogUndefined; +} + + +// Handle response to INVITE or MESSAGE. +// Only responses (>=200) to INVITE get an ACK. Specifically, not MESSAGE. +void SipDialog::handleInviteResponse(int status, + bool sendAck) // TRUE if transaction is INVITE. We used to use this for MESSAGE also, in which case it was false. +{ + LOG(DEBUG) <blah in xmlin and return "blah". +static string xmlFind(const char *xmlin, const char *tag) +{ + char tagbuf[56]; + assert(strlen(tag) < 50); + sprintf(tagbuf,"<%s>",tag); + const char *start = strstr(xmlin,tagbuf); + if (!start) return string(""); + const char *result = start + strlen(tagbuf); + sprintf(tagbuf,"",tag); + const char *end = strstr(start,tagbuf); + if (!start) return string(""); + return string(result,end-result); +} + +// The incoming USSD BYE message could have a payload to be sent to the MS. +void SipDialog::handleUssdBye(SipMessage *msg) +{ + // There could be multiple BYE messages, hopefully all identical, but we only want to send one DialogMessage. + if (getSipState() == Cleared) return; + DialogUssdMessage *dmsg = new DialogUssdMessage(mTranId,DialogState::dialogBye,0); + // Is it is ok for there to be no response string? + // We have to send something to the MS so in that case return an empty string. + if (msg->smGetMessageContentType().find("application/vnd.3gpp.ussd+xml") == string::npos) { + LOG(INFO) << "UUSD response does not contain correct body type"; + } else { + dmsg->dmMsgPayload = xmlFind(msg->smGetMessageBody().c_str(),"ussd-string"); + if (dmsg->dmMsgPayload == "") { + // This is ok. + LOG(INFO) << "Missing UUSD response does not contain correct body type"; + } + } + dialogQueueMessage(dmsg); + if (dsPeer()->ipIsReliableTransport()) { + dialogPushState(Cleared,0); + } else { + dialogPushState(MTDClearing,0); + setTimerJ(); + } +} + + +// The SIPInterface sends this to us based on mCallId. +// We will process the message and possibly send replies or DialogMessages to the L3 State machine. +// Blah, this should be handled by Dialog sub-classes. +void SipDialog::dialogWriteDownlink(SipMessage *msg) +{ + LOG(DEBUG) <<"received SIP" /*<text() <smGetCode(); + + //if (code == 200) { saveResponse(msg); } + //if (code >= 400) { mFailCode = code; } + + //SipDialog::msgState nextDialogState = sipMessage2DialogState(msg); + + switch (mDialogType) { + case SIPDTRegister: + case SIPDTUnregister: + LOG(ERR) << "REGISTER transaction received unexpected message:"<isBYE()) { // It is a SIP Request. Switch based on the method. + // Grab any xml ussd response from the BYE message. + handleUssdBye(msg); + } + goto otherwise; + case SIPDTMOC: // This is a MOC transaction. + case SIPDTMTC: // This is a MTC transaction. Could be an inbound handover. + LOG(DEBUG); + otherwise: + if (code == 0) { // It is a SIP Request. Switch based on the method. + if (msg->isBYE()) { + SipMTBye(msg); + } else if (msg->isCANCEL()) { + // This is an error since we have already passed the ACK stage, but lets cancel the dialog anyway. + SipMTCancel(msg); + } else { + // Not expecting any others. Must send 405 error. + LOG(ALERT)<<"SIP Message ignored:"<mTranId); + // We never expire the dialog associated with REGISTER. + case SIPDTRegister: + case SIPDTUnregister: + case SIPDTUndefined: + return false; // We never delete the Register dialog. + default: + assert(0); + } +} + +// Called periodicially to check for SIP timer expiration. +bool SipDialog::dialogPeriodicService() +{ + // Take care. This is a potential deadlock if somone tries to add a locked SipDialog into the DialogMap, + // because the kicker code locks the whole DialogMap against modification. + ScopedLock lock(mDialogLock,__FILE__,__LINE__); + // Now we use TransactionUsers for client transactions, so this code handles only server transactions. + // The in-dialog server transactions are trivial - the transaction-layer simply resends the final + // response each time the request is received. + switch (mDialogType) { + case SIPDTUndefined: + case SIPDTRegister: + case SIPDTUnregister: + // FIXME: I dont think we delete these, ever. + break; + case SIPDTMTSMS: + case SIPDTMTC: + return mtPeriodicService(); + break; + case SIPDTMOSMS: + case SIPDTMOC: + case SIPDTMOUssd: + return moPeriodicService(); + break; + //default: break; + } + return false; +} + + +const char *DialogState::msgStateString(DialogState::msgState dstate) +{ + switch (dstate) { + case DialogState::dialogUndefined: return "undefined"; + case DialogState::dialogStarted: return "Started"; + case DialogState::dialogProceeding: return "Proceeding"; + case DialogState::dialogRinging: return "Ringing"; + case DialogState::dialogActive: return "Active"; + case DialogState::dialogBye: return "Bye"; + case DialogState::dialogFail: return "Fail"; + case DialogState::dialogDtmf: return "DTMF"; + }; + return "unknown_DialogState"; +} + +string SipDialog::dialogText(bool verbose) const +{ + std::ostringstream ss; + ss << " SipDialog("<dialogText(); else os << "(null SipDialog)"; + return os; +} +std::ostream& operator<<(std::ostream& os, const SipDialog&dg) { os << dg.dialogText(); return os; } // stupid language + +std::ostream& operator<<(std::ostream& os, const DialogState::msgState dstate) +{ + os << DialogState::msgStateString(dstate); + return os; +} + +std::ostream& operator<<(std::ostream& os, const DialogMessage*dmsg) +{ + if (dmsg) { + os <<"DialogMessage("<mMsgState)) <mSipStatusCode)<<")"; + } else { + os << "(null DialogMessage)"; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const DialogMessage&dmsg) { os << &dmsg; return os; } // stupid language + + +}; // namespace diff --git a/SIP/SIPDialog.h b/SIP/SIPDialog.h new file mode 100644 index 0000000..33af288 --- /dev/null +++ b/SIP/SIPDialog.h @@ -0,0 +1,123 @@ +/* +* 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 distribuion. +* +* 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. + +#ifndef SIPDIALOG_H +#define SIPDIALOG_H +#include +#include "SIPExport.h" +#include "SIPMessage.h" +#include "SIPBase.h" +#include "SIPTransaction.h" + + +namespace SIP { + + +// This is the start of a RFC-3261 Transaction Layer state machine per call_id to filter the messages before delivery to L3. +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipDialog,MemCheckSipDialog) +// The antecedent classes are virtual because they all are descendents of a single SipBase. +class SipDialog : public MemCheckSipDialog, public virtual SipBase, + public virtual SipMOInviteClientTransactionLayer, public virtual SipMTInviteServerTransactionLayer +{ + + private: + + // We only send one state change message to L3 for each change of DialogState. This is the previous one we sent. + // There is no current dialog state - it is derived from SIPBase::mState + DialogState::msgState mPrevDialogState; + + bool permittedTransition(DialogState::msgState oldState, DialogState::msgState newState); + // Change the sip state and possibly push a message to L3. + public: void dialogPushState(SipState newState, int code, char timer = 0); + // Possibly send a message to L3 if the SIP state has changed. + private: void dialogChangeState(SipMessage *dmsg); + void registerHandleSipCode(SipMessage *msg); + + public: + // To work around the buggy smqueue we need to resend the SMS message so we need to save it. + // There is another copy saved down in the transaction layer, but it is cleaner to just save it up here + // than try to dig it out of the lower layer resend machinery. + string smsBody, smsContentType; // Temporary, until smqueue is fixed. + + void dgReset(); + DialogState::msgState getDialogState() const; + bool isActive() const { return getDialogState() == DialogState::dialogActive; } + bool isFinished() const { DialogState::msgState st = getDialogState(); return st == DialogState::dialogFail || st == DialogState::dialogBye; } + bool dgIsDeletable() const; + const char *dialogStateString() const { return DialogState::msgStateString(getDialogState()); } + //void dialogOpen(const char *userid); // The userid is the IMSI. + //void dialogClose(); + void MODSendBYE(); + void sendInfoDtmf(unsigned bcdkey); + + // Send an error code that terminates the dialog. + void sendError(int code, const char *errorString) {LOG(ALERT) << "unimplemented"< -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SIPInterface.h" -#include "SIPUtility.h" -#include "SIPMessage.h" -#include "SIPEngine.h" -#include "TransactionTable.h" - -#undef WARNING - -using namespace std; -using namespace SIP; -using namespace Control; - -int get_rtp_tev_type(char dtmf){ - switch (dtmf){ - case '1': return TEV_DTMF_1; - case '2': return TEV_DTMF_2; - case '3': return TEV_DTMF_3; - case '4': return TEV_DTMF_4; - case '5': return TEV_DTMF_5; - case '6': return TEV_DTMF_6; - case '7': return TEV_DTMF_7; - case '8': return TEV_DTMF_8; - case '9': return TEV_DTMF_9; - case '0': return TEV_DTMF_0; - case '*': return TEV_DTMF_STAR; - case '#': return TEV_DTMF_POUND; - case 'a': - case 'A': return TEV_DTMF_A; - case 'B': - case 'b': return TEV_DTMF_B; - case 'C': - case 'c': return TEV_DTMF_C; - case 'D': - case 'd': return TEV_DTMF_D; - case '!': return TEV_FLASH; - default: - LOG(WARNING) << "Bad dtmf: " << dtmf; - return -1; - } -} - - -const char* SIP::SIPStateString(SIPState s) -{ - switch(s) - { - case NullState: return "Null"; - case Timeout: return "Timeout"; - case Starting: return "Starting"; - case Proceeding: return "Proceeding"; - case Ringing: return "Ringing"; - case Connecting: return "Connecting"; - case Active: return "Active"; - case Fail: return "Fail"; - case Busy: return "Busy"; - case MODClearing: return "MODClearing"; - case MODCanceling: return "MODCanceling"; - case MTDClearing: return "MTDClearing"; - case MTDCanceling: return "MTDCanceling"; - case Canceled: return "Canceled"; - case Cleared: return "Cleared"; - case MessageSubmit: return "SMS-Submit"; - case HandoverInbound: return "HandoverInbound"; - case HandoverInboundReferred: return "HandoverInboundReferred"; - case HandoverOutbound: return "HandoverOutbound"; - default: return NULL; - } -} - -ostream& SIP::operator<<(ostream& os, SIPState s) -{ - const char* str = SIPStateString(s); - if (str) os << str; - else os << "?" << ((int)s) << "?"; - return os; -} - - - -SIPEngine::SIPEngine(const char* proxy, const char* IMSI) - :mCSeq(random()%1000), - mMyToFromHeader(NULL), mRemoteToFromHeader(NULL), - mCallIDHeader(NULL), - mSIPPort(gConfig.getNum("SIP.Local.Port")), - mSIPIP(gConfig.getStr("SIP.Local.IP")), - mINVITE(NULL), mLastResponse(NULL), mBYE(NULL), - mCANCEL(NULL), mERROR(NULL), mSession(NULL), - mTxTime(0), mRxTime(0), mState(NullState), mInstigator(false), - mDTMF('\0'),mDTMFDuration(0) -{ - assert(proxy); - if (IMSI) user(IMSI); - if (!resolveAddress(&mProxyAddr,proxy)) { - LOG(ALERT) << "cannot resolve IP address for " << proxy; - return; - } - char host[256]; - const char* ret = inet_ntop(AF_INET,&(mProxyAddr.sin_addr),host,255); - if (!ret) { - LOG(ALERT) << "cannot translate proxy IP address"; - return; - } - mProxyIP = string(host); - mProxyPort = ntohs(mProxyAddr.sin_port); - - // generate a tag now - char tmp[50]; - make_tag(tmp); - mMyTag=tmp; - // set our CSeq in case we need one - mCSeq = random()%600; - - //to make sure noise doesn't magically equal a valid RTP port - mRTPPort = 0; -} - - -SIPEngine::~SIPEngine() -{ - if (mINVITE!=NULL) osip_message_free(mINVITE); - if (mLastResponse!=NULL) osip_message_free(mLastResponse); - if (mBYE!=NULL) osip_message_free(mBYE); - if (mCANCEL!=NULL) osip_message_free(mCANCEL); - if (mERROR!=NULL) osip_message_free(mERROR); - // FIXME -- Do we need to dispose of the RtpSesion *mSesison? -} - - - -void SIPEngine::saveINVITE(const osip_message_t *INVITE, bool mine) -{ - // Instead of cloning, why not just keep the old one? - // Because that doesn't work in all calling contexts. - // This simplifies the call-handling logic. - if (mINVITE!=NULL) osip_message_free(mINVITE); - osip_message_clone(INVITE,&mINVITE); - - // #238-private - if (mINVITE==NULL){ - LOG(ALERT) << "Message cloning failed, skipping this message."; - return; - } - - mCallIDHeader = mINVITE->call_id; - - // If this our own INVITE? Did we initiate the transaciton? - if (mine) { - mMyToFromHeader = mINVITE->from; - mRemoteToFromHeader = mINVITE->to; - return; - } - - // It's not our own. The From: is the remote party. - mMyToFromHeader = mINVITE->to; - mRemoteToFromHeader = mINVITE->from; - - // We need to set our tag, too. - osip_from_set_tag(mMyToFromHeader, strdup(mMyTag.c_str())); -} - - - -void SIPEngine::saveResponse(osip_message_t *response) -{ - if (mLastResponse!=NULL) osip_message_free(mLastResponse); - osip_message_clone(response,&mLastResponse); - - // The To: is the remote party and might have an new tag. - mRemoteToFromHeader = mLastResponse->to; -} - - - -void SIPEngine::saveBYE(const osip_message_t *BYE, bool mine) -{ - // Instead of cloning, why not just keep the old one? - // Because that doesn't work in all calling contexts. - // This simplifies the call-handling logic. - if (mBYE!=NULL) osip_message_free(mBYE); - osip_message_clone(BYE,&mBYE); -} - -void SIPEngine::saveCANCEL(const osip_message_t *CANCEL, bool mine) -{ - // Instead of cloning, why not just keep the old one? - // Because that doesn't work in all calling contexts. - // This simplifies the call-handling logic. - if (mCANCEL!=NULL) osip_message_free(mCANCEL); - osip_message_clone(CANCEL,&mCANCEL); -} - -void SIPEngine::saveERROR(const osip_message_t *ERROR, bool mine) -{ - // Instead of cloning, why not just keep the old one? - // Because that doesn't work in all calling contexts. - // This simplifies the call-handling logic. - if (mERROR!=NULL) osip_message_free(mERROR); - osip_message_clone(ERROR,&mERROR); -} - -#if 0 -This was replaced with a simple flag set during MO transactions. -/* we're going to figure if the from field is us or not */ -bool SIPEngine::instigator() -{ - assert(mINVITE); - osip_uri_t * from_uri = mINVITE->from->url; - return (!strncmp(from_uri->username,mSIPUsername.c_str(),15) && - !strncmp(from_uri->host, mSIPIP.c_str(), 30)); -} -#endif - -void SIPEngine::user( const char * IMSI ) -{ - LOG(DEBUG) << "IMSI=" << IMSI; - unsigned id = random(); - char tmp[20]; - sprintf(tmp, "%u", id); - mCallID = tmp; - // IMSI gets prefixed with "IMSI" to form a SIP username - mSIPUsername = string("IMSI") + IMSI; -} - - - -void SIPEngine::user( const char * wCallID, const char * IMSI, const char *origID, const char *origHost) -{ - LOG(DEBUG) << "IMSI=" << IMSI << " " << wCallID << " " << origID << "@" << origHost; - mSIPUsername = string("IMSI") + IMSI; - mCallID = string(wCallID); - mRemoteUsername = string(origID); - mRemoteDomain = string(origHost); -} - -string randy401(osip_message_t *msg) -{ - if (msg->status_code != 401) return ""; - osip_www_authenticate_t *auth = (osip_www_authenticate_t*)osip_list_get(&msg->www_authenticates, 0); - if (auth == NULL) return ""; - char *rand = osip_www_authenticate_get_nonce(auth); - string rands = rand ? string(rand) : ""; - if (rands.length()!=32) { - LOG(WARNING) << "SIP RAND wrong length: " << rands; - return ""; - } - return rands; -} - - -void SIPEngine::writePrivateHeaders(osip_message_t *msg, const GSM::LogicalChannel *chan) -{ - // P-PHY-Info - // This is a non-standard private header in OpenBTS. - // TA= TE= UpRSSI= TxPwr= - // DnRSSIdBm= time= - // Get the values. - if (chan) { - LOG(DEBUG); - char phy_info[400]; - sprintf(phy_info,"OpenBTS; TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d time=%9.3lf", - chan->actualMSTiming(), chan->timingError(), - chan->RSSI(), chan->actualMSPower(), - chan->measurementResults().RXLEV_FULL_SERVING_CELL_dBm(), - chan->timestamp()); - LOG(DEBUG) << "PHY-info: " << phy_info; - osip_message_set_header(msg,"P-PHY-Info",phy_info); - } - - // P-Access-Network-Info - // See 3GPP 24.229 7.2. - char cgi_3gpp[256]; - sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x", - gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(), - (unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI")); - osip_message_set_header(msg,"P-Access-Network-Info",cgi_3gpp); - - // P-Preferred-Identity - // See RFC-3325. - char pref_id[350]; - sprintf(pref_id,"", - mSIPUsername.c_str(), - gConfig.getStr("SIP.Proxy.Speech").c_str()); - osip_message_set_header(msg,"P-Preferred-Identity",pref_id); - // Check for illegal hostname length. 253 bytes for domain name + 6 bytes for port and colon. - // This isn't "pretty", but it should be fast, and gives us a ballpark. Their hostname will - // fail elsewhere if it is longer than 253 bytes (since this assumes a 5 byte port string). - if (gConfig.getStr("SIP.Proxy.Speech").length() > 259) { - LOG(ALERT) << "Configured SIP.Proxy.Speech hostname is great than 253 bytes!"; - } - - // FIXME -- Use the subscriber registry to look up the E.164 - // and make a second P-Preferred-Identity header. - -} - - -bool SIPEngine::Register( Method wMethod , const GSM::LogicalChannel* chan, string *RAND, const char *IMSI, const char *SRES) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState << " " << wMethod << " callID " << mCallID; - - // Before start, need to add mCallID - gSIPInterface.addCall(mCallID); - - // Initial configuration for sip message. - // Make a new from tag and new branch. - // make new mCSeq. - - // Generate SIP Message - // Either a register or unregister. Only difference - // is expiration period. - osip_message_t * reg; - if (wMethod == SIPRegister ){ - reg = sip_register( mSIPUsername.c_str(), - 60*gConfig.getNum("SIP.RegistrationPeriod"), - mSIPPort, mSIPIP.c_str(), - mProxyIP.c_str(), mMyTag.c_str(), - mViaBranch.c_str(), mCallID.c_str(), mCSeq, - RAND, IMSI, SRES - ); - } else if (wMethod == SIPUnregister ) { - reg = sip_register( mSIPUsername.c_str(), - 0, - mSIPPort, mSIPIP.c_str(), - mProxyIP.c_str(), mMyTag.c_str(), - mViaBranch.c_str(), mCallID.c_str(), mCSeq, - NULL, NULL, NULL - ); - } else { assert(0); } - - writePrivateHeaders(reg,chan); - gReports.incr("OpenBTS.SIP.REGISTER.Out"); - - LOG(DEBUG) << "writing registration " << reg; - gSIPInterface.write(&mProxyAddr,reg); - - bool success = false; - osip_message_t *msg = NULL; - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - while (!timeout.passed()) { - try { - // SIPInterface::read will throw SIPTIimeout if it times out. - // It should not return NULL. - msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),NULL); - } catch (SIPTimeout) { - // send again - LOG(NOTICE) << "SIP REGISTER packet to " << mProxyIP << ":" << mProxyPort << " timeout; resending"; - gSIPInterface.write(&mProxyAddr,reg); - continue; - } - - assert(msg); - int status = msg->status_code; - LOG(INFO) << "received status " << msg->status_code << " " << msg->reason_phrase; - // specific status - if (status==200) { - LOG(INFO) << "REGISTER success"; - success = true; - break; - } - if (status==401) { - string wRAND = randy401(msg); - // if rand is included on 401 unauthorized, then the challenge-response game is afoot - if (wRAND.length() != 0 && RAND != NULL) { - LOG(INFO) << "REGISTER challenge RAND=" << wRAND; - *RAND = wRAND; - osip_message_free(msg); - osip_message_free(reg); - return false; - } else { - LOG(INFO) << "REGISTER fail -- unauthorized"; - break; - } - } - if (status==404) { - LOG(INFO) << "REGISTER fail -- not found"; - break; - } - if (status>=200) { - LOG(NOTICE) << "REGISTER unexpected response " << status; - break; - } - } - - if (!msg) { - LOG(ALERT) << "SIP REGISTER timed out; is the registration server " << mProxyIP << ":" << mProxyPort << " OK?"; - throw SIPTimeout(); - } - - osip_message_free(reg); - osip_message_free(msg); - // We remove the call FIFO here because there - // is no transaction entry associated with the REGISTER. - gSIPInterface.removeCall(mCallID); - return success; -} - - -float geodecode1(const char **p, int *err, bool colonExpected) -{ - float n = 0; - const char *q = *p; - while (**p >= '0' && **p <= '9') { - n = n * 10 + **p - '0'; - (*p)++; - } - if (q == *p) *err = 1; - if (colonExpected) { - if (**p == ':') { - (*p)++; - } else { - *err = 1; - } - } - return n; -} - - -float geodecode(const char **p, int *err) -{ - float n = 0; - float m = 1; - while (**p == ' ') { - (*p)++; - } - if (**p == '-') { - m = -1; - (*p)++; - } - n = geodecode1(p, err, true); - n += geodecode1(p, err, true)/60.0; - n += geodecode1(p, err, false)/3600.0; - if (**p == ' ' || **p == 0) return n * m; - switch (**p) { - case 'N': - case 'E': - (*p)++; - return n * m; - case 'S': - case 'W': - (*p)++; - return n * m * -1.0; - } - *err = 1; - return 0; -} - -SIPState SIPEngine::MOCSendINVITE( const char * wCalledUsername, - const char * wCalledDomain , short wRtp_port, unsigned wCodec, - const GSM::LogicalChannel *chan) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - // Before start, need to add mCallID - gSIPInterface.addCall(mCallID); - mInstigator = true; - gReports.incr("OpenBTS.SIP.INVITE.Out"); - - // Set Invite params. - // new CSEQ and codec - char tmp[50]; - make_branch(tmp); - mViaBranch = tmp; - mCodec = wCodec; - mCSeq++; - - mRemoteUsername = wCalledUsername; - mRemoteDomain = wCalledDomain; - mRTPPort= wRtp_port; - - LOG(DEBUG) << "mRemoteUsername=" << mRemoteUsername; - LOG(DEBUG) << "mSIPUsername=" << mSIPUsername; - - osip_message_t * invite = sip_invite( - mRemoteUsername.c_str(), mRTPPort, mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec); - - writePrivateHeaders(invite,chan); - - // Send Invite. - gSIPInterface.write(&mProxyAddr,invite); - saveINVITE(invite,true); - osip_message_free(invite); - mState = Starting; - return mState; -}; - - -SIPState SIPEngine::MOCResendINVITE() -{ - assert(mINVITE); - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - LOG(NOTICE) << "SIP INVITE packet to " << mProxyIP << ":" << mProxyPort << " timedout; resending"; - gSIPInterface.write(&mProxyAddr,mINVITE); - return mState; -} - -SIPState SIPEngine::MOCCheckForOK(Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - //if (mState==Fail) return Fail; - - osip_message_t * msg; - //osip_message_t * msg = NULL; - - // Read off the fifo. if time out will - // clean up and return false. - try { - msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"),lock); - } - catch (SIPTimeout& e) { - LOG(DEBUG) << "timeout"; - //if we got a 100 TRYING (SIP::Proceeding) - //don't time out - if (mState != SIP::Proceeding){ - mState = Timeout; - } - return mState; - } - - int status = msg->status_code; - LOG(DEBUG) << "received status " << status; - saveResponse(msg); - switch (status) { - // class 1XX: Provisional messages - case 100: // Trying - case 181: // Call Is Being Forwarded - case 182: // Queued - case 183: // Session Progress FIXME we need to setup the sound channel (early media) - mState = Proceeding; - break; - case 180: // Ringing - mState = Ringing; - break; - - // calss 2XX: Success - case 200: // OK - // Save the response and update the state, - // but the ACK doesn't happen until the call connects. - mState = Active; - break; - - // class 3xx: Redirection - case 300: // Multiple Choices - case 301: // Moved Permanently - case 302: // Moved Temporarily - case 305: // Use Proxy - case 380: // Alternative Service - LOG(NOTICE) << "redirection not supported code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.3xx"); - MOCSendACK(); - break; - // Anything 400 or above terminates the call, so we ACK. - // FIXME -- It would be nice to save more information about the - // specific failure cause. - - // class 4XX: Request failures - case 400: // Bad Request - case 401: // Unauthorized: Used only by registrars. Proxys should use proxy authorization 407 - case 402: // Payment Required (Reserved for future use) - case 403: // Forbidden - case 404: // Not Found: User not found - case 405: // Method Not Allowed - case 406: // Not Acceptable - case 407: // Proxy Authentication Required - case 408: // Request Timeout: Couldn't find the user in time - case 409: // Conflict - case 410: // Gone: The user existed once, but is not available here any more. - case 413: // Request Entity Too Large - case 414: // Request-URI Too Long - case 415: // Unsupported Media Type - case 416: // Unsupported URI Scheme - case 420: // Bad Extension: Bad SIP Protocol Extension used, not understood by the server - case 421: // Extension Required - case 422: // Session Interval Too Small - case 423: // Interval Too Brief - case 480: // Temporarily Unavailable - case 481: // Call/Transaction Does Not Exist - case 482: // Loop Detected - case 483: // Too Many Hops - case 484: // Address Incomplete - case 485: // Ambiguous - LOG(NOTICE) << "request failure code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.4xx"); - MOCSendACK(); - break; - - case 486: // Busy Here - LOG(NOTICE) << "remote end busy code " << status; - mState = Busy; - MOCSendACK(); - break; - case 487: // Request Terminated - case 488: // Not Acceptable Here - case 491: // Request Pending - case 493: // Undecipherable: Could not decrypt S/MIME body part - LOG(NOTICE) << "request failure code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.4xx"); - MOCSendACK(); - break; - - // class 5XX: Server failures - case 500: // Server Internal Error - case 501: // Not Implemented: The SIP request method is not implemented here - case 502: // Bad Gateway - case 503: // Service Unavailable - case 504: // Server Time-out - case 505: // Version Not Supported: The server does not support this version of the SIP protocol - case 513: // Message Too Large - LOG(NOTICE) << "server failure code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.5xx"); - MOCSendACK(); - break; - - // class 6XX: Global failures - case 600: // Busy Everywhere - case 603: // Decline - mState = Busy; - MOCSendACK(); - break; - case 604: // Does Not Exist Anywhere - case 606: // Not Acceptable - LOG(NOTICE) << "global failure code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.6xx"); - MOCSendACK(); - default: - LOG(NOTICE) << "unhandled status code " << status; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.xxx"); - MOCSendACK(); - } - osip_message_free(msg); - LOG(DEBUG) << " new state: " << mState; - return mState; -} - -SIPState SIPEngine::MOCSendACK() -{ - assert(mLastResponse); - - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - - osip_message_t* ack = sip_ack( mRemoteDomain.c_str(), - mRemoteUsername.c_str(), - mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyToFromHeader, mRemoteToFromHeader, - mViaBranch.c_str(), mCallIDHeader, mCSeq - ); - - gSIPInterface.write(&mProxyAddr,ack); - osip_message_free(ack); - - return mState; -} - - -SIPState SIPEngine::MODSendBYE() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mINVITE); - gReports.incr("OpenBTS.SIP.BYE.Out"); - char tmp[50]; - make_branch(tmp); - mViaBranch = tmp; - mCSeq++; - - osip_message_t * bye = sip_bye(mRemoteDomain.c_str(), mRemoteUsername.c_str(), - mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), mProxyPort, - mMyToFromHeader, mRemoteToFromHeader, - mViaBranch.c_str(), mCallIDHeader, mCSeq ); - - gSIPInterface.write(&mProxyAddr,bye); - saveBYE(bye,true); - osip_message_free(bye); - mState = MODClearing; - return mState; -} - -SIPState SIPEngine::MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - if (NULL == cause){ - if (!mINVITE){ - LOG(WARNING) << "Sending ERROR without invite, probably a CLI generated message"; - return mState; - } - cause = mINVITE; - } - - osip_message_t * error = sip_error(cause, mSIPIP.c_str(), - mSIPUsername.c_str(), mSIPPort, - code, reason); - gSIPInterface.write(&mProxyAddr,error); - saveERROR(error, true); - osip_message_free(error); - if (cancel){ - mState = MODCanceling; - } - return mState; -} - -SIPState SIPEngine::MODSendCANCEL() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mINVITE); - - osip_message_t * cancel = sip_cancel(mINVITE, mSIPIP.c_str(), - mSIPUsername.c_str(), mSIPPort); - gSIPInterface.write(&mProxyAddr,cancel); - saveCANCEL(cancel, true); - osip_message_free(cancel); - mState = MODCanceling; - return mState; -} - -SIPState SIPEngine::MODResendBYE() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mState==MODClearing); - assert(mBYE); - gSIPInterface.write(&mProxyAddr,mBYE); - return mState; -} - -SIPState SIPEngine::MODResendCANCEL() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - if (mState!=MODCanceling) LOG(ERR) << "incorrect state for this method"; - assert(mCANCEL); - gSIPInterface.write(&mProxyAddr,mCANCEL); - return mState; -} - -SIPState SIPEngine::MODResendERROR(bool cancel) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - if (cancel){ - if (mState!=MODCanceling) LOG(ERR) << "incorrect state for this method"; - } - assert(mERROR); - gSIPInterface.write(&mProxyAddr,mERROR); - return mState; -} - -/* there shouldn't be any more communications on this fifo, but we might - get a 487 RequestTerminated. We only need to respond and move on -kurtis */ -SIPState SIPEngine::MODWaitFor487(Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - osip_message_t * msg; - try { - msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"), lock); - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "487 Timeout"; - return mState; - } - //ok, message arrived - if (msg->status_code != 487){ - LOG(WARNING) << "unexpected " << msg->status_code << - " response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort; - return mState; - } else { - osip_message_t* ack = sip_ack( mRemoteDomain.c_str(), - mRemoteUsername.c_str(), - mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyToFromHeader, mRemoteToFromHeader, - mViaBranch.c_str(), mCallIDHeader, mCSeq - ); - gSIPInterface.write(&mProxyAddr,ack); - osip_message_free(ack); - return mState; - } -} - -SIPState SIPEngine::MODWaitForBYEOK(Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - bool responded = false; - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - while (!timeout.passed()) { - try { - osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); - responded = true; - unsigned code = ok->status_code; - saveResponse(ok); - osip_message_free(ok); - if (code!=200) { - LOG(WARNING) << "unexpected " << code << " response to BYE, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared"; - } else { - gReports.incr("OpenBTS.SIP.BYE-OK.In"); - } - break; - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "response timeout, resending BYE"; - MODResendBYE(); - } - } - - if (!responded) { - LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; - gReports.incr("OpenBTS.SIP.LostProxy"); - } - - mState = Cleared; - - return mState; -} - -SIPState SIPEngine::MODWaitForCANCELOK(Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - bool responded = false; - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - while (!timeout.passed()) { - try { - osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); - responded = true; - unsigned code = ok->status_code; - saveResponse(ok); - osip_message_free(ok); - if (code!=200) { - LOG(WARNING) << "unexpected " << code << " response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared"; - } - break; - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "response timeout, resending CANCEL"; - MODResendCANCEL(); - } - } - - if (!responded) { - LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; - gReports.incr("OpenBTS.SIP.LostProxy"); - } - - mState = Canceled; - - return mState; -} - -static bool containsResponse(vector *validResponses, unsigned code) -{ - for (int i = 0; i < validResponses->size(); i++) { - if (validResponses->at(i) == code) - return true; - } - return false; -} - -SIPState SIPEngine::MODWaitForResponse(vector *validResponses, Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(validResponses); - bool responded = false; - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - while (!timeout.passed()) { - try { - osip_message_t * resp = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); - responded = true; - unsigned code = resp->status_code; - if (code==200) { - saveResponse(resp); - mState = Canceled; - } - if (code==487) { - osip_message_t* ack = sip_ack( mRemoteDomain.c_str(), - mRemoteUsername.c_str(), - mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyToFromHeader, mRemoteToFromHeader, - mViaBranch.c_str(), mCallIDHeader, mCSeq); - gSIPInterface.write(&mProxyAddr,ack); - osip_message_free(ack); - } - osip_message_free(resp); - if (!containsResponse(validResponses, code)) { - LOG(WARNING) << "unexpected " << code << " response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared"; - } - break; - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "response timeout, resending CANCEL"; - MODResendCANCEL(); - } - } - - if (!responded) { LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; } - - return mState; -} - -SIPState SIPEngine::MODWaitForERRORACK(bool cancel, Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - bool responded = false; - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - while (!timeout.passed()) { - try { - osip_message_t * ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"),lock); - responded = true; - saveResponse(ack); - if ((NULL == ack->sip_method) || !strncmp(ack->sip_method,"ACK", 4)) { - LOG(WARNING) << "unexpected response to ERROR, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared"; - } - osip_message_free(ack); - break; - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "response timeout, resending ERROR"; - MODResendERROR(cancel); - } - } - - if (!responded) { - LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; - gReports.incr("OpenBTS.SIP.LostProxy"); - } - - if (cancel){ - mState = Canceled; - } - - return mState; -} - -SIPState SIPEngine::MTDCheckBYE() -{ - //LOG(DEBUG) << "user " << mSIPUsername << " state " << mState; - // If the call is not active, there should be nothing to check. - if (mState!=Active) return mState; - - // Need to check size of osip_message_t* fifo, - // so need to get fifo pointer and get size. - // HACK -- reach deep inside to get damn thing - int fifoSize = gSIPInterface.fifoSize(mCallID); - - - // Size of -1 means the FIFO does not exist. - // Treat the call as cleared. - if (fifoSize==-1) { - LOG(NOTICE) << "MTDCheckBYE attempt to check BYE on non-existant SIP FIFO"; - mState=Cleared; - return mState; - } - - // If no messages, there is no change in state. - if (fifoSize==0) return mState; - - osip_message_t * msg = gSIPInterface.read(mCallID,0,NULL); - - - if (msg->sip_method) { - if (strcmp(msg->sip_method,"BYE")==0) { - LOG(DEBUG) << "found msg="<sip_method; - saveBYE(msg,false); - gReports.incr("OpenBTS.SIP.BYE.In"); - mState = MTDClearing; - } - //repeated ACK, send OK - //pretty sure this never happens, but someone else left a fixme before... -kurtis - if (strcmp(msg->sip_method,"ACK")==0) { - LOG(DEBUG) << "Not responding to repeated ACK. FIXME"; - } - } - - //repeated OK, send ack - //MOC because that's the only time we ACK - if (msg->status_code==200){ - LOG(DEBUG) << "Repeated OK, resending ACK"; - MOCSendACK(); - } - - osip_message_free(msg); - return mState; -} - - -SIPState SIPEngine::MTDSendBYEOK() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mBYE); - gReports.incr("OpenBTS.SIP.BYE-OK.Out"); - osip_message_t * okay = sip_b_okay(mBYE); - gSIPInterface.write(&mProxyAddr,okay); - osip_message_free(okay); - mState = Cleared; - return mState; -} - -SIPState SIPEngine::MTDSendCANCELOK() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mCANCEL); - osip_message_t * okay = sip_b_okay(mCANCEL); - gSIPInterface.write(&mProxyAddr,okay); - osip_message_free(okay); - mState = Canceled; - return mState; -} - - -SIPState SIPEngine::MTCSendTrying() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - if (mINVITE==NULL) { - mState=Fail; - gReports.incr("OpenBTS.SIP.Failed.Local"); - } - if (mState==Fail) return mState; - - osip_message_t * trying = sip_trying(mINVITE, mSIPUsername.c_str(), mProxyIP.c_str()); - gSIPInterface.write(&mProxyAddr,trying); - osip_message_free(trying); - mState=Proceeding; - return mState; -} - - -SIPState SIPEngine::MTCSendRinging() -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mINVITE); - - LOG(DEBUG) << "send ringing"; - osip_message_t * ringing = sip_ringing(mINVITE, - mSIPUsername.c_str(), mProxyIP.c_str()); - gSIPInterface.write(&mProxyAddr,ringing); - osip_message_free(ringing); - - mState = Proceeding; - return mState; -} - - - -SIPState SIPEngine::MTCSendOK( short wRTPPort, unsigned wCodec, const GSM::LogicalChannel *chan) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - assert(mINVITE); - gReports.incr("OpenBTS.SIP.INVITE-OK.Out"); - mRTPPort = wRTPPort; - mCodec = wCodec; - LOG(DEBUG) << "port=" << wRTPPort << " codec=" << mCodec; - // Form ack from invite and new parameters. - osip_message_t * okay = sip_okay_sdp(mINVITE, mSIPUsername.c_str(), - mSIPIP.c_str(), mSIPPort, mRTPPort, mCodec); - writePrivateHeaders(okay,chan); - gSIPInterface.write(&mProxyAddr,okay); - osip_message_free(okay); - mState=Connecting; - return mState; -} - -SIPState SIPEngine::MTCCheckForACK(Mutex *lock) -{ - // wait for ack,set this to timeout of - // of call channel. If want a longer timeout - // period, need to split into 2 handle situation - // like MOC where this fxn is called multiple times. - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - //if (mState==Fail) return mState; - //osip_message_t * ack = NULL; - osip_message_t * ack; - - try { - ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.H"), lock); - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "timeout"; - gReports.incr("OpenBTS.SIP.ReadTimeout"); - mState = Timeout; - return mState; - } - catch (SIPError& e) { - LOG(NOTICE) << "read error"; - gReports.incr("OpenBTS.SIP.Failed.Local"); - mState = Fail; - return mState; - } - - if (ack->sip_method==NULL) { - LOG(NOTICE) << "SIP message with no method, status " << ack->status_code; - gReports.incr("OpenBTS.SIP.Failed.Local"); - mState = Fail; - osip_message_free(ack); - return mState; - } - - LOG(INFO) << "received sip_method="<sip_method; - - // check for duplicated INVITE - if( strcmp(ack->sip_method,"INVITE") == 0){ - LOG(NOTICE) << "received duplicate INVITE"; - } - // check for the ACK - else if( strcmp(ack->sip_method,"ACK") == 0){ - LOG(INFO) << "received ACK"; - mState=Active; - } - // check for the CANCEL - else if( strcmp(ack->sip_method,"CANCEL") == 0){ - LOG(INFO) << "received CANCEL"; - saveCANCEL(ack, false); - mState=MTDCanceling; - } - // check for strays - else { - LOG(NOTICE) << "unexpected Message "<sip_method; - gReports.incr("OpenBTS.SIP.Failed.Local"); - mState = Fail; - } - - osip_message_free(ack); - return mState; -} - - -SIPState SIPEngine::MTCCheckForCancel() -{ - // wait for ack,set this to timeout of - // of call channel. If want a longer timeout - // period, need to split into 2 handle situation - // like MOC where this fxn if called multiple times. - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - //if (mState=Fail) return Fail; - //osip_message_t * msg = NULL; - osip_message_t * msg; - - try { - msg = gSIPInterface.read(mCallID,0,NULL); - } - catch (SIPTimeout& e) { - gReports.incr("OpenBTS.SIP.ReadTimeout"); - return mState; - } - catch (SIPError& e) { - LOG(NOTICE) << "read error"; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Local"); - return mState; - } - - if (msg->sip_method==NULL) { - LOG(NOTICE) << "SIP message with no method, status " << msg->status_code; - if (mState!=Fail) { - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Local"); - } - osip_message_free(msg); - return mState; - } - - LOG(INFO) << "received sip_method=" << msg->sip_method; - - // check for duplicated INVITE - if (strcmp(msg->sip_method,"INVITE")==0) { - LOG(NOTICE) << "received duplicate INVITE"; - } - // check for the CANCEL - else if (strcmp(msg->sip_method,"CANCEL")==0) { - LOG(INFO) << "received CANCEL"; - saveCANCEL(msg, false); - mState=MTDCanceling; - } - // check for strays - else { - LOG(NOTICE) << "unexpected Message " << msg->sip_method; - gReports.incr("OpenBTS.SIP.Failed.Local"); - mState = Fail; - } - - osip_message_free(msg); - return mState; -} - - -void SIPEngine::InitRTP(const osip_message_t * msg ) -{ - if(mSession == NULL) - mSession = rtp_session_new(RTP_SESSION_SENDRECV); - - bool rfc2833 = gConfig.defines("SIP.DTMF.RFC2833"); - if (rfc2833) { - RtpProfile* profile = rtp_session_get_send_profile(mSession); - int index = gConfig.getNum("SIP.DTMF.RFC2833.PayloadType"); - rtp_profile_set_payload(profile,index,&payload_type_telephone_event); - // Do we really need this next line? - rtp_session_set_send_profile(mSession,profile); - } - - rtp_session_set_blocking_mode(mSession, TRUE); - rtp_session_set_scheduling_mode(mSession, TRUE); - rtp_session_set_connected_mode(mSession, TRUE); - rtp_session_set_symmetric_rtp(mSession, TRUE); - // Hardcode RTP session type to GSM full rate (GSM 06.10). - // FIXME -- Make this work for multiple vocoder types. - rtp_session_set_payload_type(mSession, 3); - - char d_ip_addr[20]; - char d_port[10]; - get_rtp_params(msg, d_port, d_ip_addr); - LOG(DEBUG) << "IP="< 0) return true; - // Error? Turn off DTMF sending. - LOG(WARNING) << "DTMF RFC-2833 failed on start."; - mDTMF = '\0'; - return false; -} - -void SIPEngine::stopDTMF() -{ - //false means not start - mblk_t *m = rtp_session_create_telephone_event_packet(mSession,false); - //volume 10 for some magic reason, end is true - int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),true,10,mDTMFDuration); - int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime); - mDTMFDuration += 160; - LOG (DEBUG) << "DTMF RFC-2833 sending " << mDTMF << " " << mDTMFDuration; - // Turn it off if there's an error. - if (code || bytes <= 0) { - LOG(ERR) << "DTMF RFC-2833 failed at end"; - } - mDTMF='\0'; - -} - - -void SIPEngine::txFrame(unsigned char* frame ) -{ - if(mState!=Active) return; - - // HACK -- Hardcoded for GSM/8000. - // FIXME -- Make this work for multiple vocoder types. - rtp_session_send_with_ts(mSession, frame, 33, mTxTime); - mTxTime += 160; - - if (mDTMF) { - //false means not start - mblk_t *m = rtp_session_create_telephone_event_packet(mSession,false); - //volume 10 for some magic reason, false means not end - int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),false,10,mDTMFDuration); - int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime); - mDTMFDuration += 160; - LOG (DEBUG) << "DTMF RFC-2833 sending " << mDTMF << " " << mDTMFDuration; - // Turn it off if there's an error. - if (code || bytes <=0) { - LOG(ERR) << "DTMF RFC-2833 failed after start."; - mDTMF='\0'; - } - } -} - - -int SIPEngine::rxFrame(unsigned char* frame) -{ - if(mState!=Active) return 0; - - int more; - int ret=0; - // HACK -- Hardcoded for GSM/8000. - // FIXME -- Make this work for multiple vocoder types. - ret = rtp_session_recv_with_ts(mSession, frame, 33, mRxTime, &more); - mRxTime += 160; - return ret; -} - - - - -SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, - const char * wCalledDomain , const char *messageText, const char *contentType, - const GSM::LogicalChannel *chan) -{ - LOG(DEBUG) << "mState=" << mState; - LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText; - // Before start, need to add mCallID - gSIPInterface.addCall(mCallID); - mInstigator = true; - gReports.incr("OpenBTS.SIP.MESSAGE.Out"); - - // Set MESSAGE params. - char tmp[50]; - make_branch(tmp); - mViaBranch = tmp; - mCSeq++; - - mRemoteUsername = wCalledUsername; - mRemoteDomain = wCalledDomain; - - osip_message_t * message = sip_message( - mRemoteUsername.c_str(), mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, - messageText, contentType); - - writePrivateHeaders(message,chan); - - // Send Invite to the SIP proxy. - gSIPInterface.write(&mProxyAddr,message); - saveINVITE(message,true); - osip_message_free(message); - mState = MessageSubmit; - return mState; -}; - - -SIPState SIPEngine::MOSMSWaitForSubmit(Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - - Timeval timeout(gConfig.getNum("SIP.Timer.B")); - assert(mINVITE); - osip_message_t *ok = NULL; - // have we received a 100 TRYING message? If so, don't retransmit after timeout - bool recv_trying = false; - while (!timeout.passed()) { - try { - // SIPInterface::read will throw SIPTIimeout if it times out. - // It should not return NULL. - ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"),lock); - } - catch (SIPTimeout& e) { - if (!recv_trying){ - LOG(NOTICE) << "SIP MESSAGE packet to " << mProxyIP << ":" << mProxyPort << " timedout; resending"; - gSIPInterface.write(&mProxyAddr,mINVITE); - } else { - LOG(NOTICE) << "SIP MESSAGE packet to " << mProxyIP << ":" << mProxyPort << " timedout; ignoring (got 100 TRYING)"; - } - continue; - } - assert(ok); - if((ok->status_code==100)) { - recv_trying = true; - LOG(INFO) << "received TRYING MESSAGE"; - } - if((ok->status_code==200) || (ok->status_code==202) ) { - mState = Cleared; - LOG(INFO) << "successful SIP MESSAGE SMS submit to " << mProxyIP << ":" << mProxyPort << ": " << mINVITE; - break; - } - //demonstrate that these are not forwarded correctly - if (ok->status_code >= 400){ - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Remote.4xx"); - LOG (ALERT) << "SIP MESSAGE rejected: " << ok->status_code << " " << ok->reason_phrase; - break; - } - LOG(WARNING) << "unhandled response " << ok->status_code; - osip_message_free(ok); - ok = NULL; - } - - if (!ok) { - //changed from "throw SIPTimeout()", as this seems more correct -k - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed.Local"); - gReports.incr("OpenBTS.SIP.ReadTimeout"); - LOG(ALERT) << "SIP MESSAGE timed out; is the smqueue server " << mProxyIP << ":" << mProxyPort << " OK?"; - gReports.incr("OpenBTS.SIP.LostProxy"); - } else { - osip_message_free(ok); - } - return mState; - -} - - - -SIPState SIPEngine::MTSMSSendOK(const GSM::LogicalChannel *chan) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - // If this operation was initiated from the CLI, there was no INVITE. - if (!mINVITE) { - LOG(INFO) << "clearing CLI-generated transaction"; - mState=Cleared; - return mState; - } - // Form ack from invite and new parameters. - osip_message_t * okay = sip_okay(mINVITE, mSIPUsername.c_str(), - mSIPIP.c_str(), mSIPPort); - writePrivateHeaders(okay,chan); - gSIPInterface.write(&mProxyAddr,okay); - osip_message_free(okay); - mState=Cleared; - return mState; -} - - - -bool SIPEngine::sendINFOAndWaitForOK(unsigned wInfo, Mutex *lock) -{ - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - - char tmp[50]; - make_branch(tmp); - mViaBranch = tmp; - mCSeq++; - osip_message_t * info = sip_info( wInfo, - mRemoteUsername.c_str(), mRTPPort, mSIPUsername.c_str(), - mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), - mMyTag.c_str(), mViaBranch.c_str(), mCallIDHeader, mCSeq); - gSIPInterface.write(&mProxyAddr,info); - - Timeval timeout(gConfig.getNum("SIP.Timer.F")); - osip_message_t *ok = NULL; - while (!timeout.passed()) { - try { - // This will timeout on failure. It will not return NULL. - ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"), lock); - LOG(DEBUG) << "received status " << ok->status_code << " " << ok->reason_phrase; - } - catch (SIPTimeout& e) { - LOG(NOTICE) << "SIP RFC-2967 INFO packet to " << mProxyIP << ":" << mProxyPort << " timedout; resending"; - gSIPInterface.write(&mProxyAddr,info); - continue; - } - } - osip_message_free(info); - if (!ok) { - LOG(ALERT) << "SIP RFC-2967 INFO timed out; is the proxy at " << mProxyIP << ":" << mProxyPort << " OK?"; - gReports.incr("OpenBTS.SIP.LostProxy"); - return false; - } - LOG(DEBUG) << "received status " << ok->status_code << " " << ok->reason_phrase; - bool retVal = (ok->status_code==200); - osip_message_free(ok); - if (!retVal) LOG(WARNING) << "SIP RFC-2967 INFO failed on server " << mProxyIP << ":" << mProxyPort << " OK?"; - return retVal; -} - -/* reinvite stuff */ -/* return true if this is the same invite as the one we have stored */ -bool SIPEngine::sameINVITE(osip_message_t * msg){ - assert(mINVITE); - if (NULL == msg){ - LOG(NOTICE) << "trying to compare empty message"; - return false; - } - // We are assuming that the callids match. - // Otherwise, this would not have been called. - // FIXME -- Check callids and assrt if they down match. - // So we just check the CSeq. - // FIXME -- Check all of the pointers along these chains and log ERR if anthing is missing. - - const char *cn1 = msg->cseq->number; - if (!cn1) { - LOG(ERR) << "no cseq in msg"; - return false; - } - int n1 = atoi(cn1); - - const char *cn2 = mINVITE->cseq->number; - if (!cn2) { - LOG(ERR) << "no cseq in mINVITE"; - return false; - } - int n2 = atoi(cn2); - - if (n1!=n2) { - LOG(NOTICE) << "possible reinvite CSeq A " << cn1 << " (" << n1 << ") CSeq B " << cn2 << " (" << n2 << ")"; - } - - return n1==n2; -} - - -SIPState SIPEngine::inboundHandoverCheckForOK(Mutex *lock) -{ - return MOCCheckForOK(lock); -} - - - -SIPState SIPEngine::inboundHandoverSendACK() -{ - return MOCSendACK(); -} - -SIPState SIPEngine::inboundHandoverSendINVITE(TransactionEntry *transaction, unsigned wRTPPort) -{ - // We are "BS2" in the handover ladder diagram. - - LOG(INFO) << "user " << mSIPUsername << " state " << mState; - // Before start, need to add mCallID - mCallID = transaction->CallID(); - gSIPInterface.addCall(mCallID); - - // Set Invite params. - // New from tag + via branch - // new CSEQ and codec - char tmp[100]; - make_tag(tmp); - make_branch(tmp); - mViaBranch = tmp; - mCodec = transaction->Codec(); - mCSeq = transaction->CSeq(); - mCSeq++; - - mRemoteDomain = transaction->ToIP(); - mRemoteUsername = transaction->ToUsername(); - mRTPPort = wRTPPort; - mRTPRemPort = transaction->RTPRemPort(); - mRTPRemIP = transaction->RTPRemIP(); - mSIPUsername = transaction->FromUsername(); - string SIPDisplayname = transaction->FromUsername(); - mFromTag = transaction->FromTag(); - - if(mSession == NULL) { - mSession = rtp_session_new(RTP_SESSION_SENDRECV); - // do what we need to from InitRTP() without a message - bool rfc2833 = gConfig.defines("SIP.DTMF.RFC2833"); - if (rfc2833) { - RtpProfile* profile = rtp_session_get_send_profile(mSession); - int index = gConfig.getNum("SIP.DTMF.RFC2833.PayloadType"); - rtp_profile_set_payload(profile,index,&payload_type_telephone_event); - // Do we really need this next line? - rtp_session_set_send_profile(mSession,profile); - } - rtp_session_set_blocking_mode(mSession, TRUE); - rtp_session_set_scheduling_mode(mSession, TRUE); - rtp_session_set_connected_mode(mSession, TRUE); - rtp_session_set_symmetric_rtp(mSession, TRUE); - // Hardcode RTP session type to GSM full rate (GSM 06.10). - // FIXME -- Make this work for multiple vocoder types. - rtp_session_set_payload_type(mSession, 3); - rtp_session_set_local_addr(mSession, "0.0.0.0", mRTPPort ); - rtp_session_set_remote_addr(mSession, mRTPRemIP.c_str(), mRTPRemPort); - // Check for event support. - int code = rtp_session_telephone_events_supported(mSession); - if (code == -1) { - if (rfc2833) { LOG(ALERT) << "RTP session does not support selected DTMF method RFC-2833"; } - else { LOG(WARNING) << "RTP session does not support telephone events"; } - } - } - - // unpack RTP state and shove it into the session structure - char *items = strdup(transaction->RTPState().c_str()); - char *thisItem; - vector RTPState; - while ((thisItem=strsep(&items,","))!=NULL) { - RTPState.push_back(strtol(thisItem,NULL,10)); - } - free(items); - assert(RTPState.size() == 19); - /* Out of desperation, when the RTP refused to work, I transferred from BS1 to BS2 just about - * all the state in this struct. Well, it turns out NONE of it is necessary. Something else - * entirely was the problem. (Or, technically, state in a different struct.) Anyway, I'm - * leaving the transferring, and just not copying in anything here. So if any of it appears - * to be important some day, it will be easy to experiment. You're welcome. - mSession->rtp.snd_time_offset = RTPState[0]; - mSession->rtp.snd_ts_offset = RTPState[1]; - mSession->rtp.snd_rand_offset = RTPState[2]; - mSession->rtp.snd_last_ts = RTPState[3]; - mSession->rtp.rcv_time_offset = RTPState[4]; - mSession->rtp.rcv_ts_offset = RTPState[5]; - mSession->rtp.rcv_query_ts_offset = RTPState[6]; - mSession->rtp.rcv_last_ts = RTPState[7]; - mSession->rtp.rcv_last_app_ts = RTPState[8]; - mSession->rtp.rcv_last_ret_ts = RTPState[9]; - mSession->rtp.hwrcv_extseq = RTPState[10]; - mSession->rtp.hwrcv_seq_at_last_SR = RTPState[11]; - mSession->rtp.hwrcv_since_last_SR = RTPState[12]; - mSession->rtp.last_rcv_SR_ts = RTPState[13]; - mSession->rtp.last_rcv_SR_time.tv_sec = RTPState[14]; mSession->rtp.last_rcv_SR_time.tv_usec = RTPState[15]; - mSession->rtp.snd_seq = RTPState[16]; - mSession->rtp.last_rtcp_report_snt_r = RTPState[17]; - mSession->rtp.last_rtcp_report_snt_s = RTPState[18]; - mSession->rtp.rtcp_report_snt_interval = RTPState[19]; - mSession->rtp.last_rtcp_packet_count = RTPState[20]; - mSession->rtp.sent_payload_bytes = RTPState[21]; - */ - - osip_message_t * invite = sip_reinvite( - mRemoteUsername.c_str(), mRemoteDomain.c_str(), - SIPDisplayname.c_str(), mSIPUsername.c_str(), - transaction->FromTag().c_str(), transaction->FromUsername().c_str(), transaction->FromIP().c_str(), - transaction->ToTag().c_str(), transaction->ToUsername().c_str(), transaction->ToIP().c_str(), - mViaBranch.c_str(), mCallID.c_str(), transaction->CallIP().c_str(), - mCSeq, mCodec, mRTPPort, - transaction->SessionID().c_str(), transaction->SessionVersion().c_str()); - - // Send Invite to remote party. - struct sockaddr_in rmt; - if (!resolveAddress(&rmt, transaction->RmtIP().c_str(), transaction->RmtPort())) { - LOG(ALERT) << "unable to resolve IP address of remote party to send INVITE"; - mState = Fail; - gReports.incr("OpenBTS.SIP.Failed"); - gReports.incr("OpenBTS.SIP.LostProxy"); - return mState; - } - gSIPInterface.write(&rmt, invite); - saveINVITE(invite, true); - osip_message_free(invite); - // FIXME - is this the right state? - mState = Starting; - return mState; -} - - -// vim: ts=4 sw=4 diff --git a/SIP/SIPEngine.h b/SIP/SIPEngine.h deleted file mode 100644 index 5f47158..0000000 --- a/SIP/SIPEngine.h +++ /dev/null @@ -1,420 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - -#ifndef SIPENGINE_H -#define SIPENGINE_H - -#include -#include -#include -#include - - -#include -#include - -#include -#include - - -namespace Control { -class TransactionEntry; -} - -namespace GSM { -class GSMLogicalChannel; -} - - -namespace SIP { - - -class SIPInterface; - - -enum SIPState { - NullState, - Timeout, - Starting, - Proceeding, - Ringing, - Busy, - Connecting, - Active, - MODClearing, - MODCanceling, - MTDClearing, - MTDCanceling, - Canceled, - Cleared, - Fail, - MessageSubmit, - HandoverInbound, - HandoverInboundReferred, - HandoverOutbound -}; - - -const char* SIPStateString(SIPState s); -std::ostream& operator<<(std::ostream& os, SIPState s); - -class SIPEngine -{ - -public: - - enum Method { SIPRegister =0, SIPUnregister=1 }; - -private: - - - /**@name General SIP tags and ids. */ - //@{ - std::string mRemoteUsername; - std::string mRemoteDomain; - std::string mMyTag; - std::string mCallID; - std::string mViaBranch; - std::string mSIPUsername; ///< our SIP username - unsigned mCSeq; - std::string mAsteriskIP; - std::string mFromTag; - /**@name Pre-formed headers. These point into mINVITE. */ - //@{ - osip_from_t* mMyToFromHeader; ///< To/From header for us - osip_from_t* mRemoteToFromHeader; ///< To/From header for the remote party - osip_call_id_t *mCallIDHeader; ///< the call ID header for this transaction - //@} - //@} - - /**@name SIP UDP parameters */ - //@{ - unsigned mSIPPort; ///< our local SIP port - std::string mSIPIP; ///< our SIP IP address as seen by the proxy - std::string mProxyIP; ///< IP address of the SIP proxy - unsigned mProxyPort; ///< UDP port number of the SIP proxy - struct ::sockaddr_in mProxyAddr; ///< the ready-to-use UDP address - //@} - - /**@name Saved SIP messages. */ - //@{ - osip_message_t * mINVITE; ///< the INVITE message for this transaction - osip_message_t * mLastResponse; ///< the last response received for this transaction - //we should maybe push these together sometime? -kurtis - osip_message_t * mBYE; ///< the BYE message for this transaction - osip_message_t * mCANCEL; ///< the CANCEL message for this transaction - osip_message_t * mERROR; ///< the ERROR message for this transaction - //@} - - /**@name RTP state and parameters. */ - //@{ - short mRTPPort; - short mRTPRemPort; - string mRTPRemIP; - unsigned mCodec; - RtpSession * mSession; ///< RTP media session - unsigned int mTxTime; ///< RTP transmission timestamp in 8 kHz samples - unsigned int mRxTime; ///< RTP receive timestamp in 8 kHz samples - //@} - - SIPState mState; ///< current SIP call state - bool mInstigator; ///< true if this side initiated the call - - /**@name RFC-2833 DTMF state. */ - //@{ - char mDTMF; ///< current DTMF digit, \0 if none - unsigned mDTMFDuration; ///< duration of DTMF event so far - unsigned mDTMFStartTime; ///< start time of the DTMF key event - //@} - - -public: - - /** - Default constructor. Initialize the object. - @param proxy : - */ - SIPEngine(const char* proxy, const char* IMSI=NULL); - - /** Destroy held message copies. */ - ~SIPEngine(); - - const std::string& callID() const { return mCallID; } - void callID(const std::string wCallID) { mCallID = wCallID; } - - const std::string& proxyIP() const { return mProxyIP; } - unsigned proxyPort() const { return mProxyPort; } - - /** Return the current SIP call state. */ - SIPState state() const { return mState; } - - /** Return the from tag. */ - std::string FromTag() const { return mFromTag; } - - /** Return the INVITE. */ - osip_message_t * INVITE() const { return mINVITE; } - - /** Return the last response. */ - osip_message_t * LastResponse() const { return mLastResponse; } - - /** Return To/From header for us */ - osip_from_t * MyToFromHeader() const { return mMyToFromHeader; } - - /** Return To/From header for the remote party */ - osip_from_t * RemoteToFromHeader() const { return mRemoteToFromHeader; } - - /** Return RTP session */ - RtpSession * RTPSession() const { return mSession; } - - /** Force the state externally. */ - void state(SIPState wState) { mState=wState; } - - /** Return the RTP Port being used. */ - short RTPPort() const { return mRTPPort; } - - /** Return if the call has finished, successful for not. */ - bool finished() const { return (mState==Cleared || mState==Canceled || mState==Fail); } - - /** Return if the communication was started by us (true) or not (false) */ - /* requires an mINVITE be established */ - bool instigator() const { return mInstigator; } - - /** Set the user to IMSI and generate a call ID; for mobile-originated transactions. */ - void user( const char * IMSI ); - - /** Set the use to IMSI and set the other SIP call parameters; for network-originated transactions. */ - void user( const char * wCallID, const char * IMSI , const char *origID, const char *origHost); - - /**@name Messages for SIP registration. */ - //@{ - - /** - Send sip register and look at return msg. - Can throw SIPTimeout(). - @return True on success. - */ - bool Register(Method wMethod=SIPRegister, const GSM::LogicalChannel* chan = NULL, string *RAND = NULL, const char *IMSI = NULL, const char *SRES = NULL); - - // (pat) The UMTS code is still using the old function prototype without the chan arg. - // It would be better to add new arguments to the end of the list. - bool Register(Method wMethod, string *wRAND, const char *wIMSI=0, const char *wSRES=0) { - return Register(wMethod , NULL, wRAND, wIMSI, wSRES); - } - - /** - Send sip unregister and look at return msg. - Can throw SIPTimeout(). - @return True on success. - */ - bool unregister() { return (Register(SIPUnregister)); }; - - //@} - - - //SIPState SOSResendINVITE(); - - //SIPState SOSCheckForOK(); - - //SIPState SOSSendACK(); - - //@} - - - - /**@name Messages associated with MOC procedure. */ - //@{ - - /** - Send an invite message. - @param calledUser SIP userid or E.164 address. - @param calledDomain SIP user's domain. - @param rtpPort UDP port to use for speech (will use this and next port) - @param codec Code for codec to be used. - @return New SIP call state. - */ - SIPState MOCSendINVITE(const char * calledUser, - const char * calledDomain, short rtpPort, unsigned codec, - const GSM::LogicalChannel *chan = NULL); - - - SIPState MOCResendINVITE(); - - SIPState MOCCheckForOK(Mutex *lock); - - SIPState MOCSendACK(); - - //@} - - /**@name Messages associated with MOSMS procedure. */ - //@{ - - /** - Send an instant message. - @param calledUsername SIP userid or E.164 address. - @param calledDomain SIP user's domain. - @param messageText MESSAGE payload as a C string. - @return New SIP call state. - */ - SIPState MOSMSSendMESSAGE(const char * calledUsername, - const char * calledDomain, const char *messageText, - const char *contentType, - const GSM::LogicalChannel *chan = NULL); - - SIPState MOSMSWaitForSubmit(Mutex *lock=NULL); - - SIPState MTSMSSendOK(const GSM::LogicalChannel *chan = NULL); - - //@} - - - /**@name Messages associated with MTC procedure. */ - //@{ - SIPState MTCSendTrying(); - - SIPState MTCSendRinging(); - - SIPState MTCSendOK(short rtpPort, unsigned codec, const GSM::LogicalChannel *chan = NULL); - - SIPState MTCCheckForACK(Mutex *lock); - - SIPState MTCCheckForCancel(); - //@} - - /**@name Messages associated with MTSMS procedure. */ - //@{ - - SIPState MTCSendOK(const GSM::LogicalChannel *chan = NULL); - - //@} - - /**@name Messages for MOD procedure. */ - //@{ - SIPState MODSendBYE(); - - SIPState MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel); - - SIPState MODSendCANCEL(); - - SIPState MODResendBYE(); - - SIPState MODResendCANCEL(); - - SIPState MODResendERROR(bool cancel); - - SIPState MODWaitForBYEOK(Mutex *lock=NULL); - - SIPState MODWaitForCANCELOK(Mutex *lock=NULL); - - SIPState MODWaitForERRORACK(bool cancel, Mutex *lock=NULL); - - SIPState MODWaitFor487(Mutex *lock=NULL); - - SIPState MODWaitForResponse(vector *validResponses, Mutex *lock=NULL); - //@} - - - /**@name Messages for MTD procedure. */ - //@{ - SIPState MTDCheckBYE(); - - SIPState MTDSendBYEOK(); - - SIPState MTDSendCANCELOK(); - //@} - - /**@name Messages for Handover procedure. */ - //@{ - SIPState inboundHandoverSendINVITE(Control::TransactionEntry*, unsigned int); - SIPState inboundHandoverCheckForOK(Mutex *lock); - SIPState inboundHandoverSendACK(); - //@} - - - /** Set up to start sending RFC2833 DTMF event frames in the RTP stream. */ - bool startDTMF(char key); - - /** Send a DTMF end frame and turn off the DTMF events. */ - void stopDTMF(); - - /** Send a vocoder frame over RTP. */ - void txFrame(unsigned char* frame); - - /** - Receive a vocoder frame over RTP. - @param The vocoder frame - @return new RTP timestamp - */ - int rxFrame(unsigned char* frame); - - void MOCInitRTP(); - void MTCInitRTP(); - - /** In-call Signalling */ - //@{ - - /** - Send a SIP INFO message, usually for DTMF. - Most parameters taken from current SIPEngine state. - This call blocks for the response. - @param wInfo The DTMF signalling code. - @return Success/Fail flag. - */ - bool sendINFOAndWaitForOK(unsigned wInfo, Mutex *lock=NULL); - - //@} - - - /** Save a copy of an INVITE or MESSAGE message in the engine. */ - void saveINVITE(const osip_message_t *INVITE, bool mine); - - /** Save a copy of a response message in the engine. */ - void saveResponse(osip_message_t *respsonse); - - /** Save a copy of a BYE message in the engine. */ - void saveBYE(const osip_message_t *BYE, bool mine); - - /** Save a copy of a CANCEL message in the engine. */ - void saveCANCEL(const osip_message_t *CANCEL, bool mine); - - /** Save a copy of a ERROR message in the engine. */ - void saveERROR(const osip_message_t *ERROR, bool mine); - - - /** Determine if this invite matches the saved one */ - bool sameINVITE(osip_message_t * msg); - - private: - - /** - Initialize the RTP session. - @param msg A SIP INVITE or 200 OK wth RTP parameters - */ - void InitRTP(const osip_message_t * msg ); - - /** - Generate a standard set of private headers on initiating messages. - */ - void writePrivateHeaders(osip_message_t *msg, const GSM::LogicalChannel *chan); -}; - - -}; - -#endif // SIPENGINE_H -// vim: ts=4 sw=4 diff --git a/SIP/SIPExport.h b/SIP/SIPExport.h new file mode 100644 index 0000000..081273b --- /dev/null +++ b/SIP/SIPExport.h @@ -0,0 +1,99 @@ +/* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +*/ +#ifndef _SIPEXPORTH_ +#define _SIPEXPORTH_ 1 + +#include + +namespace SIP { +extern void startRegister(const Control::FullMobileId &msic, const string rand, const string sres, L3LogicalChannel *chan); +class SipDialog; +extern SipDialog *getRegistrar(); + +// These are the SipDialog states that are sent as messages to the L3 state machines. +// They are a subset of SIPState, because SIPState tracks the exact state of +// the various acknowledgements, while this only cares about the overall state of the dialog. +// For example, if the dialog is being cleared or canceled, L3 no longer cares about who initiated the clearing. +// Note: ORDER IS IMPORTANT. In general you can transition only forward through these states +// so instead of having a big allowed state transition table, we only allow forward progress, +// with any exceptions handled specially. +struct DialogState { + enum msgState { + dialogUndefined, // The initial state until something happens. For MO we stay here until answer to INVITE + dialogStarted, // initial INVITE sent. + dialogProceeding, + dialogRinging, + dialogActive, + dialogBye, + dialogFail, // busy, cancel or fail for any reason. + + // Other messages not related to the current dialog state. + dialogDtmf, + }; + static const char *msgStateString(DialogState::msgState dstate); +}; + +struct SipCode { + int mCode; + const char *mReason; + SipCode() : mCode(0), mReason("") {} + SipCode(int wCode, const char *wReason) : mCode(wCode), mReason(wReason) {} +}; + +class DialogMessage { + virtual void _define_vtable(); + public: + virtual ~DialogMessage() {} + Control::TranEntryId mTranId; // The associated TransactionEntry or 0 for the old MobilityManagement SipBase which has none. + // By using the TransactionId instead of a pointer, we dont crash if the TransactionEntry disappears + // while this message is in flight. + // Update: now the message is queued into the TranEntry, so this probably is not used. + DialogState::msgState mMsgState; + //SipMethod::MethodType mMethod; // If not a method then SipMethod:Undefined. + unsigned mSipStatusCode; // eg 200 for OK. + DialogMessage(Control::TranEntryId wTranId,DialogState::msgState nextState, unsigned code) : + mTranId(wTranId), mMsgState(nextState), mSipStatusCode(code) {} + DialogState::msgState dialogState() const { return mMsgState; } + unsigned sipStatusCode() const { return mSipStatusCode; } + + // Works, but not used: + // bool isBusy() const { return mSipStatusCode == 486 || mSipStatusCode == 600 || mSipStatusCode == 603; } + + // What a brain dead language. +#define DIALOG_MESSAGE_CONSTRUCTOR(subclass) \ + subclass(Control::TranEntryId wTranId,DialogState::msgState nextState, unsigned code) : DialogMessage(wTranId,nextState,code) {} +}; + +struct DialogUssdMessage : public DialogMessage { + string dmMsgPayload; // For USSD message. + DIALOG_MESSAGE_CONSTRUCTOR(DialogUssdMessage) +}; + +struct DialogChallengeMessage : public DialogMessage { + string dmRand; // for 401 message. + Int_z dmRejectCause; + DIALOG_MESSAGE_CONSTRUCTOR(DialogChallengeMessage) +}; + +struct DialogAuthMessage : public DialogMessage { + string dmKc; + string dmPAssociatedUri; + string dmPAssertedIdentity; + DIALOG_MESSAGE_CONSTRUCTOR(DialogAuthMessage) +}; + +std::ostream& operator<<(std::ostream& os, const DialogMessage&); +std::ostream& operator<<(std::ostream& os, const DialogMessage*); +}; +#endif diff --git a/SIP/SIPInterface.cpp b/SIP/SIPInterface.cpp deleted file mode 100644 index 55badb4..0000000 --- a/SIP/SIPInterface.cpp +++ /dev/null @@ -1,594 +0,0 @@ -/* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -* Copyright 2011 Range Networks, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - - - -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "SIPUtility.h" -#include "SIPInterface.h" -#include "SIPMessage.h" - -#include - -#undef WARNING - - - -using namespace std; -using namespace SIP; - -using namespace GSM; -using namespace Control; - - - - -// SIPMessageMap method definitions. - -void SIPMessageMap::write(const std::string& call_id, osip_message_t * msg) -{ - LOG(DEBUG) << "call_id=" << call_id << " msg=" << msg; - - OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id); - if( fifo==NULL ) { - // FIXME -- If this write fails, send "call leg non-existent" response on SIP interface. - LOG(NOTICE) << "missing SIP FIFO "<write(msg); -} - -osip_message_t * SIPMessageMap::read(const std::string& call_id, unsigned readTimeout, Mutex *lock) -{ - LOG(DEBUG) << "call_id=" << call_id; - OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id); - if (!fifo) { - LOG(NOTICE) << "missing SIP FIFO "<unlock(); - osip_message_t * msg = fifo->read(readTimeout); - if (lock) lock->lock(); - if (!msg) throw SIPTimeout(); - return msg; -} - - -osip_message_t * SIPMessageMap::read(const std::string& call_id, Mutex *lock) -{ - LOG(DEBUG) << "call_id=" << call_id; - OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id); - if (!fifo) { - LOG(NOTICE) << "missing SIP FIFO "<unlock(); - osip_message_t * msg = fifo->read(); - if (lock) lock->lock(); - if (!msg) throw SIPTimeout(); - return msg; -} - - -bool SIPMessageMap::add(const std::string& call_id, const struct sockaddr_in* returnAddress) -{ - // Check for duplicates. - if (mMap.readNoBlock(call_id)) { - LOG(WARNING) << "attempt to add duplicate SIP message FIFO for " << call_id; - return true; - } - OSIPMessageFIFO * fifo = new OSIPMessageFIFO(returnAddress); - mMap.write(call_id, fifo); - return true; -} - -bool SIPMessageMap::remove(const std::string& call_id) -{ - OSIPMessageFIFO * fifo = mMap.readNoBlock(call_id); - if(fifo == NULL) return false; - mMap.remove(call_id); - return true; -} - - - - - -// SIPInterface method definitions. - -bool SIPInterface::addCall(const string &call_id) -{ - LOG(INFO) << "creating SIP message FIFO callID " << call_id; - return mSIPMap.add(call_id,mSIPSocket.source()); -} - - -bool SIPInterface::removeCall(const string &call_id) -{ - LOG(INFO) << "removing SIP message FIFO callID " << call_id; - return mSIPMap.remove(call_id); -} - -int SIPInterface::fifoSize(const std::string& call_id ) -{ - OSIPMessageFIFO * fifo = mSIPMap.map().read(call_id,0); - if(fifo==NULL) return -1; - return fifo->size(); -} - - - - - -void SIP::driveLoop( SIPInterface * si){ - while (true) { - si->drive(); - } -} - -void SIPInterface::start(){ - // Start all the osip/ortp stuff. - parser_init(); - ortp_init(); - ortp_scheduler_init(); - // FIXME -- Can we coordinate this with the global logger? - //ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR); - mDriveThread.start((void *(*)(void*))driveLoop,this ); -} - - - - -void SIPInterface::write(const struct sockaddr_in* dest, osip_message_t *msg) -{ - char * str; - size_t msgSize; - osip_message_to_str(msg, &str, &msgSize); - if (!str) { - LOG(ALERT) << "osip_message_to_str produced a NULL pointer."; - return; - } - //if it's any of these transactions, record it in the database - // FIXME - We should really remove all direct access to the SR. - string name = osip_message_get_from(msg)->url->username; - if (msg->sip_method && - (!strncmp(msg->sip_method, "INVITE", 6) || - !strncmp(msg->sip_method, "REGISTER", 8) || - !strncmp(msg->sip_method, "MESSAGE", 7))) { - if (gSubscriberRegistry.imsiSet(name, "ipaddr", - osip_message_get_from(msg)->url->host)){ - LOG(INFO) << "SR ipaddr Update Problem"; - } - if (gSubscriberRegistry.imsiSet(name, "port", - gConfig.getStr("SIP.Local.Port"))){ - LOG(INFO) << "SR port Update Problem"; - } - } - char firstLine[100]; - sscanf(str,"%100[^\n]",firstLine); - LOG(INFO) << "write " << firstLine; - LOG(DEBUG) << "write " << str; - - if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss")) { - LOG(NOTICE) << "simulating dropped outbound SIP packet: " << firstLine; - free(str); - return; - } - - mSocketLock.lock(); - mSIPSocket.send((const struct sockaddr*)dest,str,strlen(str)); - mSocketLock.unlock(); - free(str); -} - - - - -void SIPInterface::drive() -{ - // All inbound SIP messages go here for processing. - - LOG(DEBUG) << "blocking on socket"; - int numRead = mSIPSocket.read(mReadBuffer); - if (numRead<0) { - LOG(ALERT) << "cannot read SIP socket."; - return; - } - if (numRead<10) { - LOG(WARNING) << "malformed packet (" << numRead << " bytes) on SIP socket"; - return; - } - mReadBuffer[numRead] = '\0'; - if (random()%100 < gConfig.getNum("Test.SIP.SimulatedPacketLoss")) { - LOG(NOTICE) << "simulating dropped inbound SIP packet: " << mReadBuffer; - return; - } - - // Get the proxy from the inbound message. -#if 0 - const struct sockaddr_in* sourceAddr = mSIPSocket.source(); - char msgHost[256]; - const char* msgHostRet = inet_ntop(AF_INET,&(sourceAddr->sin_addr),msgHost,255); - if (!msgHostRet) { - LOG(ALERT) << "cannot translate SIP source address for " << mReadBuffer; - return; - } - unsigned msgPortNumber = sourceAddr->sin_port; - char msgPort[20]; - sprintf(msgPort,"%u",msgPortNumber); - string proxy = string(msgHost) + string(":") + string(msgPort); -#endif - - char firstLine[101]; - sscanf(mReadBuffer,"%100[^\n]",firstLine); - LOG(INFO) << "read " << firstLine; - LOG(DEBUG) << "read " << mReadBuffer; - - - try { - - // Parse the mesage. - osip_message_t * msg; - int i = osip_message_init(&msg); - LOG(DEBUG) << "osip_message_init " << i; - int j = osip_message_parse(msg, mReadBuffer, strlen(mReadBuffer)); - // seems like it ought to do something more than display an error, - // but it used to not even do that. - LOG(DEBUG) << "osip_message_parse " << j; - // heroic efforts to get it to parse the www-authenticate header failed, - // so we'll just crowbar that sucker in. - char *p = strcasestr(mReadBuffer, "nonce"); - if (p && p[-1] != 'c') { // nonce but not cnonce - p += 6; - char *q = p; - while (isalnum(*q)) { q++; } - string RAND = string(mReadBuffer, p-mReadBuffer, q-p); - LOG(INFO) << "crowbar www-authenticate " << RAND; - osip_www_authenticate_t *auth; - osip_www_authenticate_init(&auth); - string auth_type = "Digest"; - osip_www_authenticate_set_auth_type(auth, osip_strdup(auth_type.c_str())); - osip_www_authenticate_set_nonce(auth, osip_strdup(RAND.c_str())); - int k = osip_list_add (&msg->www_authenticates, auth, -1); - if (k < 0) LOG(ERR) << "problem adding www_authenticate"; - } - - // The parser doesn't seem to be interested in authentication info either. - // Get kc from there and put it in tmsi table. - char *pp = strcasestr(mReadBuffer, "cnonce"); - if (pp) { - pp += 7; - char *qq = pp; - while (isalnum(*qq)) { qq++; } - string kc = string(mReadBuffer, pp-mReadBuffer, qq-pp); - LOG(INFO) << "storing kc in TMSI table"; // mustn't display kc in log - const char *imsi = osip_uri_get_username(msg->to->url); - if (imsi && strlen(imsi) > 0) { - gTMSITable.putKc(imsi+4, kc); - } else { - LOG(ERR) << "can't find imsi to store kc"; - } - } - - if (msg->sip_method) LOG(DEBUG) << "read method " << msg->sip_method; - - // Must check if msg is an invite. - // if it is, handle appropriatly. - // FIXME -- Check return value in case this failed. - // FIXME -- If we support USSD via SIP, we will need to check the map first. - checkInvite(msg); - - // Multiplex out the received SIP message to active calls. - - // If we write to non-existent call_id. - // this is errant message so need to catch - // Internal error excatpion. and give nice - // message (instead of aborting) - // Don't free call_id_num. It points into msg->call_id. - char * call_id_num = osip_call_id_get_number(msg->call_id); - if( call_id_num == NULL ) { - LOG(WARNING) << "message with no call id"; - throw SIPError(); - } - LOG(DEBUG) << "got message " << msg << " with call id " << call_id_num << " and writing it to the map."; - string call_num(call_id_num); - // Don't free msg. Whoever reads the FIFO will do that. - mSIPMap.write(call_num, msg); - } - catch(SIPException) { - LOG(NOTICE) << "discarded out-of-place SIP message: " << mReadBuffer; - } -} - - - - -const char* extractIMSI(const osip_message_t *msg) -{ - // Get request username (IMSI) from invite. - // Form of the name is IMSI, and it should always be 18 or 19 char. - const char * IMSI = msg->req_uri->username; - LOG(INFO) << msg->sip_method << " to "<< IMSI; - // IMSIs are 14 or 15 char + "IMSI" prefix - // FIXME -- We need better validity-checking. - unsigned namelen = strlen(IMSI); - if ((namelen>19)||(namelen<18)) { - LOG(WARNING) << "INVITE with malformed username \"" << IMSI << "\""; - return NULL; - } - // Skip first 4 char "IMSI". - return IMSI+4; -} - - -const char* extractCallID(const osip_message_t* msg) -{ - if (!msg->call_id) return NULL; - return osip_call_id_get_number(msg->call_id); - -} - - - -void SIPInterface::sendEarlyError(osip_message_t * cause, - const char *proxy, - int code, const char * reason) -{ - const char *user = cause->req_uri->username; - unsigned port = mSIPSocket.port(); - struct ::sockaddr_in remote; - if (!resolveAddress(&remote,proxy)) { - LOG(ALERT) << "cannot resolve IP address for " << proxy; - return; - } - - osip_message_t * error = sip_error(cause, gConfig.getStr("SIP.Local.IP").c_str(), - user, port, - code, reason); - write(&remote,error); - osip_message_free(error); -} - - -bool SIPInterface::checkInvite( osip_message_t * msg) -{ - LOG(DEBUG); - - // This code dispatches new transactions coming from the network-side SIP interface. - // All transactions originating here are going to be mobile-terminated. - // Yes, this method is too long and needs to be broken up into smaller steps. - - // Is there even a method? - const char *method = msg->sip_method; - if (!method) return false; - - // Check for INVITE or MESSAGE methods. - // Check channel availability now, too, - // even if we are not actually assigning the channel yet. - GSM::ChannelType requiredChannel; - bool channelAvailable = false; - GSM::L3CMServiceType serviceType; - string proxy = get_return_address(msg); - if (strcmp(method,"INVITE") == 0) { - // INVITE is for MTC. - // Set the required channel type to match the assignment style. - if (gConfig.getBool("Control.VEA")) { - // Very early assignment. - requiredChannel = GSM::TCHFType; - channelAvailable = gBTS.TCHAvailable(); - } else { - // Early assignment - requiredChannel = GSM::SDCCHType; - channelAvailable = gBTS.SDCCHAvailable() && gBTS.TCHAvailable(); - } - serviceType = L3CMServiceType::MobileTerminatedCall; - gReports.incr("OpenBTS.SIP.INVITE.In"); - } - else if (strcmp(method,"MESSAGE") == 0) { - // MESSAGE is for MTSMS. - requiredChannel = GSM::SDCCHType; - channelAvailable = gBTS.SDCCHAvailable(); - serviceType = L3CMServiceType::MobileTerminatedShortMessage; - gReports.incr("OpenBTS.SIP.MESSAGE.In"); - } - else { - // Not a method handled here. - LOG(DEBUG) << "non-initiating SIP method " << method; - return false; - } - - // Get request username (IMSI) from invite. - const char* IMSI = extractIMSI(msg); - if (!IMSI) { - // FIXME -- Send appropriate error (404) on SIP interface. - LOG(WARNING) << "Incoming INVITE/MESSAGE with no IMSI"; - sendEarlyError(msg,proxy.c_str(),404,"Not Found"); - return false; - } - L3MobileIdentity mobileID(IMSI); - - // Get the SIP call ID. - const char * callIDNum = extractCallID(msg); - if (!callIDNum) { - // FIXME -- Send appropriate error on SIP interface. - LOG(WARNING) << "Incoming INVITE/MESSAGE with no call ID"; - sendEarlyError(msg,proxy.c_str(),400,"Bad Request"); - return false; - } - - // Find any active transaction for this IMSI with an assigned TCH or SDCCH. - GSM::LogicalChannel *chan = gTransactionTable.findChannel(mobileID); - if (chan) { - // If the type is TCH and the service is SMS, get the SACCH. - // Otherwise, for now, just say chan=NULL. - if (serviceType==L3CMServiceType::MobileTerminatedShortMessage && chan->type()==FACCHType) { - chan = chan->SACCH(); - } else { - // FIXME -- This will change to support multiple transactions. - chan = NULL; - } - } - - // Check SIP map. Repeated entry? Page again. - if (mSIPMap.map().readNoBlock(callIDNum) != NULL) { - TransactionEntry* transaction= gTransactionTable.find(mobileID,callIDNum); - // There's a FIFO but no trasnaction record? - if (!transaction) { - LOG(WARNING) << "repeated INVITE/MESSAGE with no transaction record"; - // Delete the bogus FIFO. - mSIPMap.remove(callIDNum); - return false; - } - LOG(INFO) << "pre-existing transaction record: " << *transaction; - //if this is not the saved invite, it's a RE-invite. Respond saying we don't support it. - if (!transaction->sameINVITE(msg)){ - /* don't cancel the call */ - LOG(CRIT) << "got reinvite. transaction: " << *transaction << " SIP re-INVITE: " << msg; - transaction->MODSendERROR(msg, 488, "Not Acceptable Here", false); - /* I think we'd need to create a new transaction for this ack. Right now, just assume the ack makes it back. - if not, we'll hear another INVITE */ - //transaction->MODWaitForERRORACK(false); //don't cancel the call - return false; - } - - // Send trying, if appropriate. - bool sendTrying = serviceType!=L3CMServiceType::MobileTerminatedShortMessage; - sendTrying = sendTrying || !gConfig.getBool("SIP.RFC3428.NoTrying"); - if (sendTrying) transaction->MTCSendTrying(); - - // And if no channel is established yet, page again. - if (!chan) { - LOG(INFO) << "repeated SIP INVITE/MESSAGE, repaging for transaction " << *transaction; - gBTS.pager().addID(mobileID,requiredChannel,*transaction); - } - - return false; - } - - // So we will need a new channel. - // Check gBTS for channel availability. - if (!chan && !channelAvailable) { - LOG(CRIT) << "MTC CONGESTION, no channel availble"; - // FIXME -- We need the retry-after header. - sendEarlyError(msg,proxy.c_str(),503,"Service Unvailable"); - return false; - } - if (chan) { LOG(INFO) << "using existing channel " << chan->descriptiveString(); } - else { LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; } - - // Check for new user busy condition. - if (!chan && gTransactionTable.isBusy(mobileID)) { - LOG(NOTICE) << "user busy: " << mobileID; - sendEarlyError(msg,proxy.c_str(),486,"Busy Here"); - return true; - } - - - // Add an entry to the SIP Map to route inbound SIP messages. - addCall(callIDNum); - LOG(DEBUG) << "callIDNum " << callIDNum << " IMSI " << IMSI; - - // Get the caller ID if it's available. - const char *callerID = ""; - const char *callerHost = ""; - osip_from_t *from = osip_message_get_from(msg); - if (from) { - osip_uri_t* url = osip_contact_get_url(from); - if (url) { - if (url->username) callerID = url->username; - if (url->host) callerHost = url->host; - } - } else { - LOG(NOTICE) << "INVITE with no From: username for " << mobileID; - } - LOG(DEBUG) << "callerID " << callerID << "@" << callerHost; - - - // Build the transaction table entry. - // This constructor sets TI automatically for an MT transaction. - TransactionEntry *transaction = new TransactionEntry(proxy.c_str(),mobileID,chan,serviceType,callerID); - // FIXME -- These parameters should be arguments to the constructor above. - transaction->SIPUser(callIDNum,IMSI,callerID,callerHost); - transaction->saveINVITE(msg,false); - // Tell the sender we are trying. - if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying(); - - // SMS? Get the text message body to deliver. - if (serviceType == L3CMServiceType::MobileTerminatedShortMessage) { - osip_body_t *body; - osip_content_type_t *contentType; - osip_message_get_body(msg,0,&body); - contentType = osip_message_get_content_type(msg); - const char *text = NULL; - char *type = NULL; - if (body) text = body->body; - if (text) transaction->message(text, body->length); - else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no message body for " << mobileID; - /* Ok, so osip does some funny stuff here. The MIME type is split into type and subType. - Basically, text/plain becomes type=text, subType=plain. We need to put those together... - */ - if (contentType) { - type = (char *)malloc(strlen(contentType->type)+strlen(contentType->subtype)+2); - } - if (type) { - strcpy(type,contentType->type); - strcat(type,"/"); - strcat(type,contentType->subtype); - transaction->messageType(type); - free(type); - } - else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no content type (or memory error) for " << mobileID; - } - - LOG(INFO) << "MTC MTSMS make transaction and add to transaction table: "<< *transaction; - gTransactionTable.add(transaction); - - // If there's an existing channel, skip the paging step. - if (!chan) { - // Add to paging list. - LOG(DEBUG) << "MTC MTSMS new SIP invite, initial paging for mobile ID " << mobileID; - gBTS.pager().addID(mobileID,requiredChannel,*transaction); - } else { - // Add a transaction to an existing channel. - chan->addTransaction(transaction); - // FIXME -- We need to write something into the channel to trigger the new transaction. - // We need to send a message into the chan's dispatch loop, - // becasue we can't block this thread to run the transaction. - } - - return true; -} - - - - - -// vim: ts=4 sw=4 diff --git a/SIP/SIPInterface.h b/SIP/SIPInterface.h deleted file mode 100644 index 13e1f87..0000000 --- a/SIP/SIPInterface.h +++ /dev/null @@ -1,221 +0,0 @@ -/* -* Copyright 2008 Free Software Foundation, Inc. -* -* This software is distributed under multiple licenses; -* see the COPYING file in the main directory for licensing -* information for this specific distribuion. -* -* 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. - -*/ - - - -#ifndef SIPINTERFACE_H -#define SIPINTERFACE_H - -#include -#include -#include -#include - -#include - - -namespace GSM { - -class L3MobileIdentity; - -} - - -namespace SIP { - - -typedef InterthreadQueue _OSIPMessageFIFO; - -class OSIPMessageFIFO : public _OSIPMessageFIFO { - - private: - - struct sockaddr_in mReturnAddress; - - virtual void freeElement(osip_message_t* element) const { osip_message_free(element); }; - - public: - - OSIPMessageFIFO(const struct sockaddr_in* wReturnAddress) - :_OSIPMessageFIFO() - { - memcpy(&mReturnAddress,wReturnAddress,sizeof(mReturnAddress)); - } - - virtual ~OSIPMessageFIFO() - { - // We must call clear() here, because if it is called from InterthreadQueue - // destructor, then clear() will call InterthreadQueue's freeElement() which - // is not what we want. This destructor behaviour is intntional, because - // inherited object's data is already destroyed at the time parent's destructor - // is called. - clear(); - } - - const struct sockaddr_in* returnAddress() const { return &mReturnAddress; } - - size_t addressSize() const { return sizeof(mReturnAddress); } - -}; - - - -class OSIPMessageFIFOMap : public InterthreadMap {}; - - -std::ostream& operator<<(std::ostream& os, const OSIPMessageFIFO& m); - - -/** - A Map the keeps a SIP message FIFO for each active SIP transaction. - Keyed by SIP call ID string. - Overall map is thread-safe. Each FIFO is also thread-safe. -*/ -class SIPMessageMap -{ - -private: - - OSIPMessageFIFOMap mMap; - -public: - - /** Write sip message to the map+fifo. used by sip interface. */ - void write(const std::string& call_id, osip_message_t * sip_msg ); - - /** Read sip message out of map+fifo. used by sip engine. */ - osip_message_t * read(const std::string& call_id, unsigned timeout, Mutex *lock); - - /** Read sip message out of map+fifo. used by sip engine. */ - osip_message_t * read(const std::string& call_id, Mutex *lock);; - - /** Create a new entry in the map. */ - bool add(const std::string& call_id, const struct sockaddr_in* returnAddress); - - /** - Remove a fifo from map (called at the end of a sip interaction). - @param call_id The call_id key string. - @return True if the call_id was there in the first place. - */ - bool remove(const std::string& call_id); - - /** Direct access to the map. */ - // FIXME -- This should probably be replaced with more specific methods. - OSIPMessageFIFOMap& map() {return mMap;} - -}; - -std::ostream& operator<<(std::ostream& os, const SIPMessageMap& m); - - - - -class SIPInterface -{ - -private: - - char mReadBuffer[2048]; ///< buffer for UDP reads - - UDPSocket mSIPSocket; - - Mutex mSocketLock; - Thread mDriveThread; - SIPMessageMap mSIPMap; - -public: - // 2 ways to starte sip interface. - // Ex 1. - // SIPInterface si; - // si.localAdder(port0, ip_str, port1); - // si.open(); - // or Ex 2. - // SIPInterface si(port0, ip_str, port1); - // Then after all that. si.start(); - - - /** - Create the SIP interface to watch for incoming SIP messages. - */ - SIPInterface() - :mSIPSocket(gConfig.getNum("SIP.Local.Port")) - { } - - - /** Start the SIP drive loop. */ - void start(); - - /** Receive, parse and dispatch a single SIP message. */ - void drive(); - - /** - Look for incoming INVITE messages to start MTC. - @param msg The SIP message to check. - @return true if the message is a new INVITE - */ - bool checkInvite( osip_message_t *); - - /** - Send an error response before a transaction is even created. - */ - void sendEarlyError(osip_message_t * cause, - const char *proxy, - int code, const char * reason); - - - /** - Schedule SMS for delivery. - */ - //void deliverSMS(const GSM::L3MobileIdentity& mobile_id, const char* returnAddress, const char* text); - - // To write a msg to outside, make the osip_message_t - // then call si.write(msg); - // to read, you need to have the call_id - // then call si.read(call_id) - - void write(const struct sockaddr_in*, osip_message_t*); - - osip_message_t* read(const std::string& call_id, unsigned readTimeout, Mutex *lock=NULL) - { return mSIPMap.read(call_id, readTimeout, lock); } - - osip_message_t* read(const std::string& call_id, Mutex *lock=NULL) - { return mSIPMap.read(call_id, lock); } - - - /** Create a new message FIFO in the SIP interface. */ - bool addCall(const std::string& call_id); - - bool removeCall(const std::string& call_id); - - int fifoSize(const std::string& call_id ); - -}; - -void driveLoop(SIPInterface*); - - -}; // namespace SIP. - - -/*@addtogroup Globals */ -//@{ -/** A single global SIPInterface in the global namespace. */ -extern SIP::SIPInterface gSIPInterface; -//@} - - -#endif // SIPINTERFACE_H -// vim: ts=4 sw=4 diff --git a/SIP/SIPMessage.cpp b/SIP/SIPMessage.cpp index d5587ea..46f0228 100644 --- a/SIP/SIPMessage.cpp +++ b/SIP/SIPMessage.cpp @@ -14,1138 +14,581 @@ +#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging + #include #include #include #include -#include -#include -#include "SIPInterface.h" #include "SIPUtility.h" +#include "SIPBase.h" #include "SIPMessage.h" +#include "SIPDialog.h" +#include +#undef WARNING // The nimrods defined this to "warning" +#undef CR // This too using namespace std; -using namespace SIP; +using namespace Control; + +namespace SIP { -#define DEBUG 1 -#define MAX_VIA 10 +// Matching replies to requests: +// RFC3261 is an evolved total botch-up. Forensically, messages were originally identified by method + cseq, +// then the random call-id was added, but that did not allow differentiation of multiple replies, +// so the to-tag and and apparently completely redundant from-tag were added, but that was insufficient for proxies, +// so finally the via-branch was added, which now trumps all other id methods. +// The call-id is constant for all transactions within a dialog, and for all REGISTER to a Registrar. +// The via-branch is unique for all requests except for CANCEL and ACK, which use the via-branch of the request being cancelled, +// however, this is a new feature. The via header is copied into the response. +// The current rules as per RFC3261 are as follows: +// 17.1.3 specifies how to match responses to client transactions: +// o If the via branch and the cseq-method match the request. (cseq-method needed because CANCEL +// uses the same via-branch of the initial INVITE.) This is unique because we added that via-branch ourselves. +// Note that this rule is universally applicable for both SIP endpoints and proxies; as a sip-endpoint +// we could use a different system, for example call-id + from-tag + cseq-method + cseq-number. +// Note that this rule matches responses to requests, but does not differentiate multiple replies +// from different endpoints to the same request, which would be differentiated by from/to-tag for dialogs. +// For direct BTS-to-BTS connection where there are two calls on the same BTS talking to each other directly +// without an intervening SIP server, then the via-branch, call-id are identical +// for both dialogs. (Because the the response copies call-id, from-tag and via-header from the request.) +// Options for differentiating the messages are to use the local-tag (which is identical in both dialogs +// but is the from-tag in the originating dialog and the to-tag in the terminating dialog. +// or to use the to-tag (which is not known when original response to dialog is received), +// or the CSEQ-number and make sure the CSEQ-number of the two dialogs are non-overlapping. +// A -- INVITE -> B +// A <- 200 OK -- B (with to-tag provided by B) +// A -- ACK -> B +// 17.2.3 specifies how to match requests for the purpose of identifying a duplicate request. +// 1. If via-branch begins with "z9hG4bK" (they couldnt rev the spec number?) then: +// top via branch and via sent-by match, and method matches request except for ACK which matches INVITE. +// 2. Otherwise: The request-URI, to-tag, from-tag, call-id, cseq, and top-via-header all match those of the +// request that is being duplicated. +// For duplicated ACK detection, the to-tag must match the to-tag of the response sent by the server (of course, +// which is just another way of saying the ACK is the same as the previous ACK.) +// Note: the initial INVITE request does not contain a to-tag, so the 'to-tag' part of this rule for matching +// a duplicated INVITE means the to-tag is empty, because a re-INVITE will include the to-tag of the final response +// to the INVITE. -void openbts_message_init(osip_message_t ** msg){ - osip_message_init(msg); - //I think it's like 40 characters - static const char* userAgent = "OpenBTS " VERSION " Build Date " __DATE__; - const char *tag = userAgent; - osip_message_set_user_agent(*msg, strdup(tag)); + +// 8.1.3.3: The Via header in an inbound response must have only one via, or it should be discarded. +// The vias in an inbound request include all the proxies, and must be copied verbatim to the outbound response. +// ACK and CANCEL have a single via equal the top via header of the original request, which is interesting +// because it means the proxies must be stateful. +// 18.2.1: for server transport layer: if top via-sent-by is a domain name, must add "received" param with IP address it came from. + +// Request inside INVITE +// Write: +// BYE sip:2600@127.0.0.1 SIP/2.0^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch=z9hG4bKobts28b7bf3680791a653^M +// From: IMSI001010002220002 ;tag=tagvcocwyifsgstxlvb^M +// To: ;tag=as1ad40dc5^M +// Call-ID: 987045165@127.0.0.1^M +// CSeq: 198 BYE^M +// Contact: IMSI001010002220002 ^M +// User-Agent: OpenBTS 4.0TRUNK Build Date May 25 2013^M +// Max-Forwards: 70^M +// Content-Length: 0^M +// +// Receive: +// SIP/2.0 200 OK^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch=z9hG4bKobts28b7bf3680791a653;received=127.0.0.1;rport=5062^M +// From: IMSI001010002220002 ;tag=tagvcocwyifsgstxlvb^M +// To: ;tag=as1ad40dc5^M +// Call-ID: 987045165@127.0.0.1^M +// CSeq: 198 BYE^M +// Server: Asterisk PBX 10.0.0^M +// Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH^M +// Supported: replaces, timer^M +// Content-Length: 0^M + +// Request outside INVITE +// Write: +// REGISTER sip:127.0.0.1 SIP/2.0^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M +// From: IMSI001010002220002 ;tag=tagxwojprsxydjpfaqf^M +// To: IMSI001010002220002 ^M +// Call-ID: 1543864025@127.0.0.1^M +// CSeq: 244 REGISTER^M +// Contact: ;expires=5400^M +// Authorization: Digest, nonce=7fa68566fa8af919c926247b1b2a04c7, uri=001010002220002, response=a4b2f099^M +// User-Agent: OpenBTS 4.0TRUNK Build Date May 25 2013^M +// Max-Forwards: 70^M +// P-PHY-Info: OpenBTS; TA=0 TE=0.122070 UpRSSI=-36.000000 TxPwr=17 DnRSSIdBm=-77^M +// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-Preferred-Identity: ^M +// Content-Length: 0^M +// Receive: +// SIP/2.0 200 OK^M +// Via: SIP/2.0/UDP localhost:5064;branch=1;received=string_address@foo.bar^M +// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M +// From: IMSI001010002220002 ;tag=tagxwojprsxydjpfaqf^M +// To: IMSI001010002220002 ^M +// Call-ID: 1543864025@127.0.0.1^M +// CSeq: 244 REGISTER^M +// Contact: ;expires=5400^M +// User-agent: OpenBTS 4.0TRUNK Build Date May 25 2013^M +// Max-forwards: 70^M +// P-phy-info: OpenBTS; TA=0 TE=0.122070 UpRSSI=-36.000000 TxPwr=17 DnRSSIdBm=-77^M +// P-access-network-info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-preferred-identity: ^M +// Content-Length: 0^M + +// Receive: +// MESSAGE sip:IMSI001690000000002@127.0.0.1:5062 SIP/2.0^M +// Via: SIP/2.0/UDP 127.0.0.1:5063;branch=123^M +// From: 101 ;tag=9679^M +// To: ^M +// Call-ID: xqwLM/@127.0.0.1^M +// CSeq: 9679 MESSAGE^M +// Content-Type: application/vnd.3gpp.sms^M +// Content-Length: 158^M +// Write: +// SIP/2.0 200 OK^M +// Via: SIP/2.0/UDP 127.0.0.1:5063;branch=123^M +// From: 101 ;tag=9679^M +// To: ;tag=onkcqsbyygpcavoa^M +// Call-ID: xqwLM/@127.0.0.1^M +// CSeq: 9679 MESSAGE^M +// User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M +// P-PHY-Info: OpenBTS; TA=1 TE=-0.012695 UpRSSI=-47.250000 TxPwr=9 DnRSSIdBm=-48^M +// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M +// P-Preferred-Identity: ^M +// Content-Length: 0^M + + + + +static void appendHeader(string *result,const char *name,string value) +{ + if (value.empty()) return; + result->append(name); + result->append(": "); + result->append(value); + result->append("\r\n"); +} +static void appendHeader(string *result,const char *name,const char *value) +{ + if (*value == 0) return; + result->append(name); + result->append(": "); + result->append(value); + result->append("\r\n"); +} +static void appendHeader(string *result,const char *name,int val) +{ + char buf[30]; + snprintf(buf,30,"%d",val); + appendHeader(result,name,buf); } -#define MSG_NO_ERROR (0) -#define MSG_INVALID_PARAM (-1) -#define MSG_EMPTY_HDR (-2) -#define MSG_STR_MEM (-3) - -int openbts_message_set_sdp(osip_message_t *request, sdp_message_t *sdp) +// Generate the string representing this sip message. +string SipMessage::smGenerate() { - char * sdp_str = NULL; - - if (!request || !sdp) - return MSG_INVALID_PARAM; - - sdp_message_to_str(sdp, &sdp_str); - if (sdp_str) { - osip_message_set_body(request, sdp_str, strlen(sdp_str)); - osip_free(sdp_str); + string result; + result.reserve(1000); + char buf[200]; + + // First line. + if (msmCode == 0) { + // It is a request + string uri = this->msmReqUri; // This includes the URI params and headers, if any. + snprintf(buf,200,"%s %s SIP/2.0\r\n", this->msmReqMethod.c_str(), uri.c_str()); } else { - return MSG_STR_MEM; + // It is a reply. + snprintf(buf,200,"SIP/2.0 %u %s\r\n", msmCode, msmReason.c_str()); } - osip_message_set_content_type(request, strdup("application/sdp")); - return MSG_NO_ERROR; + result.append(buf); + + appendHeader(&result,"To",this->msmTo.value()); + appendHeader(&result,"From",this->msmFrom.value()); + + appendHeader(&result,"Via",this->msmVias); + + appendHeader(&result,"Route",this->msmRoutes); + + appendHeader(&result,"Call-ID",this->msmCallId); + + snprintf(buf,200,"%d %s",msmCSeqNum,msmCSeqMethod.c_str()); + appendHeader(&result,"CSeq",buf); + + if (! msmContactValue.empty()) { appendHeader(&result,"Contact",msmContactValue); } + if (! msmAuthorizationValue.empty()) { appendHeader(&result,"Authorization",msmAuthorizationValue); } + // The WWW-Authenticate header occurs only in inbound replies from the Registrar, so we ignore it here. + + // These are other headers we dont otherwise process. + for (SipParamList::iterator it = msmHeaders.begin(); it != msmHeaders.end(); it++) { + appendHeader(&result,it->mName.c_str(),it->mValue); + } + + static const char* userAgent1 = "OpenBTS " VERSION " Build Date " __DATE__; + const char *userAgent = userAgent1; + appendHeader(&result,"User-Agent",userAgent); + + appendHeader(&result,"Max-Forwards",gConfig.getNum("SIP.MaxForwards")); + + // Create the body, if any. + appendHeader(&result,"Content-Type",msmContentType); + appendHeader(&result,"Content-Length",msmBody.size()); + result.append("\r\n"); + result.append(msmBody); + msmContent = result; + return msmContent; } -int openbts_message_set_via(osip_message_t *response, osip_message_t *orig) +// Copy the top via from other into this. +void SipMessage::smCopyTopVia(SipMessage *other) { - osip_via_t * via = NULL; - - if (!orig || !response) - return MSG_INVALID_PARAM; - - osip_message_get_via(orig, 0, &via); - if (via) { - char * via_str = NULL; - osip_via_to_str(via, &via_str); - if (via_str) { - osip_message_set_via(response, via_str); - osip_free(via_str); + this->msmVias = commaListFront(other->msmVias); +} + +// Add a new Via with a new unique branch. +void SipMessage::smAddViaBranch(string transport, string branch) +{ + string newvia = format("SIP/2.0/%s %s;branch=%s\r\n",transport,localIPAndPort(),branch); + commaListPushFront(&msmVias,newvia); +} +void SipMessage::smAddViaBranch(SipBase *dialog, string branch) +{ + // Add a visible hint to the tag for debugging. Use the Request Method, if any. + string newvia = format("SIP/2.0/%s %s;branch=%s\r\n",dialog->transportName(),dialog->localIPAndPort(),branch); + commaListPushFront(&msmVias,newvia); +} + +string SipMessage::smGetBranch() +{ + return SipVia(msmVias).mViaBranch; +} + +string SipMessage::smGetReturnIPAndPort() +{ + string contactUri; + if (! crackUri(msmContactValue, NULL,&contactUri,NULL)) { + LOG(ERR); + } + string contact = SipUri(contactUri).uriHostAndPort(); + return contact; +} + + +string SipMessage::text(bool verbose) const +{ + std::ostringstream ss; + ss << "SipMessage("; + { int code = smGetCode(); ss < msmBodies; + if (!msmContentType.empty()) ss<mName <<"="<mValue; + } + + if (verbose) { ss << LOGVARM(msmContent); } else { ss << LOGVAR2("firstLine",smGetFirstLine()); } + ss << ")"; + return ss.str(); +} +ostream& operator<<(ostream& os, const SipMessage&msg) { os << msg.text(); return os; } +ostream& operator<<(ostream& os, const SipMessage*msg) { os << (msg ? msg->text(false) : "(null SipMessage)"); return os; } + +string SipMessage::smGetFirstLine() const +{ + return string(msmContent,0, msmContent.find('\n')); +} + +static bool isNumeric(const char *str) +{ + for ( ; *str; str++) { if (!isdigit(*str)) return false; } + return true; +} + +// Validate the IMSI string "IMSI"+digits; return pointer to digits or NULL if invalid. +const char* extractIMSI(const char *IMSI) +{ + // Form of the name is IMSI, and it should always be 18 or 19 char. + // IMSIs are 14 or 15 char + "IMSI" prefix + unsigned namelen = strlen(IMSI); + if (strncmp(IMSI,"IMSI",4) || (namelen>19) || (namelen<18) || !isNumeric(IMSI+4)) { + // Note that if you use this on a non-INVITE it will fail, because it may have fields like "222-0123" which are not IMSIs. + // Dont warn here. We print a better warning just once in newCheckInvite. + // LOG(WARNING) << "INVITE with malformed username \"" << IMSI << "\""; + return ""; + } + // Skip first 4 char "IMSI". + return IMSI+4; +} + +string SipMessage::smGetPrecis() const +{ + const char *methodname = smGetMethodName(); // May be empty, but never NULL + if (*methodname) { + return format("method=%s to=%s",methodname,smGetToHeader().c_str()); + } else { + return format("response code=%d %s %s to=%s",smGetCode(),smGetReason(),smCSeqMethod().c_str(),smGetToHeader().c_str()); + } +} + + +// Multipart SIP body looks like this: +// --terminator +// Content-Type: ... +// eol +// body +// eol +// --terminator +// Content-Type: ... +// eol +// body +// eol +// --terminator-- +void SipMessage::smAddBody(string contentType, string body1) +{ + static string separator("--zzyzx\r\n"); + static string terminator("--zzyzx--\r\n"); + static string eol("\r\n"); + if (msmBody.empty()) { + msmContentType = contentType; + msmBody = body1; + } else { + // TODO: TEST THIS! + string ctline = format("Content-Type: %s\r\n",contentType.c_str()); + string newbody; + if (msmContentType.substr(0,strlen("multipart")) != "multipart") { + // We are switching to multipart now. + // Move the original content-type/body into the multipart body. + newbody = separator + ctline + eol + msmBody + eol; + msmContentType = "multipart/mixed;boundary=zzyzx"; } else { - return MSG_STR_MEM; + newbody = msmBody; + // Chop off the previous terminator. + newbody = newbody.substr(0,newbody.size() - terminator.size()); } + // Add the new contentType and body. + newbody.reserve(msmBody.size() + body1.size() + 100); + // This requires C++11: + // newbody.append(separator, ctline, eol, body1, eol, terminator); + newbody.append(separator + ctline + eol + body1 + eol + terminator); + msmBody = newbody; + } +} + +string SipMessage::smGetMessageBody() const +{ + //if (msmBodies.size() != 1) { + // LOG(ERR) << "Message Body invalid "<msmHeaders.paramFind("www-authenticate"); + if (authenticate.size() == 0) return ""; + parseAuthenticate(authenticate, params); + return params.paramFind("nonce"); +} + + + +// TODO: This could now be constructed from the dialog state instead of saving the INVITE. +SipMessageAckOrCancel::SipMessageAckOrCancel(string method, SipMessage *other) { + this->msmCallId = other->msmCallId; + this->msmReqMethod = method; + this->msmCSeqMethod = method; + //this->msmReqUri = format("sip:%s@%s",this->mRemoteUsername.c_str(),this->mRemoteDomain.c_str()); // dialed_number@remote_ip + this->msmReqUri = other->msmReqUri; + this->msmTo = other->msmTo; // Has the tag already fixed. + this->msmFrom = other->msmFrom; // Has the tag already fixed. + // For ACK and CANCEL the CSeq equals the CSeq of the INVITE. + // Note: For others (BYE), they need their own CSeq. + this->msmCSeqNum = other->msmCSeqNum; + this->smCopyTopVia(other); +} + +// RFC3261 section 4 page 16 describes routing as follows: +// 1. The INVITE is necessarily sent via proxies, which add their own "via" headers. +// 2. The reply to the INVITE must include the "via" headers so it can get back. +// 3. Subsequently, if there is a Contact field, all messages bypass the proxies and are sent directly to the Contact. +// 4. But the proxies might want to see the messages too, so they can add a "required-route" parameter which trumps +// the "contact" header and specifies that messages are sent there instead. This is called "Loose Routing." What a mess. +// And I quote: "These procedures separate the destination of the request (present in the Request-URI) from +// the set of proxies that need to be visited along the way (present in the Route header field)." +// In contrast, A Strict Router "follows the Route processing rules of RFC 2543 and many prior work in +// progress versions of this RFC. That rule caused proxies to destroy the contents of the Request-URI +// when a Route header field was present." +// 8.1.1.1: Normally the request-URI is equal to the To: field. But if there is a configured proxy (our case) +// this is called a "pre-existing route set" and we must follow 12.2.1.1 using the request-URI as the +// remote target URI(???) +// 12.2: The route-set is immutably defined by the initial INVITE. You can change the remote-URI in a re-INVITE +// (aka target-refresh-request) but not the route-set. +// Remote-URI: Intial request remote-URI must == To: field. + +// Vias: +// o Initial request contains one via which is the resolved return address plus a transaction branch param. + +// A request inside an invite is ACK, CANCEL, BYE, INFO, or re-INVITE. +// The only target-refresh-request is re-INVITE, which can change the "Dialog State". +// For ACK sec 17.1.1.3 the To must equal the To of the response being acked, which specifically contains a tag. +// ACK contains only one via == top via of original request with matching branch. +// TODO: ACK must copy route header fields from INVITE. +// sec 9.1 For CANCEL, all fields must == INVITE except the method. Note CSeq must match too. Must have single via +// matching the [invite] request being cancelled, with matching branch. TODO: Must copy route header from request. +// 15.1.1: BYE is a new request within a dialog as per section 12.2. +// transaction constructed as per with a new tag, branch, etc. +// It should not include CONTACT because it is non-target-refresh. +// +// TODO: +// 12.2.1.1 The request-URI must be set as follows: +// 1. If there is a non-empty route-set and the first route-set URI contains "lr" param, request-URI = remote-target-URI +// and must include a Route Header field including all params. +// 2. If there is a non-empty route-set and the first UR does not contain "lr" param, +// request-URI = first route-route URI with params stripped, and generate new route set with first route removed, +// andn then add the remote-target-URI at the end of the route header. +// 3. If no route-set, request-URI = remote-target-URI +// The remote-target-URI is set by the Contact header only from a INVITE or re-INVITE. +SipMessageRequestWithinDialog::SipMessageRequestWithinDialog(string reqMethod, SipBase *dialog, string branch) { + this->msmCallId = dialog->callId(); + this->msmReqMethod = reqMethod; + this->msmCSeqMethod = reqMethod; + //this->msmReqUri = dialog->mInvite->msmReqUri; // TODO: WRONG! see comments above. + this->msmReqUri = dialog->dsInDialogRequestURI(); // TODO: WRONG! see comments above. + this->msmTo = *dialog->dsRequestToHeader(); + this->msmFrom = *dialog->dsRequestFromHeader(); + this->msmCSeqNum = dialog->dsNextCSeq(); // The BYE seq number must be incremental within the enclosing INVITE dialog. + if (branch.empty()) { branch = make_branch(reqMethod.c_str()); } + this->smAddViaBranch(dialog,branch); +} + +// This message exists to encapsulate the Dialog State into a SIP message. +// It is created on BS1 to send to BS2, which then uses it as a re-invite. +// So fill it out as a re-invite but leaving the parts based on the BS2 address empty, ie, no via or contact. +// Note that when we send it to BS2 the via and contact are BS1, which BS2 must fix. +// We need to pass the the remote proxy address that we derived from the 'contact' from the peer, +// that was passed in in the initial incoming invite or the response to our outgoing invite. +// We place it in the Refer-To header, and BS2 turns the REFER into an INVITE to that URI. +// This goes in the URI of the header, implying that we cannot send this message to BS2 using normal SIP +// unless we move that to another field, eg, Contact. +SipMessageHandoverRefer::SipMessageHandoverRefer(const SipBase *dialog, string peer) +{ + // The request URI will be the BTS we are sending it to... + this->msmReqMethod = string("REFER"); + this->msmReqUri = makeUri("handover",peer); + this->msmTo = *dialog->dsRequestToHeader(); + this->msmFrom = *dialog->dsRequestFromHeader(); + this->msmCallId = dialog->callId(); + this->msmCSeqMethod = this->msmReqMethod; // It is "INVITE". + // The CSeq num of the are-INVITE follows. + // RFC3261 14.1: The Cseq for re-INVITE follows same rules for in-dialog requests, namely, CSeq num must be + // incremented by exactly one. We do not know if BTS-2 is going to send this this re-INVITE or not, + // so we send the current CSeqNum without incrementing it and let BTS-2 increment it. + this->msmCSeqNum = dialog->mLocalCSeq; + SipParam refer("Refer-To",dialog->dsRemoteURI()); // This is the proxy from the Contact or route header. + this->msmHeaders.push_back(refer); + + // We need to send the SDP answer, which is the local SDP for MTC or the remote SDP for MOC, + // but we need to send the peer (remote) RTP port in either case. + // For the re-invite, you can put nearly anything in here, but you must not just use + // the identical o= line of the original without at least incrementing the version number. + SdpInfo sdpRefer, sdpRemote; + //sdpRefer.sdpInitOffer(dialog); + sdpRemote.sdpParse(dialog->getSdpRemote()); + + // Put the remote SIP server port in the REFER message. BTS-2 will grab it then substitute its own local port. + //sdpRefer.sdpRtpPort = sdpRemote.sdpRtpPort; + + sdpRefer.sdpInitRefer(dialog, sdpRemote.sdpRtpPort); + + // TODO: Put the remote session id and version, incrementing the version. Paul at yate says these should be 0 + + //SdpInfo sdpRefer; sdpRefer.sdpParse(dialog->mSdpAnswer); + //sdpRefer.sdpUsername = dialog->sipLocalusername(); + + // Update: This did not work for asterisk either. + //sdpRefer.sdpUsername = dialog->sipLocalUsername(); // This modification was recommended by Paul for Yate. + + //this->smAddBody(string("application/sdp"),sdpRefer.sdpValue()); + // Try just using the sdpremote verbatim? Gosh, that worked too. + this->smAddBody(string("application/sdp"),sdpRefer.sdpValue()); + + // The via and contact will be filled in by BS2: + // this->smAddViaBranch(dialog); + // this->msmContactValue = dialog->dsRemoteURI(); + // TODO: route set. +} + +// section 4: must preserve the vias in the reply to INVITE. +// 12.1.1: Dialog reply must have a contact. +// TODO: Preserve the route set. +// If dialog is non-NULL this is a reply within a dialog. Note that we create a SipDialog class +// for REGISTER and MESSAGE, and those are not dialogs. +SipMessageReply::SipMessageReply(SipMessage *request,int code, string reason, SipBase *dialog) { + msmCode = code; + msmReason = reason; + if (dialog) { + // TODO: The route set goes here too. + // This is a reply, so the initiating request was incoming, so to is local and from is remote. + msmTo = *dialog->dsReplyToHeader(); + msmFrom = *dialog->dsReplyFromHeader(); } else { - return MSG_EMPTY_HDR; + msmTo = request->msmTo; + msmFrom = request->msmFrom; } - - return MSG_NO_ERROR; -} - -int openbts_message_set_contact(osip_message_t *response, osip_message_t *orig) -{ - osip_contact_t * cont = NULL; - - if (!orig || !response) - return MSG_INVALID_PARAM; - - osip_message_get_contact(orig, 0, &cont); - if (cont) { - char * cont_str = NULL; - osip_contact_to_str(cont, &cont_str); - if (cont_str) { - osip_message_set_contact(response, cont_str); - osip_free(cont_str); - } else { - return MSG_STR_MEM; - } - } else { - return MSG_EMPTY_HDR; + msmVias = request->msmVias; + msmCSeqNum = request->msmCSeqNum; + msmCSeqMethod = request->msmCSeqMethod; + msmCallId = request->msmCallId; + if (dialog && request->msmReqMethod == "INVITE") { + msmContactValue = dialog->localContact(dialog->sipLocalUsername()); } - - return MSG_NO_ERROR; + // These fields are not needed: + // string mReqMethod; // It is a reply, not a request. + //string mContactValue; // The request is non-target-refresh so contact is meaningless. } -int openbts_message_set_cseq(osip_message_t *response, osip_message_t *orig) +bool sameMsg(SipMessage *msg1, SipMessage *msg2) { - osip_cseq_t * cseq = NULL; - - if (!orig || !response) - return MSG_INVALID_PARAM; - - cseq = osip_message_get_cseq(orig); - if (cseq) { - char * cseq_str = NULL; - osip_cseq_to_str(cseq ,&cseq_str); - if (cseq_str) { - osip_message_set_cseq(response, cseq_str); - osip_free(cseq_str); - } else { - return MSG_STR_MEM; - } - } else { - return MSG_EMPTY_HDR; - } - - return MSG_NO_ERROR; -} - -int openbts_message_set_rr(osip_message_t *response, osip_message_t *orig) -{ - osip_record_route_t * rr = NULL; - - if (!orig || !response) - return MSG_INVALID_PARAM; - - osip_message_get_record_route(orig, 0, &rr); - if (rr) { - char * rr_str = NULL; - osip_record_route_to_str(rr, &rr_str); - if (rr_str) { - osip_message_set_record_route(response, rr_str); - osip_free(rr_str); - } else { - return MSG_STR_MEM; - } - } else { - return MSG_EMPTY_HDR; - } - - return MSG_NO_ERROR; -} - -osip_message_t * SIP::sip_register( const char * sip_username, short timeout, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, string *RAND, const char *IMSI, const char *SRES) { - - char local_port[10]; - sprintf(local_port,"%i",wlocal_port); - - // Message URI - osip_message_t * request; - openbts_message_init(&request); - // FIXME -- Should use the "force_update" function. - request->message_property = 2; // buffer is not synchronized with object - request->sip_method = strdup("REGISTER"); - osip_message_set_version(request, strdup("SIP/2.0")); - osip_uri_init(&request->req_uri); - osip_uri_set_host(request->req_uri, strdup(proxy_ip)); - - - // VIA - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - - // VIA BRANCH - osip_via_set_branch(via, strdup(via_branch)); - - // MAX FORWARDS - osip_message_set_max_forwards(request, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - char * via_str; - osip_via_to_str(via, &via_str); - osip_message_set_via(request, via_str); - - - // FROM - osip_from_init(&request->from); - osip_from_set_displayname(request->from, strdup(sip_username)); - - // FROM TAG - osip_from_set_tag(request->from, strdup(from_tag)); - osip_uri_init(&request->from->url); - osip_uri_set_host(request->from->url, strdup(proxy_ip)); - osip_uri_set_username(request->from->url, strdup(sip_username)); - - // TO - osip_to_init(&request->to); - osip_to_set_displayname(request->to, strdup(sip_username)); - osip_uri_init(&request->to->url); - osip_uri_set_host(request->to->url, strdup(proxy_ip)); - osip_uri_set_username(request->to->url, strdup(sip_username)); - - // CALL ID - osip_call_id_init(&request->call_id); - osip_call_id_set_host(request->call_id, strdup(local_ip)); - osip_call_id_set_number(request->call_id, strdup(call_id)); - - // CSEQ - osip_cseq_init(&request->cseq); - osip_cseq_set_method(request->cseq, strdup("REGISTER")); - char temp_buf[14]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(request->cseq, strdup(temp_buf)); - - // CONTACT - osip_contact_t * con; - osip_to_init(&con); - - // CONTACT URI - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(local_ip)); - osip_uri_set_port(con->url, strdup(local_port)); - osip_uri_set_username(con->url, strdup(sip_username)); - char numbuf[10]; - sprintf(numbuf,"%d",timeout); - osip_contact_param_add(con, strdup("expires"), strdup(numbuf) ); - - // add contact - osip_list_add(&request->contacts, con, -1); - - if (SRES) { - // add authentication - osip_authorization_t *auth; - osip_authorization_init(&auth); - osip_authorization_set_auth_type(auth, osip_strdup("Digest")); - osip_authorization_set_nonce(auth, osip_strdup(RAND->c_str())); - osip_authorization_set_uri(auth, osip_strdup(IMSI)); - osip_authorization_set_response(auth, osip_strdup(SRES)); - osip_list_add(&request->authorizations, auth, -1); - } - - return request; + return msg1->msmCSeqNum == msg2->msmCSeqNum && msg1->msmCSeqMethod == msg2->msmCSeqMethod; } - -osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type) { - - char local_port[10]; - sprintf(local_port, "%i", wlocal_port); - - osip_message_t * request; - openbts_message_init(&request); - // FIXME -- Should use the "force_update" function. - request->message_property = 2; - - // METHOD - request->sip_method = strdup("MESSAGE"); - osip_message_set_version(request, strdup("SIP/2.0")); - - // REQ.URI - osip_uri_init(&request->req_uri); - osip_uri_set_host(request->req_uri, strdup(proxy_ip)); - osip_uri_set_username(request->req_uri, strdup(dialed_number)); - - // VIA - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - osip_via_set_branch(via, strdup(via_branch)); - - // MAX FORWARDS - osip_message_set_max_forwards(request, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // add via - osip_list_add(&request->vias, via, -1); - - // FROM - osip_from_init(&request->from); - osip_from_set_displayname(request->from, strdup(sip_username)); - osip_uri_init(&request->from->url); - osip_uri_set_host(request->from->url, strdup(proxy_ip)); - osip_uri_set_username(request->from->url, strdup(sip_username)); - // FROM TAG - osip_from_set_tag(request->from, strdup(from_tag)); - - // TO - osip_to_init(&request->to); - osip_to_set_displayname(request->to, strdup(dialed_number)); - osip_uri_init(&request->to->url); - osip_uri_set_host(request->to->url, strdup(proxy_ip)); - osip_uri_set_username(request->to->url, strdup(dialed_number)); - - // CALL ID - osip_call_id_init(&request->call_id); - osip_call_id_set_host(request->call_id, strdup(local_ip)); - osip_call_id_set_number(request->call_id, strdup(call_id)); - - // CSEQ - osip_cseq_init(&request->cseq); - osip_cseq_set_method(request->cseq, strdup("MESSAGE")); - char temp_buf[21]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(request->cseq, strdup(temp_buf)); - - // Content-Type - if (content_type) - { - // Explicit value provided - osip_message_set_content_type(request, strdup(content_type)); - } else { - // Default to text/plain - osip_message_set_content_type(request, strdup("text/plain")); - } - - // Content-Length - sprintf(temp_buf,"%u",strlen(message)); - osip_message_set_content_length(request, strdup(temp_buf)); - - // Payload. - osip_message_set_body(request,message,strlen(message)); - - return request; -} - -osip_message_t * SIP::sip_invite( const char * dialed_number, short rtp_port, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, unsigned codec) { - - char local_port[10]; - sprintf(local_port, "%i", wlocal_port); - - osip_message_t * request; - openbts_message_init(&request); - // FIXME -- Should use the "force_update" function. - request->message_property = 2; - request->sip_method = strdup("INVITE"); - osip_message_set_version(request, strdup("SIP/2.0")); - osip_uri_init(&request->req_uri); - osip_uri_set_host(request->req_uri, strdup(proxy_ip)); - osip_uri_set_username(request->req_uri, strdup(dialed_number)); - - // VIA - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - - // VIA BRANCH - osip_via_set_branch(via, strdup(via_branch)); - - // MAX FORWARDS - osip_message_set_max_forwards(request, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // add via - osip_list_add(&request->vias, via, -1); - - // FROM - osip_from_init(&request->from); - osip_from_set_displayname(request->from, strdup(sip_username)); - - // FROM TAG - osip_from_set_tag(request->from, strdup(from_tag)); - - osip_uri_init(&request->from->url); - osip_uri_set_host(request->from->url, strdup(proxy_ip)); - osip_uri_set_username(request->from->url, strdup(sip_username)); - - // TO - osip_to_init(&request->to); - osip_to_set_displayname(request->to, strdup("")); - osip_uri_init(&request->to->url); - osip_uri_set_host(request->to->url, strdup(proxy_ip)); - osip_uri_set_username(request->to->url, strdup(dialed_number)); - - // If response, we need a to tag. - //osip_uri_param_t * to_tag_param; - //osip_from_get_tag(rsp->to, &to_tag_param); - - // CALL ID - osip_call_id_init(&request->call_id); - osip_call_id_set_host(request->call_id, strdup(local_ip)); - osip_call_id_set_number(request->call_id, strdup(call_id)); - - // CSEQ - osip_cseq_init(&request->cseq); - osip_cseq_set_method(request->cseq, strdup("INVITE")); - char temp_buf[14]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(request->cseq, strdup(temp_buf)); - - // CONTACT - osip_contact_t * con; - osip_to_init(&con); - - // CONTACT URI - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(local_ip)); - osip_uri_set_port(con->url, strdup(local_port)); - osip_uri_set_username(con->url, strdup(sip_username)); - osip_contact_param_add(con, strdup("expires"), strdup("3600") ); - - // add contact - osip_list_add(&request->contacts, con, -1); - - sdp_message_t * sdp; - sdp_message_init(&sdp); - sdp_message_v_version_set(sdp, strdup("0")); - sdp_message_o_origin_set(sdp, strdup(sip_username), strdup("0"), - strdup("0"), strdup("IN"), strdup("IP4"), strdup(local_ip)); - - sdp_message_s_name_set(sdp, strdup("Talk Time")); - sdp_message_t_time_descr_add(sdp, strdup("0"), strdup("0") ); - - sprintf(temp_buf,"%i",rtp_port); - sdp_message_m_media_add(sdp, strdup("audio"), - strdup(temp_buf), NULL, strdup("RTP/AVP")); - sdp_message_c_connection_add - (sdp, 0, strdup("IN"), strdup("IP4"), strdup(local_ip),NULL, NULL); - - // FIXME -- This should also be inside the switch? - sdp_message_m_payload_add(sdp,0,strdup("3")); - switch (codec) { - case RTPuLaw: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("0 PCMU/8000")); - break; - case RTPGSM610: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("3 GSM/8000")); - break; - default: assert(0); - }; - - /* - * We construct a sdp_message_t, turn it into a string, and then treat it - * like an osip_body_t. This works, and perhaps is how it is supposed to - * be done, but in any case we're going to have to do the extra processing - * to turn it into a string first. - */ - openbts_message_set_sdp(request, sdp); - // TODO: In the unlikely event that sdp_str is null, we should probably do some nice cleanup. - - return request; -} - - -osip_message_t * SIP::sip_reinvite( - const char* RemoteUsername, - const char* RemoteIP, - - const char* SIPDisplayname, - const char* SIPUsername, - - const char* fromTag, - const char* fromUsername, - const char* fromIP, - - const char* toTag, - const char* toUsername, - const char* toIP, - - const char* viaBranch, - const char* callID, - const char* callIP, - - int cseq, - unsigned codec, - short wRTPPort, - - const char* SessionID, - const char* SessionVersion) -{ - - char RTPPort[10]; - sprintf(RTPPort, "%i", wRTPPort); - - const char * BS2port = gConfig.getStr("SIP.Local.Port").c_str(); - const char * BS2IP = gConfig.getStr("SIP.Local.IP").c_str(); - - osip_message_t * request; - osip_message_init(&request); - request->message_property = 2; - request->sip_method = strdup("INVITE"); - osip_message_set_version(request, strdup("SIP/2.0")); - osip_uri_init(&request->req_uri); - osip_uri_set_host(request->req_uri, strdup(RemoteIP)); - osip_uri_set_username(request->req_uri, strdup(RemoteUsername)); - - // VIA - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(BS2IP)); - via_set_port(via, strdup(BS2port)); - - // VIA BRANCH - osip_via_set_branch(via, strdup(viaBranch)); - - // MAX FORWARDS - osip_message_set_max_forwards(request, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // add via - osip_list_add(&request->vias, via, -1); - - // FROM - osip_from_init(&request->from); - // osip_from_set_displayname(request->from, strdup(fromDisplayname)); - osip_from_set_tag(request->from, strdup(fromTag)); - osip_uri_init(&request->from->url); - osip_uri_set_host(request->from->url, strdup(fromIP)); - osip_uri_set_username(request->from->url, strdup(fromUsername)); - - // TO - osip_to_init(&request->to); - // osip_to_set_displayname(request->to, strdup(toDisplayname)); - osip_to_set_tag(request->to, strdup(toTag)); - osip_uri_init(&request->to->url); - osip_uri_set_host(request->to->url, strdup(toIP)); - osip_uri_set_username(request->to->url, strdup(toUsername)); - - // CALL ID - osip_call_id_init(&request->call_id); - if (*callIP) osip_call_id_set_host(request->call_id, strdup(callIP)); - osip_call_id_set_number(request->call_id, strdup(callID)); - - // CSEQ - osip_cseq_init(&request->cseq); - osip_cseq_set_method(request->cseq, strdup("INVITE")); - char temp_buf[14]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(request->cseq, strdup(temp_buf)); - - // CONTACT - osip_contact_t * con; - osip_to_init(&con); - - // CONTACT URI - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(BS2IP)); - osip_uri_set_port(con->url, strdup(BS2port)); - osip_uri_set_username(con->url, strdup(SIPUsername)); - osip_contact_param_add(con, strdup("expires"), strdup("3600") ); - - // add contact - osip_list_add(&request->contacts, con, -1); - - sdp_message_t * sdp; - sdp_message_init(&sdp); - sdp_message_v_version_set(sdp, strdup("0")); - sdp_message_o_origin_set(sdp, strdup(SIPDisplayname), strdup(SessionID), - strdup(SessionVersion), strdup("IN"), strdup("IP4"), strdup(BS2IP)); - - sdp_message_s_name_set(sdp, strdup("Talk Time")); - sdp_message_t_time_descr_add(sdp, strdup("0"), strdup("0") ); - - sdp_message_m_media_add(sdp, strdup("audio"), - strdup(RTPPort), NULL, strdup("RTP/AVP")); - sdp_message_c_connection_add - (sdp, 0, strdup("IN"), strdup("IP4"), strdup(BS2IP),NULL, NULL); - - // FIXME -- This should also be inside the switch? - sdp_message_m_payload_add(sdp,0,strdup("3")); - switch (codec) { - case RTPuLaw: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("0 PCMU/8000")); - break; - case RTPGSM610: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("3 GSM/8000")); - break; - default: assert(0); - }; - - /* - * We construct a sdp_message_t, turn it into a string, and then treat it - * like an osip_body_t. This works, and perhaps is how it is supposed to - * be done, but in any case we're going to have to do the extra processing - * to turn it into a string first. - */ - openbts_message_set_sdp(request, sdp); - // TODO: In the unlikely event that sdp_str is null, we should probably do some nice cleanup. - - return request; -} - - -// Take the authorization produced by an earlier invite message. - -osip_message_t * SIP::sip_ack(const char * req_uri, const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const osip_from_t *from_header, const osip_to_t* to_header, const char * via_branch, const osip_call_id_t* call_id_header, int cseq) { - - char local_port[20]; - sprintf(local_port, "%i", wlocal_port); - - osip_message_t * ack; - openbts_message_init(&ack); - // FIXME -- Should use the "force_update" function. - ack->message_property = 2; - ack->sip_method = strdup("ACK"); - osip_message_set_version(ack, strdup("SIP/2.0")); - - osip_uri_init(&ack->req_uri); - - // If we are Acking a BYE message then need to - // set the req_uri to the owner address thats taken from the 200 Okay. - if( req_uri == NULL ) { - osip_uri_set_host(ack->req_uri, strdup(proxy_ip)); - } else { - osip_uri_set_host(ack->req_uri, strdup(req_uri)); - } - - osip_uri_set_username(ack->req_uri, strdup(dialed_number)); - - // Via - osip_via_t *via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - - // VIA BRANCH - osip_via_set_branch(via, strdup(via_branch)); - - // MAX FORWARDS - osip_message_set_max_forwards(ack, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // add via - osip_list_add(&ack->vias, via, -1); - - osip_from_init(&ack->from); - osip_from_set_displayname(ack->from, strdup(sip_username)); - osip_uri_init(&ack->from->url); - osip_uri_set_host(ack->from->url, strdup(proxy_ip)); - osip_uri_set_username(ack->from->url, strdup(sip_username)); - - // from/to headers - osip_from_clone(from_header, &ack->from); - osip_to_clone(to_header, &ack->to); - - // call id - osip_call_id_clone(call_id_header, &ack->call_id); - - osip_cseq_init(&ack->cseq); - osip_cseq_set_method(ack->cseq, strdup("ACK")); - char temp_buf[14]; - sprintf(temp_buf, "%i", cseq); - osip_cseq_set_number(ack->cseq, strdup(temp_buf)); - - return ack; -} - - -osip_message_t * SIP::sip_bye(const char * req_uri, const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, short wproxy_port, const osip_from_t* from_header, const osip_to_t* to_header, const char * via_branch, const osip_call_id_t* call_id_header, int cseq) { - - // FIXME -- We really need some NULL-value error checking in here. - - char local_port[10]; - sprintf(local_port,"%i",wlocal_port); - - char proxy_port[10]; - sprintf(proxy_port,"%i",wproxy_port); - - osip_message_t * bye; - openbts_message_init(&bye); - // FIXME -- Should use the "force_update" function. - bye->message_property = 2; - bye->sip_method = strdup("BYE"); - osip_message_set_version(bye, strdup("SIP/2.0")); - - //char o_addr[30]; - //get_owner_ip(okay, o_addr); - - osip_uri_init(&bye->req_uri); - osip_uri_set_host(bye->req_uri, strdup(req_uri)); - osip_uri_set_username(bye->req_uri, strdup(dialed_number)); - - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - - // via branch + max forwards - osip_via_set_branch(via, strdup(via_branch)); - osip_message_set_max_forwards(bye, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // add via - osip_list_add(&bye->vias, via, -1); - - // from/to header - osip_from_clone(from_header, &bye->from); - osip_to_clone(to_header, &bye->to); - - // Call Id Header - osip_call_id_clone(call_id_header, &bye->call_id); - - // Cseq Number - osip_cseq_init(&bye->cseq); - osip_cseq_set_method(bye->cseq, strdup("BYE")); - char temp_buf[12]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(bye->cseq, strdup(temp_buf)); - - // Contact - osip_contact_t * contact; - osip_contact_init(&contact); - osip_contact_set_displayname(contact, strdup(sip_username) ); - osip_uri_init(&contact->url); - osip_uri_set_host(contact->url, strdup(local_ip)); - osip_uri_set_username(contact->url, strdup(sip_username)); - osip_uri_set_port(contact->url, strdup(local_port)); - - // add contact - osip_list_add(&bye->contacts, contact, -1); - - return bye; -} - -osip_message_t * SIP::sip_error(osip_message_t * invite, const char * host, const char * username, short port, short code, const char* reason) -{ - - if(invite==NULL){ return NULL;} - - osip_message_t * unavail; - openbts_message_init(&unavail); - //clone doesn't work -kurtis - // FIXME -- Should use the "force_update" function. - unavail->message_property = 2; - //header stuff first - unavail->status_code = code; - unavail->reason_phrase = strdup(reason); - osip_message_set_version(unavail, strdup("SIP/2.0")); - - char local_port[10]; - sprintf(local_port, "%i", port); - - //uri - osip_uri_init(&unavail->req_uri); - osip_uri_set_host(unavail->req_uri, strdup(host)); - osip_uri_set_username(unavail->req_uri, strdup(username)); - osip_uri_set_port(unavail->req_uri, strdup(local_port)); - - //via - openbts_message_set_via(unavail, invite); - - // MAX FORWARDS - osip_message_set_max_forwards(unavail, strdup(gConfig.getStr("SIP.MaxForwards").c_str())); - - // from/to header - osip_from_clone(invite->from, &unavail->from); - osip_to_clone(invite->to, &unavail->to); - - //contact - openbts_message_set_contact(unavail, invite); - - // Get Call-ID. - osip_call_id_clone(invite->call_id, &unavail->call_id); - - // Get Cseq. - openbts_message_set_cseq(unavail, invite); - - return unavail; -} - -/* Cancel a previous invite */ -osip_message_t * SIP::sip_cancel( osip_message_t * invite, const char * host, const char * username, short port) -{ - - if(invite==NULL){ return NULL;} - - osip_message_t * cancel; - openbts_message_init(&cancel); - //clone doesn't work -kurtis - //osip_message_clone(invite, &cancel) - // FIXME -- Should use the "force_update" function. - cancel->message_property = 2; - //header stuff first - cancel->sip_method = strdup("CANCEL"); - osip_message_set_version(cancel, strdup("SIP/2.0")); - - char local_port[10]; - sprintf(local_port, "%i", port); - - //uri - osip_uri_init(&cancel->req_uri); - osip_uri_set_host(cancel->req_uri, strdup(host)); - osip_uri_set_username(cancel->req_uri, strdup(username)); - osip_uri_set_port(cancel->req_uri, strdup(local_port)); - - //via - openbts_message_set_via(cancel, invite); - - // from/to header - osip_from_clone(invite->from, &cancel->from); - osip_to_clone(invite->to, &cancel->to); - - //contact - openbts_message_set_contact(cancel, invite); - - // Get Call-ID. - osip_call_id_clone(invite->call_id, &cancel->call_id); - - // Get Cseq. - openbts_message_set_cseq(cancel, invite); - - //update message type - osip_cseq_set_method(cancel->cseq, strdup("CANCEL")); - - return cancel; -} - -osip_message_t * SIP::sip_okay_sdp( osip_message_t * inv, const char * sip_username, const char * local_ip, short wlocal_port, short rtp_port, unsigned audio_codec) -{ - - // Check for consistency. - if(inv==NULL){ return NULL;} - - char local_port[10]; - sprintf(local_port, "%i", wlocal_port); - // k used for error conditions on various osip operations. - - osip_message_t * okay; - openbts_message_init(&okay); - // FIXME -- Should use the "force_update" function. - okay->message_property = 2; - - // Set Header stuff. - okay->status_code = 200; - okay->reason_phrase = strdup("OK"); - osip_message_set_version(okay, strdup("SIP/2.0")); - osip_uri_init(&okay->req_uri); - - // Get Record Route. - // FIXME -- Should use _clone() routines. - openbts_message_set_rr(okay, inv); - - // SIP Okay needs to repeat the Via tags from the INVITE Message. - openbts_message_set_via(okay, inv); - - // from/to header - osip_from_clone(inv->from, &okay->from); - osip_to_clone(inv->to, &okay->to); - - // CONTACT URI - osip_contact_t * con; - osip_to_init(&con); - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(local_ip)); - osip_uri_set_port(con->url, strdup(local_port)); - osip_uri_set_username(con->url, strdup(sip_username)); - osip_contact_param_add(con, strdup("expires"), strdup("3600") ); - - // add contact - osip_list_add(&okay->contacts, con, -1); - - // Get Call-ID. - osip_call_id_clone(inv->call_id, &okay->call_id); - - // Get Cseq. - openbts_message_set_cseq(okay, inv); - - // Session Description Protocol. - sdp_message_t * sdp; - sdp_message_init(&sdp); - sdp_message_v_version_set(sdp, strdup("0")); - sdp_message_o_origin_set(sdp, strdup(sip_username), strdup("0"), - strdup("0"), strdup("IN"), strdup("IP4"), strdup(local_ip)); - - sdp_message_s_name_set(sdp, strdup("Talk Time")); - sdp_message_t_time_descr_add(sdp, strdup("0"), strdup("0") ); - char temp_buf[10]; - sprintf(temp_buf,"%i", rtp_port); - sdp_message_m_media_add(sdp, strdup("audio"), - strdup(temp_buf), NULL, strdup("RTP/AVP")); - sdp_message_c_connection_add - (sdp, 0, strdup("IN"), strdup("IP4"), strdup(local_ip),NULL, NULL); - - // FIXME -- This should also be inside the switch? - sdp_message_m_payload_add(sdp,0,strdup("3")); - switch (audio_codec) { - case RTPuLaw: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("0 PCMU/8000")); - break; - case RTPGSM610: - sdp_message_a_attribute_add(sdp,0,strdup("rtpmap"),strdup("3 GSM/8000")); - break; - default: assert(0); - }; - - openbts_message_set_sdp(okay, sdp); - - return okay; -} - - -osip_message_t * SIP::sip_b_okay( osip_message_t * bye ) -{ - // Check for consistency. - if(bye==NULL){ return NULL;} - - // k used for error conditions on various osip operations. - - osip_message_t * okay; - openbts_message_init(&okay); - // FIXME -- Should use the "force_update" function. - okay->message_property = 2; - - // Set Header stuff. - okay->status_code = 200; - okay->reason_phrase = strdup("OK"); - osip_message_set_version(okay, strdup("SIP/2.0")); - osip_uri_init(&okay->req_uri); - - // SIP Okay needs to repeat the Via tags from the BYE Message. - openbts_message_set_via(okay, bye); - - // from/to header - osip_from_clone(bye->from, &okay->from); - osip_to_clone(bye->to, &okay->to); - - // Get Call-ID. - osip_call_id_clone(bye->call_id, &okay->call_id); - - // Get Cseq. - openbts_message_set_cseq(okay, bye); - - return okay; -} - - -osip_message_t * SIP::sip_trying( osip_message_t * invite, const char * sip_username, const char * local_ip ) -{ - osip_message_t * trying; - openbts_message_init(&trying); - // FIXME -- Should use the "force_update" function. - trying->message_property = 2; - - // Set Header stuff. - trying->status_code = 100; - trying->reason_phrase = strdup("Trying"); - osip_message_set_version(trying, strdup("SIP/2.0")); - osip_uri_init(&invite->req_uri); // FIXME? -- Invite rather than trying? - - // Get Via - openbts_message_set_via(trying, invite); - - // from/to header - osip_from_clone(invite->from, &trying->from); - osip_to_clone(invite->to, &trying->to); - - // Get Call-ID. - osip_call_id_clone(invite->call_id, &trying->call_id); - - // Get Cseq. - openbts_message_set_cseq(trying, invite); - - // CONTACT URI - osip_contact_t * con; - osip_to_init(&con); - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(local_ip)); - //osip_uri_set_port(con->url, strdup(local_port)); // FIXME ?? - osip_uri_set_username(con->url, strdup(sip_username)); - - // add contact - osip_list_add(&trying->contacts, con, -1); - - return trying; -} - - -osip_message_t * SIP::sip_ringing( osip_message_t * invite, const char * sip_username, const char *local_ip) -{ - osip_message_t * ringing; - openbts_message_init(&ringing); - // FIXME -- Should use the "force_update" function. - ringing->message_property = 2; - - // Set Header stuff. - ringing->status_code = 180; - ringing->reason_phrase = strdup("Ringing"); - osip_message_set_version(ringing, strdup("SIP/2.0")); - //osip_uri_init(&invite->req_uri); - - // Get Via. - openbts_message_set_via(ringing, invite); - - // from/to header - osip_from_clone(invite->from, &ringing->from); - osip_to_clone(invite->to, &ringing->to); - - // Get Call-ID. - osip_call_id_clone(invite->call_id, &ringing->call_id); - - // Get Cseq. - openbts_message_set_cseq(ringing, invite); - - // CONTACT URI - osip_contact_t * con; - osip_to_init(&con); - osip_uri_init(&con->url); - osip_uri_set_host(con->url, strdup(local_ip)); - osip_uri_set_username(con->url, strdup(sip_username)); - - // add contact - osip_list_add(&ringing->contacts, con, -1); - - return ringing; -} - - -osip_message_t * SIP::sip_okay( osip_message_t * inv, const char * sip_username, const char * local_ip, short wlocal_port) -{ - - // Check for consistency. - if(inv==NULL){ return NULL;} - - char local_port[20]; - sprintf(local_port, "%i", wlocal_port); - - osip_message_t * okay; - openbts_message_init(&okay); - // FIXME -- Should use the "force_update" function. - okay->message_property = 2; - - // FIXME -- Do we really need all of this string conversion? - - // Set Header stuff. - okay->status_code = 200; - okay->reason_phrase = strdup("OK"); - osip_message_set_version(okay, strdup("SIP/2.0")); - osip_uri_init(&okay->req_uri); - - // Get Record Route. - openbts_message_set_rr(okay, inv); - - // SIP Okay needs to repeat the Via tags from the INVITE Message. -// osip_via_clone(inv->via, &okay->via); - openbts_message_set_via(okay, inv); - - // from/to header - osip_from_clone(inv->from, &okay->from); - osip_to_clone(inv->to, &okay->to); - - // Get Call-ID. - osip_call_id_clone(inv->call_id, &okay->call_id); - - // Get Cseq. - openbts_message_set_cseq(okay, inv); - - return okay; -} - - -osip_message_t * SIP::sip_info(unsigned info, const char *dialed_number, short rtp_port, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const osip_call_id_t *call_id_header, int cseq) { - - char local_port[10]; - sprintf(local_port, "%i", wlocal_port); - - osip_message_t * request; - openbts_message_init(&request); - // FIXME -- Should use the "force_update" function. - request->message_property = 2; - request->sip_method = strdup("INFO"); - osip_message_set_version(request, strdup("SIP/2.0")); - osip_uri_init(&request->req_uri); - osip_uri_set_host(request->req_uri, strdup(proxy_ip)); - osip_uri_set_username(request->req_uri, strdup(dialed_number)); - - // VIA - osip_via_t * via; - osip_via_init(&via); - via_set_version(via, strdup("2.0")); - via_set_protocol(via, strdup("UDP")); - via_set_host(via, strdup(local_ip)); - via_set_port(via, strdup(local_port)); - - // VIA BRANCH - osip_via_set_branch(via, strdup(via_branch)); - - // add via - osip_list_add(&request->vias, via, -1); - - // FROM - osip_from_init(&request->from); - osip_from_set_displayname(request->from, strdup(sip_username)); - - // FROM TAG - osip_from_set_tag(request->from, strdup(from_tag)); - - osip_uri_init(&request->from->url); - osip_uri_set_host(request->from->url, strdup(proxy_ip)); - osip_uri_set_username(request->from->url, strdup(sip_username)); - - // TO - osip_to_init(&request->to); - osip_to_set_displayname(request->to, strdup("")); - osip_uri_init(&request->to->url); - osip_uri_set_host(request->to->url, strdup(proxy_ip)); - osip_uri_set_username(request->to->url, strdup(dialed_number)); - - // CALL ID - osip_call_id_clone(call_id_header, &request->call_id); - - // CSEQ - osip_cseq_init(&request->cseq); - osip_cseq_set_method(request->cseq, strdup("INFO")); - char temp_buf[21]; - sprintf(temp_buf,"%i",cseq); - osip_cseq_set_number(request->cseq, strdup(temp_buf)); - - osip_message_set_content_type(request, strdup("application/dtmf-relay")); - char message[31]; - // FIXME -- This duration should probably come from a config file. - switch (info) { - case 11: - snprintf(message,sizeof(message),"Signal=*\nDuration=200"); - break; - case 12: - snprintf(message,sizeof(message),"Signal=#\nDuration=200"); - break; - default: - snprintf(message,sizeof(message),"Signal=%i\nDuration=200",info); - } - sprintf(temp_buf,"%lu",strlen(message)); - osip_message_set_content_length(request, strdup(temp_buf)); - - // Payload. - osip_message_set_body(request,message,strlen(message)); - - return request; -} - - - -// vim: ts=4 sw=4 +}; // namespace SIP diff --git a/SIP/SIPMessage.h b/SIP/SIPMessage.h index 232f8e8..24c6c65 100644 --- a/SIP/SIPMessage.h +++ b/SIP/SIPMessage.h @@ -16,50 +16,129 @@ -#ifndef SIP_MESSAGE_H -#define SIP_MESSAGE_H +#ifndef _SIP2MESSAGE_H_ +#define _SIP2MESSAGE_H_ +#include +#include "SIPParse.h" +#include "config.h" // For VERSION + +#include // For CodecSet namespace SIP { +using namespace std; +class SipDialog; +class SipBase; +//struct SipBody { +// string mContentType; +// string mBody; +// SipBody(string wContentType,string wBody) : mContentType(wContentType),mBody(wBody) {} +//}; -osip_message_t * sip_register( const char * sip_username, short timeout, short local_port, const char * local_ip, -const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, -string *RAND, const char *IMSI, const char *SRES); +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipMessage,MemCheckSipMessage) +class SipMessage : public MemCheckSipMessage { + friend ostream& operator<<(ostream& os, const SipMessage*msg); + friend ostream& operator<<(ostream& os, const SipMessage&msg); + public: + string msmContent; // The full message content as a string of chars. + + string msmCallId; // Put in every message to identify dialog. + // Historically we use the callid without the @host for dialog identification purposes. (Which is wrong.) + //string smGetCallId() const { return string(msmCallId,0,msmCallId.find_first_of('@')); } + string smGetCallId() const { return msmCallId; } + + string msmReqUri; + SipPreposition msmTo; + SipPreposition msmFrom; + string msmReqMethod; + string msmVias; // Via lines, including the first one with mViaBranch and mViaSentBy broken out. + string msmRoutes; // Via lines, including the first one with mViaBranch and mViaSentBy broken out. + string msmRecordRoutes; // Via lines, including the first one with mViaBranch and mViaSentBy broken out. + string msmContactValue; + //list msmBodies; + string msmContentType; + string msmBody; + int msmCode; + string msmReason; + int msmCSeqNum; + string msmCSeqMethod; + SipParamList msmHeaders; // Other unrecognized headers. We use SipParamList because its the same. + string msmAuthenticateValue; // The www-authenticate value as a string. + string msmAuthorizationValue; // The Authorization value as a string. + + void smAddViaBranch(string transport, string branch); + void smAddViaBranch(SipBase *dialog,string branch); + string smGetBranch(); + string smGetReturnIPAndPort(); + void smCopyTopVia(SipMessage *other); + void smAddBody(string contentType, string body); + string smGenerate(); + SipMessage() : msmCode(0), msmCSeqNum(0) {} // mCSeq will be filled in later. + int smCSeqNum() const { return msmCSeqNum; } + void smAddHeader(string name, string value) { + SipParam param(name,value); + msmHeaders.push_back(param); + } + + // Accessors: + bool isRequest() { return msmCode == 0; } + string smGetRemoteTag() { return isRequest() ? msmFrom.mTag : msmTo.mTag; } + string smGetLocalTag() { return isRequest() ? msmTo.mTag : msmFrom.mTag; } + string smCSeqMethod() const { return msmCSeqMethod; } + string smGetToHeader() const { return msmTo.value(); } + string smUriUsername(); + string smGetInviteImsi(); + string smGetMessageBody() const; + string smGetMessageContentType() const; + string smGetProxy() const { + string topvia = commaListFront(msmVias); + if (topvia.empty()) { return string(""); } // oops + SipVia via(topvia); + return via.mSentBy; + } + const char *smGetMethodName() const { return msmReqMethod.c_str(); } + const char *smGetReason() const { return msmReason.c_str(); } + string smGetRand401(); + int smGetCode() const { return msmCode; } + int smGetCodeClass() const { return (msmCode / 100) * 100; } + + // Other accessors. + string smGetFirstLine() const; + string smGetPrecis() const; // A short message description for messages. + string text(bool verbose = false) const; + + // These get inlined. + bool isINVITE() const { return !strcmp(smGetMethodName(),"INVITE"); } + bool isMESSAGE() const { return !strcmp(smGetMethodName(),"MESSAGE"); } + bool isCANCEL() const { return !strcmp(smGetMethodName(),"CANCEL"); } + bool isBYE() const { return !strcmp(smGetMethodName(),"BYE"); } + bool isACK() const { return !strcmp(smGetMethodName(),"ACK"); } + // Has the message been initialized yet? We always add callid first thing, so check that. + bool smIsEmpty() const { return msmContent.empty() && msmCallId.empty(); } +}; + +bool sameMsg(SipMessage *msg1, SipMessage *msg2); +struct SipMessageAckOrCancel : SipMessage { + SipMessageAckOrCancel(string method, SipMessage *other); +}; -osip_message_t * sip_message( const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type=NULL); - -osip_message_t * sip_invite( const char * dialed_number, short rtp_port,const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, unsigned codec); - -osip_message_t * sip_reinvite( const char* RemoteUsername, const char* RemoteIP, const char* SIPDisplayname, const char* SIPUsername, const char* fromTag, const char* fromUsername, const char* fromIP, const char* toTag, const char* toUsername, const char* toIP, const char* viaBranch, const char* callID, const char* callIP, int cseq, unsigned codec, short wRTPPort, const char* wSessionID, const char* wSessionVersion); - -osip_message_t * sip_invite_referred( const char * dialed_number, short rtp_port,const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, unsigned codec); - -osip_message_t * sip_ack( const char * req_uri, const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const osip_from_t* from_header, const osip_to_t* to_header, const char * via_branch, const osip_call_id_t* call_id_header, int cseq); - - -osip_message_t * sip_bye( const char * req_uri, const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, short proxy_port, const osip_from_t *from_header, const osip_to_t * to_header, const char * via_branch, const osip_call_id_t* call_id_header, int cseq); - -osip_message_t * sip_cancel( osip_message_t * invite, const char * host, const char * username, short port); - -osip_message_t * sip_okay_sdp( osip_message_t * inv, const char * sip_username, const char * local_ip, short wlocal_port, short rtp_port, unsigned audio_codecs ); - -osip_message_t * sip_okay( osip_message_t * inv, const char * sip_username, const char * local_ip, short wlocal_port); - -osip_message_t * sip_error( osip_message_t * invite, const char * host, const char * username, short port, short code, const char* reason); - -osip_message_t * sip_info(unsigned info, const char *dialed_number, short rtp_port,const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const osip_call_id_t* call_id_header, int cseq); - -osip_message_t * sip_b_okay( osip_message_t * bye ); - -osip_message_t * sip_trying( osip_message_t * invite, const char * sip_username, const char * local_ip); - -osip_message_t * sip_ringing( osip_message_t * invite, const char * sip_username, const char * local_ip); +struct SipMessageRequestWithinDialog : SipMessage { + SipMessageRequestWithinDialog(string reqMethod, SipBase *dialog, string branch=""); +}; +struct SipMessageReply : SipMessage { + SipMessageReply(SipMessage *request,int code, string reason, SipBase *dialog); +}; +struct SipMessageHandoverRefer : SipMessage { + SipMessageHandoverRefer(const SipBase *dialog, string peer); +}; +ostream& operator<<(ostream& os, const SipMessage&msg); +ostream& operator<<(ostream& os, const SipMessage*msg); +extern const char *extractIMSI(const char *imsistring); }; #endif - diff --git a/SIP/SIPParse.cpp b/SIP/SIPParse.cpp new file mode 100644 index 0000000..0c29d00 --- /dev/null +++ b/SIP/SIPParse.cpp @@ -0,0 +1,749 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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::SIP // Can set Log.Level.SIP for debugging +#include +#include +#include +#include +#include +#include +#include "SIPParse.h" +#include "SIPMessage.h" +#include "SIPBase.h" +#include "SIPDialog.h" + + +namespace SIP { +using namespace std; +using namespace Control; + +struct SipParseError : public std::exception { + SipParseError() { LOG(DEBUG) << "SipParseError"; } + virtual const char *what() const throw() { + return "SipParseError"; + } +}; + + +// endpos is the index of one char past the end of the string, eg, of the trailing nul. +// Move endpos backward until we find a char that is not one of the trimchars, and leave +// endpos at the index one past that char, which is the appropriate length to substr everything but that char. +// Example: string result = input.substr(input.c_str(),input.size()); +size_t trimrightn(const char *startp, size_t endpos, const char *trimchars /*=" \t\r\n"*/) +{ + while (endpos > 0 && strchr(trimchars,startp[endpos-1])) { endpos--; } + return endpos; +} + +// Return the index of the first char not in trimchars, or if none, of the trailing nul in the string. +// Note that if you trimleft and trimright a string of spaces, the two indicies would cross, so be careful. See trimboth. +size_t trimleftn(const char *startp, size_t startpos/*=0*/, const char *trimchars /*=" \t\r\n"*/) +{ + while (startp[startpos] && strchr(trimchars,startp[startpos])) { startpos++; } + return startpos; +} + +// Trim both ends of a string. +string trimboth(string input, const char *trimchars /*=" \t\r\n"*/) +{ + size_t end = trimrightn(input.c_str(),input.size()); + size_t start = (end==0) ? 0 : trimleftn(input.c_str(),0,trimchars); + return input.substr(start,end); + //const char *bp = str.c_str(); + //const char *endp = str.c_str() + str.size(), *ep = endp; // points to the trailing nul. + //while (*bp && strchr(trimchars,*bp)) { bp++; } + //while (ep > bp && strchr(trimchars,ep[-1])) { ep--; } + //return (bp == str.c_str() && bp == endp) ? str : string(bp,bp-ep); +} + +void commaListPushBack(string *cl, string val) +{ + val = trimboth(val," ,"); // Make sure there is no errant garbage around the new value. + if (! cl->empty()) { cl->append(","); } + cl->append(val); +} + +void commaListPushFront(string *cl, string val) +{ + val = trimboth(val," ,"); // Make sure there is no errant garbage around the new value. + if (cl->empty()) { + *cl = val; + } else { + *cl = val + "," + *cl; + } +} + +string commaListFront(string cl) +{ + size_t cn = cl.find_first_of(','); + if (cn == string::npos) { cn = cl.size(); } + return cl.substr(0,trimrightn(cl.c_str(),cn)); +} + + +// Case insenstive string comparison +// Yes there is a way to do this in C++ by changing the character traits, but who cares. +bool strcaseeql(const char *a,const char *b) { return 0==strcasecmp((a),(b)); } +bool strncaseeql(const char *a,const char *b, unsigned n) { return 0==strncasecmp((a),(b),(n)); } +bool strceql(const string a, const string b) { return strcaseeql(a.c_str(),b.c_str()); } + + +//static const char *reserved = ";/?:@&=+$,"; +//static const char *mark = "-_.!~*'()"; +//static const char *param_unreserved = "[]/:&+$"; +static const char *token = "-.!%*_+`'~"; // or alphanum + +class SipChar { + typedef unsigned char uchar; + static char charClassData[256]; + enum { + ccIsToken = 1 + }; + public: + SipChar() { + memset(charClassData,0,256); + for (uchar ch = 'a'; ch <= 'z'; ch++) { charClassData[ch] = ccIsToken; } + for (uchar ch = 'A'; ch <= 'Z'; ch++) { charClassData[ch] = ccIsToken; } + for (uchar ch = '0'; ch <= '9'; ch++) { charClassData[ch] = ccIsToken; } + for (uchar *cp = (uchar*)token; *cp; cp++) { charClassData[*cp] |= ccIsToken; } + } + static bool isToken(const uchar ch) { return charClassData[ch] & ccIsToken; } + // unreserved is alphanum + mark. +} gSipChar; // Without the global variable the class is never initialized. + +char SipChar::charClassData[256]; + + +// This is pretty easy to parse. The major demarcation chars are reserved: @ ; ? & +// [sip: | sips: ] user [: password] @ host [: port] [;param=value]* [? hname=havalue [& hname=hvalue]* ] +// absoluteURI ::= scheme: (hierpart | opaquepart) +// scheme ::= alphanum|[+-.] +// hierpart ::= // blah blah | / blah blah + + +// This can not distinguish between a missing param and one with an empty value. +string SipParamList::paramFind(const char*name) +{ + for (SipParamList::iterator it = this->begin(); it != this->end(); it++) { + if (strceql(it->mName.c_str(),name)) { return it->mValue; } + } + return string(""); +} + + +struct SipParseLine { + const char *currentLine; // Start of current line being parsed, used only for error messages. + const char *pp; // Pointer into the parse buffer. It is const* for this class, but not for SipParseMessage + + void spLineInit(const char *buffer) { currentLine = pp = buffer; } + SipParseLine(const char* buffer) { spLineInit(buffer); } + SipParseLine() {} + + virtual void spError(const char *msg) { + LOG(ERR) << "SIP Parse error "< \t\f",params); + } + while (*pp == '?' || *pp == '&') { + pp++; + scanUriParam("&> \t\f",headers); + } + return address; + } +}; + +// We are not currently using this because we dont care about them. +// The uri-parameters appear within the and are: transport,user,moethod,ttl,lr. +// The headers appear after '&' with the +// In To: and From: there are generic-parameters after the URI outside the <>, including tag. +string parseURI(const char *buffer, SipParamList &uriparams, SipParamList &headers) +{ + try { + SipParseLine parser(buffer); + return parser.parseLineURI(uriparams,headers); + } catch (...) { + LOG(DEBUG) << "Caught SIP Parse error"; + return ""; + } +} + +// Extract a SIP parameter from a string containing a list of parameters. They look like ;param1=value;param2=value ... +// The input string need not start exactly at the beginning of the list. +// The paramid is specified with the semi-colon and =, example: ";tag=" +static string extractParam(const char *pp, const char *paramid) +{ + const int taghdrlen = strlen(paramid); // strlen(";tag="); + if (const char *tp = strstr(pp,paramid)) { + pp += taghdrlen; + const char *ep = strchr(pp,';'); + return ep ? string(pp,ep-tp) : string(pp); + } + return string(""); // Not found. +} + +void SipPreposition::rebuild() +{ + if (mTag.empty()) { + mFullHeader = format("%s <%s>",mDisplayName,mUri.uriValue()); + } else { + mFullHeader = format("%s <%s>;tag=%s",mDisplayName,mUri.uriValue(),mTag); + } + LOG(DEBUG) <tail +// The pointers can be NULL. +// Return false if it is invalid. +bool crackUri(string header, string *before, string *uri, string *tail) +{ + //string junk; + //int n = myscanf("%s <%[^>]>%s",before?before:&junk,uri?uri:&junk,tail?tail:&junk); + size_t nUriBegin = header.find_first_of('<'); + if (nUriBegin == string::npos) { + // Old format allows the URI without <> but it is not possible to specify the tag parameter that way. + // It should begin with "sip:" or "sips:" but we are not checking. + if (uri) *uri = header; + return true; + } + size_t nUriEnd = header.find_first_of('>',nUriBegin); + if (nUriBegin == string::npos) { return false; } + // Warning: The display name may be a quoted string or multiple unquoted tokens. + if (before) *before = header.substr(0,trimrightn(header.c_str(),nUriBegin)); + if (uri) *uri = header.substr(nUriBegin+1,nUriEnd-nUriBegin-1); + if (tail) *tail = header.substr(nUriEnd+1); + return true; // happiness +} + +// Parse immediately, only we're not going to do a full parse on this either. All we care about is the tag. +// Note that the tag param is outside the +void SipPreposition::prepSet(const string header) +{ + mFullHeader = header; + mDisplayName.clear(); + mTag.clear(); + mUri.clear(); + if (header.empty()) { return; } + try { + if (1) { + // We dont need the parser for this. It is trivial. + string uri, tail; + if (!crackUri(header,&mDisplayName,&uri,&tail)) { + LOG(ERR) << "Bad SIP Contact field:"<'); + if (!ep) { + LOG(ERR) << "Bad SIP Contact field:"< off the ends of the URI. + parser.pp = ep; + mTag = extractParam(parser.pp,";tag="); + } else { + mUri.uriSet(thing); + } + } + } catch (...) { + LOG(DEBUG) << "Caught SIP Parse error"; + } +} + +void parseToParams(string stuff, SipParamList ¶ms) +{ + SipParseLine parser(stuff.c_str()); + try { + do { + SipParam param; + if (! parser.scanGenericParam(param)) break; + params.push_back(param); + } while (parser.scanChar(',')); + } catch(SipParseError) { + // error was already logged. + LOG(DEBUG) << "Caught SIP Parse error"; + return; + } +} + +// This is only for www-authenticate, not for Authentication-Info +void parseAuthenticate(string stuff, SipParamList ¶ms) +{ + SipParseLine parser(stuff.c_str()); + + try { + string challengeType = parser.scanToken(); + if (! strceql(challengeType,"Digest")) { + LOG(ERR) << format("unrecognized challenge type:%s",challengeType.c_str()); + return; + } + + do { + SipParam param; + if (! parser.scanGenericParam(param)) break; + params.push_back(param); + } while (parser.scanChar(',')); + } catch(SipParseError) { + // error was already logged. + LOG(DEBUG) << "Caught SIP Parse error"; + return; + } +} + +// You can pass in the comma-separated list of vias and it will parse just the first. +void SipVia::viaParse(string vialine) +{ + LOG(DEBUG) <assign(vialine); + // Spaces are allowed anywhere in the via spec even though most people dont insert htem. + // Example: "SIP / 2.0 / UDP host : port ; branch = branchstring" + // The port may be empty. There may be options after the port. + try { + SipParseLine parser(vialine.c_str()); + parser.scanToken(); // protocol-name + parser.scanChar('/'); + parser.scanToken(); // protocol-version + parser.scanChar('/'); + parser.scanToken(); // transport + mSentBy = parser.scanToken(); // host + if (parser.scanChar(':')) { + mSentBy.append(":"); + mSentBy.append(parser.scanToken()); // port + } + // Now the list of via-params + //SipParam param; + //while (parser.scanGenericParam(param)) { + // if (strcaseeql(param.mName.c_str(),"branch")) { // not sure if this is case insensitive, but be safe. + // mViaBranch = param.mValue; + // break; // We can break; we dont care about any other parameters. + // } + //} + while (parser.scanChar(';')) { + SipParam param; + while (parser.scanGenericParam(param)) { + if (strcaseeql(param.mName.c_str(),"branch")) { // not sure if this is case insensitive, but be safe. + mViaBranch = param.mValue; + break; // We can break; we dont care about any other parameters. + } + } + } + } catch(...) { + LOG(ERR) << "Error parsing via:"<msmCode = scanInt(); + skipSpace(); + sipmsg->msmReason = string(pp); // Rest of the line is the reason. + } else { + // This is a request. + sipmsg->msmReqMethod = scanNonSpace(); + sipmsg->msmReqUri = scanNonSpace(); + skipSpace(); + scanSipVersion(); + } + skipSpace(); + //LOG(DEBUG); + + // Get the rest of the header lines. + while (nextLine()) { + // We have a header line. The headers names themselves are case insensitive. + LOG(DEBUG) << "nextLine="<msmTo.prepSet(string(pp)); + } else if (strceql(name,"from") || strceql(name,"f")) { + sipmsg->msmFrom.prepSet(string(pp)); + } else if (strceql(name,"contact") || strceql(name,"m")) { + sipmsg->msmContactValue = string(pp); + } else if (strceql(name,"CSeq")) { + sipmsg->msmCSeqNum = scanInt(); + sipmsg->msmCSeqMethod = scanToken(); + } else if (strceql(name,"call-id") || strceql(name,"i")) { + // The call-id string is defined as word[@word], but unless we are really interested + // in validating incoming SIP messages, we can simply scan for non-space. + sipmsg->msmCallId = scanNonSpace(); + } else if (strceql(name,"via") || strceql(name,"v")) { + // Multiple vias can appear on separate lines or be comma separated in one line, + // so for simplicity we will just keep them all in a comma separated list. + commaListPushBack(&sipmsg->msmVias,pp); + } else if (strceql(name,"record-route")) { + commaListPushBack(&sipmsg->msmRecordRoutes,pp); + } else if (strceql(name,"route")) { + commaListPushBack(&sipmsg->msmRoutes,pp); + // No need to treat this specially here, even though authenticate info does not follow normal SIP parsing rules. + //} else if (strceql(name,"www-authenticate")) { + // // authenticate info does not follow normal SIP parsing rules. + // sipmsg->msmAuthenticateValue = string(pp); + } else if (strceql(name,"content-type")) { + sipmsg->msmContentType = string(pp); + } else { + SipParam param(name,string(pp)); + sipmsg->msmHeaders.push_back(param); + } + } + LOG(DEBUG) << "end"; + if (pp[0] == '\r' && pp[1] == '\n') { + pp += 2; + sipmsg->msmBody = string(pp); + } else { + // Dont abort for this. Just log an error. + logError("Missing message body"); + } + return sipmsg; + } + + SipParseMessage(char *buffer) { spInit(buffer); } +}; + +SipMessage *sipParseBuffer(const char *buffer) +{ + LOG(DEBUG) << "DEBUG"; + // The SIP Parser modifies buffer, but puts it back the way it found it. + // This is OK because no thread contention for the buffer is possible because all callers pass a unique string for parsing. + SipParseMessage parser(Unconst(buffer)); + SipMessage *result; + try { + result = parser.sipParse(); + } catch (SipParseError) { + // error was already logged. + LOG(DEBUG) << "SipParseError caught"; + return NULL; + } + return result; +} + +void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs) +{ + attrs->clear(); + attrs->reserve(80); + codeclist->reserve(20); + // We are using the same code for offers and answers, so for now only included the one we want: + /** + if (codecs.isSet(PCMULAW)) { + attrs->append("a=rtpmap:0 PCMU/8000\r\n"); + if (!codeclist->empty()) { codeclist->append(" "); } + codeclist->append("0"); + } + ***/ + if (codecs.isSet(GSM_FR) || codecs.isSet(GSM_HR) || codecs.isEmpty()) { + attrs->append("a=rtpmap:3 GSM/8000\r\n"); + if (!codeclist->empty()) { codeclist->append(" "); } + codeclist->append("3"); + } + // TODO: Does the half-rate codec need a special RTP format is conversion performed lower down? + // RFC5993 7.1 says it is "a=rtpmap:append("a=rtpmap:96 AMR/8000\r\n"); + if (!codeclist->empty()) { codeclist->append(" "); } + codeclist->append("96"); + } + **/ +} + + +// We dont fully parse it; just pull out the o,m,c,a lines. +void SdpInfo::sdpParse(const char *buffer) +{ + const char *bp, *eol; + for (bp = buffer; bp && *bp; bp = eol ? eol+1 : NULL) { + eol = strchr(bp,'\n'); + switch (*bp) { + case 'o': + if (myscanf(bp,"o=%s %s %s IN IP4 %s",&sdpUsername,&sdpSessionId, &sdpVersionId, &sdpHost) < 4) { + LOG(ERR) << "SDP unrecognized o= line, sdp:"<sipLocalUsername(); + sdpRtpPort = dialog->mRTPPort; + sdpHost = dialog->localIP(); + codecsToSdp(dialog->mCodec,&sdpCodecList,&sdpAttrs); + static const string zero("0"); + sdpSessionId = sdpVersionId = zero; +} + +void SdpInfo::sdpInitRefer(const SipBase *dialog, int remotePort) +{ + sdpInitOffer(dialog); + // Same as above, except: + sdpRtpPort = remotePort; + sdpVersionId = format("%lu",time(NULL)); +} + +// Note that sdp is not completely order independent. +string SdpInfo::sdpValue() +{ + string result; result.reserve(100); + char buf[302]; + result.append("v=0\r\n"); // SDP protocol version + // originator, session id, ip address. + snprintf(buf,300,"o=%s %s %s IN IP4 %s\r\n",sdpUsername.c_str(),sdpSessionId.c_str(),sdpVersionId.c_str(),sdpHost.c_str()); + result.append(buf); + // RFC3264 5: And I quote: + // "The SDP 's=' line conveys the subject of the session, which is reasonably defined for multicast, + // but ill defined for unicast. For unicast sessions, it is RECOMMENDED that it consist of a single space + // character (0x20) or a dash (-)." + // I dont know why we are setting it to 'Talk Time'. + result.append("s=Talk Time\r\n"); + result.append("t=0 0\r\n"); // time session is active; 0 means unbounded. + snprintf(buf,300,"m=audio %u RTP/AVP %s\r\n",sdpRtpPort,sdpCodecList.c_str()); // media name and transport address. + result.append(buf); // media name and transport address. + // Optional connection information. Redundant because we included in 'o='. + snprintf(buf,300,"c=IN IP4 %s\r\n",sdpHost.c_str()); + result.append(buf); + result.append(sdpAttrs); + return result; +} + +}; // namespace SIP diff --git a/SIP/SIPParse.h b/SIP/SIPParse.h new file mode 100644 index 0000000..e68ad0d --- /dev/null +++ b/SIP/SIPParse.h @@ -0,0 +1,149 @@ +/* +* Copyright 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* 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. +#ifndef _SIPPARSE_H_ +#define _SIPPARSE_H_ 1 +#include +#include +//#include + +namespace SIP { +using namespace std; +class SipMessage; +class SipBase; +class DialogStateVars; + +extern string makeUriWithTag(string username, string ip, string tag); +extern string makeUri(string username, string ip, unsigned port=0); +extern size_t trimrightn(const char *startp, size_t endpos, const char *trimchars=" \t\r\n"); +extern size_t trimleftn(const char *startp, size_t startpos=0, const char *trimchars=" \t\r\n"); +extern string trimboth(string input, const char *trimchars=" \t\r\n"); +extern void commaListPushFront(string *cl, string val); +extern void commaListPushBack(string *cl, string val); +extern string commaListFront(string cl); + +// We use this for headers also. +struct SipParam { + string mName, mValue; + SipParam(string wName, string wValue) : mName(wName), mValue(wValue) {} + SipParam() {} +}; +struct SipParamList : public list { + string paramFind(const char *name); + string paramFind(string name) { return paramFind(name.c_str()); } +}; + +string parseURI(const char *buffer, SipParamList ¶ms, SipParamList &headers); + +// This is the subset of a full URI that we use. +// We dont care about URI params or headers. +// Note that the tag is a From or To generic-parameter, not a URI-parameter. +class SipUri : public string { // The base class string contains the full URI. + static const unsigned start = 4; // strlen("sip:") + public: + string uriValue() const { return substr(); } // return the whole thing. + string uriUsername() const { // username without the host or password. + if (size() < start) return string(""); + string result = substr(start,find_first_of(";@:",start)-start); // chop off :password or @host or if no @host, ;params + //LOG(DEBUG) << LOGVAR2("uri",substr())< chop off the < and > + void uriSet(string fullUri) { + if (fullUri.size() == 0) { clear(); return; } // Dont let fullUri[0] will throw an exception. + if (fullUri[0] == '<') { + assign(fullUri.substr(1,fullUri.rfind('>')-1)); + } else { + assign(fullUri); + } + } + // Create a uri from a username and host. + //void uriSetName(string username,string host, unsigned port=0) { // The host could also contain a port spec; we dont care. + // assign(makeUri(username,host,port)); + //} + SipUri() {} + SipUri(string wFullUri) { uriSet(wFullUri); } +}; + +struct SipVia : string { + string mSentBy; + string mViaBranch; + void viaParse(string); + SipVia(string vialine) { viaParse(vialine); } +}; + +// This is a From: or To: +class SipPreposition { + friend class DialogStateVars; + friend class SipMessage; + string mFullHeader; + string mDisplayName; + SipUri mUri; + string mTag; + void rebuild(); + public: + void prepSet(const string header); // immediately parses the string as a Contact. + void prepSetUri(string uri) { + mUri.uriSet(uri); + rebuild(); + } + void setTag(string wTag) { mTag = wTag; rebuild(); } + string getTag() const { return mTag; } + string value() const { return mFullHeader; } + string toFromValue() const { return mFullHeader; } + string uriUsername() const { return mUri.uriUsername(); } + string uriHostAndPort() const { return mUri.uriHostAndPort(); } + + SipPreposition(string wDisplayName,string wUri, string wTag="") : mDisplayName(wDisplayName), mUri(wUri), mTag(wTag) { + rebuild(); + } + SipPreposition(string fullHeader) { prepSet(fullHeader); } + SipPreposition() {} +}; +// TODO: operator<< + +struct SdpInfo { + unsigned sdpRtpPort; + string sdpUsername; + string sdpHost; + string sdpSessionId; + string sdpVersionId; + string sdpCodecList; // An offer has multiple numbers here, a reply has just one. + string sdpAttrs; + + void sdpParse(const char *buffer); + void sdpParse(string sdp) { sdpParse(sdp.c_str()); } + void sdpInitOffer(const SipBase *dialog); + void sdpInitRefer(const SipBase *dialog, int remotePort); + string sdpValue(); +}; + +extern SipMessage *sipParseBuffer(const char *buffer); +extern void parseAuthenticate(string stuff, SipParamList ¶ms); +extern void parseToParams(string stuff, SipParamList ¶ms); +extern bool crackUri(string header, string *before, string *uri, string *tail); + +}; // namespace +#endif diff --git a/SIP/SIPTransaction.cpp b/SIP/SIPTransaction.cpp new file mode 100644 index 0000000..0b3b564 --- /dev/null +++ b/SIP/SIPTransaction.cpp @@ -0,0 +1,670 @@ +/* +* 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 distribuion. +* +* 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::SIP // Can set Log.Level.SIP for debugging + +#include "SIPUtility.h" +#include "SIPBase.h" +#include "SIPDialog.h" +#include "SIP2Interface.h" +#include "SIPMessage.h" +#include "SIPTransaction.h" +#include "SIPExport.h" + +namespace SIP { +static const string cINVITEstr("INVITE"); +static const string cREGISTERstr("REGISTER"); + +// Note: Most of the documentation is in SIPMessage.cpp + +// TERMS: +// URI = Uniform Resource Identifier. 19.1: In SIP it includes sip:user:password@host:port;parameters?headers +// userinfo part of a URI is: The name:password@ The userinfo is optional. +// UAC = User Agent Client (request initiator) +// UAS = User Agent Server (request recipient) +// UA = User Agent, either client or server. +// Session = the SDP session for media created in a dialog. +// SIP Layers: +// 1. Message syntax/encoding +// 2. Transport Layer sec 18. +// 3. Transaction Layer: handles matching responses to requests, retransmissions, timeouts. +// (Transaction Layer is kind of a crappy name since the whole thing is called a Transaction) +// 4. TU = Trasaction User. + +// The From and To in a response match a request, ie, in a response they are the reverse of the message direction. +// Tags 8.1.1: +// The from-tag identifies the client and must be in a request. +// The to-tag identifies the peer. A request must not have a To-tag. To-tag is set in the response. + + + +// Requests: +// Request-Line - first line in SIP request, contains: method Request-URI SIP-Version +// The request-URI is sip or sips uri indicating user or service to which this request is addressed. +// Responses: +// Status-Line - first line in SIP response, contains: SIP-Version Status-Code Reason-Phrase + +void SipTransaction::_define_vtable() {} +void SipTransaction::TUTimeoutV() { LOG(DEBUG); } + +void SipTransaction::stInitNonDialogTransaction(TranEntryId tranid, string wBranch, SipMessage *request, const IPAddressSpec *wPeer) { + mstTranId = tranid; + mstBranch = wBranch; + stSaveRequestId(request); + mstPeer = *wPeer; +} +void SipTransaction::stInitNonDialogTransaction(TranEntryId tranid, string wBranch, SipMessage *request, string wProxy, const char *wProxyProvenance) { + mstTranId = tranid; + mstBranch = wBranch; + stSaveRequestId(request); + mstPeer.ipSet(wProxy, wProxyProvenance); +} +void SipTransaction::stInitInDialogTransaction(SipDialog *wDialog, string wBranch, SipMessage *request) { + mstDialog = wDialog; + mstTranId = wDialog->mTranId; + mstPeer = *wDialog->dsPeer(); + mstBranch = wBranch; + stSaveRequestId(request); +} + +void SipTransaction::stWrite(SipMessage *sipmsg) +{ + if (!mstPeer.mipValid) { + LOG(ERR) << "Attempt to write to invalid peer address:"<dmRand = rand; + dmsg->dmRejectCause = atoi(gsmRejectCode.c_str()); + NewTransactionTable_ttAddMessage(mstTranId,dmsg); +} + +void SipTransaction::sendAuthOKMessage(SipMessage *sipmsg) +{ + string imsi = sipmsg->msmTo.uriUsername(); // The To: and From: have the same value for a REGISTER message. + // Validate the imsi: + if (imsi.empty()) { + LOG(ERR) << "can't find imsi to store kc"; + sendAuthFailMessage(400,"",""); + return; + } + if (0 == strncasecmp(imsi.c_str(),"imsi",4)) { + // happiness + } else if (0 == strncasecmp(imsi.c_str(),"tmsi",4)) { + // TODO: In future the user name could be a TMSI. + LOG(ERR) << "SIP REGISTER message with TMSI not supported, userid="<msmContent; + sendAuthFailMessage(400,"",""); + return; + } + + DialogAuthMessage *dmsg = new DialogAuthMessage(mstTranId, DialogState::dialogActive,200); + dmsg->dmPAssociatedUri = sipmsg->msmHeaders.paramFind("P-Associated-URI"); // case doesnt matter + dmsg->dmPAssertedIdentity = sipmsg->msmHeaders.paramFind("P-Asserted-Identity"); + + string authinfo = sipmsg->msmHeaders.paramFind("Authentication-Info"); + if (! authinfo.empty()) { + SipParamList params; + parseToParams(authinfo, params); + dmsg->dmKc = params.paramFind("cnonce"); // This is the way SR passes the Kc. + } else { + // There will not be a cnonce if the Registrar does not know it. Dont worry about it. + //LOG(INFO) << "No Authenticate-Info header in SIP REGISTER response:"<msmContent; + } + + if (dmsg->dmKc.empty()) { + dmsg->dmKc = sipmsg->msmHeaders.paramFind("P-GSM-Kc"); // This is the way Yate passes the Kc. + } + + WATCHF("CNONCE: imsi=%s Kc=%s\n",imsi.c_str(),dmsg->dmKc.c_str()); + if (! dmsg->dmKc.empty()) { + LOG(DEBUG) << "Storing Kc:"<dmKc.size()); // We dont put Kc itself in the log. + //gTMSITable.putKc(imsi.c_str()+4, kc, pAssociatedUri, pAssertedIdentity); + } else { + LOG(NOTICE) << "No Kc in SIP REGISTER response:"<msmContent; + } + + NewTransactionTable_ttAddMessage(mstTranId,dmsg); +} + +// The cause is not currently used. +void SipTransaction::stSetDialogState(SipState newState, int code, char timer) const +{ + if (SipDialog *dialog = mstDialog) { + dialog->dialogPushState(newState,code,timer); + } +} + +// This is the default. The INVITE ones are static and live in the dialog so they are not destroyed. +//void SipTransaction::stDestroyV() +//{ +// if (mstDialog) { +// // Do what? +// //mstDialog->removeTransaction(this); +// } +// gSipInterface.tuMapRemove(this,true); +// delete this; +//} + +void SipTransaction::stFail(int code) +{ + stSetDialogState(SSFail,code,0); // Dont need more timer, this is invoked after timer expiry. + stDestroyV(); +} + +ostream& operator<<(ostream& os, const SipTransaction&st) +{ + os << " SipTransaction("<smViaBranch() == mstOutRequest.smViaBranch() && msg->smCSeqMethod() == mstOutRequest.smCSeqMethod()); + return false; +} + +// 17.2.3: Matching requests to Server [inbound] Transactions: +// If branch has the magic cookie z9hG4bK: +// 1. branch param in request equals the top via header branch of the request that created the transaction +// 2. and the sent-by value in in top via of request is equal +// 3. method matches, exceptfor ACK, in which case the method of the requested that created the transaction is INVITE. +// bool SipServerTrLayer::stMatchesMessageV(SipMessage *msg) +// { +// SipVia msgvia(msg->msmVias), requestvia(mstInRequest.msmVias); +// if (msgvia.mViaBranch == requestvia.mViaBranch && msgvia.mSentBy == requestvia.mSentBy) { +// if (msg->isACK()) { +// return mstInRequest.msmReqMethod == "INVITE"; +// } else { +// return msg->msmReqMethod == mstInRequest.msmReqMethod; +// } +// } +// return false; +// } + + +// The transactions methods we support are: +// INVITE, ACK, MESSAGE, BYE, CANCEL, INFO. + + +bool SipClientTrLayer::TLWriteHighSideV(SipMessage *sipmsg) +{ + int code = sipmsg->smGetCode(); + LOG(DEBUG) <= 300) { mstDialog->MOCSendACK(); } + else { stDestroyV(); return true; } // The TU is responssible for sending the ACK, after connection setup. + } + if (stIsReliableTransport()) { stDestroyV(); return true; } else { mTimerDK.set(32*1000); } + return true; + } + case stCompleted: + switch ((code / 100) * 100) { + case 100: + case 200: + return false; // suppress duplicate messages. + default: + if (stIsInvite()) { mstDialog->MOCSendACK(); } + return false; // suppress duplicate messages. + } + case stTerminated: + // Now what? + LOG(ERR) << "SIP Message received on terminated client transaction."<isACK()); } + mstOutRequest = *request; + devassert(mstState == stInitializing); + stWrite(request); + mstState = stCallingOrTrying; +} + +void SipClientTrLayer::sctInitRegisterClientTransaction(SipDialog *wDialog, SipMessage *request, string branch) +{ + stInitInDialogTransaction(wDialog, branch, request); // Do this first. + mstOutRequest = *request; +} + +void SipClientTrLayer::sctInitInDialogClientTransaction(SipDialog *wDialog, SipMessage *request, string branch) +{ + stInitInDialogTransaction(wDialog, branch, request); // Do this first. + mTimerBF.setOnce(64*T1); // this is init time so we could have used just set. + if (!stIsReliableTransport()) { mTimerAE.set(T1); } + mstOutRequest = *request; +} + +void SipClientTrLayer::sctStart() +{ + LOG(DEBUG); + ScopedLock lock(mstLock); + // Must add to the tu map before sending the message because the reply can be fast enough to cause a race. + gSipInterface.tuMapAdd(this); + devassert(mstState == stInitializing); + stWrite(&mstOutRequest); // Send the initial message. + mstState = stCallingOrTrying; +} + +// Return TRUE to delete it. +bool SipClientTrLayer::TLPeriodicServiceV() +{ + LOG(DEBUG) << LOGVAR(mTimerDK) <| |<-------+ +// | | Transport Err. +// | | Inform TU +// | |--------------->+ +// +-----------+ | +//300-699 from TU | |2xx from TU | +//send response | |send response | +// | +------------------>+ +// | | +//INVITE V Timer G fires | +//send response+-----------+ send response | +// +--------| |--------+ | +// | | Completed | | | +// +------->| |<-------+ | +// +-----------+ | +// | | | +// ACK | | | +// - | +------------------>+ +// | Timer H fires | +// V or Transport Err.| +// +-----------+ Inform TU | +// | | | +// | Confirmed | | +// | | | +// +-----------+ | +// | | +// |Timer I fires | +// |- | +// | | +// V | +// +-----------+ | +// | | | +// | Terminated|<---------------+ +// | | +// +-----------+ +//Figure 7: INVITE server transaction + + + +// // The request should be INVITE or ACK. +// // Return TRUE if the message should be sent to the TU. +// bool SipServerTrLayer::TLWriteHighSideV(SipMessage *wRequest) +// { +// LOG(DEBUG); +// switch (mstState) { +// case stTrying: +// // Discard retransmissions until the TU server responds. +// // For invites, if this takes longer than 200ms we are supposed to manufacture a 100 Trying response, +// // but we're just going to hope that does not happen and treat it the same. +// if (sameMsg(wRequest,&mstInRequest)) { +// // discard duplicate. +// // TODO: Arent we supposed to send 100 Trying? +// return false; +// } +// TUWriteHighSideV(wRequest); +// return true; +// case stProceeding: +// case stCompleted: +// // The TU already sent something so resend it. +// // As specified in RFC3261 on receipt of a request we are required to resend the last response, +// // even if we just did so, which is a waste of resources. +// // I am modifying this slightly so we do not send two duplicate responses immediately after eaach other. +// if (mLastSent.elapsed() >= 100) { +// stWrite(&mstResponse); // resend most recent response. +// } +// return false; // This is a repeat message. +// case stConfirmed: +// // This Confirmed state exists simply to ignore this duplicate ACK. +// // TODO: print an error if it is not an ACK. +// return false; // This is a repeat ACK. +// case stTerminated: +// // Now what? +// LOG(ERR) << "SIP Message received on terminated server transaction."<isACK()) { +// mstState = stConfirmed; +// if (stIsReliableTransport()) { stDestroyV(); } else { mTimerI.set(T4); } +// } +// TUWriteHighSideV(wRequest); +// return true; +// } +// +// // The transaction was established by some incoming request, and now the TU is sending a response. +// void SipInviteServerTrLayer::TLWriteLowSideV(SipMessage *wResponse) +// { +// LOG(DEBUG); +// ScopedLock lock(mstLock); +// mstResponse = *wResponse; +// stWrite(&mstResponse); mLastSent.now(); +// assert(stIsInvite()); +// +// switch (mstState) { +// case stTrying: +// case stProceeding: +// switch ((wResponse->msmCode / 100) * 100) { +// case 100: // sending provisional response. +// //assert(mstState == stTrying || mstState == stProceeding); +// mstState = stProceeding; +// stSetDialogState(Proceeding,wResponse->msmCode); +// return; +// case 200: +// // The spec indicates that the TL layer is terminated when 200 is sent, +// // and I quote: "retransmissions of the 2xx responses are handled by the TU" +// // maybe because they have SDP content that may change? +// mstState = stTerminated; +// // stDestroyV(); +// return; +// default: +// //assert(mstState != stConfirmed); +// mstState = stCompleted; +// mTimerHJ.set(64*T1); +// if (!stIsReliableTransport()) { +// mTimerG.set(min(T2,mGCount * T1)); +// mGCount *= 2; +// } +// return; +// } +// case stCompleted: +// case stConfirmed: +// case stTerminated: // TODO: Is this right? +// assert(0); +// } +// } +// +// void SipNonInviteServerTrLayer::TLWriteLowSideV(SipMessage *wResponse) +// { +// LOG(DEBUG); +// ScopedLock lock(mstLock); +// mstResponse = *wResponse; +// stWrite(&mstResponse); mLastSent.now(); +// assert(! stIsInvite()); +// switch (mstState) { +// case stTrying: +// case stProceeding: +// switch ((wResponse->msmCode / 100) * 100) { +// case 100: // sending provisional response. +// mstState = stProceeding; +// stSetDialogState(Proceeding,wResponse->msmCode); +// return; +// default: +// mstState = stCompleted; +// // We have to rely on the TU layer to set the dialog state. +// //setDialogState(?); +// if (stIsReliableTransport()) { vstDestroy(); } else { mTimerHJ.set(64*T1); } +// return; +// } +// case stCompleted: +// case stConfirmed: +// case stTerminated: +// assert(0); +// } +// } +// +// bool SipServerTrLayer::TLPeriodicServiceV() +// { +// LOG(DEBUG) << LOGVAR(mTimerHJ) <msmTo.uriUsername(); // The To: and From: have the same value for a REGISTER message. +// // Validate the imsi: +// if (imsi.empty()) { +// LOG(ERR) << "can't find imsi to store kc"; +// return; +// } +// if (0 == strncasecmp(imsi.c_str(),"imsi",4)) { +// // happiness +// } else if (0 == strncasecmp(imsi.c_str(),"tmsi",4)) { +// // TODO: In future the user name could be a TMSI. +// LOG(ERR) << "SIP REGISTER message with TMSI not supported, userid="<msmContent; +// return; +// } +// +// string pAssociatedUri = sipmsg->msmHeaders.paramFind("P-Associated-URI"); // case doesnt matter +// string pAssertedIdentity = sipmsg->msmHeaders.paramFind("P-Asserted-Identity"); +// +// string authinfo = sipmsg->msmHeaders.paramFind("Authentication-Info"); +// if (authinfo.empty()) { +// LOG(NOTICE) << "No Authenticate-Info in SIP REGISTER response:"<msmContent; +// return; +// } +// +// SipParamList params; +// parseToParams(authinfo, params); +// string kc = params.paramFind("cnonce"); // This is the way SR passes the Kc. +// if (kc.empty()) { +// kc = sipmsg->msmHeaders.paramFind("P-GSM-Kc"); // This is the way Yate passes the Kc. +// } +// WATCHF("CNONCE: imsi=%s Kc=%s\n",imsi.c_str(),kc.c_str()); +// if (! kc.empty()) { +// LOG(DEBUG) << "Storing Kc:"<msmContent; +// } +//} + +// It is hardly worth the effort to make a transaction for REGISTER, which occurs outside a dialog +// and has only one reply, but we need to know when to destroy it. +void SipRegisterTU::TUWriteHighSideV(SipMessage *sipmsg) { + int code = sipmsg->msmCode; + static const char *pRejectCauseHeader = "P-GSM-Reject-Cause"; + LOG(DEBUG) <smGetRand401(),sipmsg->msmHeaders.paramFind(pRejectCauseHeader)); + setTransactionState(stCompleted); + } else if (code == 200) { + // (pat) We can no longer stash Kc directly in the TMSI table because the entry is not created until the MS is validated. + //stashAuthInfo(sipmsg); // Stash the Kc from the message. + sendAuthOKMessage(sipmsg); + } else switch ((code/100) * 100) { + // These SetDialogState are really only for in-dialog messages. + case 100: return; // we dont care. + case 200: LOG(ERR) << "invalid SIP REGISTER code:"<msmHeaders.paramFind(pRejectCauseHeader)); + return; + } +} + +SipRegisterTU::SipRegisterTU(SipDialog *registrar, SipMessage *request) +{ + // Kinda dumb to fish out the branch from the request, but its ok. + //SipDialog *registrar = getRegistrar(); + //SipMessage *request = registrar->makeRegisterMsg(SIPDTRegister,chan,rand,msid,sres.c_str()); + // It is in the dummy dialog established for the registrar. + sctInitRegisterClientTransaction(registrar, request, request->smGetBranch()); +} + +void startRegister(const FullMobileId &msid, const string rand, const string sres, L3LogicalChannel *chan) // msid is imsi and/or tmsi +{ + LOG(DEBUG) <makeRegisterMsg(SIPDTRegister,chan,rand,msid,sres.c_str()); + SipRegisterTU *reg = new SipRegisterTU(registrar,request); + delete request; // sctInitRegisterTransaction made a copy. Kind of wasteful. + reg->sctStart(); +} + + +// =========================================================================================== + +void SipMOByeTU::TUWriteHighSideV(SipMessage *sipmsg) { + LOG(DEBUG); + if (sipmsg->msmCode >= 200) { stSetDialogState(Cleared,sipmsg->msmCode,'K'); } +} +//void SipMOByeTU::TUTimeoutV() { stSetDialogState(Cleared,0); } + +SipMOByeTU::SipMOByeTU(SipDialog *wDialog) // : SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog) +{ + LOG(DEBUG); + string branch = make_branch(); + SipMessage *bye = new SipMessageRequestWithinDialog(stGetMethodNameV(),wDialog,branch); + sctInitInDialogClientTransaction(wDialog, bye, branch); + delete bye; +} + +void SipMOCancelTU::TUWriteHighSideV(SipMessage *sipmsg) { + if (sipmsg->msmCode >= 200) { stSetDialogState(Canceled,sipmsg->msmCode,'K'); } +} + +//void SipMOCancelTU::TUTimeoutV() { stSetDialogState(Canceled,0); } + +SipMOCancelTU::SipMOCancelTU(SipDialog *wDialog) // : SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog) +{ + string branch = make_branch(); + SipMessage *cancelMsg = new SipMessageRequestWithinDialog(this->stGetMethodNameV(),wDialog,branch); + this->sctInitInDialogClientTransaction(wDialog, cancelMsg, branch); + delete cancelMsg; +} + +SipDtmfTU::SipDtmfTU(SipDialog *wDialog, unsigned wInfo) //: SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog) +{ + string branch = make_branch(); + SipMessage *msg = new SipMessageRequestWithinDialog(stGetMethodNameV(),wDialog,branch); + static const string applicationDtmf("application/dtmf-relay"); + string body; + switch (wInfo) { + case 11: body = string("Signal=*\nDuration=200"); break; + case 12: body = string("Signal=#\nDuration=200"); break; + default: body = format("Signal=%i\nDuration=200",wInfo); break; + } + msg->smAddBody(applicationDtmf,body); + sctInitInDialogClientTransaction(wDialog, msg, branch); + delete msg; +} + +void SipDtmfTU::TUWriteHighSideV(SipMessage *sipmsg) { + switch ((sipmsg->msmCode*100)/100) { + case 100: return; // keep going. + default: + sendSimpleMessage(DialogState::dialogDtmf,sipmsg->msmCode); + return; + } +} + +}; // namespace SIP diff --git a/SIP/SIPTransaction.h b/SIP/SIPTransaction.h new file mode 100644 index 0000000..e3f9d06 --- /dev/null +++ b/SIP/SIPTransaction.h @@ -0,0 +1,237 @@ +/* +* 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 distribuion. +* +* 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. +#ifndef _SIPTRANSACTION_H_ +#define _SIPTRANSACTION_H_ 1 + +#include "SIPUtility.h" // For SipTimer, IPAddressSpec +#include "SIPBase.h" +#include "ControlTransfer.h" + +namespace SIP { +using namespace std; +using namespace Control; + +// (pat) The RFC3261 Transaction Layer is responsible for resending messages. +// Note that a SIP Transaction is defined with 4 layers, one of which is absurdly called +// the "Transaction Layer" which is what this code implements. +// RFC3261 distinguishes only INVITE and non-INVITE transactions, but in reality there are 4 substantially +// different kinds SIP Transactions, each of which has a Client (initiating) and Server (receiving) side, for 8 types total. +// a. INVITE, +// b. non-INVITE and outside of any dialog. +// c. non-INVITE within a dialog. +// d. REGISTER, which are sufficiently different to be a whole type by themselves. +// I started by translating the state machines from RFC3261 sec 17 directly into code, and intended +// to use them for all types of SIP Transactions, but that just did not work well. +// The INVITE and non-INVITE types are too different, and additionally, the RFC3261 state machine +// for INVITE only goes part-way, then dumps control onto the Transaction User. +// For non-INVITE server transactions, the only thing we need to do is repeat the message each time the +// same request comes in, which is often more easily handled at the Transaction User level +// (eg if you get a second CANCEL request for a dialog, just send 200 OK again) so that +// code does not need the complicated state machinery. And one more thing, the message routing +// is clearer if the transaction layer classes are a base class of the client class (either dialog or TU [Transaction User]) +// rather than being passed to a separate Transaction Layer machine and back. +// So this is how it ended up: +// The INVITE (a) SIP Transaction code has been moved to SipMTInviteServerTransactionLayer and SipMTInviteServerTransactionLayer, +// which are base classes of the Dialog, and makes passing the messages through the TransactionLayer much clearer. +// The MESSAGE (b) is handled the same way because it was easier to connect to the Control layer with a Dialog. +// It would be better to have a base class which is the connection layer to the Control directory, but I have to stop cleaning up somewhere. +// The (c) Server side is handled by simple sip message handlers in the Dialog class. +// So this class is used only for Client-side (c) and (d) and could be simplified. + + +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipTransaction,MemCheckSipTransaction) +class SipTransaction : public MemCheckSipTransaction, public SipTimers +{ + virtual void _define_vtable(); // Unused method to insure the compiler link phase is passified. + protected: + mutable Mutex mstLock; + IPAddressSpec mstPeer; // The remote peer. Copied from mDialog at startup, or specified by Transaction creator. + string mstBranch; // no longer used. + // TODO: Maybe this should be a SipEngine... + SipDialog *mstDialog; // Transaction owner, or NULL for out-of-dialog transactions. + TranEntryId mstTranId; // The associed L3 Transaction, if any. TODO: Now this could use a RefCntPointer to the transaction. + + public: + string mstMethod; int mstSeqNum; + string mstCallId; + virtual string stGetMethodNameV() = 0; + + protected: + void stWrite(SipMessage *msg); + bool stIsReliableTransport() const { return mstPeer.ipIsReliableTransport(); } + string stTransportName() { return mstPeer.ipTransportName(); } + // Send a message to the TranEntry associated with this Dialog. + void sendSimpleMessage(DialogState::msgState wInfo, int code); + // (pat) Yes, it is ugly having specialized methods in a base class. + void sendAuthFailMessage(int code, string rand, string gsmRejectCode); + void sendAuthOKMessage(SipMessage *sipmsg); + + // I dont think we are going to use this: + virtual bool stMatchesMessageV(SipMessage *msg) = 0; + // Inbound is toward the radio, Outbound is toward the outside world. + virtual bool TLWriteHighSideV(SipMessage *msg) = 0; // TL processes an incoming message from the outside world, returns true if should go to TU. + virtual void TUWriteHighSideV(SipMessage *msg) = 0; // TU overrides this to receive messages. + virtual void TUTimeoutV(); // TU may optionally override this to be informed. + + void stSetDialogState(SipState newState, int code, char timer) const; + //SipDialog *dialog() { return mDialog; } + //void stSetSipState(SipState wState) { mstDialog->setSipState(wState); } + void stSetTranEntryId(TranEntryId tid) { mstTranId = tid; } + + private: + void stSaveRequestId(SipMessage *request) { + mstMethod = request->msmCSeqMethod; + mstSeqNum = request->msmCSeqNum; + mstCallId = request->msmCallId; + } + + protected: + // The idiotic C++ constructor paradigm obfuscates construction so badly in this case that we are not going to use it. + // A SipTransaction is created locked both to make sure the periodic service routine does not process + // it before it is completely constructed and to avoid the problem of an incoming message being routed + // to the transaction during its initialization. + SipTransaction() : mstDialog(NULL), mstTranId(0) { /*mstLock.lock();*/ } + // A transaction always starts with a request, either inbound request for a server transaction or + // outbound request for a client transaction. + // These differ only in how the peer is specified. + void stInitNonDialogTransaction(TranEntryId tranid, string wBranch, SipMessage *request, const IPAddressSpec *wPeer); // currently unused + void stInitNonDialogTransaction(TranEntryId tranid, string wBranch, SipMessage *request, string wProxy, const char* wProxyProvenance); // currently unused + void stInitInDialogTransaction(SipDialog *wDialog, string wBranch, SipMessage *request); + + virtual void stDestroyV() = 0; + + void stFail(int code); + + // These objects are used by multiple threads by their nature; the TransactionLayer receives input from: + // the external SIP interface; layer3 control; periodic service. + // Therefore we carefully mutex protect them. + // Please dont go making more of this class public without mutex protecting it. + public: + string stBranch() { return mstBranch; } + // unused virtual bool stIsTerminated() const = 0; + virtual void TLWriteLowSideV(SipMessage *msg) = 0; // TL processes uplink message to the outside world. + void TLWriteHighSide(SipMessage *msg) { // SIP Interface sends incoming messages here. + LOG(DEBUG); + ScopedLock lock(mstLock); + TLWriteHighSideV(msg); + } + virtual bool TLPeriodicServiceV() = 0; + + //void stUnlock() { mstLock.unlock(); } + virtual ~SipTransaction() { // Do not delete this method even if empty. + // Do we need to lock this? What is the point. It is deleted only from + // inside the SipTUMap class, which holds the mTUMap lock throughout the procedure, + // preventing any incoming messages. + } +}; +ostream& operator<<(ostream& os, const SipTransaction*st); +ostream& operator<<(ostream& os, const SipTransaction&st); + + +// SIP Transaction Layer for client (outbound) transactions. +// The transaction layer does not modify messages - it is responsible only for resends. +// Therefore it is informed of all inbound and outbound messages. +// Outbound messages are just saved for possible retransmission. +// Inbound messages may be discarded at this layer if they are repeats. +// +// RFC 3261 17.1.1 and Figure 5. client INVITE transaction. +// Timers A, B, D +// INVITE->peer +// <-1xxx peer +// <-2xxx peer +// send to dialog, which is responsible for ACK +// <-3xx,4xx,5xx,6xx peer +// ACK->peer, send fail to dialog +// MESSAGE,REGISTER->peer +// <- 1xx peer +// RFC 3261 17.1.2 and Figure 6. client non-INIVITE transaction, eg MESSAGE, REGISTER +// Timers E, F, K +// (pat) Update: We are no longer using this for MESSAGE transactions. +class SipClientTrLayer : public SipTransaction +{ + SipMessage mstOutRequest; // outbound request, eg INVITE, MESSAGE, REGISTER. + SipTimer mTimerAE, mTimerBF, mTimerDK; + protected: + bool stIsInvite() { return mstOutRequest.isINVITE(); } // We ended up not using this class for INVITE, but some code still here. + enum States { // These are transaction states, not dialog states. + stInitializing, stCallingOrTrying, stProceeding, stCompleted, stTerminated + } mstState; + // Downlink is toward the radio, Uplink is toward the outside world. + bool TLWriteHighSideV(SipMessage *msg); // TL processes an incoming message from the outside world, returns true if should go to TU. + void TLWriteLowSideV(SipMessage *msg); // TL processes uplink message to the outside world. + SipClientTrLayer() { mstState = stInitializing; } + void stDestroyV() { mstState = stTerminated; } + + public: + // unused bool stIsTerminated() const { return mstState == stTerminated; } + void setTransactionState(States st) { mstState = st; } + bool stMatchesMessageV(SipMessage *msg); + bool TLPeriodicServiceV(); + SipMessage *vstGetRequest(); + // We use a client transaction for REGISTER even though it is not technically a TU, it acts like one + // except there are no resends, which we implement just by not setting any timers. + void sctInitRegisterClientTransaction(SipDialog *wDialog, SipMessage *request, string branch); + void sctInitInDialogClientTransaction(SipDialog *wDialog, SipMessage *request, string branch); + void sctStart(); +}; + +class SipInviteClientTrLayer : public SipClientTrLayer +{ + string stGetMethodNameV() { static const string inviteStr("INVITE"); return inviteStr; } + void TUWriteHighSideV(SipMessage * /*sipmsg*/) {} // ?? +}; + + +// It is hardly worth the effort to make a transaction for REGISTER, which occurs outside a dialog +// and has only one reply, but we need to know when to destroy it. +struct SipRegisterTU : public SipClientTrLayer +{ + string stGetMethodNameV() { static const string registerStr("REGISTER"); return registerStr; } + void TUWriteHighSideV(SipMessage *sipmsg); + //SipRegisterTU(const FullMobileId &msid, const string &rand, const string &sres, L3LogicalChannel *chan); // msid is imsi and/or tmsi + SipRegisterTU(SipDialog *registrar, SipMessage *request); +}; + + +struct SipMOByeTU: public SipClientTrLayer +{ + string stGetMethodNameV() { static const string cByeStr("BYE"); return cByeStr; } + void TUWriteHighSideV(SipMessage *sipmsg); + // TUTimeoutV not needed; on timeout we set dialog state to SSFail. + //void TUTimeoutV(); + SipMOByeTU(SipDialog *wDialog); +}; + +struct SipMOCancelTU: public SipClientTrLayer +{ + string stGetMethodNameV() { static const string cCancelStr("CANCEL"); return cCancelStr; } + void TUWriteHighSideV(SipMessage *sipmsg); + // TUTimeoutV not needed; on timeout we set dialog state to SSFail. + //void TUTimeoutV(); + SipMOCancelTU(SipDialog *wDialog); +}; + + +struct SipDtmfTU: public SipClientTrLayer +{ + string stGetMethodNameV() { static const string infostr("INFO"); return infostr; } + SipDtmfTU(SipDialog *wDialog, unsigned wInfo); + void TUWriteHighSideV(SipMessage *sipmsg); +}; + +}; // namespace SIP +#endif diff --git a/SIP/SIPUtility.cpp b/SIP/SIPUtility.cpp index 513eb0d..19e4b7e 100644 --- a/SIP/SIPUtility.cpp +++ b/SIP/SIPUtility.cpp @@ -12,6 +12,7 @@ */ +#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging. If there were any LOG() calls in this file. #include #include @@ -21,99 +22,309 @@ #include #include -#include -#include +#include -#include "SIPInterface.h" +//#include "SIPInterface.h" #include "SIPUtility.h" +#include +#include +#include -using namespace SIP; + +namespace SIP { using namespace std; +// Unused, but here it is if you want it: +// Pair is goofed up, so just make our own. It is trivial: +// +//template +//struct Pair { +// T1 first; +// T2 second; +// Pair(T1 a, T2 b) : first(a), second(b) {} +//}; +//template +//Pair make_pair(T1 x, T2 y) +//{ +// return Pair(x,y); +//} +//typedef Pair SipParam; // Used for both params and 'headers', which is stuff in a URI. - -#define DEBUG 1 - - - - - -bool SIP::get_owner_ip( osip_message_t * msg, char * o_addr ) +#if 0 // Dont bother. Added isINVITE(), isACK(), etc. +// Note that all methods establish a new transaction, including ACK. +// This is still not handled correctly. +enum SipMethodType { + MethodUndefined, + MethodACK, + MethodBYE, + MethodCANCEL, + MethodINVITE, + MethodMESSAGE, + MethodOPTIONS, + MethodINFO, + MethodREFER, + MethodNOTIFY, + MethodSUBSCRIBE, + MethodPRACK, + MethodUPDATE, + MethodREGISTER, + MethodPUBLISH +}; +typedef std::pair SipMethodPair; +typedef std::map SipMethodMap; +static SipMethodMap gMethodMap; +SIPMethodType SipMessage::str2MethodType(string &methodname) { - osip_body_t * sdp_body = (osip_body_t*)osip_list_get(&msg->bodies, 0); - if (!sdp_body) return false; - char * sdp_str = sdp_body->body; - if (!sdp_str) return false; - - sdp_message_t * sdp; - sdp_message_init(&sdp); - sdp_message_parse(sdp, sdp_str); - strcpy(o_addr, sdp->o_addr); - return true; -} - -bool SIP::get_rtp_params(const osip_message_t * msg, char * port, char * ip_addr ) -{ - osip_body_t * sdp_body = (osip_body_t*)osip_list_get(&msg->bodies, 0); - if (!sdp_body) return false; - char * sdp_str = sdp_body->body; - if (!sdp_str) return false; - - sdp_message_t * sdp; - sdp_message_init(&sdp); - sdp_message_parse(sdp, sdp_str); - - strcpy(port,sdp_message_m_port_get(sdp,0)); - strcpy(ip_addr, sdp->c_connection->c_addr); - return true; -} - -void SIP::make_tag(char * tag) -{ - uint64_t r1 = random(); - uint64_t r2 = random(); - uint64_t val = (r1<<32) + r2; - - // map [0->26] to [a-z] - int k; - for (k=0; k<16; k++) { - tag[k] = val%26+'a'; - val = val >> 4; + if (gMethodMap.size() == 0) { + // init the map +#define HELPER_ONE_SIP_METHOD(name) gMethodMap.insert(SipMethodPair(#name, Method##name)); + HELPER_ONE_SIP_METHOD(ACK) + HELPER_ONE_SIP_METHOD(BYE) + HELPER_ONE_SIP_METHOD(CANCEL) + HELPER_ONE_SIP_METHOD(INVITE) + HELPER_ONE_SIP_METHOD(MESSAGE) + HELPER_ONE_SIP_METHOD(OPTIONS) + HELPER_ONE_SIP_METHOD(INFO) + HELPER_ONE_SIP_METHOD(REFER) + HELPER_ONE_SIP_METHOD(NOTIFY) + HELPER_ONE_SIP_METHOD(SUBSCRIBE) + HELPER_ONE_SIP_METHOD(PRACK) + HELPER_ONE_SIP_METHOD(UPDATE) + HELPER_ONE_SIP_METHOD(REGISTER) + HELPER_ONE_SIP_METHOD(PUBLISH) + gMethodMap.insert(SipMethodPair("",MethodUndefined)); // empty string maps to Undefined method. } - tag[k] = '\0'; -} + //for (SipMethodMap::iterator i = gMethodMap.begin(); i != gMethodMap.end(); ++i) { + // printf("SipMethodMap %s = %d\n",i->first,i->second); + //} -void SIP::make_branch( char * branch ) + if (methodname == NULL) { return MethodUndefined; } + SipMethodMap::iterator it = gMethodMap.find(methodname); + if (it == gMethodMap.end()) { printf("MethodMap %s not found\n",methodname); } + return it == gMethodMap.end() ? MethodUndefined : it->second; +} +SIPMethodType SipMessage::smGetMethod() const { - uint64_t r1 = random(); - uint64_t r2 = random(); - uint64_t val = (r1<<32) + r2; - sprintf(branch,"z9hG4bKobts28%llx", val); -} - -/* get the return address from the SIP VIA header - if port not available, guess at 5060 - if header not available, guess from SIP.Proxy.SMS - -kurtis -*/ -string SIP::get_return_address(osip_message_t * msg){ - - string result; - osip_via* via = (osip_via *) osip_list_get(&msg->vias,0); - if (via){ - result = string(via->host); - if (via->port) { - result += string(":") + string(via->port); - } - else { - result += string(":") + string("5060"); //assume SIP - } - } - else { //no via header? Take best guess from conf -k - result = gConfig.getStr("SIP.Proxy.SMS"); + if (omsg()->sip_method == NULL) { + return MethodUndefined; // Hmm. } + string methname(smGetMethodName()); + SIPMethodType result = str2MethodType(methname); + LOG(DEBUG)<second; +} + + +// On success, set the resolved address stuff, mipValid, and return true. +bool IPAddressSpec::ipSet(string addrSpec, const char * provenance) +{ + mipName = addrSpec; + + // (pat 7-230-2013) Someone else added this, I am preserving it: + // Check for illegal hostname length. 253 bytes for domain name + 6 bytes for port and colon. + // This isn't "pretty", but it should be fast, and gives us a ballpark. Their hostname will + // fail elsewhere if it is longer than 253 bytes (since this assumes a 5 byte port string). + if (addrSpec.size() == 0) { + LOG(ALERT) << "hostname is empty from "< 259) { + LOG(ALERT) << "hostname is greater than 253 bytes from "<26] to [a-z] + strcpy(tag,start); + char *cp = tag+strlen(start); + for (int k=0; k<16; k++) { + *cp++ = val%26+'a'; + val = val >> 4; + } + *cp = '\0'; + return string(tag); +} + +string make_tag() +{ + return make_tag1("OBTS"); +} + +string make_branch(const char * /*hint*/) // hint is unused. +{ + // RFC3261 17.2.3: The branch parameter should begin with the magic string "z9hG4bK" to + // indicate compliance with this specification. + return make_tag1("z9hG4bKOBTS"); +} + +string dequote(const string in) +{ + if (in[0] != '"') return in; + if (in.size() < 2 || in[in.size() - 1] != '"') { + LOG(ERR) << "Invalid quoted string:"< +#include +#include +#include +#include +#include +#include +#include +#include + + + +template +class ThreadSafeList { + typedef std::list BaseType; + BaseType mList; + Mutex mMapLock; + public: + Mutex &getLock() { return mMapLock; } + + public: +#if USE_SCOPED_ITERATORS + typedef ScopedIteratorTemplate ScopedIterator; +#endif + // The caller is supposed to lock the mutex via getLock before the start of any use of begin or end. + typename BaseType::iterator begin() { devassert(mMapLock.lockcnt()); return mList.begin(); } + typename BaseType::iterator end() { devassert(mMapLock.lockcnt()); return mList.end(); } + typedef typename BaseType::iterator iterator; + + int size() { + ScopedLock(this->mMapLock); + return mList.size(); + } + void push_back(ValueType val) { + ScopedLock(this->mMapLock); + mList.push_back(val); + } + void push_front(ValueType val) { + ScopedLock(this->mMapLock); + mList.push_front(val); + } + ValueType pop_frontr(ValueType val) { // Like pop_front, but return the value. + ScopedLock(this->mMapLock); + ValueType result = mList.front(); + mList.pop_front(val); + return result; + } + ValueType pop_backr(ValueType val) { // Like pop_back, but return the value. + ScopedLock(this->mMapLock); + ValueType result = mList.back(); + mList.pop_back(val); + return result; + } + typename BaseType::iterator erase(typename BaseType::iterator position) { + ScopedLock(this->mMapLock); + return this->mList.erase(position); + } +}; + namespace SIP { +using namespace std; -/**@name SIP-specific exceptions. */ -//@{ -class SIPException { - protected: - unsigned mTransactionID; +struct IPAddressSpec { + string mipName; // Original address, which may be an IP address in dotted notation or possibly a domain name, and optional port. + string mipIP; // The IP address of mipName as a string. If mipName is already an IP address, it is the same. + unsigned mipPort; + Bool_z mipValid; // Is the address valid, ie, was resolution successful? + struct ::sockaddr_in mipSockAddr; ///< the ready-to-use UDP address + bool ipSet(string addrSpec, const char *provenance); + string ipToText() const; + string ipTransportName() const { return "UDP"; } + bool ipIsReliableTransport() const { return false; } // Right now we only support UDP + IPAddressSpec() : mipPort(0) {} +}; + +// Ticket #1158 +class ResponseTable { + + typedef std::map ResponseMap; + ResponseMap mMap; + void addResponse(unsigned,const char *); public: - SIPException(unsigned wTransactionID=0) - :mTransactionID(wTransactionID) - { } - unsigned transactionID() const { return mTransactionID; } + //void operator[](unsigned code, const char* name) { mMap[code]=name; } + string operator[](unsigned code) const; + + /** Get the string. Log ERR and return "undefined" if the code is not in the table. */ + //string response(unsigned code) const; + static string get(unsigned code); + ResponseTable(); +}; +extern ResponseTable gResponseTable; + +// Update: 3GPP 24.229 table 7.8 provides new values for SIP Timers recommended for UE transactions, +// but I think they only apply to SIP transactions bound for a UE using IMS. +struct SipTimers +{ + static const int T1 = 500; // 500 ms. 17.1.2.2 + static const int T2 = 4*1000; // 4 seconds 17.1.2.2 + static const int T4 = 5*1000; // 5 seconds 17.1.2.2 }; -class SIPError : public SIPException {}; -class SIPTimeout : public SIPException {}; -//@} +class SipTimer +{ + bool mActive; ///< true if timer is active + Timeval mEndTime; ///< the time at which this timer will expire + unsigned long mLimitTime; ///< timeout in milliseconds + //int mNextState; // Payload. Used as Procedure state to be invoked on timeout. -1 means abort the procedure. + + public: + SipTimer() : mActive(false), mLimitTime(0) {} + bool isActive() { return mActive; } + + bool expired() const { + if (!mActive) { return false; } // A non-active timer does not expire. + return mEndTime.passed(); + } + + void setTime(long wTime) { mLimitTime = wTime; } + + void set() { + assert(mLimitTime!=0); + mEndTime = Timeval(mLimitTime); + mActive=true; + } + void set(long wLimitTime) { + mLimitTime = wLimitTime; + set(); + } + // Set the timer but only if it is not already running. + void setOnce(long wLimitTime) { + if (!mActive) set(wLimitTime); + } + // Double the timer value but no more than maxTime. Lots of UDP connection timers in SIP use this. + void setDouble(unsigned maxTime=10000) { + assert(mLimitTime!=0); + unsigned long newTime = mLimitTime * 2; + if (newTime > maxTime) { newTime = maxTime; } + set(mLimitTime * 2); + } + void stop() { mActive = false; } -/** Codec codes, from RFC-3551, Table 4. */ -enum RTPCodec { - RTPuLaw=0, - RTPGSM610=3 + long remaining() const { + if (!mActive) return 0; + long rem = mEndTime.remaining(); + if (rem<0) rem=0; + return rem; + } + + string text() const { + return mActive ? + format("(active=%d remaining=%ld limit=%ld",mActive,mEndTime.remaining(),mLimitTime) : + string("(active=0)"); + } + }; +std::ostream& operator<<(std::ostream& os, const SipTimer&); -/** Get owner IP address; return NULL if none found. */ -bool get_owner_ip( osip_message_t * msg, char * o_addr ); - -/** Get RTP parameters; return NULL if none found. */ -bool get_rtp_params(const osip_message_t * msg, char * port, char * ip_addr ); - -void make_tag( char * tag ); - -void make_branch(char * branch); - -string get_return_address(osip_message_t * msg); - - +extern string make_tag(); +extern string make_branch(const char *name=NULL); +extern string globallyUniqueId(const char *start); +extern string dequote(const string); }; #endif // vim: ts=4 sw=4 diff --git a/SMS/SMSMessages.cpp b/SMS/SMSMessages.cpp index b3198cd..87b165a 100644 --- a/SMS/SMSMessages.cpp +++ b/SMS/SMSMessages.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2010, 2014 Free Software Foundation, Inc. * This program is distributed in the hope that it will be useful, @@ -54,6 +54,7 @@ CPMessage * SMS::CPFactory(CPMessage::MessageType val) +// (pat) This parses an incoming SMS message from the MS, called from CPMessage * SMS::parseSMS( const GSM::L3Frame& frame ) { CPMessage::MessageType MTI = (CPMessage::MessageType)(frame.MTI()); @@ -62,7 +63,13 @@ CPMessage * SMS::parseSMS( const GSM::L3Frame& frame ) CPMessage * retVal = CPFactory(MTI); if( retVal==NULL ) return NULL; retVal->TI(frame.TI()); - retVal->parse(frame); + // Documentation courtesy pat: + // The L3Message::CPMessage is a base class for CPData, CPAck, CPError, one of which is created by the CPFactory above. + // The below calls L3Message::parse which calls the parseBody from the derived class. + // For CPAck and CPError, parseBody is null (or worse, assert out - a former bug.) + // For CPData messages: calls CPData::parseBody which then calls L3ProtocolElement::parseLV which calls: + // CPUserData::parseV, which just copies the data into CPUserData::mRPDU; which is an L3Frame::RLFrame + retVal->parse(frame); LOG(DEBUG) << *retVal; return retVal; } @@ -72,7 +79,7 @@ RPData *SMS::hex2rpdata(const char *hexstring) { RPData *rp_data = NULL; - BitVector RPDUbits(strlen(hexstring)*4); + BitVector2 RPDUbits(strlen(hexstring)*4); if (!RPDUbits.unhex(hexstring)) { return false; } @@ -102,11 +109,12 @@ RPData *SMS::hex2rpdata(const char *hexstring) return rp_data; } -TLMessage *SMS::parseTPDU(const TLFrame& TPDU) +TLMessage *SMS::parseTPDU(const TLFrame& TPDU, bool directionUplink) { LOG(DEBUG) << "SMS: parseTPDU MTI=" << TPDU.MTI(); - // Handle just the uplink cases. - switch ((TLMessage::MessageType)TPDU.MTI()) { + if (directionUplink) { + // Handle just the uplink cases. + switch ((TLMessage::MessageType)TPDU.MTI()) { case TLMessage::DELIVER_REPORT: case TLMessage::STATUS_REPORT: // FIXME -- Not implemented yet. @@ -120,13 +128,25 @@ TLMessage *SMS::parseTPDU(const TLFrame& TPDU) } default: return NULL; + } + } else { + switch ((TLMessage::MessageType)TPDU.MTI()) { + // 10-2013: Pat added the DELIVER which is the downlink message so we can parse it for reporting purposes. + case TLMessage::DELIVER: { + TLDeliver *deliver = new TLDeliver(TPDU); + return deliver; + } + default: + LOG(WARNING) << "parsing unsupported TPDU type: " << (TLMessage::MessageType)TPDU.MTI(); + return NULL; + } } } void CPMessage::text(ostream& os) const { os << (CPMessage::MessageType)MTI(); - os <<" TI=" << mTI; + os <text(os); + os << ")"; + delete tlmsg; + } +#if 0 + int RPOriginatorAddressLength = 8*readField(rp,8); + if (rp + RPOriginatorAddressLength > size()) { + os << " out-of-bounds"<MS direction this is zero. + if (rp + RPDestinationAddressLength > size()) { + os << " out-of-bounds"< size()) { + os << " out-of-bounds"<= 32 ? peekField(25,7) : 0; } // Only valid for RP-Error message. - RLFrame(SMSPrimitive wPrimitive=UNDEFINED_PRIMITIVE, size_t len=0) +#if ORIGINAL + RLFrame(SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE, size_t len=0) :L3Frame(GSM::DATA,len), mPrimitive(wPrimitive) { } - RLFrame(const BitVector& source, SMSPrimitive wPrimitive=UNDEFINED_PRIMITIVE) + RLFrame(const BitVector2& source, SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE) :L3Frame(source), mPrimitive(wPrimitive) { } +#endif + RLFrame(size_t bitsNeeded=0) :L3Frame(GSM::DATA,bitsNeeded) { /*RLFrameInit();*/ } + RLFrame(const BitVector2& source) :L3Frame(GSM::SAPIUndefined,source) { /*RLFrameInit();*/ } + void text(std::ostream& os) const; +#if UNUSED_PRIMITIVE SMSPrimitive primitive() const { return mPrimitive; } +#endif }; std::ostream& operator<<(std::ostream& os, const RLFrame& ); @@ -79,22 +94,29 @@ std::ostream& operator<<(std::ostream& os, const RLFrame& ); class TLFrame : public GSM::L3Frame { - +#if UNUSED_PRIMITIVE SMSPrimitive mPrimitive; + void TLFrameInit() { mPrimitive = SMS_UNDEFINED_PRIMITIVE; } +#endif public: - unsigned MTI() const { return peekField(6,2); } - TLFrame(SMSPrimitive wPrimitive=UNDEFINED_PRIMITIVE, size_t len=0) +#if ORIGINAL + TLFrame(SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE, size_t len=0) :L3Frame(GSM::DATA,len), mPrimitive(wPrimitive) { } - TLFrame(const BitVector& source, SMSPrimitive wPrimitive=UNDEFINED_PRIMITIVE) + TLFrame(const BitVector2& source, SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE) :L3Frame(source), mPrimitive(wPrimitive) { } +#endif + TLFrame(size_t bitsNeeded=0) :L3Frame(GSM::DATA,bitsNeeded) { /*TLFrameInit();*/ } + TLFrame(const BitVector2& source) :L3Frame(GSM::SAPIUndefined,source) { /*TLFrameInit();*/ } +#if UNUSED_PRIMITIVE SMSPrimitive primitive() const { return mPrimitive; } +#endif }; diff --git a/SR b/SR deleted file mode 160000 index 226fc1c..0000000 --- a/SR +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 226fc1c28a82307f7fc0e3f99f47a025be7cae4c diff --git a/Scanning/Makefile.am b/Scanning/Makefile.am new file mode 100644 index 0000000..70f7318 --- /dev/null +++ b/Scanning/Makefile.am @@ -0,0 +1,32 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This software is distributed under the terms of the GNU Public License. +# See the COPYING file in the main directory for details. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) +AM_CXXFLAGS = -Wall + +noinst_LTLIBRARIES = libscanning.la + +libscanning_la_SOURCES = Scanning.cpp $(SQLITE_LA) + +noinst_PROGRAMS = + +noinst_HEADERS = Scanning.h diff --git a/Scanning/Scanning.cpp b/Scanning/Scanning.cpp new file mode 100644 index 0000000..5c1ba00 --- /dev/null +++ b/Scanning/Scanning.cpp @@ -0,0 +1,203 @@ +/* +* Copyright 2011 Range Networks, Inc. +* +*/ + + +#include "Scanning.h" +#include +#include +#include + +#include + +#include + +#include + + +using namespace std; + + + + +static const char* createEnvironmentMap = { + "CREATE TABLE IF NOT EXISTS ENVIRONMENT_MAP (" + "KEYSTRING TEXT UNIQUE NOT NULL, " // key string of MCC:MNC:LAC:CI + "TIMESTAMP INTEGER NOT NULL, " // Unix timestamp of last update + "BAND INTEGER NOT NULL, " // operating band (850, 900, 1800, 1900) + "C0 INTEGER NOT NULL, " // C0 ARFCN + "MCC INTEGER NOT NULL, " // GSM MCC + "MNC INTEGER NOT NULL, " // GSM MNC + "LAC INTEGER NOT NULL, " // GSM LAC + "CI INTEGER NOT NULL, " // GSM CI + "FREQ_OFFSET FLOAT NOT NULL, " // C0 carrier offset in Hz + "NEIGHBORS TEXT NOT NULL, " // GSM neighbor list + "CA TEXT NOT NULL " // GSM cell allocation + ")" +}; + +static const char* createSpectrumMap = { + "CREATE TABLE IF NOT EXISTS SPECTRUM_MAP (" + "BAND INTEGER NOT NULL, " // operating band (850, 900, 1800, 1900) + "TIMESTAMP INTEGER NOT NULL, " // Unix time of the measurement + "ARFCN INTEGER NOT NULL, " // ARFCN in question + "RSSI FLOAT NOT NULL, " // RSSI in dBm + "LINK TEXT CHECK(LINK IN ('up', 'down')) NOT NULL, " // Link direction + "FREQ FLOAT NOT NULL" // Frequency + ")" +}; + + +const int SpectrumMap::LinkDirection::Up = 1; +const int SpectrumMap::LinkDirection::Down = 2; + +SpectrumMap::SpectrumMap(const char* path) +{ + int rc = sqlite3_open(path,&mDB); + if (rc) { + LOG(ALERT) << "Cannot open environment map database: " << sqlite3_errmsg(mDB); + sqlite3_close(mDB); + mDB = NULL; + return; + } + if (!sqlite3_command(mDB,createSpectrumMap)) { + LOG(ALERT) << "Cannot create spectrum map"; + } +} + +SpectrumMap::~SpectrumMap() +{ + if (mDB) sqlite3_close(mDB); +} + + +void SpectrumMap::clear() +{ + sqlite3_command(mDB,"DELETE FROM SPECTRUM_MAP WHERE 1"); +} + +void SpectrumMap::power(GSM::GSMBand band, unsigned ARFCN, float freq, LinkDirection& linkDir, float dBm) +{ + char q1[200]; + sprintf(q1,"INSERT OR REPLACE INTO SPECTRUM_MAP (BAND,TIMESTAMP,ARFCN,RSSI,FREQ,LINK) " + "VALUES (%u,%u,%u,%f,%f,'%s')", + (int)band,(unsigned)time(NULL),ARFCN,dBm,freq,linkDir.string()); + bool s = sqlite3_command(mDB,q1); + if (!s) LOG(ALERT) << "write to spectrum map failed"; +} + +void SpectrumMap::power(GSM::GSMBand band, unsigned ARFCN, LinkDirection& linkDir, float dBm) +{ + float frequency = 1000.0F; + + if (linkDir == SpectrumMap::LinkDirection::Up) { + frequency *= GSM::uplinkFreqKHz(band, ARFCN); + } else if (linkDir == SpectrumMap::LinkDirection::Down) { + frequency *= GSM::downlinkFreqKHz(band, ARFCN); + } else { + // Invalid direction + return; + } + + power(band, ARFCN, frequency, linkDir, dBm); +} + +/*void SpectrumMap::power(GSM::GSMBand band, unsigned ARFCN, float freq, float dBm) +{ + +}*/ + +ARFCNList SpectrumMap::topPower(GSM::GSMBand band, unsigned count) const +{ + char q[200]; + sprintf(q,"SELECT ARFCN FROM SPECTRUM_MAP WHERE BAND=%u ORDER BY RSSI DESC", band); + + ARFCNList retVal; + sqlite3_stmt *stmt; + sqlite3_prepare_statement(mDB,&stmt,q); + int src = sqlite3_run_query(mDB,stmt); + while ((retVal.size()& ARFCNs) +{ + for (unsigned i=0; i +#include + +struct sqlite3; + +namespace GSM { + class L3SystemInformationType1; + class L3SystemInformationType2; + class L3SystemInformationType3; +} + + +typedef std::list ARFCNList; + + +/** A C++ API to the SPECTRUM_MAP database table. */ +class SpectrumMap { + + private: + + sqlite3 *mDB; ///< database connection + + + public: + + class LinkDirection { + public: + static const int Up; + static const int Down; + + LinkDirection() { + mDir = Up; + } + LinkDirection(const LinkDirection &that) { + mDir = that.mDir; + } + LinkDirection(const int that) { + mDir = that; + } + + bool operator==(LinkDirection& that) const { + return mDir == that.mDir; + } + bool operator==(int that) const { + return mDir == that; + } + void operator=(int dir) { + if (dir == Up || dir == Down) { + mDir = dir; + } + } + const char *string() const { + if (mDir == Up) { + return "up"; + } else if (mDir == Down) { + return "down"; + } + return ""; + } + + private: + int mDir; + }; + + /** The constructor connects to the database and inits the table. */ + SpectrumMap(const char* path); + + /** The destructor closes the database connection. */ + ~SpectrumMap(); + + /** Clear the map. */ + void clear(); + + /** Mark a given ARFCN as having a given power level. */ + void power(GSM::GSMBand band, unsigned ARFCN, float freq, LinkDirection& linkDir, float dBm); + + /** Mark a given ARFCN as having a given power level. */ + void power(GSM::GSMBand band, unsigned ARFCN, LinkDirection& linkDir, float dBm); + + /** Mark a given ARFCN as having a given power level. */ + //void power(GSM::GSMBand band, unsigned ARFCN, float freq, float dBm); + + /** Return a list of up to count of the most powerful ARFCNs in a given band. */ + ARFCNList topPower(GSM::GSMBand band, unsigned count) const; + + /** Return a list of up to count of the least powerful ARFCNs in a given band. */ + ARFCNList minimumPower(GSM::GSMBand band, unsigned count) const; + +}; + + +#if 0 +class BTSRecord { + + private: + + GSM::GSMBand mBand; + unsigned mC0; + unsigned mMCC; + unsigned mMNC; + unsigned mLAC; + unsigned mCI; + unsigned mBSIC; + float mFreqOffset; + ARFCNList mNeighbors; + ARFCNList mCA; + + public: + + BTSRecord( + GSM::GSMBand wBand, + unsigned wC0, + unsigned wMCC, unsigned wMNC, unsigned wLAC, + unsigned wCI, + unsigned wBSIC, + float wFreqOffset) + :mBand(wBand),mC0(wC0), + mMCC(wMCC),mMNC(wMNC),mLAC(wLAC), + mCI(wCI), + mBSIC(wBSIC), + mFreqOffset(wFreqOffset) + { } + + void neighbors(const ARFCNList& wNeighbors) + { mNeighbors = wNeighbors; } + + void CA(const ARFCNList& wCA) + { mCA = wCA; } + + /** Return a key string based on MCC:MNC:LAC:CI that is unique for this BTS. */ + std::string keyString() const; + +}; +#endif + + +/** A list of ARFCNs waiting to be scanned. */ +class ScanList { + + private: + + ARFCNList mNewARFCNs; ///< ARFCNs that still need to be scanned. + ARFCNList mOldARFCNs; ///< ARFCNs that have already been scanned. + + public: + + size_t size() const { return mNewARFCNs.size(); } + + /** Add ARFCNs to the mNewARFCNs if it is not already scanned or pending for scanning. */ + void add(const ARFCNList& moreARFCNs); + + void add(const std::vector& moreARFCNs); + + /** Add an ARFCN to the mNewARFCNs if it is not already scanned or pending for scanning. */ + void add(unsigned ARFCN); + + int front() const { return mNewARFCNs.front(); } + + /** Pop and return the next ARFCN or -1 if the list is empty. */ + int next(); + + /** Return true if the scan is complete. */ + bool allDone() const { return mNewARFCNs.size()==0; } + + /** Clear both lists; reset the object. */ + void clear(); + + private: + + /** Return true if a given ARFCN is already in mNewARFCNs or mOldARFCNs. */ + bool alreadyListed(unsigned ARFCN) const; +}; + + + + + +#endif + +// vim: ts=4 sw=4 diff --git a/TRXManager/TRXManager.cpp b/TRXManager/TRXManager.cpp index 3a722bf..b6b1f3e 100644 --- a/TRXManager/TRXManager.cpp +++ b/TRXManager/TRXManager.cpp @@ -1,6 +1,6 @@ /* * Copyright 2008, 2010 Free Software Foundation, Inc. -* Copyright 2012 Range Networks, Inc. +* Copyright 2012, 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 distribuion. * @@ -77,7 +77,6 @@ void* ClockLoopAdapter(TransceiverManager *transceiver) // This loop has a period of about 3 seconds. gResetWatchdog(); - Timeval nextContact; while (1) { transceiver->clockHandler(); LOG(DEBUG) << "watchdog timer expires in " << gWatchdogRemaining() << " seconds"; @@ -101,7 +100,7 @@ void TransceiverManager::clockHandler() if (msgLen<0) { LOG(EMERG) << "TRX clock interface timed out, assuming TRX is dead."; gReports.incr("OpenBTS.Exit.Error.TransceiverHeartbeat"); -#ifdef RN_DEVELOPER_MODE +#if RN_DEVELOPER_MODE // (pat) Added so you can keep debugging without the radio. static int foo = 0; pthread_exit(&foo); @@ -285,6 +284,11 @@ int ::ARFCNManager::sendCommandPacket(const char* command, char* response) break; } LOG(WARNING) << "TRX link timeout on attempt " << retry+1; + // Dont wait so long. + if (gConfig.defines("DeveloperMode") && 0 == strcmp(command,"CMD POWERON")) { + printf("Developer mode, curtailing transceiver poweron test\n"); fflush(stdout); + return 0; + } } mControlLock.unlock(); @@ -431,7 +435,7 @@ bool ::ARFCNManager::powerOff() { int status = sendCommand("POWEROFF"); if (status!=0) { - LOG(ALERT) << "POWEROFF failed with status " << status; + LOG(INFO) << "POWEROFF failed with status " << status; return false; } return true; @@ -453,6 +457,12 @@ bool ::ARFCNManager::powerOn(bool warn) return true; } +bool ::ARFCNManager::trxRunning() +{ + int status = sendCommand("POWEROFF"); + return status == 0; +} + @@ -546,7 +556,7 @@ bool ::ARFCNManager::clearHandover(unsigned TN) assert(TN<8); int status = sendCommand("NOHANDOVER",TN); if (status!=0) { - LOG(WARNING) << "NOHANDOVER failed with status " << status; + LOG(ALERT) << "NOHANDOVER failed with status " << status; return false; } return true; diff --git a/TRXManager/TRXManager.h b/TRXManager/TRXManager.h index 505eddd..c35aeb3 100644 --- a/TRXManager/TRXManager.h +++ b/TRXManager/TRXManager.h @@ -174,6 +174,9 @@ class ARFCNManager { */ bool powerOn(bool warn); + /** Just test if the transceiver is running without printing alarming messages. */ + bool trxRunning(); + /** Set maximum expected delay spread. @param km Max network range in kilometers. diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp index 2c7d9c7..d47e800 100644 --- a/Transceiver52M/UHDDevice.cpp +++ b/Transceiver52M/UHDDevice.cpp @@ -32,7 +32,7 @@ #include "config.h" #endif -#define B2XX_CLK_RT 26e6 +#define B2XX_CLK_RT 52e6 #define B2XX_BASE_RT GSMRATE #define B100_BASE_RT 400000 #define USRP2_BASE_RT 390625 diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp index 9324cc2..0e081f0 100644 --- a/Transceiver52M/sigProcLib.cpp +++ b/Transceiver52M/sigProcLib.cpp @@ -1051,12 +1051,12 @@ bool generateMidamble(int sps, int tsc) delete gMidambles[tsc]; /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */ - midMidamble = modulateBurst(gTrainingSequence[tsc].segment(5,16), 0, sps, true); + midMidamble = modulateBurst(gTrainingSequence[tsc].alias().segment(5,16), 0, sps, true); if (!midMidamble) return false; /* Simulated receive sequence is pulse shaped */ - midamble = modulateBurst(gTrainingSequence[tsc], 0, sps, false); + midamble = modulateBurst(gTrainingSequence[tsc].alias(), 0, sps, false); if (!midamble) { status = false; goto release; @@ -1127,7 +1127,7 @@ bool generateRACHSequence(int sps) if (!seq0) return false; - seq1 = modulateBurst(gRACHSynchSequence.segment(0, 40), 0, sps, true); + seq1 = modulateBurst(gRACHSynchSequence.alias().segment(0, 40), 0, sps, true); if (!seq1) { status = false; goto release; diff --git a/TransceiverRAD1/Makefile.am b/TransceiverRAD1/Makefile.am index eadfabf..046e835 100644 --- a/TransceiverRAD1/Makefile.am +++ b/TransceiverRAD1/Makefile.am @@ -51,7 +51,8 @@ noinst_PROGRAMS = \ RAD1Cmd \ RAD1SN \ RAD1RxRawPowerSweep \ - RAD1RxRawPower + RAD1RxRawPower \ + PowerScanner noinst_HEADERS = \ ad9862.h \ @@ -112,6 +113,12 @@ sigProcLibTest_LDADD = \ $(GSM_LA) \ $(COMMON_LA) $(SQLITE_LA) +PowerScanner_SOURCES = PowerScanner.cpp ../apps/GetConfigurationKeys.cpp +PowerScanner_LDADD = \ + libtransceiver.la \ + $(SCANNING_LA) \ + $(GSM_LA) \ + $(COMMON_LA) $(SQLITE_LA) MOSTLYCLEANFILES += @@ -122,9 +129,10 @@ MOSTLYCLEANFILES += #sweepGenerator.cpp #testRadio.cpp -install: transceiver +install: transceiver PowerScanner @mkdir -p "$(DESTDIR)/OpenBTS/" install transceiver "$(DESTDIR)/OpenBTS/" install ezusb.ihx "$(DESTDIR)/OpenBTS/" install fpga.rbf "$(DESTDIR)/OpenBTS/" + install PowerScanner "$(DESTDIR)/OpenBTS/" diff --git a/TransceiverRAD1/PowerScanner.cpp b/TransceiverRAD1/PowerScanner.cpp new file mode 100644 index 0000000..c21765f --- /dev/null +++ b/TransceiverRAD1/PowerScanner.cpp @@ -0,0 +1,213 @@ +/* +* Copyright 2010 Kestrel Signal Processing, Inc. +* Copyright 2012, 2013 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* +* 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. + +*/ + +#include +#include +#include +#include +#include "RAD1Device.h" +#include +#include +#include + +#include + +ConfigurationKeyMap getAllConfigurationKeys(); +ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS.db", "PowerScanner", getAllConfigurationKeys()); + + +using namespace std; + +typedef unsigned (*FrequencyConverter)(GSM::GSMBand, unsigned); +int scanDirection(RAD1Device *rad, TIMESTAMP ×tamp, // Physical RAD values + GSM::GSMBand band, int startARFCN, int stopARFCN, // GSM band values + FrequencyConverter arfcnToFreqKHz, // Frequency determiner + SpectrumMap::LinkDirection linkDir, + SpectrumMap &spectrumMap); + + +class ARFCNVector { + + private: + + double mPower; + unsigned mARFCN; + float mFreq; + SpectrumMap::LinkDirection mLinkDir; + + public: + + ARFCNVector(double &wPower, unsigned &wARFCN, float &freq, SpectrumMap::LinkDirection &linkDir) { + mPower = wPower; + mARFCN = wARFCN; + mFreq = freq; + mLinkDir = linkDir; + } + + unsigned ARFCN() const { return mARFCN;} + + double power() const {return mPower;} + + SpectrumMap::LinkDirection linkDirection() const { return mLinkDir; } + + float frequency() const { return mFreq; } + + bool operator>(const ARFCNVector &other) const {return mPower < other.mPower;} +}; + + +int main(int argc, char *argv[]) +{ + try { + GSM::GSMBand band = (GSM::GSMBand)gConfig.getNum("GSM.Radio.Band"); + SpectrumMap spectrumMap(gConfig.getStr("PowerScanner.DBPath").c_str()); + int startARFCN, stopARFCN; + switch (band) { + case GSM::GSM850: startARFCN=130; stopARFCN=251; break; + case GSM::EGSM900: startARFCN=0; stopARFCN=124; break; + case GSM::DCS1800: startARFCN=512; stopARFCN=885; break; + case GSM::PCS1900: startARFCN=512; stopARFCN=810; break; + default: + LOG(ALERT) << "Unsupported GSM Band specified (config key GSM.Radio.Band). Exiting..."; + exit(-1); + } + + RAD1Device *rad = new RAD1Device(1625.0e3/6.0); + rad->make(false, 0); + rad->start(); + TIMESTAMP timestamp = 19000; + + cout << endl << "Scanning Uplink" << endl; + scanDirection(rad, timestamp, band, startARFCN, stopARFCN, &GSM::uplinkFreqKHz, SpectrumMap::LinkDirection::Up, spectrumMap); + cout << endl << "Scanning Downlink" << endl; + scanDirection(rad, timestamp, band, startARFCN, stopARFCN, &GSM::downlinkFreqKHz, SpectrumMap::LinkDirection::Down, spectrumMap); + + } catch (ConfigurationTableKeyNotFound e) { + cout << "Required configuration parameter " << e.key() << " not defined, exiting."; + return -1; + } + + return 0; +} + +int scanDirection(RAD1Device *rad, TIMESTAMP ×tamp, GSM::GSMBand band, int startARFCN, int stopARFCN, + FrequencyConverter arfcnToFreqKHz, SpectrumMap::LinkDirection linkDir, SpectrumMap &spectrumMap) +{ + list results; + float integrationTime = ((float)gConfig.getNum("PowerScanner.IntegrationTime"))/1000.0F; + + float startFreq = arfcnToFreqKHz(band, startARFCN) * 1000.0F; + rad->setRxFreq(startFreq); + + rad->updateAlignment(40000); + rad->updateAlignment(41000); + + rad->setRxGain(gConfig.getNum("PowerScanner.RxGain")); + + for (unsigned ARFCN=startARFCN; ARFCN <= stopARFCN; ARFCN++) { + float currFreq = arfcnToFreqKHz(band, ARFCN) * 1000.0F; + double sum = 0.0; + double num = 0.0; + rad->setRxFreq(currFreq); + + while (num < integrationTime*270833.33) { + short readBuf[512*2]; + bool underrun; + int rd = rad->readSamples(readBuf,512,&underrun,timestamp); + if (rd) { + for (int i = 0; i < rd; i++) { + uint32_t *wordPtr = (uint32_t *) &readBuf[2*i]; + *wordPtr = usrp_to_host_u32(*wordPtr); + sum += (readBuf[2*i+1]*readBuf[2*i+1] + readBuf[2*i]*readBuf[2*i]); + num += 1.0; + } + } + timestamp += rd; + } + + double currPower = sum/num; + // HACK + printf("At freq %f (ARFCN %5d), the average power is %10.2f\n",currFreq,ARFCN,currPower); + ARFCNVector res(currPower,ARFCN, currFreq, linkDir); + results.push_back(res); + } + + for (list::iterator rp=results.begin(); rp!=results.end(); ++rp) { + double currPower = rp->power(); + unsigned ARFCN = rp->ARFCN(); + float freq = rp->frequency(); + SpectrumMap::LinkDirection linkDir = rp->linkDirection(); + + if (currPower==0.0) continue; + + double dBm = 10.0F*log10(currPower) + gConfig.getNum("PowerScanner.dBmOffset"); + spectrumMap.power(band,ARFCN,freq,linkDir,dBm); + } + + return 0; +} + +ConfigurationKeyMap getAllConfigurationKeys() +{ + extern ConfigurationKeyMap getConfigurationKeys(); + ConfigurationKeyMap map = getConfigurationKeys(); + ConfigurationKey *tmp; + + tmp = new ConfigurationKey("PowerScanner.dBmOffset","0", + "dBm", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "0:100", + false, + "Calibrated dBm level corresponding to full scale on the receiver." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("PowerScanner.DBPath","/var/run/OpenBTS/PowerScannerResults.db", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::FILEPATH, + "", + true, + "Path to the scanning results database." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("PowerScanner.IntegrationTime","250", + "milliseconds", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "50:5000", + false, + "Power detection integration time in milliseconds." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("PowerScanner.RxGain","97", + "dB", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "0:200", + false, + "Receiver gain for the power scanner program, raw value to setRxGain." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + return map; +} diff --git a/TransceiverRAD1/RAD1Device.cpp b/TransceiverRAD1/RAD1Device.cpp index 69a85c4..9733a21 100644 --- a/TransceiverRAD1/RAD1Device.cpp +++ b/TransceiverRAD1/RAD1Device.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. @@ -189,11 +189,10 @@ bool RAD1Device::make(bool wSkipRx, int devID) LOG(INFO) << "making RAD1 device.."; #ifndef SWLOOPBACK string rbf = "fpga.rbf"; - //string rbf = "inband_1rxhb_1tx.rbf"; + if (!skipRx) { try { - m_uRx = rnrad1Rx::make(devID,decimRate, - rbf,"ezusb.ihx"); + m_uRx = rnrad1Rx::make(devID,decimRate, rbf,"ezusb.ihx"); m_uRx->setFpgaMasterClockFreq(masterClockRate); } @@ -220,8 +219,7 @@ bool RAD1Device::make(bool wSkipRx, int devID) } try { - m_uTx = rnrad1Tx::make(devID,decimRate*2, - rbf,"ezusb.ihx"); + m_uTx = rnrad1Tx::make(devID,decimRate*2, rbf,"ezusb.ihx"); m_uTx->setFpgaMasterClockFreq(masterClockRate); } diff --git a/TransceiverRAD1/RAD1Device.h b/TransceiverRAD1/RAD1Device.h index bfa6f37..52fe15c 100644 --- a/TransceiverRAD1/RAD1Device.h +++ b/TransceiverRAD1/RAD1Device.h @@ -1,5 +1,5 @@ /* -* Copyright 2008 Free Software Foundation, Inc. +* Copyright 2008, 2014 Free Software Foundation, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. diff --git a/TransceiverRAD1/RAD1RxRawPowerSweep.cpp b/TransceiverRAD1/RAD1RxRawPowerSweep.cpp index c997fd2..e7ad89c 100644 --- a/TransceiverRAD1/RAD1RxRawPowerSweep.cpp +++ b/TransceiverRAD1/RAD1RxRawPowerSweep.cpp @@ -37,7 +37,7 @@ using namespace std; int main(int argc, char *argv[]) { - gLogInit("openbts","INFO",LOG_LOCAL7); + gLogInit("RAD1RxRawPowerSweep","INFO",LOG_LOCAL7); int whichBoard = 0; if (argc > 1) whichBoard = atoi(argv[1]); diff --git a/TransceiverRAD1/Transceiver.cpp b/TransceiverRAD1/Transceiver.cpp index 59a319f..5da2e74 100644 --- a/TransceiverRAD1/Transceiver.cpp +++ b/TransceiverRAD1/Transceiver.cpp @@ -700,7 +700,7 @@ void Transceiver::driveControl(unsigned ARFCN) } else if (strcmp(command,"READFACTORY")==0) { char param[16]; - sscanf(buffer,"%3s %s %s",cmdcheck,command,¶m); + sscanf(buffer,"%3s %s %s",cmdcheck,command,param); // (pat) this was formerly ¶m which was incorrect. int ret = gFactoryCalibration.getValue(std::string(param)); // TODO : this should actually return the param name requested sprintf(response,"RSP READFACTORY 0 %d", ret); diff --git a/TransceiverRAD1/radioInterface.cpp b/TransceiverRAD1/radioInterface.cpp index bfc8caf..79d5353 100644 --- a/TransceiverRAD1/radioInterface.cpp +++ b/TransceiverRAD1/radioInterface.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2008, 2009, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * @@ -170,7 +170,7 @@ void RadioInterface::pushBuffer(void) { writeTimestamp += (TIMESTAMP) samplesWritten; if (sendCursor > 2*samplesWritten) - memcpy(sendBuffer,sendBuffer+samplesWritten*2,sizeof(short)*(sendCursor-2*samplesWritten)); + memcpy(sendBuffer,sendBuffer+samplesWritten*2,sizeof(short)*2*(sendCursor/2-samplesWritten)); sendCursor = sendCursor - 2*samplesWritten; } @@ -338,7 +338,7 @@ void RadioInterface::driveReceiveRadio() { } if (readSz > 0) { - memcpy(rcvBuffer,rcvBuffer+2*readSz,sizeof(short)*(rcvCursor-2*readSz)); + memcpy(rcvBuffer,rcvBuffer+2*readSz,sizeof(short)*2*(rcvCursor/2-readSz)); rcvCursor = rcvCursor-2*readSz; } } diff --git a/TransceiverRAD1/radioInterface.h b/TransceiverRAD1/radioInterface.h index f632ce2..4e99046 100644 --- a/TransceiverRAD1/radioInterface.h +++ b/TransceiverRAD1/radioInterface.h @@ -1,5 +1,5 @@ /* -* Copyright 2008 Free Software Foundation, Inc. +* Copyright 2008, 2014 Free Software Foundation, Inc. * * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. * diff --git a/TransceiverRAD1/runTransceiver.cpp b/TransceiverRAD1/runTransceiver.cpp index c365eec..7d2781a 100644 --- a/TransceiverRAD1/runTransceiver.cpp +++ b/TransceiverRAD1/runTransceiver.cpp @@ -30,9 +30,8 @@ using namespace std; -std::vector configurationCrossCheck(const std::string& key); +ConfigurationKeyMap getConfigurationKeys(); static const char *cOpenBTSConfigEnv = "OpenBTSConfigFile"; -// Load configuration from a file. ConfigurationTable gConfig(getenv(cOpenBTSConfigEnv)?getenv(cOpenBTSConfigEnv):"/etc/OpenBTS/OpenBTS.db","transceiver", getConfigurationKeys()); FactoryCalibration gFactoryCalibration; @@ -151,6 +150,28 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKeyMap map; ConfigurationKey *tmp; + tmp = new ConfigurationKey("TRX.IP","127.0.0.1", + "", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::IPADDRESS, + "", + true, + "IP address of the transceiver application." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("TRX.Port","5700", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::PORT, + "", + true, + "IP port of the transceiver application." + ); + map[tmp->getName()] = *tmp; + delete tmp; + tmp = new ConfigurationKey("TRX.RadioFrequencyOffset","128", "~170Hz steps", ConfigurationKey::FACTORY, @@ -165,6 +186,17 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + tmp = new ConfigurationKey("TRX.RadioNumber","0", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::VALRANGE, + "0:9", // Not likely to have 10 radios on the same computer. Not likely to have >1 + true, + "If non-0, use multiple radios on the same cpu, numbered 1-9. Must change TRX.Port also. Provide a separate config file for each OpenBTS+Radio combination using the environment variable or --config command line option." + ); + map[tmp->getName()] = *tmp; + delete(tmp); + tmp = new ConfigurationKey("TRX.TxAttenOffset","0", "dB of attenuation", ConfigurationKey::FACTORY, diff --git a/TransceiverRAD1/sigProcLib.cpp b/TransceiverRAD1/sigProcLib.cpp index 4504d9e..bfd460e 100644 --- a/TransceiverRAD1/sigProcLib.cpp +++ b/TransceiverRAD1/sigProcLib.cpp @@ -888,11 +888,12 @@ bool generateMidamble(signalVector &gsmPulse, *(emptyPulse.begin()) = 1.0; // only use middle 16 bits of each TSC - signalVector *middleMidamble = modulateBurst(gTrainingSequence[TSC].segment(5,16), + // (pat) fwiw the uses of gTrainingSequence formerly did clones here. I added the call to alias() to prevent that. + signalVector *middleMidamble = modulateBurst(gTrainingSequence[TSC].alias().segment(5,16), emptyPulse, 0, samplesPerSymbol); - signalVector *midamble = modulateBurst(gTrainingSequence[TSC], + signalVector *midamble = modulateBurst(gTrainingSequence[TSC].alias(), gsmPulse, 0, samplesPerSymbol); diff --git a/apps/CLI b/apps/CLI new file mode 100755 index 0000000..ec8a814 --- /dev/null +++ b/apps/CLI @@ -0,0 +1,3 @@ +#!/bin/bash +sudo /OpenBTS/OpenBTSCLI + diff --git a/apps/GetConfigurationKeys.cpp b/apps/GetConfigurationKeys.cpp index 3e6cb03..347986a 100644 --- a/apps/GetConfigurationKeys.cpp +++ b/apps/GetConfigurationKeys.cpp @@ -1,7 +1,7 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2013, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -23,16 +23,88 @@ #include + +std::string getARFCNsString(unsigned band) { + std::stringstream ss; + int i; + float downlink; + float uplink; + + if (band == 850) { + // 128:251 GSM850 + downlink = 869.2; + uplink = 824.2; + for (i = 128; i <= 251; i++) { + ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; + downlink += 0.2; + uplink += 0.2; + } + + } else if (band == 900) { + // 0:124 PGSM900 + downlink = 935.2; + uplink = 890.2; + for (i = 0; i <= 124; i++) { + ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; + downlink += 0.2; + uplink += 0.2; + } + + // 975:1023 EGSM900 + downlink = 1130; + uplink = 1085; + for (i = 975; i <= 1023; i++) { + ss << i << "|EGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; + downlink += 0.2; + uplink += 0.2; + } + + } else if (band == 1800) { + // 512:885 DCS1800 + downlink = 1805.2; + uplink = 1710.2; + for (i = 512; i <= 885; i++) { + ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; + downlink += 0.2; + uplink += 0.2; + } + + } else if (band == 1900) { + // 512:810 PCS1900 + downlink = 1930.2; + uplink = 1850.2; + for (i = 512; i <= 810; i++) { + ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; + downlink += 0.2; + uplink += 0.2; + } + } + + std::string tmp = ss.str(); + return tmp.substr(0, tmp.size()-1); +} + ConfigurationKeyMap getConfigurationKeys() { //VALIDVALUES NOTATION: + // (pat) for ConfigurationKey::CHOICE and CHOICE_OPT: // * A:B : range from A to B in steps of 1 // * A:B(C) : range from A to B in steps of C // * A:B,D:E : range from A to B and from D to E // * A,B : multiple choices of value A and B // * X|A,Y|B : multiple choices of string "A" with value X and string "B" with value Y + // For CHOICE_OPT, the input value can also be empty. + // + // (pat) I believe the following is valid for ConfigurationKey::REGEX and REGEX_OPT // * ^REGEX$ : string must match regular expression + // For REGEX_OPT, the input value can also be empty. + // + // (pat) for ConfigurationKey::VALRANGE looks like the syntax is this subset: + // A:B : range from A to B in steps of 1 + // A:B(C) : range from A to B in steps of C, but C is ignored. + // and a comma is silently ignored? + // The input value is constrained to be a number. /* TODO : double check: sometimes symbol periods == 0.55km and other times 1.1km? @@ -57,9 +129,9 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKeyMap map; ConfigurationKey *tmp; - tmp = new ConfigurationKey("CLI.SocketPath","/var/run/command", + tmp = new ConfigurationKey("CLI.SocketPath","/var/run/OpenBTS/command", "", - ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::FACTORY, ConfigurationKey::FILEPATH, "", false, @@ -70,7 +142,7 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("Control.Call.QueryRRLP.Early","0", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, "", false, @@ -81,7 +153,7 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("Control.Call.QueryRRLP.Late","0", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, "", false, @@ -90,6 +162,7 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + tmp = new ConfigurationKey("Control.GSMTAP.GPRS","0", "", ConfigurationKey::CUSTOMERWARN, @@ -125,13 +198,30 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("Control.LUR.AttachDetach","1", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::CUSTOMERWARN, // (pat) We have never tested with AttachDetach == 0; so customers should not use it! ConfigurationKey::BOOLEAN, "", false, "Use attach/detach procedure. " "This will make initial LUR more prompt. " - "It will also cause an un-regstration if the handset powers off and really heavy LUR loads in areas with spotty coverage." + "It will also cause an un-registration if the handset powers off and really heavy LUR loads in areas with spotty coverage. " + "Range Networks strongly recommends setting this to 1. " // (pat) added, until someone tests this! + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("Control.LUR.RegistrationMessageFrequency","FIRST", + "^PLMN|NORMAL|FIRST$", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::STRING, + "", + false, + "This option helps determine when a registration message is sent by the BTS to a handset. " + "If 'PLMN' the message is sent only when the handset first registers in the PLMN, as reported by the handset. " + "If 'NORMAL' the message is sent whenever the handset enters the cell, as reported by the handset. " + "If 'FIRST' the message is sent the first time this BTS sees this MS as determined by the WELCOME_SENT field of the TMSI_TABLE." + "This option is not completely reliable because the functioning of this option depends on information provided " + "by the handset during their initial attach procedure, and some handsets set this information improperly." ); map[tmp->getName()] = *tmp; delete tmp; @@ -140,7 +230,7 @@ ConfigurationKeyMap getConfigurationKeys() "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING_OPT,// audited - "^[[:print:]]+$", + "^[ -~]+$", false, "Send this text message, followed by the IMSI, to unprovisioned handsets that are denied registration." ); @@ -153,7 +243,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::STRING, "^[0-9]+$", false, - "The return address for the failed registration message." + "The return address for the failed registration message. " + "If unset, the message will not be sent. " ); map[tmp->getName()] = *tmp; delete tmp; @@ -162,12 +253,12 @@ ConfigurationKeyMap getConfigurationKeys() "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING_OPT,// audited - "^[[:print:]]+$", + "^[ -~]+$", false, - "The text message (followed by the IMSI) to be sent to provisioned handsets when they attach on Um. " + "The text message, followed by the IMSI, to be sent to provisioned handsets when they attach on Um. " "By default, no message is sent. " "To have a message sent, specify one. " - "To stop sending messages again, execute \"unconfig Control.LUR.NormalRegistration.Message\"." + "To stop sending messages again, execute \"unconfig Control.LUR.NormalRegistration.Message\". " ); map[tmp->getName()] = *tmp; delete tmp; @@ -178,7 +269,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::STRING, "^[0-9]+$", false, - "The return address for the normal registration message." + "The return address for the normal registration message. " + "If unset, the message will not be sent. " ); map[tmp->getName()] = *tmp; delete tmp; @@ -189,10 +281,10 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::REGEX_OPT,// audited "", false, - "This is value is a regular expression. " + "This value is a regular expression. " "Any handset with an IMSI matching the regular expression is allowed to register, even if it is not provisioned. " "By default, this feature is disabled. " - "To enable open registration, specify a regular expression e.g. ^460 (which matches any IMSI starting with 460, the MCC for China). " + "To enable open registration, specify a regular expression to match. E.g. ^001, which matches any IMSI starting with 001, the MCC for test networks. " "To disable open registration again, execute \"unconfig Control.LUR.OpenRegistration\"." ); map[tmp->getName()] = *tmp; @@ -202,7 +294,7 @@ ConfigurationKeyMap getConfigurationKeys() "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, - "^[[:print:]]+$", + "^[ -~]+$", false, "Send this text message, followed by the IMSI, to unprovisioned handsets when they attach on Um due to open registration." ); @@ -215,11 +307,11 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::REGEX_OPT,// audited "", false, - "This is value is a regular expression. " + "This value is a regular expression. " "Any unprovisioned handset with an IMSI matching the regular expression is rejected for registration, even if it matches Control.LUR.OpenRegistration. " - "By default, this feature is disabled. " - "To enable this filter, specify a regular expression e.g. ^460 (which matches any IMSI starting with 460, the MCC for China). " - "To disable this filter again, execute \"unconfig Control.LUR.OpenRegistration.Reject\". " + "By default, this filter is disabled. " + "To enable the filter, specify a regular expression. E.g. ^666 matches any IMSI starting with 666, which currently does not correspond to any known MCC. Stay on the light side of the Force!" + "To disable the filter again, execute \"unconfig Control.LUR.OpenRegistration.Reject\". " "If Control.LUR.OpenRegistration is disabled, this parameter has no effect." ); map[tmp->getName()] = *tmp; @@ -253,14 +345,14 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Query every MS for IMEI during LUR." + "Query every MS for IMEI during initial LUR." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("Control.LUR.QueryRRLP","0", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, "", false, @@ -269,6 +361,7 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + tmp = new ConfigurationKey("Control.LUR.SendTMSIs","0", "", ConfigurationKey::CUSTOMER, @@ -280,6 +373,24 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + tmp = new ConfigurationKey("Control.LUR.FailMode","ACCEPT", + "", // no units + ConfigurationKey::CUSTOMER, + ConfigurationKey::CHOICE, + "ACCEPT,FAIL,OPEN", + false, // not static + "Action to take after registration failure due to network failure, error in Registrar, or other unexpected error. " + "This does not apply to regular authorization failure handled by other config options. " + "If ACCEPT the handset is authorized for service. If FAIL the handset is denied service. If OPEN the open registration procedure is applied." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + + // (pat) 6-2013: If you send cause==4, the MS continues to retry. + // Cause==3 and 6 are commented out because: + // "David constantly stressed them being very disruptive to out-of-network phones. + // "You're not just saying "go away from me" you're saying "this phone has been stolen and should now cease to operate until you restart it." tmp = new ConfigurationKey("Control.LUR.UnprovisionedRejectCause","0x04", "", ConfigurationKey::CUSTOMERWARN, @@ -308,36 +419,15 @@ ConfigurationKeyMap getConfigurationKeys() "0x65|Message not compatible with the protocol state," "0x6F|Unspecified protocol error", false, - "Reject cause for location updating failures for unprovisioned phones. " + "Reject cause for location updating failures for unprovisioned phones, that is, the IMSI was not found in the Registrar database. " + "The SIP result code from the Registrar in this case is 401. " "Reject causes come from GSM 04.08 10.5.3.6. " - "Reject cause 0x04, IMSI not in VLR, is usually the right one." + "Reject cause 0x02 or 0x04 is usually the right one." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.LUR.WhiteList","0", - "", - ConfigurationKey::CUSTOMER, - ConfigurationKey::BOOLEAN, - "", - false, - "Whitelist checking is performed." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.LUR.WhiteListing.Message","Your handset needs to be added to the whitelist.", - "", - ConfigurationKey::CUSTOMER, - ConfigurationKey::STRING, - "^[[:print:]]+$", - false, - "The whitelisting notification message." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.LUR.WhiteListing.RejectCause","0x04", + tmp = new ConfigurationKey("Control.LUR.404RejectCause","0x04", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -365,25 +455,37 @@ ConfigurationKeyMap getConfigurationKeys() "0x65|Message not compatible with the protocol state," "0x6F|Unspecified protocol error", false, - "Reject cause for handset not in the whitelist, when whitelisting is enforced. " + "Reject cause for location updating failures for phones that fail authentication. " + "The SIP result code from the Registrar in this case is 404. " "Reject causes come from GSM 04.08 10.5.3.6. " - "Reject cause 0x04, IMSI not in VLR, is usually the right one." + "Reject cause 0x02 or 0x04 is usually the right one." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.LUR.WhiteListing.ShortCode","1000", + tmp = new ConfigurationKey("Control.LUR.TestMode","0", "", - ConfigurationKey::CUSTOMER, - ConfigurationKey::STRING, - "^[0-9]+$", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "0:1", false, - "The return address for the whitelisting notificiation message." + "Used for testing the LUR procedure." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.Reporting.PhysStatusTable","/var/run/ChannelTable.db", + tmp = new ConfigurationKey("Control.NumSQLTries","3", + "attempts", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:10",// educated guess + false, + "Number of times to retry SQL queries before declaring a database access failure." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("Control.Reporting.PhysStatusTable","/var/run/OpenBTS/ChannelTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -405,7 +507,7 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.Reporting.TMSITable","/var/run/TMSITable.db", + tmp = new ConfigurationKey("Control.Reporting.TMSITable","/var/run/OpenBTS/TMSITable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -416,7 +518,31 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.Reporting.TransactionTable","/var/run/TransactionTable.db", + tmp = new ConfigurationKey("Control.TMSITable.MaxAge","576", + "", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::VALRANGE, + "1:999999999", + false, // is static? Right now this is only done at startup so its kind of static. + "Maximum allowed age in hours for a TMSI entry in the TMSITable. " + "This is not the authorization/registration expiry period, this is how long the BTS remembers assigned TMSIs. " + "Currently old entries are only discarded at startup. " + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("Control.Reporting.TransactionMaxCompletedRecords","100", + "record", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:10000", + true, + "Maximum completed records to be stored for gathering by an external stats tool." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("Control.Reporting.TransactionTable","/var/run/OpenBTS/TransactionTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -433,14 +559,14 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "1:3",// educated guess false, - "Decrease the RSSI by this amount to induce more power in the MS each time we fail to receive a response from it." + "Decrease the RSSI by this amount to induce more power in the MS each time we fail to receive a response from it on SACCH." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("Control.SMS.QueryRRLP","0", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, "", false, @@ -457,66 +583,12 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for SMSCB scheduling database. " "By default, this feature is disabled. " - "To enable, specify a file path for the database e.g. /var/run/SMSCB.db. " + "To enable, specify a file path for the database e.g. /var/run/OpenBTS/SMSCB.db. " "To disable again, execute \"unconfig Control.SMSCB.Table\"." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.TestCall.AutomaticModeChange","0", - "", - ConfigurationKey::DEVELOPER, - ConfigurationKey::BOOLEAN, - "", - false, - "Automatically change the channel mode of a TCH/FACCH from signaling-only to speech-V1 before starting the fuzzing interface." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.TestCall.LocalPort","24020", - "", - ConfigurationKey::DEVELOPER, - ConfigurationKey::PORT, - "", - false, - "Port number part of source for L3 payloads received from the handset in fuzzing interface." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.TestCall.PollTime","100", - "milliseconds", - ConfigurationKey::DEVELOPER, - ConfigurationKey::VALRANGE, - "50:200(10)",// educated guess - false, - "Polling time of the fuzzing interface in milliseconds." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.TestCall.RemoteHost","127.0.0.1", - "", - ConfigurationKey::DEVELOPER, - ConfigurationKey::IPADDRESS, - "", - false, - "Host part of destination for L3 payloads received from the handset in fuzzing interface." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("Control.TestCall.RemotePort","24021", - "", - ConfigurationKey::DEVELOPER, - ConfigurationKey::PORT, - "", - false, - "Port number part of destination for L3 payloads received from the handset in fuzzing interface." - ); - map[tmp->getName()] = *tmp; - delete tmp; tmp = new ConfigurationKey("Control.VEA","0", "", @@ -533,13 +605,13 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Control.WatchdogMinutes","60", + tmp = new ConfigurationKey("Control.WatchdogMinutes","0", "minutes", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "0:6000",// educated guess + "0:1440", false, - "Number of minutes before the radio watchdog expires and OpenBTS is restarted." + "Number of minutes before the radio watchdog expires and OpenBTS is restarted, set to 0 to disable." ); map[tmp->getName()] = *tmp; delete tmp; @@ -551,8 +623,8 @@ ConfigurationKeyMap getConfigurationKeys() "", true, "The list of DNS servers to be used by downstream clients. " - "By default, DNS servers are taken from the host system. " - "To override, specify a space-separated list of the DNS servers, in IP dotted notation, eg: 1.2.3.4 5.6.7.8. " + "By default, DNS servers of the host system are used. " + "To override, specify a space-separated list of DNS servers, in IP dotted notation, eg: 1.2.3.4 5.6.7.8. " "To use the host system DNS servers again, execute \"unconfig GGSN.DNS\"." ); map[tmp->getName()] = *tmp; @@ -611,7 +683,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::FILEPATH_OPT, "", true, - "If specified, internet traffic is logged to this file e.g. ggsn.log" + "If specified, internet traffic is logged to this file. E.g. ggsn.log." ); map[tmp->getName()] = *tmp; delete tmp; @@ -705,7 +777,7 @@ ConfigurationKeyMap getConfigurationKeys() "Timer 3168 in the MS controls the wait time after sending a Packet Resource Request to initiate a TBF before giving up or reattempting a Packet Access Procedure, which may imply sending a new RACH. " "This code is broadcast to the MS in the C0T0 beacon in the GPRS Cell Options IE. " "See GSM 04.60 12.24. " - "Range 0..7 to represent 0.5sec to 4sec in 0.5sec steps." + "Range 0..7, representing values from 0.5sec to 4sec in 0.5sec steps." ); map[tmp->getName()] = *tmp; delete tmp; @@ -733,11 +805,11 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("GPRS.ChannelCodingControl.RSSI","-40", "dB", - ConfigurationKey::DEVELOPER, + ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "-65:-15",// educated guess false, - "If the initial unlink signal strength is less than this amount in DB GPRS uses a lower bandwidth but more robust encoding CS-1. " + "If the initial unlink signal strength is less than this amount in dB, GPRS uses a lower bandwidth but more robust encoding CS-1. " "This value should normally be GSM.Radio.RSSITarget + 10 dB." ); map[tmp->getName()] = *tmp; @@ -770,7 +842,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:7", - false, + true, "Minimum number of channels allocated for GPRS service on ARFCN C0." ); map[tmp->getName()] = *tmp; @@ -781,7 +853,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:100", - false, + true, "Minimum number of channels allocated for GPRS service on ARFCNs other than C0." ); map[tmp->getName()] = *tmp; @@ -800,24 +872,31 @@ ConfigurationKeyMap getConfigurationKeys() delete tmp; #endif - tmp = new ConfigurationKey("GPRS.Codecs.Downlink","14", + // (pat 10-2013) Added commas in this list to make it more clear that the value is a list. + // It does not matter whether commas appear in the string or not, + // only appearance or non-appearance of the digits '1' .. '4' is significant. + // You could even stick in "CS1,CS4" if the regular expression allowed it. + tmp = new ConfigurationKey("GPRS.Codecs.Downlink","1,4", "", ConfigurationKey::DEVELOPER, - ConfigurationKey::STRING, - "^1{0,1}2{0,1}3{0,1}4{0,1}$",// "1234" with each number optional + ConfigurationKey::STRING_OPT, + //"^1{0,1}2{0,1}3{0,1}4{0,1}$",// "1234" with each number optional + "^[CS1234,]*$", // "1,2,3,4" with each number optional, or CS1,CS2,CS3,CS4. false, - "List of allowed GPRS downlink codecs 1..4 for CS-1..CS-4. Currently, only 1 and 4 are supported e.g. 14." + "An empty value specifies GPRS may use all available codecs. " + "Otherwise list of allowed GPRS downlink codecs 1..4 for CS-1..CS-4. Currently, only 1 and 4 are supported e.g. 1,4." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GPRS.Codecs.Uplink","14", + tmp = new ConfigurationKey("GPRS.Codecs.Uplink","1,4", "", ConfigurationKey::DEVELOPER, - ConfigurationKey::STRING, - "^1{0,1}2{0,1}3{0,1}4{0,1}$",// "1234" with each number optional + ConfigurationKey::STRING_OPT, + "^[CS1234,]*$", false, - "List of allowed GPRS uplink codecs 1..4 for CS-1..CS-4. Currently, only 1 and 4 are supported e.g. 14." + "An empty value specifies GPRS may use all available codecs. " + "Otherwise list of allowed GPRS uplink codecs 1..4 for CS-1..CS-4. Currently, only 1 and 4 are supported e.g. 1,4." ); map[tmp->getName()] = *tmp; delete tmp; @@ -828,7 +907,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "5:15",// educated guess false, - "Maximum number of assign messages sent" + "Maximum number of assign messages sent." ); map[tmp->getName()] = *tmp; delete tmp; @@ -841,7 +920,7 @@ ConfigurationKeyMap getConfigurationKeys() false, "Counts unused USF responses to detect nonresponsive MS. " "Should be > 8. " - "See GSM04.60 sec 13." + "See GSM04.60 Sec 13." ); map[tmp->getName()] = *tmp; delete tmp; @@ -864,8 +943,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "6:18",// educated guess false, - "Counts unused RRBP responses to detect nonresponsive MS. " - "See GSM04.60 sec 13." + "Counts unused RRBP responses to detect nonresponsive MS. " + "See GSM04.60 Sec 13." ); map[tmp->getName()] = *tmp; delete tmp; @@ -876,7 +955,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "3:9",// educated guess false, - "Maximum number of reassign messages sent" + "Maximum number of reassign messages sent." ); map[tmp->getName()] = *tmp; delete tmp; @@ -887,7 +966,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "3:8",// educated guess false, - "Maximum number of TBF release messages sent" + "Maximum number of TBF release messages sent." ); map[tmp->getName()] = *tmp; delete tmp; @@ -911,6 +990,7 @@ ConfigurationKeyMap getConfigurationKeys() false, "How often to send keep-alive messages for persistent TBFs in milliseconds; must be long enough to avoid simultaneous in-flight duplicates, and short enough that MS gets one every 5 seconds. " "GSM 5.08 10.2.2 indicates MS must get a block every 360ms" + // (oley) our allowed value range does not permit the recommended value of 360ms. Is this intentional? ); map[tmp->getName()] = *tmp; delete tmp; @@ -927,13 +1007,12 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GPRS.Enable","1", - + tmp = new ConfigurationKey("GPRS.Enable","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, "", - false, + true, "If enabled, GPRS service is advertised in the C0T0 beacon, and GPRS service may be started on demand. " "See also GPRS.Channels.*." ); @@ -946,7 +1025,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Enable recognition of local TLLI" + "Enable recognition of local TLLI." ); map[tmp->getName()] = *tmp; delete tmp; @@ -979,7 +1058,7 @@ ConfigurationKeyMap getConfigurationKeys() "10|1.0", false, "MS power control parameter, unitless, in steps of 0.1, so a parameter of 5 is an alpha value of 0.5. " - "Determines sensitivity of handset to variations in downlink RSSI. " + "Determines sensitivity of handset to variations in downlink RXLEV. " "Valid range is 0...10 for alpha values of 0...1.0. " "See GSM 05.08 10.2.1." ); @@ -993,7 +1072,7 @@ ConfigurationKeyMap getConfigurationKeys() "0:31", false, "MS power control parameter, in 2 dB steps. " - "Determines baseline of handset uplink power relative to downlink RSSI. " + "Determines baseline of handset uplink power relative to downlink RXLEV. " "The optimum value will tend to be lower for BTS units with higher power output. " "This default assumes a balanced link with a BTS output of 2-4 W/ARFCN. " "Valid range is 0...31 for gamma values of 0...62 dB. " @@ -1052,7 +1131,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "0:3", true, - "Controls measurement reports and cell reselection mode (MS autonomous or under network control); should not be changed. " + "Controls measurement reports and cell reselection mode (MS autonomous or under network control); should not be changed. " "See GSM 5.08 10.1.4." ); map[tmp->getName()] = *tmp; @@ -1114,12 +1193,12 @@ ConfigurationKeyMap getConfigurationKeys() delete tmp; tmp = new ConfigurationKey("GPRS.RRBP.Min","0", - "?", + "reservations", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "0:3", false, - "Minimum value for RRBP reservations, range 0..3. " + "Minimum value for Relative Reserved Block Period (RRBP) reservations, range 0..3. " "Should normally be 0. " "A non-zero value gives the MS more time to respond to the RRBP request." ); @@ -1132,7 +1211,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Enable TBF Reassignment" + "Enable TBF Reassignment." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1148,6 +1227,7 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; +#if 0 // (pat) obsolete tmp = new ConfigurationKey("GPRS.SGSN.port","1920", "", ConfigurationKey::DEVELOPER, @@ -1158,6 +1238,7 @@ ConfigurationKeyMap getConfigurationKeys() ); map[tmp->getName()] = *tmp; delete tmp; +#endif tmp = new ConfigurationKey("GPRS.TBF.Downlink.Poll1","10", "blocks", @@ -1176,8 +1257,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Allow MS to request another uplink assignment at end up of uplink TBF. " - "See GSM 4.60 9.2.3.4" + "Allow MS to request another uplink assignment at end up of uplink TBF. " + "See GSM 4.60 9.2.3.4." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1188,7 +1269,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "20000:40000",// educated guess false, - "How long to try before giving up on a TBF." + "How long in milliseconds to try before giving up on a TBF." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1199,7 +1280,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "15:25",// educated guess false, - "How many expired TBF structs to retain; they can be viewed with gprs list tbf -x" + "How many expired TBF structs to retain; they can be viewed with gprs list tbf -x." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1214,7 +1295,7 @@ ConfigurationKeyMap getConfigurationKeys() "3|Codec 3," "4|Codec 4", false, - "If 0, no tbf retry, otherwise if a tbf fails it will be retried with this codec, numbered 1..4" + "If 0, no tbf retry, otherwise if a tbf fails it will be retried with this codec, numbered 1..4." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1260,8 +1341,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "2500:7500(100)",// educated guess false, - "Nonresponsive uplink TBF resource release timer, in milliseconds. " - "See GSM04.60 sec 13." + "Nonresponsive uplink TBF resource release timer, in milliseconds. " + "See GSM04.60 Sec 13." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1272,8 +1353,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "2500:7500(100)",// educated guess false, - "Nonresponsive downlink TBF resource release timer, in milliseconds. " - "See GSM04.60 Section 13." + "Nonresponsive downlink TBF resource release timer, in milliseconds. " + "See GSM04.60 Sec 13." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1297,8 +1378,8 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "2500:7500(100)",// educated guess false, - "Nonresponsive downlink TBF resource release timer, in milliseconds. " - "See GSM04.60 Section 13." + "Nonresponsive downlink TBF resource release timer, in milliseconds. " + "See GSM04.60 Sec 13." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1320,36 +1401,52 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "0:6000(100)",// educated guess true, - "After completion, uplink TBFs are held open for this time in milliseconds. " - "If non-zero, must be greater than GPRS.Uplink.KeepAlive. " - "This is broadcast in the beacon and it cannot be changed once BTS is started." + "After completion uplink TBFs are held open for this time in milliseconds. " + "If non-zero, must be greater than GPRS.Uplink.KeepAlive. " + "This is broadcast in the beacon and cannot be changed once BTS is started." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.CCCH.AGCH.QMax","5", + // (pat 8-30-2013) We experienced a BTS lockup at Burning Man that I believe was caused + // by QMax == 5 being too high. The BTS showed all channels being allocated but none being used. + // After RACH the MS listens to BCCH and CCCH for T3126, which is defined by equations + // in 4.08/44.018 11.1.1 and 3.3.1.1.2 where T = TxInteger 14, S = 41 from Table 3.3.1.1.2.1, + // and T3126 = T+26 = 96 "slots of the mobile station's RACH" or 5 secs, whichever is less. + // For the combination V beacon there 27 RACH slots / beacon frame, which is 253ms. + // (There is a picture in the Range workshop tutorial.) + // Since QMax is applied to each AGCH channel on the beacon separately, we dont even need + // to convert to time here. 96 / 27 = 3.5 is the maximum value allowed for QMax. + // This seems nonsensical to me so I may be missing something. + // Currently we have no way of distinguishing exactly when the RACH arrived relative to when the + // reply goes out, so I am rounding down, even though a value of 4 might allow at least some + // RACH to be answered in time. + + tmp = new ConfigurationKey("GSM.CCCH.AGCH.QMax","3", "queued access grants", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "3:8",// educated guess + "2:8",// educated guess false, "Maximum number of access grants to be queued for transmission on AGCH before declaring congestion." ); map[tmp->getName()] = *tmp; delete tmp; + // (pat) This value is broadcast on the beacon but not otherwise used. + // Therefore the only affect of changing it is to make the BTS nonfunctional. tmp = new ConfigurationKey("GSM.CCCH.CCCH-CONF","1", "", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::CHOICE, "1|C-V Beacon," "2|C-IV Beacon", true, "CCCH configuration type. " + "DO NOT CHANGE THIS. Value is fixed by the implementation. " // pat added "See GSM 10.5.2.11 for encoding. " "Value of 1 means we are using a C-V beacon. " - "Any other value selects a C-IV beacon. " - "In C2.9 and earlier, the only allowed value is 1." + "Any other value selects a C-IV beacon." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1397,7 +1494,7 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.CellSelection.NCCsPermitted","1", + tmp = new ConfigurationKey("GSM.CellSelection.NCCsPermitted","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -1405,7 +1502,8 @@ ConfigurationKeyMap getConfigurationKeys() false, "NCCs Permitted. " "An 8-bit mask of allowed NCCs. " - "Unless you are coordinating with another carrier, this should probably just select your own NCC." + "The NCC of your own network is automatically included. " + "Unless you are coordinating with another carrier, this should be left at zero." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1469,7 +1567,7 @@ ConfigurationKeyMap getConfigurationKeys() true, "Number of Combination-VII timeslots to configure. " "The C-VII slot carries 8 SDCCHs, useful to handle high registration loads or SMS. " - "If C0T0 is C-IV, which it always is in C2.9 and earlier,, you must have at least one C-VII also." + "If C0T0 is C-IV, you must also have at least one C-VII." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1480,7 +1578,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "0:10",// educated guess false, - "Number of SDCCHs to reserve for non-LUR operations. " + "Number of SDCCHs to reserve for non-LUR operations. " "This can be used to force LUR transactions into a lower priority." ); map[tmp->getName()] = *tmp; @@ -1503,7 +1601,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Encrypt traffic between phone and OpenBTS." + "Encrypt traffic between MS and OpenBTS." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1553,15 +1651,24 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - // (pat) This value interacts with "GPRS.ChannelCodingControl.RSSI" which selects the GPRS codec. - // In my opinion, if GPRS is enabled, we should handover to try to get the RSSI above GPRS.ChannelCodingControl.RSSI. - tmp = new ConfigurationKey("GSM.Handover.LocalRSSIMin","-80", + tmp = new ConfigurationKey("GSM.Timer.Handover.Holdoff","10", + "seconds", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "1:120", + true, + "Handover will not be permitted until this time has elapsed after an initial channel seizure or handover." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("GSM.Handover.LocalRSSIMin","-80", // (pat) This option is misnamed. It checks RXLEV reported by the MS, which is not RSSI. "dBm", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "-100:-60",// educated guess false, - "Do not handover if downlink RSSI is above this level (in dBm), regardless of power difference." + "Do not handover if downlink RXLEV (reported by the MS) is above this level (in dBm), regardless of power difference." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1585,7 +1692,8 @@ ConfigurationKeyMap getConfigurationKeys() false, "GSM basestation color code; lower 3 bits of the BSIC. " "BCC values in a multi-BTS network should be assigned so that BTS units with overlapping coverage do not share a BCC. " - "This value will also select the training sequence used for all slots on this unit." + "This value will also select the training sequence used for all slots on this unit.", + ConfigurationKey::NEIGHBORSUNIQUE ); map[tmp->getName()] = *tmp; delete tmp; @@ -1598,7 +1706,8 @@ ConfigurationKeyMap getConfigurationKeys() false, "GSM network color code; upper 3 bits of the BSIC. " "Assigned by your national regulator. " - "Must be distinct from NCCs of other GSM operators in your area." + "Must be distinct from NCCs of other GSM operators in your area.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -1610,7 +1719,11 @@ ConfigurationKeyMap getConfigurationKeys() "0:65535", false, "Cell ID, 16 bits. " - "Should be unique." + "In some cases, the last digit of the cell id represents the sector id. " + "A last digit of 0 is used for an omnidirectional antenna. " + "A last digit of 1, 2, 3, etc indicates a sector of the multi-sector antenna. " + "Should be unique.", + ConfigurationKey::GLOBALLYUNIQUE ); map[tmp->getName()] = *tmp; delete tmp; @@ -1623,7 +1736,8 @@ ConfigurationKeyMap getConfigurationKeys() false, "Location area code, 16 bits, values 0xFFxx are reserved. " "For multi-BTS networks, assign a unique LAC to each BTS unit. " - "(That is not the normal procedure in conventional GSM networks, but is the correct procedure in OpenBTS networks.)" + "(This is not the normal procedure in conventional GSM networks, but is the correct procedure in OpenBTS networks.)", + ConfigurationKey::GLOBALLYUNIQUE ); map[tmp->getName()] = *tmp; delete tmp; @@ -1635,7 +1749,8 @@ ConfigurationKeyMap getConfigurationKeys() "^[0-9]{3}$", false, "Mobile country code; must be three digits. " - "Defined in ITU-T E.212. 001 for test networks." + "Defined in ITU-T E.212. Value of 001 for test networks.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -1647,8 +1762,9 @@ ConfigurationKeyMap getConfigurationKeys() "^[0-9]{2,3}$", false, "Mobile network code, two or three digits. " - "Assigned by your national regulator. " - "01 for test networks." + "Assigned by your national regulator. " + "01 for test networks.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -1657,21 +1773,25 @@ ConfigurationKeyMap getConfigurationKeys() "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::STRING, - "^[[:alnum:]]+$", + "^[0-9a-zA-Z]+$", false, "Network short name, displayed on some phones. " - "Optional but must be defined if you also want the network to send time-of-day." + "Optional but must be defined if you also want the network to send time-of-day.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.MS.Power.Damping","50", - "?damping value", + tmp = new ConfigurationKey("GSM.MS.Power.Damping","75", + "damping value in percent", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "25:75",// educated guess + "0:100", false, - "Damping value for MS power control loop." + "Damping value for MS power control loop in percent. The ordered MS power is based on RSSI [Received Signal Strength Indication]. " + "A value of 100 here ignores RSSI entirely; " + "a value of 0 causes the MS power to change instantaneously based on RSSI, which is inadvisable because it sets up power oscillations. " + "The ordered MS power is then clamped between GSM.MS.Power.Max and GSM.MS.Power.Min." ); map[tmp->getName()] = *tmp; delete tmp; @@ -1680,7 +1800,7 @@ ConfigurationKeyMap getConfigurationKeys() "dBm", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "5:100",// educated guess + "0:39", false, "Maximum commanded MS power level in dBm." ); @@ -1691,7 +1811,7 @@ ConfigurationKeyMap getConfigurationKeys() "dBm", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "5:100",// educated guess + "0:39", false, "Minimum commanded MS power level in dBm." ); @@ -1736,6 +1856,30 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + // (pat 8-2013) Added this to provide control over the RTP buffers. + // Originally the adaptive algorithm in the RTP library did not work very well, so you can over-ride it here. + // I turned off the RTP scheduler, and add the rtp timestamp-jump-callback, which vastly improved + // the audio quality so we probably do not need to goof with this any more. However, it is useful to completely + // turn off the adaptive algorithm, which might work better than leaving it on. + // WARNING: After a discontinuity in the transmit data stream, the receive data stream fails to resynchronize + // when this value is set much above 200 (I never determined the exact number), so I dont allow it. + tmp = new ConfigurationKey("GSM.SpeechBuffer","1", + "milliseconds", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "0:200", + false, + "Size of speech buffer in milliseconds. If set to 0, no RTP speech buffer is used. " + "If set to 1, the RTP speech buffer size is determined adaptively. " + "Any other value sets the speech buffer size. " + "The speech buffer is needed to overcome jitter caused by natural variation in the internet traffic delay. " + "Note that speech is noticeably delayed by this amount, so we want to keep it as low as possible and still have reasonably reliable delivery. " + "The specified delay is in addition to the intrinsic buffering inside OpenBTS. " + "This value is used only at the start of a call; changing it does not affect on-going calls. " + ); + map[tmp->getName()] = *tmp; + delete tmp; + tmp = new ConfigurationKey("GSM.Neighbors","", "", ConfigurationKey::CUSTOMERWARN, @@ -1743,35 +1887,71 @@ ConfigurationKeyMap getConfigurationKeys() "", false, "A list of IP addresses of neighbor BTSs available for handover. " - "By default, this feature is disabled. " - "To enable, specify a space-separated list of the BTS IP addresses, in IP dotted notation, eg: 1.2.3.4 5.6.7.8. " - "To disable again, execute \"unconfig GSM.Neighbors\"." + "By default handover is disabled. " + "To enable, specify a space-separated list of a maximum of 31 OpenBTS IP addresses in IP dotted notation, " + "optionally followed by a colon and the port number. E.g.: 1.2.3.4 5.6.7.8:16001. " + "To disable again, execute \"unconfig GSM.Neighbors\".", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Neighbors.NumToSend","8", + tmp = new ConfigurationKey("GSM.Neighbors.Averaging","4", + "reports", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "0:100", + false, + "If non-zero, neighbor measurement reports are averaged. To be considered for handover a neighbor must appear in 2 of the last GSM.Neighbors.Averaging measurement reports sent by the MS." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + + // (pat) This seems redundant with GSM.Neighbors, because if you want to limit the number of neighbors sent + // you can just leave them out of GSM.Neighbors. But not quite - this is a limit on the number of neighbors + // from the GSM.Neighbors list who actually respond to the Peer ping. + // Why would you put bad IP addresses in the GSM.Neighbors list? I dont know. + tmp = new ConfigurationKey("GSM.Neighbors.NumToSend","31", // (pat) Increased from 8 to 31. Dont know why it was limited. "neighbors", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "1:10",// educated guess + "0:31", // (pat) The measurement report (GSM 44.018 10.5.2.20) frequency index is 5 bits, but the value 31 has a special meaning for 3G so we avoid it, thus, max is 31 not 32. + // The value 0 would effectively disable handover. false, - "Maximum number of neighbors to send to handset in a neighbor list." + "Maximum number of neighbors to send to handset in the neighbor list broadcast in the beacon." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Ny1","5", + // (pat) 5 is way too low. There is no penalty for making this too big, so I am increasing it a lot. + tmp = new ConfigurationKey("GSM.Ny1","50", "repeats", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "1:10",// educated guess + "1:200",// educated guess true, "Maximum number of repeats of the Physical Information Message during handover procedure, GSM 04.08 11.1.3." ); map[tmp->getName()] = *tmp; delete tmp; + tmp = new ConfigurationKey("GSM.RACH.AC","0x0400", + "", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::CHOICE, + "0|Full Access," + "0x0400|Emergency Calls Not Supported", + false, + "Access class flags. " + "This is the raw parameter sent on the BCCH. " + "See GSM 04.08 10.5.2.29 for encoding. " + "Set to 0 to allow full access. " + "Set to 0x0400 to indicate no support for emergency calls." + ); + map[tmp->getName()] = *tmp; + delete tmp; + tmp = new ConfigurationKey("GSM.RACH.MaxRetrans","1", "", ConfigurationKey::CUSTOMERTUNE, @@ -1818,64 +1998,64 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("GSM.RRLP.ACCURACY","40", "", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "20:60",// educated guess false, "Requested accuracy of location request. " "K in r=10(1.1**K-1), where r is the accuracy in meters. " - "See 3GPP 03.32, sect 6.2" + "See 3GPP 03.32 Sec 6.2." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.ASSIST.PRESENT","0", "", - ConfigurationKey::CUSTOMER, + ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, "", false, - "Send almanac info to mobile" + "Send almanac info to mobile." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.REFRESH.TIME","24.0", "hours", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "18.0:30.0(0.1)",// educated guess false, - "How often the almanac is refreshed, in hours" + "How often the almanac is refreshed, in hours." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.URL","http://www.navcen.uscg.gov/?pageName=currentAlmanac&format=yuma", "", - ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, - "^(http|ftp)://[[:alnum:]_.-]", + "^(http|ftp)://[0-9a-zA-Z_.-]", false, - "URL of almanac source." + "URL of the almanac source." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.ASSIST.COUNT","9", "satellites", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "6:12",// educated guess false, - "number of satellites to include in navigation model" + "Number of satellites to include in navigation model." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.REFRESH.TIME","1.0", "hours", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "0.5:1.5(0.1)",// educated guess false, @@ -1886,9 +2066,9 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.URL","ftp://ftp.trimble.com/pub/eph/CurRnxN.nav", "", - ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, - "^(http|ftp)://[[:alnum:]_.-]", + "^(http|ftp)://[0-9a-zA-Z_.-]", false, "URL of ephemeris source." ); @@ -1897,20 +2077,20 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("GSM.RRLP.RESPONSETIME","4", "", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "2:6",// educated guess false, - "Mobile timeout. " + "Mobile timeout. " "(OpenBTS timeout is 130 sec = max response time + 2.) N in 2**N. " - "See 3GPP 04.31 sect A.2.2.1" + "See 3GPP 04.31 Sec A.2.2.1." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.SEED.ALTITUDE","0", "meters", - ConfigurationKey::CUSTOMERSITE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "-420:8850(5)", false, @@ -1921,33 +2101,33 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("GSM.RRLP.SEED.LATITUDE","37.777423", "degrees", - ConfigurationKey::CUSTOMERSITE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "-90.000000:90.000000", false, - "Seed latitude in degrees. " - "-90 (south pole) .. +90 (north pole)" + "Seed latitude in degrees: " + "-90 (south pole) .. +90 (north pole)." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.SEED.LONGITUDE","-122.39807", "degrees", - ConfigurationKey::CUSTOMERSITE, + ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "-180.000000:180.000000", false, - "Seed longitude in degrees. " - "-180 (west of greenwich) .. 180 (east)" + "Seed longitude in degrees: " + "-180 (west of greenwich) .. +180 (east)." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("GSM.RRLP.SERVER.URL","", "", - ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::DEVELOPER, ConfigurationKey::STRING_OPT,// audited - "^(http|ftp)://[[:alnum:]_.-]", + "^(http|ftp)://[0-9a-zA-Z_.-]", false, "URL of RRLP server. " "By default, this feature is disabled. " @@ -1961,7 +2141,7 @@ ConfigurationKeyMap getConfigurationKeys() "ARFCNs", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, - "1:10",// educated guess + "1:3", true, "The number of ARFCNs to use. " "The ARFCN set will be C0, C0+2, C0+4, etc." @@ -1979,8 +2159,9 @@ ConfigurationKeyMap getConfigurationKeys() "1900|PCS1900", true, "The GSM operating band. " - "Valid values are 850 (GSM850), 900 (PGSM900), 1800 (DCS1800) and 1900 (PCS1900). " - "For non-multiband units, this value is dictated by the hardware and should not be changed." + "Valid values are 850 for GSM850, 900 for PGSM900, 1800 for DCS1800 and 1900 for PCS1900. " + "For non-multiband units, this value is dictated by the hardware and should not be changed.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -1989,10 +2170,11 @@ ConfigurationKeyMap getConfigurationKeys() "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::CHOICE, - ConfigurationKey::getARFCNsString(), + getARFCNsString(900), true, "The C0 ARFCN. " - "Also the base ARFCN for a multi-ARFCN configuration." + "Also the base ARFCN for a multi-ARFCN configuration.", + ConfigurationKey::NEIGHBORSUNIQUE ); map[tmp->getName()] = *tmp; delete tmp; @@ -2005,8 +2187,7 @@ ConfigurationKeyMap getConfigurationKeys() false, "Expected worst-case delay spread in symbol periods, roughly 3.7 us or 1.1 km per unit. " "This parameter is dependent on the terrain type in the installation area. " - "Typical values are 1 for open terrain and small coverage areas. " - "For large coverage areas, a value of 4 is strongly recommended. " + "Typical values are: 1 for open terrain and small coverage areas, a value of 4 is strongly recommended for large coverage areas. " "This parameter has a large effect on computational requirements of the software radio; values greater than 4 should be avoided." ); map[tmp->getName()] = *tmp; @@ -2018,7 +2199,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Does the Radio type require the full BSIC" + "Whether the Radio type requires the full BSIC." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2047,6 +2228,8 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + // (pat) This appears to have nothing to do with power management. + // It affects the T3122 (RACH holdoff timer) in a way that does not make any sense to me. tmp = new ConfigurationKey("GSM.Radio.PowerManager.NumSamples","10", "sample count", ConfigurationKey::DEVELOPER, @@ -2075,11 +2258,12 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "1500:2500(100)",// educated guess false, - "Sample period for the output power control loopm in milliseconds." + "Sample period for the output power control loop in milliseconds." ); map[tmp->getName()] = *tmp; delete tmp; + // (pat) This is nonsense and should be removed. tmp = new ConfigurationKey("GSM.Radio.PowerManager.TargetT3122","5000", "milliseconds", ConfigurationKey::DEVELOPER, @@ -2103,15 +2287,16 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Radio.RxGain","0", + tmp = new ConfigurationKey("GSM.Radio.RxGain","47", "dB", ConfigurationKey::FACTORY, ConfigurationKey::VALRANGE, - "0:75",// educated guess + "25:75",// educated guess true, "Receiver gain setting in dB. " - "Ideal value is dictated by the hardware; 47 dB for RAD1, less for USRPs " - "This database parameter is static but the receiver gain can be modified in real time with the CLI rxgain command." + "Ideal value is dictated by the hardware; 47 dB for RAD1. " + "This database parameter is static but the receiver gain can be modified in real time with the CLI \"rxgain\" command.", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; @@ -2122,19 +2307,22 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Tell the phone to show the country name based on the MCC." + "Tell the MS to show the country name based on the MCC." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Timer.T3103","5000", + // (pat) 5 seconds is not enough. A handover failure on the Blackberry takes exactly 10 seconds. + // There is no real penalty for making this bigger, because all it does is kill the channel when it expires, + // so upping this to 12 seconds. + tmp = new ConfigurationKey("GSM.Timer.T3103","12000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "2500:7500(100)",// educated guess + "2000:30000(100)", true, "Handover timeout in milliseconds, GSM 04.08 11.1.2. " - "This is the timeout for a handset to sieze a channel during handover." + "This is the timeout for a handset to seize a channel during handover." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2145,7 +2333,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "25:75(5)",// educated guess true, - "Milliseconds for handset to respond to physical information. " + "Milliseconds for handset to respond to physical information. " "GSM 04.08 11.1.2." ); map[tmp->getName()] = *tmp; @@ -2164,31 +2352,37 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + // (pat) It is unfortunate that this is specified in msecs. tmp = new ConfigurationKey("GSM.Timer.T3122Max","255000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "127500:382500(1000)",// educated guess + "10000:255000(1000)", false, - "Maximum allowed value for T3122, the RACH holdoff timer, in milliseconds." + "Maximum allowed value for T3122, the RACH holdoff timer, in milliseconds. " + "This timer is sent to the MS with a granularity of seconds in the range 1-255. GSM 4.08 10.5.2.43." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Timer.T3122Min","2000", + // (pat) It is unfortunate that this is specified in msecs. + tmp = new ConfigurationKey("GSM.Timer.T3122Min","10000", // 2-2014: Pat upped from 2 to 10 seconds. "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "1000:3000(100)",// educated guess + "1000:255000(1000)", false, - "Minimum allowed value for T3122, the RACH holdoff timer, in milliseconds." + "Minimum allowed value for T3122, the RACH holdoff timer, in milliseconds. " + "GSM 4.08 10.5.2.43. This timer is sent to the MS with a granularity of seconds in the range 1-255. " + "The purpose is to postpone the MS RACH procedure until an SDCCH available, so there is no point making it any smaller than " + "the expected availability of the SDCCH, which will take several seconds." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("GSM.Timer.T3212","30", + tmp = new ConfigurationKey("GSM.Timer.T3212","0", "minutes", - ConfigurationKey::DEVELOPER, + ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:1530(6)", false, @@ -2200,18 +2394,18 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Peering.Neighbor.RefreshAge","60000", - "milliseconds", + tmp = new ConfigurationKey("Peering.Neighbor.RefreshAge","60", + "seconds", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "30000:90000(1000)",// educated guess + "10:3600",// educated guess false, - "Milliseconds before refreshing parameters from a neighbor." + "Seconds before refreshing parameters from a neighbor." ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Peering.NeighborTable.Path","/var/run/NeighborTable.db", + tmp = new ConfigurationKey("Peering.NeighborTable.Path","/var/run/OpenBTS/NeighborTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -2233,24 +2427,27 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("Peering.ResendCount","5", + // (pat) These Peering params will go away entirely when we switch the Peer interface to SIP. + // (pat) The product of ResendTimeout and ResendCount needs to be larger than twice the maximum possible internet rount-trip delay. + // (pat) It does not hurt to send extra messages. + tmp = new ConfigurationKey("Peering.ResendCount","20", "attempts", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, // (pat) This Peering params should not be customer tunable. ConfigurationKey::VALRANGE, - "3:8",// educated guess + "3:40",// educated guess false, - "Number of tries to send message over the peer interface before giving up" + "Number of tries to send message over the peer interface before giving up." ); map[tmp->getName()] = *tmp; delete tmp; tmp = new ConfigurationKey("Peering.ResendTimeout","100", "milliseconds", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::DEVELOPER, // (pat) This Peering params should not be customer tunable. ConfigurationKey::VALRANGE, - "50:100(10)",// educated guess + "50:1000(10)",// educated guess false, - "Milliseconds before resending a message on the peer interface" + "Milliseconds before resending a message on the peer interface." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2262,7 +2459,7 @@ ConfigurationKeyMap getConfigurationKeys() "25:200",// educated guess true, "Range of RTP port pool. " - "Pool is RTP.Start to RTP.Range-1." + "Pool is RTP.Start to RTP.Range - 1." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2274,7 +2471,7 @@ ConfigurationKeyMap getConfigurationKeys() "", true, "Base of RTP port pool. " - "Pool is RTP.Start to RTP.Range-1." + "Pool is RTP.Start to RTP.Range - 1." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2285,7 +2482,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Add layer-3 messages to the GGSN.Logfile, if any." + "Add layer 3 messages to the GGSN.Logfile, if any." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2313,17 +2510,16 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("SGSN.Timer.RAUpdate","3240", + tmp = new ConfigurationKey("SGSN.Timer.RAUpdate","0", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "0:11160(2)", // This is the allowed range of times expressed by the dopey GPRS Timer IE. Max is 31 deci-hours. + "0:11160(2)", // (pat) 0 deactivates, up to 31 deci-hours which is 11160 seconds. Minimum increment is 2 seconds. false, "Also known as T3312, 3GPP 24.008 4.7.2.2. " - "How often the MS reports into the SGSN when it is idle, in seconds. " + "How often MS reports into the SGSN when it is idle, in seconds. " "Setting to 0 or >12000 deactivates entirely, i.e., sets the timer to effective infinity. " - "Note: to prevent GPRS Routing Area Updates you must set both this and GSM.Timer.T3212 to 0. " - + "Note: to prevent GPRS Routing Area Updates you must set both this and GSM.Timer.T3212 to 0." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2362,13 +2558,28 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + // (pat) Geez, it is RFC2976, not 2967, and has been replaced by RFC6086 + // I am deprecating this, but I did not feel I could remove it because it might be in use in existing + // configs, so I marked it developer only. tmp = new ConfigurationKey("SIP.DTMF.RFC2967","0", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::BOOLEAN, + "", + false, + "Obsolete; incorrect RFC number. Use SIP.DTMF.RFC2976." + ); + map[tmp->getName()] = *tmp; + delete tmp; + + // Note that RFC2976 is deprecated by RFC6086. These discuss SIP INFO in general, not DTMF specifically. + tmp = new ConfigurationKey("SIP.DTMF.RFC2976","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, "", false, - "Use RFC-2967 (SIP INFO method) for in-call DTMF." + "Use RFC-2976 (SIP INFO method) for in-call DTMF." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2380,7 +2591,8 @@ ConfigurationKeyMap getConfigurationKeys() "", true, "IP address of the OpenBTS machine as seen by its proxies. " - "If these are all local, this can be localhost." + "If these are all local, this can be localhost.", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; @@ -2407,14 +2619,28 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + + // (pat) 5-1-2013 + tmp = new ConfigurationKey("SIP.Proxy.Mode","", // name and default value + "", // units + ConfigurationKey::DEVELOPER, // visiblity + ConfigurationKey::CHOICE_OPT, // type + "direct", // validation choices. These are comma separated + false, // is static? + "If set to direct, then direct BTS to BTS calls are permitted without an intervening SIP switch, for example, no asterisk needed." + ); + map[tmp->getName()] = *tmp; + delete tmp; + tmp = new ConfigurationKey("SIP.Proxy.Registration","127.0.0.1:5064", "", ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::IPANDPORT, + ConfigurationKey::HOSTANDPORT, "", false, - "The IP host and port of the proxy to be used for registration and authentication. " - "This should normally be the subscriber registry SIP interface, not Asterisk." + "The hostname or IP address and port of the proxy to be used for registration and authentication. " + "This should normally be the subscriber registry SIP interface, not Asterisk.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -2422,11 +2648,12 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("SIP.Proxy.SMS","127.0.0.1:5063", "", ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::IPANDPORT, + ConfigurationKey::HOSTANDPORT, "", false, - "The IP host and port of the proxy to be used for text messaging. " - "This is smqueue, for example." + "The hostname or IP address and port of the proxy to be used for text messaging. " + "This is smqueue, for example.", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -2434,11 +2661,26 @@ ConfigurationKeyMap getConfigurationKeys() tmp = new ConfigurationKey("SIP.Proxy.Speech","127.0.0.1:5060", "", ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::IPANDPORT, + ConfigurationKey::HOSTANDPORT, "", false, - "The IP host and port of the proxy to be used for normal speech calls. " - "This is Asterisk, for example." + "The hostname or IP address and port of the proxy to be used for normal speech calls. " + "This is Asterisk, for example.", + ConfigurationKey::GLOBALLYSAME + ); + map[tmp->getName()] = *tmp; + delete tmp; + + tmp = new ConfigurationKey("SIP.Proxy.USSD","", + "", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::HOSTANDPORT_OPT, + "", + false, + "The hostname or IP address and port of the proxy to be used for USSD, " + "or \"testmode\" to test by reflecting USSD messages back to the handset. " + "To disable USSD, execute \"unconfig SIP.Proxy.USSD\".", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -2461,7 +2703,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::BOOLEAN, "", false, - "Send 100 Trying response to SIP MESSAGE, even though that violates RFC-3428." + "Send \"100 Trying\" response to SIP MESSAGE, even though that violates RFC-3428." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2475,7 +2717,8 @@ ConfigurationKeyMap getConfigurationKeys() "The SMSC handler in smqueue. " "This is the entity that handles full 3GPP MIME-encapsulted TPDUs. " "If not defined, use direct numeric addressing. " - "The value should be disabled with \"unconfig SIP.SMSC\" if SMS.MIMEType is \"text/plain\" or set to \"smsc\" if SMS.MIMEType is \"application/vnd.3gpp\"." + "The value should be disabled with \"unconfig SIP.SMSC\" if SMS.MIMEType is \"text/plain\" or set to \"smsc\" if SMS.MIMEType is \"application/vnd.3gpp\".", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; @@ -2555,78 +2798,12 @@ ConfigurationKeyMap getConfigurationKeys() "text/plain", false, "This is the MIME Type that OpenBTS will use for RFC-3428 SIP MESSAGE payloads. " - "Valid values are \"application/vnd.3gpp.sms\" and \"text/plain\"." + "Valid values are \"application/vnd.3gpp.sms\" and \"text/plain\".", + ConfigurationKey::GLOBALLYSAME ); map[tmp->getName()] = *tmp; delete tmp; - tmp = new ConfigurationKey("SubscriberRegistry.A3A8","/OpenBTS/comp128", - "", - ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::FILEPATH, - "", - false, - "Path to the program that implements the A3/A8 algorithm." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("SubscriberRegistry.db","/var/lib/asterisk/sqlite3dir/sqlite3.db", - "", - ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::FILEPATH, - "", - false, - "The location of the sqlite3 database holding the subscriber registry." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("SubscriberRegistry.Manager.Title","Subscriber Registry", - "", - ConfigurationKey::CUSTOMER, - ConfigurationKey::STRING, - "^[[:print:]]+$", - false, - "Title text to be displayed on the subscriber registry manager." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("SubscriberRegistry.Manager.VisibleColumns","name username type context host", - "", - ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::STRING, - "^(name){0,1} (username){0,1} (type){0,1} (context){0,1} (host){0,1}$", - false, - "A space separated list of columns to display in the subscriber registry manager." - ); - - tmp = new ConfigurationKey("SubscriberRegistry.Port","5064", - "", - ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::PORT, - "", - false, - "Port used by the SIP Authentication Server. NOTE: In some older releases (pre-2.8.1) this is called SIP.myPort." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("SubscriberRegistry.UpstreamServer","", - "", - ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::STRING_OPT,// audited - "", - false, - "URL of the subscriber registry HTTP interface on the upstream server. " - "By default, this feature is disabled. " - "To enable, specify a server URL eg: http://localhost/cgi/subreg.cgi. " - "To disable again, execute \"unconfig SubscriberRegistry.UpstreamServer\"." - ); - map[tmp->getName()] = *tmp; - delete tmp; - tmp = new ConfigurationKey("TRX.IP","127.0.0.1", "", ConfigurationKey::CUSTOMERWARN, @@ -2647,7 +2824,8 @@ ConfigurationKeyMap getConfigurationKeys() "Bursts received at the physical layer below this threshold are automatically ignored. " "Values in dB. " "Set at the factory. " - "Do not adjust without proper calibration." + "Do not adjust without proper calibration.", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; @@ -2669,10 +2847,11 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "64:192", true, - "Fine-tuning adjustment for the transceiver master clock. " + "Fine-tuning adjustment for the Transceiver master clock. " "Roughly 170 Hz/step. " "Set at the factory. " - "Do not adjust without proper calibration." + "Do not adjust without proper calibration.", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; @@ -2683,7 +2862,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "5:15",// educated guess false, - "How long to wait during a read operation from the transceiver before giving up." + "How long to wait during a read operation from the Transceiver before giving up." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2695,7 +2874,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "1:3",// educated guess false, - "How long to wait during system startup before checking to see if the transceiver can be reached." + "How long to wait during system startup before checking to see if the Transceiver can be reached." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2706,9 +2885,10 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::VALRANGE, "0:100",// educated guess true, - "Hardware-specific gain adjustment for transmitter, matched to the power amplifier, expessed as an attenuationi in dB. " + "Hardware-specific gain adjustment for transmitter, matched to the power amplifier, expessed as an attenuation in dB. " "Set at the factory. " - "Do not adjust without proper calibration." + "Do not adjust without proper calibration.", + ConfigurationKey::NODESPECIFIC ); map[tmp->getName()] = *tmp; delete tmp; @@ -2720,7 +2900,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::STRING, "", false, - "Extra arguments for the Transceiver" + "Extra arguments for the Transceiver." ); map[tmp->getName()] = *tmp; delete tmp; @@ -2730,7 +2910,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "0:100(5)",// educated guess - true, + false, // this option takes effect immediately "Probability (0-100) of dropping any downlink frame to test robustness." ); map[tmp->getName()] = *tmp; @@ -2741,7 +2921,7 @@ ConfigurationKeyMap getConfigurationKeys() ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "0:100(5)",// educated guess - true, + false, // this option takes effect immediately "Probability (0-100) of dropping any uplink frame to test robustness." ); map[tmp->getName()] = *tmp; @@ -2771,3 +2951,4 @@ ConfigurationKeyMap getConfigurationKeys() return map; } + diff --git a/apps/Makefile.am b/apps/Makefile.am index fc2bc49..1f58358 100644 --- a/apps/Makefile.am +++ b/apps/Makefile.am @@ -39,30 +39,29 @@ ourlibs = \ $(CLI_LA) \ $(SIP_LA) \ $(GSM_LA) \ + $(GSMSHARE_LA) \ $(GPRS_LA) \ $(SGSNGGSN_LA) \ $(TRX_LA) \ $(GLOBALS_LA) \ $(CONTROL_LA) \ - $(SR_LA) \ $(COMMON_LA) \ $(SQLITE_LA) \ $(SMS_LA) \ $(PEERING_LA) \ - $(OSIP_LIBS) \ + $(NODEMANAGER_LA) \ $(ORTP_LIBS) -OpenBTS_LDADD = $(ourlibs) -ldl -losipparser2 -losip2 -lortp -la53 +OpenBTS_LDADD = $(ourlibs) -ldl -lortp -la53 OpenBTSCLI_SOURCES = OpenBTSCLI.cpp -# RedHat RHEL5 needs to have ncurses included -OpenBTSCLI_LDADD = -lreadline -lncurses +OpenBTSCLI_LDADD = -lreadline OpenBTSDo_SOURCES = OpenBTSDo.cpp EXTRA_DIST = \ OpenBTS.example.sql \ setUpFiles.sh \ - runloop.OpenBTS.sh \ + openbts.conf \ generateConfigTable.sh \ exportConfigTable.sh \ importConfigTable.sh \ @@ -74,7 +73,8 @@ install: OpenBTS OpenBTSCLI OpenBTSDo install OpenBTS "$(DESTDIR)/OpenBTS/" install OpenBTSCLI "$(DESTDIR)/OpenBTS/" install OpenBTSDo "$(DESTDIR)/OpenBTS/" - install runloop.OpenBTS.sh "$(DESTDIR)/OpenBTS/" + mkdir -p "$(DESTDIR)/etc/init/" + install openbts.conf "$(DESTDIR)/etc/init/" mkdir -p "$(DESTDIR)/etc/OpenBTS/" install iptables.rules "$(DESTDIR)/etc/OpenBTS/" install OpenBTS.example.sql "$(DESTDIR)/etc/OpenBTS/" @@ -82,3 +82,6 @@ install: OpenBTS OpenBTSCLI OpenBTSDo mkdir -p "$(DESTDIR)/etc/logrotate.d/" install rsyslogd.OpenBTS.conf "$(DESTDIR)/etc/rsyslog.d/OpenBTS.conf" install OpenBTS.logrotate "$(DESTDIR)/etc/logrotate.d/OpenBTS" + mkdir -p "$(DESTDIR)/home/openbts/" + install CLI "$(DESTDIR)/home/openbts/" + install openbtsconfig "$(DESTDIR)/home/openbts/" diff --git a/apps/OpenBTS.cpp b/apps/OpenBTS.cpp index 46e274d..2b8699d 100644 --- a/apps/OpenBTS.cpp +++ b/apps/OpenBTS.cpp @@ -1,7 +1,7 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. -* Copyright 2011, 2012 Range Networks, Inc. +* Copyright 2011, 2012, 2013, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing @@ -20,12 +20,15 @@ #include #include #include +#include #include std::vector configurationCrossCheck(const std::string& key); -static const char *cOpenBTSConfigEnv = "OpenBTSConfigFile"; +std::string getARFCNsString(unsigned band); // Load configuration from a file. -ConfigurationTable gConfig(getenv(cOpenBTSConfigEnv)?getenv(cOpenBTSConfigEnv):"/etc/OpenBTS/OpenBTS.db","OpenBTS", getConfigurationKeys()); +static const char *cOpenBTSConfigEnv = "OpenBTSConfigFile"; +static const char *cOpenBTSConfigFile = getenv(cOpenBTSConfigEnv)?getenv(cOpenBTSConfigEnv):"/etc/OpenBTS/OpenBTS.db"; +ConfigurationTable gConfig(cOpenBTSConfigFile,"OpenBTS", getConfigurationKeys()); #include Log dummy("openbts",gConfig.getStr("Log.Level").c_str(),LOG_LOCAL7); @@ -34,23 +37,22 @@ Log dummy("openbts",gConfig.getStr("Log.Level").c_str(),LOG_LOCAL7); ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); #include -#include +//#include #include -#include -#include +//#include +//#include #include -#include -#include +#include +#include -#include #include #include #include #include #include -#include +#include #include "NeighborTable.h" #include @@ -65,31 +67,34 @@ ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); // Set env MALLOC_TRACE=logfilename // Call mtrace() in the program. // post-process the logfilename with mtrace (a perl script.) -// #include +//#include using namespace std; using namespace GSM; -int gBtsXg = 0; // Enable gprs - const char* gDateTime = __DATE__ " " __TIME__; // All of the other globals that rely on the global configuration file need to // be declared here. +// (pat) That is because the order that constructors are called is indeterminate, and we must +// ensure that the ConfigurationTable is constructed before any other classes. +// In general it is unwise to put non-trivial initialization code in constructors for this reason. +// If you dont call gConfig in your class constructor, you dont need to init your class here. +// Another way to handle this would be to substitute gConfig.get...(...) throughout OpenBTS with +// a function call that inits the ConfigurationTable if needed. +// It would be much kinder on the compiler as well. And if someone goes to that effort, while you +// are at it change the char* arguments to constants. // The TMSI Table. -Control::TMSITable gTMSITable; +//moved to Control directory: Control::TMSITable gTMSITable; // The transaction table. -Control::TransactionTable gTransactionTable; +// moved to Control directory: Control::TransactionTable gTransactionTable; // Physical status reporting GSM::PhysicalStatus gPhysStatus; -// The global SIPInterface object. -SIP::SIPInterface gSIPInterface; - // Configure the BTS object based on the config file. // So don't create this until AFTER loading the config file. GSMConfig gBTS; @@ -103,9 +108,6 @@ GSMConfig gBTS; // Our interface to the software-defined radio. TransceiverManager gTRX(gConfig.getNum("GSM.Radio.ARFCNs"), gConfig.getStr("TRX.IP").c_str(), gConfig.getNum("TRX.Port")); -// Subscriber registry and http authentication -SubscriberRegistry gSubscriberRegistry; - /** The global peering interface. */ Peering::PeerInterface gPeerInterface; @@ -116,10 +118,12 @@ Peering::NeighborTable gNeighborTable; /** Define a function to call any time the configuration database changes. */ void purgeConfig(void*,int,char const*, char const*, sqlite3_int64) { - LOG(INFO) << "purging configuration cache"; + // (pat) NO NO NO. Do not call LOG from here - it may result in infinite recursion. + // LOG(INFO) << "purging configuration cache"; gConfig.purge(); gBTS.regenerateBeacon(); gResetWatchdog(); + gLogGroup.setAll(); } @@ -200,8 +204,6 @@ void createStats() gReports.create("OpenBTS.SIP.UnresolvedHostname"); // count of INVITEs received in the SIP layer gReports.create("OpenBTS.SIP.INVITE.In"); - // count of SOS INVITEs sent from the SIP layer; these are not included in ..INVITE.OUT - gReports.create("OpenBTS.SIP.INVITE-SOS.Out"); // count of INVITEs sent from the in SIP layer gReports.create("OpenBTS.SIP.INVITE.Out"); // count of INVITE-OKs sent from the in SIP layer (connection established) @@ -304,25 +306,66 @@ void createStats() +// (pat) Using multiple radios on the same CPU: +// 1. Provide a seprate config OpenBTS.db file for each OpenBTS + transceiver pair. +// I run each OpenBTS+transceiver pair in a separate directory with its own OpenBTS.db set as below. +// To set the config file You can use the --config option or set the OpenBTSConfigFile environment variable, +// which also works with gdb. +// 2. Set TRX.RadioNumber to 1,2,3,... +// 3. Set transceiver communication TRX.Port differently. TRX uses >100 ports, so use: 5700, 5900, 6100, etc. +// 4. Set GSM.Radio.C0 differently. +// 5. Set GSM.Identity.BSIC.BCC differently. +// 6. Set GSM.Identity.LAC differently, maybe, but this depends on what you want to do. +// 7. Change all the external application ports: Peering.Port, RTP.Start, SIP.Local.Port +// 8. The neighbor tables need to point at each other. See example below. +// 9. Change the Peering.NeighborTable.Path. I just set it to a .db in the current directory. Changing the other .db files is optional +// 10. If you have old radios, dont forget to set the TRX.RadioFrequencyOffset for each radio. +// 11. Doug recommends increasing GSM.Ny1 for handover testing. +// Note reserved ports: SR uses port 5064 and asterisk uses port 5060. +// Example using two radios on one computer: +// TRX.RadioNumber 1 2 +// TRX.Port 5700 5900 +// GSM.Radio.C0 51 60 +// GSM.Identity.BSIC.BCC 2 3 +// GSM.Identity.LAC 1007 1008 +// SIP.Local.Port 5062 5066 +// RTP.Start 16484 16600 +// Peering.Port 16001 16002 +// GSM.Neighbors 127.0.0.1:16002 127.0.0.1:16001 +// Each BTS needs separate versions of these .db files that normally reside in /var/run: Just put them in the cur dir like this: +// Peering.NeighborTable.Path NeighborTable.db +// Control.Reporting.TransactionTable TransactionTable.db +// Control.Reporting.TMSITable TMSITable.db +// Control.Reporting.StatsTable StatsTable.db +// Control.Reporting.PhysStatusTable PhysStatusTable.db + +namespace GSM { extern void TestTCHL1FEC(); }; // (pat) This is cheating, but I dont want to include the whole GSML1FEC.h. + int main(int argc, char *argv[]) { - // mtrace(); // Enable memory leak detection. Unfortunately, huge amounts of code have been started in the constructors above. + //mtrace(); // (pat) Enable memory leak detection. Unfortunately, huge amounts of code have been started in the constructors above. + gLogGroup.setAll(); // TODO: Properly parse and handle any arguments if (argc > 1) { + bool testflag = false; for (int argi = 1; argi < argc; argi++) { // Skip argv[0] which is the program name. - if (!strcmp(argv[argi], "--version") || - !strcmp(argv[argi], "-v")) { + if (!strcmp(argv[argi], "--version") || !strcmp(argv[argi], "-v")) { + // Print the version number and exit immediately. cout << gVersionString << endl; + return 0; + } + if (!strcmp(argv[argi], "--test")) { + testflag = true; continue; } if (!strcmp(argv[argi], "--gensql")) { cout << gConfig.getDefaultSQL(string(argv[0]), gVersionString) << endl; - continue; + return 0; } if (!strcmp(argv[argi], "--gentex")) { cout << gConfig.getTeX(string(argv[0]), gVersionString) << endl; - continue; + return 0; } // (pat) Adding support for specified sql file. @@ -330,7 +373,7 @@ int main(int argc, char *argv[]) // so stick this arg in the environment, whence the ConfigurationTable can find it, and then reboot. if (!strcmp(argv[argi],"--config")) { if (++argi == argc) { - LOG(ALERT) <<"Missing argument to -sql option"; + LOG(ALERT) <<"Missing argument to --config option"; exit(2); } setenv(cOpenBTSConfigEnv,argv[argi],1); @@ -339,7 +382,7 @@ int main(int argc, char *argv[]) exit(0); } if (!strcmp(argv[argi],"--help")) { - printf("OpenBTS [--version --gensql --genex] [--config file.db]\n"); + printf("OpenBTS [--version --gensql --gentex] [--config file.db]\n"); printf("OpenBTS exiting...\n"); exit(0); } @@ -347,7 +390,7 @@ int main(int argc, char *argv[]) printf("OpenBTS: unrecognized argument: %s\nexiting...\n",argv[argi]); } - return 0; + if (testflag) { GSM::TestTCHL1FEC(); return 0; } } createStats(); @@ -373,13 +416,12 @@ int main(int argc, char *argv[]) gConfig.setUpdateHook(purgeConfig); LOG(ALERT) << "OpenBTS (re)starting, ver " << VERSION << " build date " << __DATE__; + LOG(ALERT) << "OpenBTS reading config file "<powerOn(); //sleep(gConfig.getNum("TRX.Timeout.Start")); - //bool haveTRX = gTRX.ARFCN(0)->powerOn(false); // (pat) Dont power on the radio before initing it, particularly SETTSC below; radio can crash. - bool haveTRX = gTRX.ARFCN(0)->powerOff(); + //bool haveTRX = gTRX.ARFCN(0)->powerOn(false); This prints an inapplicable warning message. + bool haveTRX = gTRX.ARFCN(0)->trxRunning(); // This does not print an inapplicable warning message. Thread transceiverThread; if (!haveTRX) { + LOG(ALERT) << "starting the transceiver"; transceiverThread.start((void*(*)(void*)) startTransceiver, NULL); // sleep to let the FPGA code load // TODO: we should be "pinging" the radio instead of sleeping @@ -403,7 +446,7 @@ int main(int argc, char *argv[]) } // Start the SIP interface. - gSIPInterface.start(); + SIP::SIPInterfaceStart(); // Start the peer interface gPeerInterface.start(); @@ -434,6 +477,9 @@ int main(int argc, char *argv[]) } } + // Limit valid ARFCNs to current band + gConfig.mSchema["GSM.Radio.C0"].updateValidValues(getARFCNsString(gConfig.getNum("GSM.Radio.Band"))); + // // Configure the radio. // @@ -515,6 +561,7 @@ int main(int argc, char *argv[]) gBTS.addAGCH(&CCCH2); // C-V C0T0 SDCCHs + // (pat) I thought config 'GSM.CCCH.CCCH-CONF' was supposed to control the number of SDCCH allocated? SDCCHLogicalChannel C0T0SDCCH[4] = { SDCCHLogicalChannel(0,0,gSDCCH_4_0), SDCCHLogicalChannel(0,0,gSDCCH_4_1), @@ -562,6 +609,14 @@ int main(int argc, char *argv[]) gConfig.set("GSM.Channels.NumC7s",numChan); } + // sanity check on channel counts + // the clamp here could be improved to take the customer's current ratio of C1:C7 and scale it back to fit in the window + if (((numARFCNs * 8) - 1) < (gConfig.getNum("GSM.Channels.NumC1s") + gConfig.getNum("GSM.Channels.NumC7s"))) { + LOG(CRIT) << "scaling back GSM.Channels.NumC1s and GSM.Channels.NumC7s to fit inside number of available timeslots"; + gConfig.set("GSM.Channels.NumC1s",numARFCNs*7); + gConfig.set("GSM.Channels.NumC7s",numARFCNs-1); + } + if (gConfig.getBool("GSM.Channels.C1sFirst")) { // Create C-I slots. for (int i=0; i configurationCrossCheck(const string& key) { vector warnings; @@ -717,7 +782,7 @@ vector configurationCrossCheck(const string& key) { int gprs = gConfig.getNum("GPRS.ChannelCodingControl.RSSI"); int gsm = gConfig.getNum("GSM.Radio.RSSITarget"); if ((gprs - gsm) != 10) { - warning << "GPRS.ChannelCodingControl.RSSI (" << gprs << ") should normally be 10db higher than GSM.Radio.RSSITarget (" << gsm << ")"; + warning << "GPRS.ChannelCodingControl.RSSI (" << gprs << ") should normally be 10db greater than GSM.Radio.RSSITarget (" << gsm << ")"; warnings.push_back(warning.str()); warning.str(std::string()); } @@ -753,36 +818,6 @@ vector configurationCrossCheck(const string& key) { warning.str(std::string()); } - // Control.LUR.WhiteList depends on Control.WhiteListing.Message, Control.LUR.WhiteListing.RejectCause and Control.WhiteListing.ShortCode - } else if (key.compare("Control.LUR.WhiteList") == 0 || key.compare("Control.WhiteListing.Message") == 0 || - key.compare("Control.LUR.WhiteListing.RejectCause") == 0 || key.compare("Control.WhiteListing.ShortCode") == 0) { - if (gConfig.getBool("Control.LUR.WhiteList")) { - if (!gConfig.getStr("Control.WhiteListing.Message").length()) { - warning << "Control.LUR.WhiteList is enabled but will not be functional until Control.WhiteListing.Message is set"; - warnings.push_back(warning.str()); - warning.str(std::string()); - } else if (!gConfig.getStr("Control.LUR.WhiteListing.RejectCause").length()) { - warning << "Control.LUR.WhiteList is enabled but will not be functional until Control.WhiteListing.RejectCause is set"; - warnings.push_back(warning.str()); - warning.str(std::string()); - } else if (!gConfig.getStr("Control.WhiteListing.ShortCode").length()) { - warning << "Control.LUR.WhiteList is enabled but will not be functional until Control.WhiteListing.ShortCode is set"; - warnings.push_back(warning.str()); - warning.str(std::string()); - } - } - - // GSM.CellSelection.NCCsPermitted needs to contain our own GSM.Identity.BSIC.NCC - } else if (key.compare("GSM.CellSelection.NCCsPermitted") == 0 || key.compare("GSM.Identity.BSIC.NCC") == 0) { - int ourNCCMask = gConfig.getNum("GSM.CellSelection.NCCsPermitted"); - int NCCMaskBit = 1 << gConfig.getNum("GSM.Identity.BSIC.NCC"); - if ((NCCMaskBit & ourNCCMask) == 0) { - warning << "GSM.CellSelection.NCCsPermitted is not set to a mask which contains the local network color code defined in GSM.Identity.BSIC.NCC. "; - warning << "Set GSM.CellSelection.NCCsPermitted to " << NCCMaskBit; - warnings.push_back(warning.str()); - warning.str(std::string()); - } - // Control.LUR.FailedRegistration.Message depends on Control.LUR.FailedRegistration.ShortCode } else if (key.compare("Control.LUR.FailedRegistration.Message") == 0 || key.compare("Control.LUR.FailedRegistration.ShortCode") == 0) { if (gConfig.getStr("Control.LUR.FailedRegistration.Message").length() && !gConfig.getStr("Control.LUR.FailedRegistration.ShortCode").length()) { @@ -816,6 +851,69 @@ vector configurationCrossCheck(const string& key) { warning << "SMS.MIMEType is set to \"application/vnc.3gpp.sms\", SIP.SMSC should usually be set to \"smsc\""; warnings.push_back(warning.str()); warning.str(std::string()); + } else if (sms.compare("text/plain") == 0 && sip.compare("") != 0) { + warning << "SMS.MIMEType is set to \"text/plain\", SIP.SMSC should usually be empty (use unconfig to clear)"; + warnings.push_back(warning.str()); + warning.str(std::string()); + } + + // Control.Emergency.Geolocation depends on Control.Emergency.GatewaySwitch + } else if (key.compare("Control.Emergency.Geolocation") == 0 || key.compare("Control.Emergency.GatewaySwitch") == 0) { + if (gConfig.getStr("Control.Emergency.Geolocation").length() && !gConfig.getStr("Control.Emergency.GatewaySwitch").length()) { + warning << "Control.Emergency.Geolocation is enabled but will not be functional until Control.Emergency.GatewaySwitch is set"; + warnings.push_back(warning.str()); + warning.str(std::string()); + } + + // SIP.Local.IP cannot be 127.0.0.1 when any of the SIP.Proxy.* settings are non-localhost + } else if (key.compare("SIP.Local.IP") == 0 || key.compare("SIP.Proxy.Emergency") == 0 || + key.compare("SIP.Proxy.Registration") == 0 || key.compare("SIP.Proxy.SMS") == 0 || + key.compare("SIP.Proxy.Speech") == 0 || key.compare("SIP.Proxy.USSD") == 0) { + string loopback = "127.0.0.1"; + string local = gConfig.getStr("SIP.Local.IP"); + if (local.compare(loopback) == 0) { + string emergency = gConfig.getStr("SIP.Proxy.Emergency"); + string registration = gConfig.getStr("SIP.Proxy.Registration"); + string sms = gConfig.getStr("SIP.Proxy.SMS"); + string speech = gConfig.getStr("SIP.Proxy.Speech"); + string ussd = gConfig.getStr("SIP.Proxy.USSD"); + if (emergency.find(loopback) == std::string::npos || registration.find(loopback) == std::string::npos || + sms.find(loopback) == std::string::npos || speech.find(loopback) == std::string::npos || + (ussd.length() && ussd.find(loopback) == std::string::npos)) { + warning << "A non-local IP is being used for one or more SIP.Proxy.* settings but SIP.Local.IP is still set to 127.0.0.1. "; + warning << "Set SIP.Local.IP to the IP address of this machine as seen by the proxies."; + warnings.push_back(warning.str()); + warning.str(std::string()); + } + } + + // GSM.MS.Power.Min cannot be higher than GSM.MS.Power.Max + } else if (key.compare("GSM.MS.Power.Min") == 0 || key.compare("GSM.MS.Power.Max") == 0) { + if (gConfig.getNum("GSM.MS.Power.Min") > gConfig.getNum("GSM.MS.Power.Max")) { + warning << "GSM.MS.Power.Min is set higher than GSM.MS.Power.Max. Swap the values or set a new minimum."; + warnings.push_back(warning.str()); + warning.str(std::string()); + } + + // GSM.Channels.NumC1s + GSM.Channels.NumC1s must fall within 8 * GSM.Radio.ARFCNs + } else if (key.compare("GSM.Radio.ARFCNs") == 0 || key.compare("GSM.Channels.NumC1s") == 0 || key.compare("GSM.Channels.NumC7s") == 0) { + int max = (8 * gConfig.getNum("GSM.Radio.ARFCNs")) - 1; + int current = gConfig.getNum("GSM.Channels.NumC1s") + gConfig.getNum("GSM.Channels.NumC7s"); + if (max < current) { + warning << "There are only " << max << " channels available but " << current << " are configured. "; + warning << "Reduce GSM.Channels.NumC1s and/or GSM.Channels.NumC7s accordingly."; + warnings.push_back(warning.str()); + warning.str(std::string()); + } else if (max > current) { + int avail = max-current; + if (avail == 1) { + warning << "There is still " << avail << " channel available for additional capacity. "; + } else { + warning << "There are still " << avail << " channels available for additional capacity. "; + } + warning << "Increase GSM.Channels.NumC1s and/or GSM.Channels.NumC7s accordingly."; + warnings.push_back(warning.str()); + warning.str(std::string()); } } diff --git a/apps/OpenBTS.logrotate b/apps/OpenBTS.logrotate index 4c92102..4002654 100755 --- a/apps/OpenBTS.logrotate +++ b/apps/OpenBTS.logrotate @@ -1,6 +1,6 @@ /var/log/OpenBTS.log { - sizem 20 - rotate 10 + size 20M + rotate 10 compress notifempty } diff --git a/apps/OpenBTSCLI.cpp b/apps/OpenBTSCLI.cpp index 3e6d43e..fe3275a 100644 --- a/apps/OpenBTSCLI.cpp +++ b/apps/OpenBTSCLI.cpp @@ -44,7 +44,7 @@ #endif -#define DEFAULT_CMD_PATH "/var/run/command" +#define DEFAULT_CMD_PATH "/var/run/OpenBTS/command" #define DEFAULT_RSP_PATH "./response" @@ -154,7 +154,7 @@ int main(int argc, char *argv[]) // use the socket if (sendto(sock,cmd,strlen(cmd)+1,0,(struct sockaddr*)&cmdSockName,sizeof(cmdSockName))<0) { perror("sending datagram"); - printf("Is the remote application running?\n"); + printf("Is the remote application (OpenBTS daemon) running?\n"); continue; } free(cmd); diff --git a/apps/OpenBTSDo.cpp b/apps/OpenBTSDo.cpp index c609ec8..4109040 100644 --- a/apps/OpenBTSDo.cpp +++ b/apps/OpenBTSDo.cpp @@ -34,7 +34,7 @@ #include -#define DEFAULT_CMD_PATH "/var/run/command" +#define DEFAULT_CMD_PATH "/var/run/OpenBTS/command" int main(int argc, char *argv[]) { diff --git a/apps/iptables.rules b/apps/iptables.rules index b02ca95..4351407 100644 --- a/apps/iptables.rules +++ b/apps/iptables.rules @@ -3,7 +3,7 @@ :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] --A POSTROUTING -o wlan0 -j MASQUERADE +-A POSTROUTING -o eth0 -j MASQUERADE COMMIT # Generated by iptables-save v1.4.4 *filter diff --git a/apps/openbts.conf b/apps/openbts.conf new file mode 100644 index 0000000..3c13517 --- /dev/null +++ b/apps/openbts.conf @@ -0,0 +1,19 @@ +# openbts - Range Networks RAN for GSM and GPRS +# +# This service runs openbts from the point the system is +# started until it is shut down again. + +start on stopped rc RUNLEVEL=[2345] +stop on runlevel [!2345] + +respawn +script + cd /OpenBTS +# Uncomment this line and comment out the default ./OpenBTS line to enable automatic gdb backtraces to /OpenBTS/gdb.txt (via .gdbinit) +# exec gdb + exec ./OpenBTS +end script + +post-stop script + if pgrep transceiver; then killall transceiver; fi +end script diff --git a/apps/openbtsconfig b/apps/openbtsconfig new file mode 100755 index 0000000..5de462b --- /dev/null +++ b/apps/openbtsconfig @@ -0,0 +1,7 @@ +#!/bin/bash +if [ $# -eq '1' ] +then + sqlite3 /etc/OpenBTS/OpenBTS.db "select KEYSTRING,VALUESTRING from CONFIG where KEYSTRING like '%$1%'"; +else + sqlite3 /etc/OpenBTS/OpenBTS.db "insert or replace into CONFIG (VALUESTRING,KEYSTRING) values ('$2','$1')"; +fi diff --git a/apps/rsyslogd.OpenBTS.conf b/apps/rsyslogd.OpenBTS.conf index c756a11..b8e6de1 100644 --- a/apps/rsyslogd.OpenBTS.conf +++ b/apps/rsyslogd.OpenBTS.conf @@ -1,4 +1,9 @@ # OpenBTS logging controls +# (pat) These options increase the rate-limit on number of messages per second. +# See http://www.rsyslog.com/changing-the-settings/ +$SystemLogRateLimitInterval 2 +$SystemLogRateLimitBurst 100000 + local7.* /var/log/OpenBTS.log diff --git a/apps/runloop.OpenBTS.sh b/apps/runloop.OpenBTS.sh deleted file mode 100755 index 61533ef..0000000 --- a/apps/runloop.OpenBTS.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# A script to restart and just keep OpenBTS running. -while true; -do - # (pat) Do not remove this 'killall OpenBTS'. If removed, the second and later iterations of this loop may - # be unable to start GPRS service. The OpenBTS.log will include: ggsn: ERROR: Could not open tun device sgsntun - killall OpenBTS - killall wget; - sleep 1; - ./OpenBTS; -done diff --git a/configure.ac b/configure.ac index 40608d5..bb8e005 100644 --- a/configure.ac +++ b/configure.ac @@ -18,11 +18,10 @@ dnl You should have received a copy of the GNU General Public License dnl along with this program. If not, see . dnl -AC_INIT(openbts-public,TRUNK) +AC_INIT(openbts,4.0TRUNK) AC_PREREQ(2.57) AC_CONFIG_SRCDIR([config/Makefile.am]) -AC_CONFIG_AUX_DIR([.]) -AC_CONFIG_MACRO_DIR([config]) +AC_CONFIG_AUX_DIR([.]) AM_CONFIG_HEADER(config.h) AC_CANONICAL_BUILD @@ -31,12 +30,17 @@ AC_CANONICAL_TARGET AM_INIT_AUTOMAKE +dnl Checks for programs. AM_PROG_AS AC_PROG_CXX AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_INSTALL AC_PATH_PROG([RM_PROG], [rm]) +#AC_PROG_AWK +#AC_PROG_CC +#AC_PROG_CPP +#AC_PROG_RANLIB AC_LIBTOOL_WIN32_DLL AC_ENABLE_SHARED dnl do build shared libraries @@ -47,6 +51,7 @@ dnl Checks for header files. AC_HEADER_STDC dnl This is required for GnuRadio includes to understand endianess correctly: AC_CHECK_HEADERS([byteswap.h]) +#AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/file.h sys/ioctl.h sys/mount.h sys/param.h sys/socket.h sys/time.h syslog.h unistd.h utime.h]) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -89,13 +94,72 @@ AS_IF([test "x$with_singledb" = "xyes"], [ AM_CONDITIONAL(UHD, [test "x$with_uhd" = "xyes"]) AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "xyes"]) -# Defines LIBUSB_TRANSFER_CANCELLED, LIBUSB_TRANSFER_COMPLETED, LIBUSB_SUCCESS, LIBUSB_ERROR_* -PKG_CHECK_MODULES(LIBUSB, libusb-1.0) +dnl Check for libzmq +if test ! -r "/usr/include/zmq.h" -a ! -r "/usr/local/include/zmq.h"; then + AC_MSG_ERROR([/usr/local/include/zmq.h not found. Install the range-libzmq package or manually build and install with $ sudo ./NodeManager/install_libzmq.sh]) +fi +if test ! -r "/usr/include/zmq.hpp" -a ! -r "/usr/local/include/zmq.hpp"; then + AC_MSG_ERROR([/usr/local/include/zmq.hpp not found. Install the range-libzmq package or manually build and install with $ sudo ./NodeManager/install_libzmq.sh]) +fi +AC_CHECK_LIB(zmq, zmq_init, ,[AC_MSG_ERROR([Cannot link with -lzmq. Install the range-libzmq package or manually build and install with $ sudo ./NodeManager/install_libzmq.sh])]) +AC_MSG_CHECKING([whether libzmq installation works]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [zmq_init(1);]) + ], + [AC_MSG_RESULT([yes])], + [AC_MSG_ERROR([no. Install the range-libzmq package or manually build and install with $ sudo ./NodeManager/install_libzmq.sh])]) -# Check for glibc-specific network functions +dnl Check for liba53 +if test ! -r "/usr/include/a53.h" -a ! -r "/usr/local/include/a53.h"; then + AC_MSG_ERROR([/usr/include/a53.h not found. Install the liba53 package or manually build and install with $ sudo ./tools/install_liba53.sh]) +fi +AC_CHECK_LIB(a53, _init, ,[AC_MSG_ERROR([Cannot link with -la53. Install the liba53 package or manually build and install with $ sudo ./tools/install_liba53.sh])]) + +dnl Check for sip+rtp libs +#AC_CHECK_LIB([ortp], [main]) + +dnl Check for other misc libs +#AC_CHECK_LIB([c], [main]) +#AC_CHECK_LIB([dl], [main]) +#AC_CHECK_LIB([pthread], [main]) +#AC_CHECK_LIB([readline], [main]) +#AC_CHECK_LIB([sqlite3], [main]) +#AC_CHECK_LIB([usrp], [main]) + +dnl Checks for typedefs, structures, and compiler characteristics. +#AC_HEADER_STDBOOL +#AC_TYPE_UID_T +#AC_C_INLINE +#AC_TYPE_INT16_T +#AC_TYPE_INT32_T +#AC_TYPE_INT64_T +#AC_TYPE_INT8_T +#AC_TYPE_MODE_T +#AC_TYPE_OFF_T +#AC_TYPE_PID_T +#AC_TYPE_SIZE_T +#AC_TYPE_SSIZE_T +#AC_CHECK_MEMBERS([struct stat.st_blksize]) +#AC_TYPE_UINT16_T +#AC_TYPE_UINT32_T +#AC_TYPE_UINT64_T +#AC_TYPE_UINT8_T + +dnl Check for glibc-specific network functions AC_CHECK_FUNC(gethostbyname_r, [AC_DEFINE(HAVE_GETHOSTBYNAME_R, 1, Define if libc implements gethostbyname_r)]) AC_CHECK_FUNC(gethostbyname2_r, [AC_DEFINE(HAVE_GETHOSTBYNAME2_R, 1, Define if libc implements gethostbyname2_r)]) +dnl Checks for library functions. +#AC_FUNC_ALLOCA +#AC_FUNC_ERROR_AT_LINE +#AC_FUNC_FORK +#AC_FUNC_MALLOC +#AC_FUNC_MMAP +#AC_FUNC_REALLOC +#AC_FUNC_STRERROR_R +#AC_FUNC_STRTOD +#AC_CHECK_FUNCS([bzero clock_gettime dup2 fdatasync floor gethostbyname getpagesize gettimeofday inet_ntoa localtime_r memchr memmove memset mkdir munmap pow regcomp rint select setenv socket sqrt strcasecmp strchr strdup strerror strncasecmp strpbrk strrchr strstr strtol strtoul sysinfo utime]) + dnl Output files AC_CONFIG_FILES([\ Makefile \ @@ -105,6 +169,7 @@ AC_CONFIG_FILES([\ CommonLibs/Makefile \ Globals/Makefile \ Control/Makefile \ + GSMShare/Makefile \ GSM/Makefile \ GPRS/Makefile \ SGSNGGSN/Makefile \ @@ -115,7 +180,8 @@ AC_CONFIG_FILES([\ TRXManager/Makefile \ CLI/Makefile \ Peering/Makefile \ - SR/Makefile \ + NodeManager/Makefile \ + Scanning/Makefile \ sqlite3/Makefile \ ]) diff --git a/ctags.sh b/ctags.sh index 1ddd21e..439ff14 100644 --- a/ctags.sh +++ b/ctags.sh @@ -1,11 +1,11 @@ #DIRS="AsteriskConfig tools" -DIRS="CLI CommonLibs Control GPRS GSM Globals Peering - SGSNGGSN SIP SMS SR TRXManager TransceiverRAD1 apps" +DIRS="CLI CommonLibs Control GPRS GSM GSMShare Globals Peering + SGSNGGSN SIP SMS SR TRXManager TransceiverRAD1 apps # sqlite3 doc pat files="" for dir in $DIRS;do - files="$files $dir/*.h $dir/*.cpp" + files="$files $dir/*.h $dir/*.c*" done eval echo $files diff --git a/debian/changelog b/debian/changelog index 7af74f2..5899b7a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,11 +1,5 @@ -openbts-public (3.2) UNRELEASED; urgency=low - - * Public sync - - -- Kurtis Thu, 01 Aug 2013 20:37:03 -0700 - -openbts-public (3.2) UNRELEASED; urgency=low +openbts (4.0) unstable; urgency=low * Test - -- Donald C. Kirker Mon, 22 Apr 2013 00:11:00 -0700 + -- Donald C. Kirker Mon, 22 Apr 2013 00:11:00 -0700 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index d00491f..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/debian/control b/debian/control index 17c47c6..9382e70 100755 --- a/debian/control +++ b/debian/control @@ -1,18 +1,15 @@ -Source: openbts-public -Provides: openbts +Source: openbts Section: comm Priority: optional Maintainer: Range Networks, Inc. Homepage: http://www.rangenetworks.com/ -Build-Depends: build-essential, debhelper (>= 7), libosip2-dev, pkg-config, autoconf +Build-Depends: build-essential, debhelper (>= 7), pkg-config, autoconf, libtool, libortp-dev, libusb-1.0-0-dev, libreadline-dev Standards-Version: 3.7.3 -Package: openbts-public -Provides: openbts -Version: TRUNK +Package: openbts Section: comm Priority: optional -Architecture: any +Architecture: i386 Essential: no -Depends: sqlite3 (>= 3.7), libosip2-4, libusb-1.0-0, libortp8, libglib2.0-0, libgl1-mesa-glx, libc6, libasound2, pkg-config, libpcre3, gawk, screen -Description: OpenBTS Public software. +Depends: sqlite3, libusb-1.0-0, libortp8, libc6-i686, pkg-config, range-libzmq, range-liba53, libreadline6 +Description: Range Networks - OpenBTS GSM+GPRS Radio Access Network Node diff --git a/debian/postinst b/debian/postinst index e8ad1e0..d66d132 100755 --- a/debian/postinst +++ b/debian/postinst @@ -20,20 +20,20 @@ configure() { -DB_LOC=/etc/OpenBTS/OpenBTS.db -DATE=$(date +'%Y-%m-%d.%H:%M:%S') -CONFIG_BACKUP=$DB_LOC.dump-$DATE +DATE=$(date --rfc-3339='date') +CONFIG_BACKUP=/etc/OpenBTS/OpenBTS.dump-$DATE + if [ ! -e $CONFIG_BACKUP ]; then - sqlite3 $DB_LOC ".dump" > $CONFIG_BACKUP + sqlite3 /etc/OpenBTS/OpenBTS.db ".dump" > $CONFIG_BACKUP fi -sqlite3 $DB_LOC ".read /etc/OpenBTS/OpenBTS.example.sql" > /dev/null 2>&1 +sqlite3 /etc/OpenBTS/OpenBTS.db ".read /etc/OpenBTS/OpenBTS.example.sql" > /dev/null 2>&1 -#chown openbts:openbts /home/openbts/CLI -#chown openbts:openbts /home/openbts/openbtsconfig -#chown openbts:openbts /home/openbts/startBTS -#chown openbts:openbts /home/openbts/killBTS +chown openbts:openbts /home/openbts/CLI +chown openbts:openbts /home/openbts/openbtsconfig +chown openbts:openbts /home/openbts/startBTS +chown openbts:openbts /home/openbts/killBTS } diff --git a/debian/postinst.old b/debian/postinst.old new file mode 100755 index 0000000..32bce3d --- /dev/null +++ b/debian/postinst.old @@ -0,0 +1,24 @@ +#!/bin/bash + +INSTALL_DIR=/OpenBTS +DATE=$(date --rfc-3339='date') +BACKUP_DIR=$INSTALL_DIR/backup_$DATE +CONFIG_BACKUP=/etc/OpenBTS/OpenBTS.dump-$DATE + +if [ ! -e $BACKUP_DIR ]; then + mkdir $BACKUP_DIR/ + + mv $INSTALL_DIR/OpenBTS $BACKUP_DIR/ + mv $INSTALL_DIR/transceiver $BACKUP_DIR/ +fi + + + +if [ ! -e $CONFIG_BACKUP ]; then + sqlite3 /etc/OpenBTS/OpenBTS.db ".dump" > $CONFIG_BACKUP +fi + +sqlite3 /etc/OpenBTS/OpenBTS.db ".read /etc/OpenBTS/OpenBTS_3.0.0.sql" &>/dev/null + + + diff --git a/debian/preinst b/debian/preinst index 836976b..30e247c 100755 --- a/debian/preinst +++ b/debian/preinst @@ -15,9 +15,8 @@ set -e install() { - INSTALL_DIR=/OpenBTS -DATE=$(date +'%Y-%m-%d.%H:%M:%S') +DATE=$(date --rfc-3339='date') BACKUP_DIR=$INSTALL_DIR/backup_$DATE if [ -f $INSTALL_DIR/OpenBTS -a -f $INSTALL_DIR/transceiver ]; then diff --git a/debian/prerm b/debian/prerm index 29b570a..2de69b1 100755 --- a/debian/prerm +++ b/debian/prerm @@ -16,20 +16,14 @@ set -e # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package -APP=OpenBTS - -remove() -{ - if [ "$(pidof $APP)" ]; then - killall $APP - else - echo "$APP not running" - fi -} +# remove() +# { +# killall runloop.OpenBTS.sh &>/dev/null +# } case "$1" in remove|upgrade|deconfigure) - remove +# remove ;; failed-upgrade) diff --git a/debian/prerm.old b/debian/prerm.old new file mode 100755 index 0000000..7e60d4a --- /dev/null +++ b/debian/prerm.old @@ -0,0 +1,7 @@ +#!/bin/bash + +# killall runloop.OpenBTS.sh &>/dev/null + + + + diff --git a/debian/rules b/debian/rules index 3429555..a96d07a 100755 --- a/debian/rules +++ b/debian/rules @@ -108,8 +108,7 @@ binary-common: # dh_perl dh_makeshlibs dh_installdeb -# dh_shlibdeps - dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb diff --git a/sqlite3 b/sqlite3 index 3d0dbe8..effc8fe 160000 --- a/sqlite3 +++ b/sqlite3 @@ -1 +1 @@ -Subproject commit 3d0dbe8e7a819585cac5064beabe9b22f8d47235 +Subproject commit effc8fe4744285c07e3710ab97231537a27b7997 diff --git a/svn.externals b/svn.externals deleted file mode 100644 index 3cc8a80..0000000 --- a/svn.externals +++ /dev/null @@ -1,4 +0,0 @@ -SR http://wush.net/svn/range/software/public/subscriberRegistry/trunk -CommonLibs http://wush.net/svn/range/software/public/CommonLibs/trunk -sqlite3 http://wush.net/svn/range/software/public/sqlite3/trunk -