From 49087580a066cb8c493ab21983c97694dd778501 Mon Sep 17 00:00:00 2001 From: Michael Iedema Date: Wed, 16 Jul 2014 23:57:22 +0200 Subject: [PATCH] merge 5.0 preview from commercial --- .gitignore | 2 +- .gitmodules | 4 - .travis.yml | 6 +- AUTHORS | 1 + CLI/CLI.cpp | 1738 +-------------------- CLI/CLI.h | 48 +- CLI/CLIBase.cpp | 246 +++ CLI/CLICommands.cpp | 1596 +++++++++++++++++++ CLI/CLIServer.cpp | 255 +++ CLI/Makefile.am | 4 + CommonLibs | 2 +- Control/CodecSet.h | 75 + Control/ControlCommon.h | 4 +- Control/ControlTransfer.cpp | 53 +- Control/ControlTransfer.h | 110 +- Control/DCCHDispatch.cpp | 17 +- Control/L3CallControl.cpp | 315 ++-- Control/L3CallControl.h | 7 +- Control/L3Handover.cpp | 446 ++---- Control/L3Handover.h | 33 + Control/L3LogicalChannel.cpp | 97 +- Control/L3LogicalChannel.h | 38 +- Control/L3MMLayer.cpp | 299 +++- Control/L3MMLayer.h | 44 +- Control/L3MobilityManagement.cpp | 110 +- Control/L3MobilityManagement.h | 8 +- Control/L3SMSControl.cpp | 127 +- Control/L3SMSControl.h | 5 +- Control/L3StateMachine.cpp | 209 +-- Control/L3StateMachine.h | 42 +- Control/L3SupServ.cpp | 27 +- Control/L3SupServ.h | 6 +- Control/L3TermCause.cpp | 486 ++++++ Control/L3TermCause.h | 97 ++ Control/L3TranEntry.cpp | 1321 +++++++--------- Control/L3TranEntry.h | 299 ++-- Control/L3Utils.cpp | 7 +- Control/L3Utils.h | 5 +- Control/Makefile.am | 21 +- Control/PagingEntry.cpp | 85 + Control/PagingEntry.h | 81 + Control/RRLPServer.cpp | 281 ---- Control/RRLPServer.h | 61 - Control/RRLP_PDU_Test.cpp | 15 + Control/SMSCB.cpp | 4 +- Control/TMSITable.cpp | 22 +- Control/TMSITable.h | 7 +- GPRS/BSSG.cpp | 7 +- GPRS/BSSG.h | 7 +- GPRS/BSSGMessages.cpp | 7 +- GPRS/BSSGMessages.h | 5 +- GPRS/ByteVector.cpp | 5 +- GPRS/ByteVector.h | 5 +- GPRS/FEC.cpp | 11 +- GPRS/FEC.h | 8 +- GPRS/GPRSCLI.cpp | 9 +- GPRS/GPRSExport.h | 19 +- GPRS/GPRSInternal.h | 9 +- GPRS/GPRSRLC.h | 7 +- GPRS/GPRSTDMA.h | 5 +- GPRS/MAC.cpp | 302 +--- GPRS/MAC.h | 34 +- GPRS/MSInfo.cpp | 20 +- GPRS/MSInfo.h | 19 +- GPRS/Makefile.am | 3 +- GPRS/MsgBase.cpp | 7 +- GPRS/MsgBase.h | 5 +- GPRS/RLC.cpp | 7 +- GPRS/RLCEngine.cpp | 7 +- GPRS/RLCEngine.h | 5 +- GPRS/RLCHdr.h | 18 +- GPRS/RLCMessages.cpp | 11 +- GPRS/RLCMessages.h | 7 +- GPRS/RList.h | 5 +- GPRS/ScalarTypes.h | 5 +- GPRS/TBF.cpp | 294 ++-- GPRS/TBF.h | 29 +- GPRS/notes.txt | 69 + GSM/AppInfTest.cpp | 17 + GSM/GSM610Tables.cpp | 5 +- GSM/GSM610Tables.h | 3 +- GSM/GSMCCCH.cpp | 576 +++++++ GSM/GSMCCCH.h | 96 ++ GSM/GSMChannelHistory.cpp | 210 +++ GSM/GSMChannelHistory.h | 172 ++ GSM/GSMCommon.cpp | 57 +- GSM/GSMCommon.h | 54 +- GSM/GSMConfig.cpp | 343 ++-- GSM/GSMConfig.h | 114 +- GSM/GSML1FEC.cpp | 660 ++++---- GSM/GSML1FEC.h | 351 +++-- GSM/GSML2LAPDm.cpp | 371 +++-- GSM/GSML2LAPDm.h | 106 +- GSM/GSML3CCElements.cpp | 29 +- GSM/GSML3CCElements.h | 110 +- GSM/GSML3CCMessages.cpp | 11 +- GSM/GSML3CCMessages.h | 43 +- GSM/GSML3CommonElements.cpp | 15 +- GSM/GSML3CommonElements.h | 8 +- GSM/GSML3GPRSElements.cpp | 5 +- GSM/GSML3GPRSElements.h | 3 +- GSM/GSML3MMElements.cpp | 25 +- GSM/GSML3MMElements.h | 51 +- GSM/GSML3MMMessages.cpp | 12 +- GSM/GSML3MMMessages.h | 15 +- GSM/GSML3Message.cpp | 7 +- GSM/GSML3Message.h | 8 +- GSM/GSML3RRElements.cpp | 93 +- GSM/GSML3RRElements.h | 140 +- GSM/GSML3RRMessages.cpp | 47 +- GSM/GSML3RRMessages.h | 41 +- GSM/GSML3SSMessages.cpp | 14 +- GSM/GSML3SSMessages.h | 18 +- GSM/GSMLogicalChannel.cpp | 1181 ++++++++------ GSM/GSMLogicalChannel.h | 657 ++++---- GSM/GSMRadioResource.cpp | 173 ++ GSM/GSMRadioResource.h | 115 ++ GSM/GSMSAPMux.cpp | 109 -- GSM/GSMSAPMux.h | 127 -- GSM/GSMSMSCBL3Messages.cpp | 5 +- GSM/GSMSMSCBL3Messages.h | 3 +- GSM/GSMTAPDump.cpp | 5 +- GSM/GSMTAPDump.h | 4 +- GSM/GSMTDMA.cpp | 29 +- GSM/GSMTDMA.h | 13 +- GSM/GSMTransfer.cpp | 69 +- GSM/GSMTransfer.h | 215 ++- GSM/Makefile.am | 11 +- GSM/PhysicalStatus.cpp | 41 +- GSM/PhysicalStatus.h | 10 +- GSM/PowerManager.cpp | 111 +- GSM/PowerManager.h | 64 +- GSMShare/A51.cpp | 2 +- GSMShare/A51Test.cpp | 6 +- GSMShare/AMRTest.cpp | 7 +- GSMShare/AmrCoder.cpp | 4 +- GSMShare/AmrCoder.h | 4 +- GSMShare/GSM503Tables.cpp | 4 +- GSMShare/GSM503Tables.h | 4 +- GSMShare/L3Enums.cpp | 250 +++ GSMShare/L3Enums.h | 273 ++++ GSMShare/Makefile.am | 34 +- GSMShare/Viterbi.h | 2 +- GSMShare/ViterbiR204.cpp | 1 + GSMShare/ViterbiR204.h | 1 + GSMShare/ViterbiTest.cpp | 5 +- Globals/GlobalVars.cpp | 86 + Globals/Globals.cpp | 62 +- Globals/Globals.h | 18 +- Globals/GrabRepoInfo.sh | 21 + Globals/Makefile.am | 3 +- LEGAL | 2 +- Makefile.am | 9 +- Makefile.common | 13 +- NodeManager | 2 +- Peering/Makefile.am | 2 +- Peering/NeighborTable.cpp | 488 ++++-- Peering/NeighborTable.h | 101 +- Peering/Peering.cpp | 216 ++- Peering/Peering.h | 20 +- README | 8 +- README.APIs | 52 + SGSNGGSN/GPRSL3Messages.cpp | 5 +- SGSNGGSN/GPRSL3Messages.h | 6 +- SGSNGGSN/Ggsn.cpp | 18 +- SGSNGGSN/Ggsn.h | 5 +- SGSNGGSN/LLC.cpp | 5 +- SGSNGGSN/LLC.h | 5 +- SGSNGGSN/Makefile.am | 2 +- SGSNGGSN/Sgsn.cpp | 19 +- SGSNGGSN/Sgsn.h | 5 +- SGSNGGSN/SgsnBase.h | 5 +- SGSNGGSN/SgsnCli.cpp | 5 +- SGSNGGSN/SgsnExport.h | 6 +- SGSNGGSN/iputils.cpp | 5 +- SGSNGGSN/miniggsn.cpp | 9 +- SGSNGGSN/miniggsn.h | 5 +- SIP/Makefile.am | 7 +- SIP/SIP2Interface.cpp | 148 +- SIP/SIP2Interface.h | 65 +- SIP/SIPBase.cpp | 1105 +------------ SIP/SIPBase.h | 337 +--- SIP/SIPDialog.cpp | 880 ++++++++++- SIP/SIPDialog.h | 173 +- SIP/SIPExport.h | 17 +- SIP/SIPMessage.cpp | 93 +- SIP/SIPMessage.h | 50 +- SIP/SIPParse.cpp | 130 +- SIP/SIPParse.h | 220 ++- SIP/SIPRtp.cpp | 438 ++++++ SIP/SIPRtp.h | 96 ++ SIP/SIPTransaction.cpp | 131 +- SIP/SIPTransaction.h | 16 +- SIP/SIPUtility.cpp | 47 +- SIP/SIPUtility.h | 14 +- SMS/Makefile.am | 1 + SMS/SMSMessages.cpp | 17 +- SMS/SMSMessages.h | 3 +- SMS/SMSTransfer.cpp | 4 +- SMS/SMSTransfer.h | 11 +- Scanning/Makefile.am | 1 + Scanning/Scanning.cpp | 14 +- Scanning/Scanning.h | 14 +- TRXManager/Makefile.am | 1 + TRXManager/TRXManager.cpp | 30 +- TRXManager/TRXManager.h | 3 +- TransceiverRAD1/Complex.h | 3 +- TransceiverRAD1/DummyLoad.cpp | 1 + TransceiverRAD1/DummyLoad.h | 1 + TransceiverRAD1/FactoryCalibration.cpp | 4 +- TransceiverRAD1/FactoryCalibration.h | 4 +- TransceiverRAD1/Makefile.am | 1 + TransceiverRAD1/PowerScanner.cpp | 2 +- TransceiverRAD1/RAD1Cmd.cpp | 3 +- TransceiverRAD1/RAD1Device.cpp | 56 +- TransceiverRAD1/RAD1Device.h | 15 +- TransceiverRAD1/RAD1RxRawPower.cpp | 16 + TransceiverRAD1/RAD1RxRawPowerSweep.cpp | 1 + TransceiverRAD1/RAD1SN.cpp | 3 +- TransceiverRAD1/RAD1ping.cpp | 5 +- TransceiverRAD1/Transceiver.cpp | 18 +- TransceiverRAD1/Transceiver.h | 3 +- TransceiverRAD1/burn-rnrad1-eeprom.sh | 14 + TransceiverRAD1/commands.h | 15 + TransceiverRAD1/interfaces.h | 15 + TransceiverRAD1/radioDevice.h | 3 +- TransceiverRAD1/radioInterface.cpp | 10 +- TransceiverRAD1/radioInterface.h | 9 +- TransceiverRAD1/rnrad1.h | 14 + TransceiverRAD1/rnrad1Core.cpp | 17 +- TransceiverRAD1/rnrad1Core.h | 15 + TransceiverRAD1/rnrad1Rx.cpp | 14 + TransceiverRAD1/rnrad1Tx.cpp | 15 + TransceiverRAD1/runTransceiver.cpp | 44 +- TransceiverRAD1/sigProcLib.cpp | 6 +- TransceiverRAD1/sigProcLib.h | 7 +- TransceiverRAD1/sigProcLibTest.cpp | 6 +- apps/.gdbinit | 6 + apps/GetConfigurationKeys.cpp | 1911 +++++++++++++---------- apps/JSONEventsClient.cpp | 40 + apps/Makefile.am | 30 +- apps/OpenBTS.cpp | 504 +++--- apps/OpenBTS.example.sql | 65 +- apps/OpenBTSCLI.cpp | 422 +++-- apps/OpenBTSConfig.h | 60 + apps/OpenBTSDo.cpp | 100 -- apps/README.DatabaseCreation | 14 + config/Makefile.am | 1 + configure.ac | 12 +- debian/changelog | 2 +- debian/control | 4 +- debian/postinst | 3 - debian/rules | 2 +- doc/Makefile.am | 1 + sqlite3 | 1 - 255 files changed, 15407 insertions(+), 10471 deletions(-) create mode 100644 CLI/CLIBase.cpp create mode 100644 CLI/CLICommands.cpp create mode 100644 CLI/CLIServer.cpp create mode 100644 Control/CodecSet.h create mode 100644 Control/L3Handover.h create mode 100644 Control/L3TermCause.cpp create mode 100644 Control/L3TermCause.h create mode 100644 Control/PagingEntry.cpp create mode 100644 Control/PagingEntry.h delete mode 100644 Control/RRLPServer.cpp delete mode 100644 Control/RRLPServer.h create mode 100644 GSM/GSMCCCH.cpp create mode 100644 GSM/GSMCCCH.h create mode 100644 GSM/GSMChannelHistory.cpp create mode 100644 GSM/GSMChannelHistory.h create mode 100644 GSM/GSMRadioResource.cpp create mode 100644 GSM/GSMRadioResource.h delete mode 100644 GSM/GSMSAPMux.cpp delete mode 100644 GSM/GSMSAPMux.h create mode 100644 GSMShare/L3Enums.cpp create mode 100644 GSMShare/L3Enums.h create mode 100644 Globals/GlobalVars.cpp create mode 100644 README.APIs create mode 100644 SIP/SIPRtp.cpp create mode 100644 SIP/SIPRtp.h create mode 100644 apps/.gdbinit create mode 100644 apps/JSONEventsClient.cpp create mode 100644 apps/OpenBTSConfig.h delete mode 100644 apps/OpenBTSDo.cpp create mode 100644 apps/README.DatabaseCreation delete mode 160000 sqlite3 diff --git a/.gitignore b/.gitignore index e777601..b2c4f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ TransceiverRAD1/sigProcLibTest TransceiverRAD1/transceiver apps/OpenBTS apps/OpenBTSCLI -apps/OpenBTSDo +apps/JSONEventsClient GSMShare/A51Test GSMShare/AMRTest GSMShare/ViterbiTest diff --git a/.gitmodules b/.gitmodules index 26a0cda..7ad1403 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = CommonLibs url = git@github.com:RangeNetworks/CommonLibs.git branch = master -[submodule "sqlite3"] - path = sqlite3 - url = git@github.com:RangeNetworks/sqlite3.git - branch = master [submodule "NodeManager"] path = NodeManager url = git@github.com:RangeNetworks/NodeManager.git diff --git a/.travis.yml b/.travis.yml index d08f3da..9c41755 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,11 +21,6 @@ before_install: - dpkg-buildpackage - cd .. - sudo dpkg -i liba53*.deb - - git clone https://github.com/RangeNetworks/libzmq.git - - cd libzmq - - ./build.sh - - sudo dpkg -i *.deb - - cd .. - git clone https://github.com/RangeNetworks/libcoredumper.git - cd libcoredumper - ./build.sh @@ -33,6 +28,7 @@ before_install: - cd .. - sed -i 's/git@github.com:/git:\/\/github.com\//' .gitmodules - git submodule update --init --recursive + - sudo ./NodeManager/install_libzmq.sh #install: # - sh bootstrap.sh diff --git a/AUTHORS b/AUTHORS index e3d7ae4..dd09d10 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,6 @@ # # Copyright 2008, 2009 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This file is part of GNU Radio # diff --git a/CLI/CLI.cpp b/CLI/CLI.cpp index 1f49173..a2ba33b 100644 --- a/CLI/CLI.cpp +++ b/CLI/CLI.cpp @@ -16,56 +16,18 @@ */ -#include -#include -#include -#include -#include #include -#include -#include -#include // for sort() - -#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 { - // Hack. - extern CommandLine::CLIStatus sgsnCLI(int argc, char **argv, std::ostream &os); -}; +#include +#include +#include #include - +#include #include "CLI.h" -#undef WARNING - -extern TransceiverManager gTRX; - namespace CommandLine { -using namespace std; -using namespace Control; + +using std::map; using std::string; using std::ostream; /** Standard responses in the CLI, much mach erorrCode enum. */ static const char* standardResponses[] = { @@ -78,12 +40,6 @@ static const char* standardResponses[] = { }; -struct CLIParseError { - string msg; - CLIParseError(string wMsg) : msg(wMsg) {} -}; - - CLIStatus Parser::execute(char* line, ostream& os) const { @@ -153,9 +109,10 @@ CLIStatus Parser::process(const char* line, ostream& os) const static Mutex oneCommandAtATime; ScopedLock lock(oneCommandAtATime); char *newLine = strdup(line); + LOG(INFO) << "CLI executing command:" <<(line?line:"null"); CLIStatus retVal = execute(newLine,os); free(newLine); - if (retVal < 0 || retVal > FAILURE) { + if (retVal < CLI_EXIT || retVal > FAILURE) { os << "Unrecognized CLI command exit status: "<process(inbuf, cout, cin)<0) { free(inbuf); break; } @@ -196,10 +154,8 @@ static void *commandLineFunc(void *arg) char inbuf[1024]; cin.clear(); // Control-D may set the eof bit which causes getline to return immediately. Fix it. cin.getline(inbuf,1024,'\n'); // istream::getline - if (!cin.fail()) { - // The parser returns -1 on exit. - if (gParser.process(inbuf,cout)<0) break; - } + // The parser returns -1 on exit. + if (parser->process(inbuf,cout)<0) break; sleep(1); // in case something goofs up here, dont steal all the cpu cycles. } #endif @@ -215,7 +171,7 @@ static void *commandLineFunc(void *arg) void Parser::startCommandLine() // (pat) Start a simple command line processor as a separate thread. { static Thread commandLineThread; - commandLineThread.start(commandLineFunc,NULL); + commandLineThread.start( (void*(*)(void*)) commandLineFunc,this); } @@ -226,13 +182,30 @@ const char * Parser::help(const string& cmd) const return hp->second.c_str(); } +void Parser::printHelp(ostream &os) const +{ + ParseTable::const_iterator cp = this->begin(); + os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl; + int c=0; + const int cols = 3; + while (cp != this->end()) { + const string& wd = cp->first; + os << wd << '\t'; + if (wd.size()<8) os << '\t'; + ++cp; + c++; + if (c%cols==0) os << endl; + } + if (c%cols!=0) os << endl; +} + // Parse options in optstring out of argc,argv. // The optstring is a space separated list of options. The options need not start with '-'. To recognize just "-" or "--" just add it in. // Return a map containing the options found; if option in optstring was followed by ':', map value will be the next argv argument, otherwise "true". // Leave argc,argv pointing at the first argument after the options, ie, on return argc is the number of non-option arguments remaining in argv. // This routine does not allow combining options, ie, -a -b != -ab -static map cliParse(int &argc, char **&argv, ostream &os, const char *optstring) +map cliParse(int &argc, char **&argv, ostream &os, const char *optstring) { map options; // The result // Skip the command name. @@ -268,1651 +241,4 @@ static map cliParse(int &argc, char **&argv, ostream &os, const c return options; } - -/**@name Commands for the CLI. */ -//@{ - -// forward refs -static CLIStatus printStats(int argc, char** argv, ostream& os); - -/* - A CLI command takes the argument in an array. - It returns 0 on success. -*/ - -/** Display system uptime and current GSM frame number. */ -static CLIStatus uptime(int argc, char** argv, ostream& os) -{ - if (argc!=1) return BAD_NUM_ARGS; - os.precision(2); - - time_t now = time(NULL); - const char* timestring = ctime(&now); - // no endl since ctime includes a "\n" in the string - os << "Unix time " << now << ", " << timestring; - - os << "watchdog timer expires in " << (gWatchdogRemaining() / 60) << " minutes" << endl; - - int seconds = gBTS.uptime(); - if (seconds<120) { - os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl; - return SUCCESS; - } - float minutes = seconds / 60.0F; - if (minutes<120) { - os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl; - return SUCCESS; - } - float hours = minutes / 60.0F; - if (hours<48) { - os << "uptime " << hours << " hours, frame " << gBTS.time() << endl; - return SUCCESS; - } - float days = hours / 24.0F; - os << "uptime " << days << " days, frame " << gBTS.time() << endl; - - return SUCCESS; -} - - -/** Give a list of available commands or describe a specific command. */ -static CLIStatus showHelp(int argc, char** argv, ostream& os) -{ - if (argc==2) { - os << argv[1] << " " << gParser.help(argv[1]) << endl; - return SUCCESS; - } - if (argc!=1) return BAD_NUM_ARGS; - ParseTable::const_iterator cp = gParser.begin(); - os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl; - int c=0; - const int cols = 3; - while (cp != gParser.end()) { - const string& wd = cp->first; - os << wd << '\t'; - if (wd.size()<8) os << '\t'; - ++cp; - c++; - if (c%cols==0) os << endl; - } - if (c%cols!=0) os << endl; - return SUCCESS; -} - - - -/** A function to return -1, the exit code for the caller. */ -static CLIStatus exit_function(int argc, char** argv, ostream& os) -{ - unsigned wait =0; - if (argc>2) return BAD_NUM_ARGS; - if (argc==2) wait = atoi(argv[1]); - - if (wait!=0) - os << "waiting up to " << wait << " seconds for clearing of " - << gBTS.TCHActive() << " active calls" << endl; - - // Block creation of new channels. - gBTS.hold(true); - // Wait up to the timeout for active channels to release. - time_t finish = time(NULL) + wait; - while (time(NULL)0) { - LOG(WARNING) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit"; - loads = true; - } - if (gBTS.TCHActive()>0) { - LOG(WARNING) << "dropping " << gBTS.TCHActive() << " calls on exit"; - loads = true; - } - if (loads) { - os << endl << "exiting with loads:" << endl; - printStats(1,NULL,os); - } - 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; -} - - -/** Print or clear the TMSI table. */ -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) -{ - // (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 (options.count("dump")) { - ofstream fileout; - string filename = options["dump"]; - if (filename.size() == 0) { os << "bad filename"<;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, 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; - -#if 0 - int numRead = sock.read(buffer,10000); - if (numRead>=0) { - buffer[numRead]='\0'; - os << "response: " << buffer << endl; - } else { - os << "timed out waiting for response"; - } -#endif - - return SUCCESS; -} - - -/** 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; - - char *IMSI = argv[1]; - char *srcAddr = argv[2]; - string rest = ""; - for (int i=3; i 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. */ -// (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; - 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. */ -static CLIStatus rawconfig(int argc, char** argv, ostream& os) -{ - // no args, just print - if (argc==1) { - gConfig.find("",os); - return SUCCESS; - } - - // one arg, pattern match and print - if (argc==2) { - gConfig.find(argv[1],os); - return SUCCESS; - } - - // >1 args: set new value - string val; - for (int i=2; igetFactoryCalibration("sdrsn"); - if (val == 0 || val == 65535) { - os << "Reading factory calibration not supported on this radio." << endl; - return SUCCESS; - } - os << "Factory Information" << endl; - os << " SDR Serial Number = " << val << endl; - - val = gTRX.ARFCN(0)->getFactoryCalibration("rfsn"); - os << " RF Serial Number = " << val << endl; - - val = gTRX.ARFCN(0)->getFactoryCalibration("band"); - os << " GSM.Radio.Band = "; - if (val == 0) { - os << "multi-band"; - } else { - os << val; - } - os << endl; - - val = gTRX.ARFCN(0)->getFactoryCalibration("rxgain"); - os << " GSM.Radio.RxGain = " << val << endl; - - val = gTRX.ARFCN(0)->getFactoryCalibration("txgain"); - os << " TRX.TxAttenOffset = " << val << endl; - - val = gTRX.ARFCN(0)->getFactoryCalibration("freq"); - os << " TRX.RadioFrequencyOffset = " << val << endl; - - return SUCCESS; -} - -/** Audit the current configuration. */ -static CLIStatus audit(int argc, char** argv, ostream& os) -{ - ConfigurationKeyMap::iterator mp; - stringstream ss; - - // value errors - mp = gConfig.mSchema.begin(); - while (mp != gConfig.mSchema.end()) { - if (!gConfig.isValidValue(mp->first, gConfig.getStr(mp->first))) { - ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl; - } - mp++; - } - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| ERROR : Invalid Values [key current-value (default)] |" << endl; - os << "| To use the default value again, execute: rmconfig key |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - - // factory calibration warnings - signed sdrsn = gTRX.ARFCN(0)->getFactoryCalibration("sdrsn"); - if (sdrsn != 0 && sdrsn != 65535) { - string factoryValue; - string configValue; - - factoryValue = gConfig.mSchema["GSM.Radio.Band"].getDefaultValue(); - configValue = gConfig.getStr("GSM.Radio.Band"); - // only warn on band changes if the unit is not multi-band - if (gTRX.ARFCN(0)->getFactoryCalibration("band") != 0 && configValue.compare(factoryValue) != 0) { - ss << "GSM.Radio.Band \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; - } - - factoryValue = gConfig.mSchema["GSM.Radio.RxGain"].getDefaultValue(); - configValue = gConfig.getStr("GSM.Radio.RxGain"); - if (configValue.compare(factoryValue) != 0) { - ss << "GSM.Radio.RxGain \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; - } - - factoryValue = gConfig.mSchema["TRX.TxAttenOffset"].getDefaultValue(); - configValue = gConfig.getStr("TRX.TxAttenOffset"); - if (configValue.compare(factoryValue) != 0) { - ss << "TRX.TxAttenOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; - } - - factoryValue = gConfig.mSchema["TRX.RadioFrequencyOffset"].getDefaultValue(); - configValue = gConfig.getStr("TRX.RadioFrequencyOffset"); - if (configValue.compare(factoryValue) != 0) { - ss << "TRX.RadioFrequencyOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; - } - - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| WARNING : Factory Radio Calibration [key current-value (factory)] |" << endl; - os << "| To use the factory value again, execute: rmconfig key |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - } - - // cross check warnings - vector allWarnings; - vector warnings; - vector::iterator warning; - mp = gConfig.mSchema.begin(); - while (mp != gConfig.mSchema.end()) { - warnings = gConfig.crossCheck(mp->first); - allWarnings.insert(allWarnings.end(), warnings.begin(), warnings.end()); - mp++; - } - sort(allWarnings.begin(), allWarnings.end()); - allWarnings.erase(unique(allWarnings.begin(), allWarnings.end() ), allWarnings.end()); - warning = allWarnings.begin(); - while (warning != allWarnings.end()) { - ss << *warning << endl; - warning++; - } - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| WARNING : Cross-Check Values |" << endl; - os << "| To quiet these warnings, follow the advice given. |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - - // site-specific values - mp = gConfig.mSchema.begin(); - while (mp != gConfig.mSchema.end()) { - if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) { - if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) == 0) { - ss << mp->first << " \"" << gConfig.mSchema[mp->first].getDefaultValue() << "\"" << endl; - } - } - mp++; - } - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| WARNING : Site Values Which Are Still Default [key current-value] |" << endl; - os << "| These should be set to fit your installation: config key value |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - - - // non-default values - mp = gConfig.mSchema.begin(); - while (mp != gConfig.mSchema.end()) { - if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE) { - if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) != 0) { - ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl; - } - } - mp++; - } - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| INFO : Non-Default Values [key current-value (default)] |" << endl; - os << "| To use the default value again, execute: rmconfig key |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - - // unknown pairs - ConfigurationRecordMap pairs = gConfig.getAllPairs(); - ConfigurationRecordMap::iterator mp2 = pairs.begin(); - while (mp2 != pairs.end()) { - if (!gConfig.keyDefinedInSchema(mp2->first)) { - // also kindly ignore SIM.Prog keys for now so the users don't kill their ability to program SIMs - string family = "SIM.Prog."; - if (mp2->first.substr(0, family.size()) != family) { - ss << mp2->first << " \"" << mp2->second.value() << "\"" << endl; - } - } - mp2++; - } - if (ss.str().length()) { - os << "+---------------------------------------------------------------------+" << endl; - os << "| INFO : Custom/Deprecated Key/Value Pairs [key current-value] |" << endl; - os << "| To clean up any extraneous keys, execute: rmconfig key |" << endl; - os << "+---------------------------------------------------------------------+" << endl; - os << ss.str(); - os << endl; - ss.str(""); - } - - return SUCCESS; -} - -/** Print or modify the global configuration table. */ -CLIStatus _config(string mode, int argc, char** argv, ostream& os) -{ - // no args, just print - if (argc==1) { - ConfigurationKeyMap::iterator mp = gConfig.mSchema.begin(); - while (mp != gConfig.mSchema.end()) { - if (mode.compare("customer") == 0) { - if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) { - ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); - } - } else if (mode.compare("developer") == 0) { - ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); - } - mp++; - } - return SUCCESS; - } - - // one arg - if (argc==2) { - // matches exactly? print single key - if (gConfig.keyDefinedInSchema(argv[1])) { - ConfigurationKey::printKey(gConfig.mSchema[argv[1]], gConfig.getStr(argv[1]), os); - ConfigurationKey::printDescription(gConfig.mSchema[argv[1]], os); - os << endl; - // ...otherwise print all similar keys - } else { - int foundCount = 0; - ConfigurationKeyMap matches = gConfig.getSimilarKeys(argv[1]); - ConfigurationKeyMap::iterator mp = matches.begin(); - while (mp != matches.end()) { - if (mode.compare("customer") == 0) { - if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || - mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) { - ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); - foundCount++; - } - } else if (mode.compare("developer") == 0) { - ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); - foundCount++; - } - mp++; - } - if (!foundCount) { - os << argv[1] << " - no keys matched"; - if (mode.compare("customer") == 0) { - os << ", developer/factory keys can be accessed with \"devconfig.\""; - } else if (mode.compare("developer") == 0) { - os << ", custom keys can be accessed with \"rawconfig.\""; - } - os << endl; - } - } - return SUCCESS; - } - - // >1 args: set new value - string val; - for (int i=2; i warnings = gConfig.crossCheck(argv[1]); - vector::iterator warning = warnings.begin(); - while (warning != warnings.end()) { - os << "WARNING: " << *warning << endl; - warning++; - } - if (gConfig.isStatic(argv[1])) { - os << argv[1] << " is static; change takes effect on restart" << endl; - } - os << argv[1] << " changed from \"" << previousVal << "\" to \"" << val << "\"" << endl; - - return SUCCESS; -} - -/** Print or modify the global configuration table. Customer access. */ -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. */ -static CLIStatus devconfig(int argc, char** argv, ostream& os) -{ - return _config("developer", argc, argv, os); -} - -/** Disable a configuration key. */ -static CLIStatus unconfig(int argc, char** argv, ostream& os) -{ - if (argc!=2) return BAD_NUM_ARGS; - - if (!gConfig.defines(argv[1])) { - os << argv[1] << " is not in the table" << endl; - return BAD_VALUE; - } - - if (gConfig.keyDefinedInSchema(argv[1]) && !gConfig.isValidValue(argv[1], "")) { - os << argv[1] << " is not disableable" << endl; - return BAD_VALUE; - } - - if (!gConfig.set(argv[1], "")) { - os << "DB ERROR: " << argv[1] << " could not be disabled" << endl; - return FAILURE; - } - - os << argv[1] << " disabled" << endl; - - return SUCCESS; -} - - -/** Set a configuration value back to default or remove from table if custom key. */ -static CLIStatus rmconfig(int argc, char** argv, ostream& os) -{ - if (argc!=2) return BAD_NUM_ARGS; - - if (!gConfig.defines(argv[1])) { - os << argv[1] << " is not in the table" << endl; - return BAD_VALUE; - } - - // TODO : removing of default values from DB disabled for now. Breaks webui. - if (gConfig.keyDefinedInSchema(argv[1])) { - if (!gConfig.set(argv[1],gConfig.mSchema[argv[1]].getDefaultValue())) { - os << "DB ERROR: " << argv[1] << " could not be set back to the default value" << endl; - return FAILURE; - } - - os << argv[1] << " set back to its default value" << endl; - vector warnings = gConfig.crossCheck(argv[1]); - vector::iterator warning = warnings.begin(); - while (warning != warnings.end()) { - os << "WARNING: " << *warning << endl; - warning++; - } - if (gConfig.isStatic(argv[1])) { - os << argv[1] << " is static; change takes effect on restart" << endl; - } - return SUCCESS; - } - - if (!gConfig.remove(argv[1])) { - os << "DB ERROR: " << argv[1] << " could not be removed from the configuration table" << endl; - return FAILURE; - } - - os << argv[1] << " removed from the configuration table" << endl; - - return SUCCESS; -} - - - -/** Change the registration timers. */ -static CLIStatus regperiod(int argc, char** argv, ostream& os) -{ - if (argc==1) { - os << "T3212 is " << gConfig.getNum("GSM.Timer.T3212") << " minutes" << endl; - os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod") << " minutes" << endl; - return SUCCESS; - } - - if (argc>3) return BAD_NUM_ARGS; - - unsigned newT3212 = strtol(argv[1],NULL,10); - if (!gConfig.isValidValue("GSM.Timer.T3212", argv[1])) { - os << "valid T3212 range is 6..1530 minutes" << endl; - return BAD_VALUE; - } - - // By default, make SIP registration period 1.5x the GSM registration period. - unsigned SIPRegPeriod = newT3212 * 1.5; - char SIPRegPeriodStr[10]; - sprintf(SIPRegPeriodStr, "%u", SIPRegPeriod); - if (argc==3) { - SIPRegPeriod = strtol(argv[2],NULL,10); - sprintf(SIPRegPeriodStr, "%s", argv[2]); - } - if (!gConfig.isValidValue("SIP.RegistrationPeriod", SIPRegPeriodStr)) { - os << "valid SIP registration range is 6..2298 minutes" << endl; - return BAD_VALUE; - } - - // Set the values in the table and on the GSM beacon. - gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod); - gConfig.set("GSM.Timer.T3212",newT3212); - // Done. - return SUCCESS; -} - - -/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << */ -static CLIStatus alarms(int argc, char** argv, ostream& os) -{ - std::ostream_iterator output( os, "\n" ); - std::list alarms = gGetLoggerAlarms(); - std::copy( alarms.begin(), alarms.end(), output ); - return SUCCESS; -} - - -/** Version string. */ -static CLIStatus version(int argc, char **argv, ostream& os) -{ - if (argc!=1) return BAD_NUM_ARGS; - os << gVersionString << endl; - return SUCCESS; -} - -/** Show start-up notices. */ -static CLIStatus notices(int argc, char **argv, ostream& os) -{ - if (argc!=1) return BAD_NUM_ARGS; - os << endl << gOpenBTSWelcome << endl; - return SUCCESS; -} - -static CLIStatus page(int argc, char **argv, ostream& os) -{ - if (argc==1) { - 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; -} - - -static CLIStatus testcall(int argc, char **argv, ostream& os) -{ - 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; - } - 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; -} - - -// 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; - 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; - } - 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)); - - - 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(); - os << " " << setw(12) << transID; - os << " " << setw(6) << chan->active(); - os << " " << setw(5) << chan->recyclable(); - char buffer[200]; - snprintf(buffer,199,"%5.2f %4d %5d %4d", - 100.0*chan->FER(), (int)round(chan->RSSI()), - chan->actualMSPower(), chan->actualMSTiming()); - os << " " << buffer; - - if (!chan->SACCH()) { - os << endl; - return; - } - - const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults(); - - if (meas.MEAS_VALID()) { - os << endl; - return; - } - - snprintf(buffer,199,"%5d %5.2f", - meas.RXLEV_FULL_SERVING_CELL_dBm(), - 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER()); - os << " " << buffer; - - if (meas.NO_NCELL()==0) { - os << endl; - return; - } - 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(); - os << endl; - return; - } - snprintf(buffer,199,"%8u %8d",ARFCNList[CN],meas.RXLEV_NCELL_dBm(0)); - os << " " << buffer; - os << endl; -} - - -CLIStatus printChansV4(std::ostream& os,bool showAll) -{ - 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; - - 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); - } - } - 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 - - - - -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 " - << gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB") - << " to " - << gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB") - << " dB" << endl; - - if (argc==1) return SUCCESS; - if (argc!=3) return BAD_NUM_ARGS; - - int min = atoi(argv[1]); - int max = atoi(argv[2]); - if (min>max) { - os << "Min is larger than max" << endl; - return BAD_VALUE; - } - - if (!gConfig.isValidValue("GSM.Radio.PowerManager.MinAttenDB", argv[1])) { - os << "Invalid new value for min. It must be in range ("; - os << gConfig.mSchema["GSM.Radio.PowerManager.MinAttenDB"].getValidValues() << ")" << endl; - return BAD_VALUE; - } - if (!gConfig.isValidValue("GSM.Radio.PowerManager.MaxAttenDB", argv[2])) { - os << "Invalid new value for max. It must be in range ("; - os << gConfig.mSchema["GSM.Radio.PowerManager.MaxAttenDB"].getValidValues() << ")" << endl; - return BAD_VALUE; - } - - gConfig.set("GSM.Radio.PowerManager.MinAttenDB",argv[1]); - gConfig.set("GSM.Radio.PowerManager.MaxAttenDB",argv[2]); - - os << "new attenuation bounds " - << gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB") - << " to " - << gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB") - << " dB" << endl; - - return SUCCESS; -} - - -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; - if (argc!=2) return BAD_NUM_ARGS; - - if (!gConfig.isValidValue("GSM.Radio.RxGain", argv[1])) { - os << "Invalid new value for RX gain. It must be in range ("; - os << gConfig.mSchema["GSM.Radio.RxGain"].getValidValues() << ")" << endl; - return BAD_VALUE; - } - - int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1])); - os << "new RX gain is " << newGain << " dB" << endl; - - gConfig.set("GSM.Radio.RxGain",newGain); - - return SUCCESS; -} - -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; - if (argc!=2) return BAD_NUM_ARGS; - - if (!gConfig.isValidValue("TRX.TxAttenOffset", argv[1])) { - os << "Invalid new value for TX attenuation. It must be in range ("; - os << gConfig.mSchema["TRX.TxAttenOffset"].getValidValues() << ")" << endl; - return BAD_VALUE; - } - - int newAtten = gTRX.ARFCN(0)->setTxAtten(atoi(argv[1])); - os << "new TX attenuation is " << newAtten << " dB" << endl; - - gConfig.set("TRX.TxAttenOffset",newAtten); - - return SUCCESS; -} - - -static CLIStatus freqcorr(int argc, char** argv, ostream& os) -{ - os << "current freq. offset is " << gConfig.getNum("TRX.RadioFrequencyOffset") << endl; - if (argc==1) return SUCCESS; - if (argc!=2) return BAD_NUM_ARGS; - - if (!gConfig.isValidValue("TRX.RadioFrequencyOffset", argv[1])) { - os << "Invalid new value for freq. offset It must be in range ("; - os << gConfig.mSchema["TRX.RadioFrequencyOffset"].getValidValues() << ")" << endl; - return BAD_VALUE; - } - - int newOffset = gTRX.ARFCN(0)->setFreqOffset(atoi(argv[1])); - os << "new freq. offset is " << newOffset << endl; - - gConfig.set("TRX.RadioFrequencyOffset",newOffset); - - return SUCCESS; -} - - - -static CLIStatus noise(int argc, char** argv, ostream& os) -{ - if (argc!=1) return BAD_NUM_ARGS; - - int noise = gTRX.ARFCN(0)->getNoiseLevel(); - int target = abs(gConfig.getNum("GSM.Radio.RSSITarget")); - int diff = noise - target; - - 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; -} - -static CLIStatus sysinfo(int argc, char** argv, ostream& os) -{ - if (argc!=1) return BAD_NUM_ARGS; - - const GSM::L3SystemInformationType1 *SI1 = gBTS.SI1(); - if (SI1) os << *SI1 << endl; - const GSM::L3SystemInformationType2 *SI2 = gBTS.SI2(); - if (SI2) os << *SI2 << endl; - const GSM::L3SystemInformationType3 *SI3 = gBTS.SI3(); - if (SI3) os << *SI3 << endl; - const GSM::L3SystemInformationType4 *SI4 = gBTS.SI4(); - if (SI4) os << *SI4 << endl; - const GSM::L3SystemInformationType5 *SI5 = gBTS.SI5(); - if (SI5) os << *SI5 << endl; - const GSM::L3SystemInformationType6 *SI6 = gBTS.SI6(); - if (SI6) os << *SI6 << endl; - - return SUCCESS; -} - - -static CLIStatus neighbors(int argc, char** argv, ostream& os) -{ - map options = cliParse(argc,argv,os,"-tab"); - if (argc) return BAD_NUM_ARGS; - bool tabSeparated = options.count("-tab"); - FILE *result = NULL; - char cmd[200]; - 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)) { - if (!fgets(line, 200, result)) break; - os << line; - } - free(line); - os << endl; - pclose(result); - return SUCCESS; -} - - -static CLIStatus memStat(int argc, char** argv, ostream& os) -{ - // These counters are always available. - os << "Count"< ParseTable; /** The help table. */ @@ -49,7 +57,19 @@ class Parser { HelpTable mHelpTable; static const int mMaxArgs = 10; + /** + Parse and execute a command string. + @line a writeable copy of the original line + @cline the original line + @os output stream + @return status code + */ + CLIStatus execute(char* line, ostream& os) const; + int Execute(bool console, const char cmdbuf[], int outfd); + void Prompt() const; + public: + std::string mCommandName; // Name of the running program, eg, "OpenBTS" void addCommands(); @@ -57,7 +77,7 @@ class Parser { Process a command line. @return 0 on sucess, -1 on exit request, error codes otherwise */ - CLIStatus process(const char* line, std::ostream& os) const; + CLIStatus process(const char* line, ostream& os) const; void startCommandLine(); // (pat) Start a simple command line processor. /** Add a command to the parsing table. */ @@ -69,21 +89,19 @@ class Parser { /** Return a help string. */ const char *help(const std::string& cmd) const; - - private: - - /** - Parse and execute a command string. - @line a writeable copy of the original line - @cline the original line - @os output stream - @return status code - */ - CLIStatus execute(char* line, std::ostream& os) const; + void printHelp(ostream &os) const; // print help for all commands into os. + void cliServer(); // Run the command processor. }; -extern CLIStatus printChansV4(std::ostream& os,bool showAll, bool longList = false, bool tabSeparated = false); +extern std::map cliParse(int &argc, char **&argv, ostream &os, const char *optstring); + +extern CLIStatus configCmd(string mode, int argc, char** argv, ostream& os); +extern CLIStatus unconfig(int argc, char** argv, ostream& os); +extern CLIStatus rmconfig(int argc, char** argv, ostream& os); +extern CLIStatus rawconfig(int argc, char** argv, ostream& os); + +extern CLIStatus printChansV4(std::ostream& os,bool showAll, int verbosity = 0, bool tabSeparated = false); } // namespace CommandLine diff --git a/CLI/CLIBase.cpp b/CLI/CLIBase.cpp new file mode 100644 index 0000000..fd0a78b --- /dev/null +++ b/CLI/CLIBase.cpp @@ -0,0 +1,246 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#include +#include +#include +#include "CLI.h" + +namespace CommandLine { + + +/** Print or modify the global configuration table. */ +CLIStatus configCmd(string mode, int argc, char** argv, ostream& os) +{ + // no args, just print + if (argc==1) { + ConfigurationKeyMap::iterator mp = gConfig.mSchema.begin(); + while (mp != gConfig.mSchema.end()) { + if (mode.compare("customer") == 0) { + if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) { + ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); + } + } else if (mode.compare("developer") == 0) { + ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); + } + mp++; + } + return SUCCESS; + } + + // one arg + if (argc==2) { + // matches exactly? print single key + if (gConfig.keyDefinedInSchema(argv[1])) { + ConfigurationKey::printKey(gConfig.mSchema[argv[1]], gConfig.getStr(argv[1]), os); + ConfigurationKey::printDescription(gConfig.mSchema[argv[1]], os); + os << endl; + // ...otherwise print all similar keys + } else { + int foundCount = 0; + ConfigurationKeyMap matches = gConfig.getSimilarKeys(argv[1]); + ConfigurationKeyMap::iterator mp = matches.begin(); + while (mp != matches.end()) { + if (mode.compare("customer") == 0) { + if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || + mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) { + ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); + foundCount++; + } + } else if (mode.compare("developer") == 0) { + ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os); + foundCount++; + } + mp++; + } + if (!foundCount) { + os << argv[1] << " - no keys matched"; + if (mode.compare("customer") == 0) { + os << ", developer/factory keys can be accessed with \"devconfig\"."; + } else if (mode.compare("developer") == 0) { + os << ", custom keys can be accessed with \"rawconfig\"."; + } + os << endl; + } + } + return SUCCESS; + } + + // >1 args: set new value + string val; + for (int i=2; i warnings = gConfig.crossCheck(argv[1]); + vector::iterator warning = warnings.begin(); + while (warning != warnings.end()) { + os << "WARNING: " << *warning << endl; + warning++; + } + if (gConfig.isStatic(argv[1])) { + os << argv[1] << " is static; change takes effect on restart" << endl; + } + return SUCCESS; + } + + if (!gConfig.remove(argv[1])) { + os << "DB ERROR: " << argv[1] << " could not be removed from the configuration table" << endl; + return FAILURE; + } + + os << argv[1] << " removed from the configuration table" << endl; + + return SUCCESS; +} + + +/** Print or modify the global configuration table. */ +CLIStatus rawconfig(int argc, char** argv, ostream& os) +{ + // no args, just print + if (argc==1) { + gConfig.find("",os); + return SUCCESS; + } + + // one arg, pattern match and print + if (argc==2) { + gConfig.find(argv[1],os); + return SUCCESS; + } + + // >1 args: set new value + string val; + for (int i=2; i +#include +#include +#include +#include +#include +#include +#include +#include // for sort() + +#include + +// #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 { + // Hack. + extern CommandLine::CLIStatus sgsnCLI(int argc, char **argv, std::ostream &os); +}; + +#include + +#include "CLI.h" + +#undef WARNING + +extern TransceiverManager gTRX; +extern NodeManager gNodeManager; + +namespace CommandLine { +using namespace std; +using namespace Control; + + + +/**@name Commands for the CLI. */ +//@{ + +// forward refs +static CLIStatus printStats(int argc, char** argv, ostream& os); + + +static string translateIMSI(string imsi) +{ + if (0==strncmp(imsi.c_str(),"IMSI",4)) return imsi.substr(4); + return imsi; +} + +static char *translateIMSI(char *imsi) +{ + if (0==strncmp(imsi,"IMSI",4)) return imsi+4; + return imsi; +} + +/* + A CLI command takes the argument in an array. + It returns 0 on success. +*/ + +/** Display system uptime and current GSM frame number. */ +static CLIStatus uptime(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os.precision(2); + + time_t now = time(NULL); + std::string timestring; + Timeval::isoTime(now, timestring, true); + os << "Local time " << now << ", " << timestring << std::endl; + os << "watchdog timer expires in " << (gWatchdogRemaining() / 60) << " minutes" << endl; + + int seconds = gBTS.uptime(); + if (seconds<120) { + os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl; + return SUCCESS; + } + float minutes = seconds / 60.0F; + if (minutes<120) { + os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl; + return SUCCESS; + } + float hours = minutes / 60.0F; + if (hours<48) { + os << "uptime " << hours << " hours, frame " << gBTS.time() << endl; + return SUCCESS; + } + float days = hours / 24.0F; + os << "uptime " << days << " days, frame " << gBTS.time() << endl; + + return SUCCESS; +} + + +/** Give a list of available commands or describe a specific command. */ +CLIStatus showHelp(int argc, char** argv, ostream& os) +{ + if (argc==2) { + os << argv[1] << " " << gParser.help(argv[1]) << endl; + return SUCCESS; + } + if (argc!=1) return BAD_NUM_ARGS; + gParser.printHelp(os); + return SUCCESS; +} + +/** A function to return -1, the exit code for the caller. */ +static CLIStatus exit_function(int argc, char** argv, ostream& os) +{ + unsigned wait =0; + if (argc>2) return BAD_NUM_ARGS; + if (argc==2) wait = atoi(argv[1]); + + if (wait!=0) + os << "waiting up to " << wait << " seconds for clearing of " + << gBTS.TCHActive() << " active calls" << endl; + + // Block creation of new channels. + gBTS.setBtsHold(true); + + // Turn off GPRS if running. + GPRS::gprsStop(); + + // Wait up to the timeout for active channels to release. + time_t finish = time(NULL) + wait; + while (time(NULL)0) { + LOG(WARNING) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit"; + loads = true; + } + if (gBTS.TCHActive()>0) { + LOG(WARNING) << "dropping " << gBTS.TCHActive() << " calls on exit"; + loads = true; + } + if (loads) { + os << endl << "exiting with loads:" << endl; + printStats(1,NULL,os); + } + + // (pat) Shut down all the other threads. If we dont, we may get a crash when we exit. + gReports.reportShutdown(); + gBTS.setBtsShutdown(); + sleep(2); // Wait for all the threads and serviceloops to exit. + + 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; +} + +/** Noop function - allows for "help" to work. The CLI program manages this. */ +static CLIStatus nop(int argc, char** argv, ostream& os) +{ + return SUCCESS; +} + + +/** Print or clear the TMSI table. */ +static const char *tmsisHelp = "[-a | -l | -ll | -r | -tab | clear | dump [-l] | delete -tmsi | delete -imsi | query set name=value] -- print the TMSI table\n" + " -l or -ll -- longer listing\n" + " -a -- lists all TMSIs (default is to show 100 most recent in table)\n" + " -r -- raw TMSI table listing\n" + " -tab -- tab separated 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, e.g.: set IMSI='12345678901234'\n" + " query -- run sql query, which may be quoted as shown: tmsis query \"UPDATE TMSI_TABLE SET AUTH=0 WHERE IMSI=='123456789012'\". This option may be removed in the future." + ; +static CLIStatus tmsis(int argc, char** argv, ostream& os) +{ + // (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 -tab dump: clear delete -imsi: -tmsi: query: set:"); + string imsiopt = translateIMSI(options["-imsi"]); + string tmsiopt = options["-tmsi"]; + unsigned tmsi = strtoul(tmsiopt.c_str(),NULL,0); // No bad effect if option is empty. + bool taboption = !!options.count("-tab"); + 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 (options.count("dump")) { + ofstream fileout; + string filename = options["dump"]; + if (filename.size() == 0) { os << "bad filename"<;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, 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; + +#if 0 + int numRead = sock.read(buffer,10000); + if (numRead>=0) { + buffer[numRead]='\0'; + os << "response: " << buffer << endl; + } else { + os << "timed out waiting for response"; + } +#endif + + return SUCCESS; +} + + +/** 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; + + char *IMSI = argv[1]; + char *srcAddr = argv[2]; + string rest = ""; + for (int i=3; i options = cliParse(argc,argv,os,"-cause:"); + if (argc!=2) return BAD_NUM_ARGS; + string imsi(translateIMSI(argv[0])); // Allow "IMSI1234..." or just "1234..." + const char *peerarg = argv[1]; + + 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(peerarg); + } + + string cause = options["-cause"]; // Empty string if it does not exist. + if (! gPeerInterface.sendHandoverRequest(peer,tran,cause)) { + return BAD_VALUE; + } + return SUCCESS; // success of the handover CLI command, not success of the handover. +} + + +/** Print table of current transactions. */ +// (pat) In version 4 this dumps the MM layer. +static const char *callsHelp = + "[-m | -a | -s | -t] -- print transaction table [or -m: mobility management tables or -s: SIP dialogs]. " + "Note this includes both CS (voice call) and SMS transactions. If -a specified with -t, show all transactions, else only active."; +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; + 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; +} +***/ + + +static CLIStatus trxfactory(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + + signed val = gTRX.ARFCN(0)->getFactoryCalibration("sdrsn"); + if (val == 0 || val == 65535) { + os << "Reading factory calibration not supported on this radio." << endl; + return SUCCESS; + } + os << "Factory Information" << endl; + os << " SDR Serial Number = " << val << endl; + + val = gTRX.ARFCN(0)->getFactoryCalibration("rfsn"); + os << " RF Serial Number = " << val << endl; + + val = gTRX.ARFCN(0)->getFactoryCalibration("band"); + os << " GSM.Radio.Band = "; + if (val == 0) { + os << "multi-band"; + } else { + os << val; + } + os << endl; + + val = gTRX.ARFCN(0)->getFactoryCalibration("rxgain"); + os << " GSM.Radio.RxGain = " << val << endl; + + val = gTRX.ARFCN(0)->getFactoryCalibration("txgain"); + os << " TRX.TxAttenOffset = " << val << endl; + + val = gTRX.ARFCN(0)->getFactoryCalibration("freq"); + os << " TRX.RadioFrequencyOffset = " << val << endl; + + return SUCCESS; +} + +/** Audit the current configuration. */ +static CLIStatus audit(int argc, char** argv, ostream& os) +{ + ConfigurationKeyMap::iterator mp; + stringstream ss; + + // value errors + mp = gConfig.mSchema.begin(); + while (mp != gConfig.mSchema.end()) { + if (!gConfig.isValidValue(mp->first, gConfig.getStr(mp->first))) { + ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl; + } + mp++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| ERROR : Invalid Values [key current-value (default)] |" << endl; + os << "| To use the default value again, execute: rmconfig key |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + // factory calibration warnings + signed sdrsn = gTRX.ARFCN(0)->getFactoryCalibration("sdrsn"); + if (sdrsn != 0 && sdrsn != 65535) { + string factoryValue; + string configValue; + + factoryValue = gConfig.mSchema["GSM.Radio.Band"].getDefaultValue(); + configValue = gConfig.getStr("GSM.Radio.Band"); + // only warn on band changes if the unit is not multi-band + if (gTRX.ARFCN(0)->getFactoryCalibration("band") != 0 && configValue.compare(factoryValue) != 0) { + ss << "GSM.Radio.Band \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; + } + + factoryValue = gConfig.mSchema["GSM.Radio.RxGain"].getDefaultValue(); + configValue = gConfig.getStr("GSM.Radio.RxGain"); + if (configValue.compare(factoryValue) != 0) { + ss << "GSM.Radio.RxGain \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; + } + + factoryValue = gConfig.mSchema["TRX.TxAttenOffset"].getDefaultValue(); + configValue = gConfig.getStr("TRX.TxAttenOffset"); + if (configValue.compare(factoryValue) != 0) { + ss << "TRX.TxAttenOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; + } + + factoryValue = gConfig.mSchema["TRX.RadioFrequencyOffset"].getDefaultValue(); + configValue = gConfig.getStr("TRX.RadioFrequencyOffset"); + if (configValue.compare(factoryValue) != 0) { + ss << "TRX.RadioFrequencyOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl; + } + + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| WARNING : Factory Radio Calibration [key current-value (factory)] |" << endl; + os << "| To use the factory value again, execute: rmconfig key |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + } + + // cross check warnings + vector allWarnings; + vector warnings; + vector::iterator warning; + mp = gConfig.mSchema.begin(); + while (mp != gConfig.mSchema.end()) { + warnings = gConfig.crossCheck(mp->first); + allWarnings.insert(allWarnings.end(), warnings.begin(), warnings.end()); + mp++; + } + sort(allWarnings.begin(), allWarnings.end()); + allWarnings.erase(unique(allWarnings.begin(), allWarnings.end() ), allWarnings.end()); + warning = allWarnings.begin(); + while (warning != allWarnings.end()) { + ss << *warning << endl; + warning++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| WARNING : Cross-Check Values |" << endl; + os << "| To quiet these warnings, follow the advice given. |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + // site-specific values + mp = gConfig.mSchema.begin(); + while (mp != gConfig.mSchema.end()) { + if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) { + if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) == 0) { + ss << mp->first << " \"" << gConfig.mSchema[mp->first].getDefaultValue() << "\"" << endl; + } + } + mp++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| WARNING : Site Values Which Are Still Default [key current-value] |" << endl; + os << "| These should be set to fit your installation: config key value |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + // unapplied values + std::map dirtyKeys = gNodeManager.getDirtyConfigurationKeysMap(); + std::map::iterator dk = dirtyKeys.begin(); + while (dk != dirtyKeys.end()) { + ss << dk->first << " \"" << gConfig.getStr(dk->first) << "\" (\"" << dk->second << "\")" << endl; + dk++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| WARNING : Unapplied Changes [key desired-value (running-value)] |" << endl; + os << "| To apply values, restart OpenBTS by executing: shutdown |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + // non-default values + mp = gConfig.mSchema.begin(); + while (mp != gConfig.mSchema.end()) { + if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE) { + if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) != 0) { + ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl; + } + } + mp++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| INFO : Non-Default Values [key current-value (default)] |" << endl; + os << "| To use the default value again, execute: rmconfig key |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + // unknown pairs + ConfigurationRecordMap pairs = gConfig.getAllPairs(); + ConfigurationRecordMap::iterator mp2 = pairs.begin(); + while (mp2 != pairs.end()) { + if (!gConfig.keyDefinedInSchema(mp2->first)) { + // also kindly ignore SIM.Prog keys for now so the users don't kill their ability to program SIMs + string family = "SIM.Prog."; + if (mp2->first.substr(0, family.size()) != family) { + ss << mp2->first << " \"" << mp2->second.value() << "\"" << endl; + } + } + mp2++; + } + if (ss.str().length()) { + os << "+---------------------------------------------------------------------+" << endl; + os << "| INFO : Custom/Deprecated Key/Value Pairs [key current-value] |" << endl; + os << "| To clean up any extraneous keys, execute: rmconfig key |" << endl; + os << "+---------------------------------------------------------------------+" << endl; + os << ss.str(); + os << endl; + ss.str(""); + } + + return SUCCESS; +} + +static void crossCheck(int argc, char **argv, ostream&os) +{ + if (argc == 1) return; + 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()) { + os << "WARNING: " << *warning << endl; + warning++; + } + if (gConfig.isStatic(argv[1])) { + os << argv[1] << " is static; change takes effect on restart" << endl; + } +} + +/** Print or modify the global configuration table. Customer access. */ +static CLIStatus config(int argc, char** argv, ostream& os) +{ + CLIStatus result = configCmd("customer", argc, argv, os); + crossCheck(argc,argv,os); + return result; +} + +/** Print or modify the global configuration table. Developer/factory access. */ +static CLIStatus devconfig(int argc, char** argv, ostream& os) +{ + CLIStatus result = configCmd("developer", argc, argv, os); + crossCheck(argc,argv,os); + return result; +} + + + +/** Change the registration timers. */ +// (pat 3-2014) Why is this a special routine? Why cant we just set the config value for this timer normally? +static CLIStatus regperiod(int argc, char** argv, ostream& os) +{ + if (argc==1) { + os << "T3212 is " << gConfig.getNum("GSM.Timer.T3212") << " minutes" << endl; + os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod") << " minutes" << endl; + return SUCCESS; + } + + if (argc>3) return BAD_NUM_ARGS; + + unsigned newT3212 = strtol(argv[1],NULL,10); + if (!gConfig.isValidValue("GSM.Timer.T3212", argv[1])) { + os << "valid T3212 range is 6..1530 minutes" << endl; + return BAD_VALUE; + } + + // By default, make SIP registration period 1.5x the GSM registration period. + unsigned SIPRegPeriod = newT3212 * 1.5; + char SIPRegPeriodStr[10]; + sprintf(SIPRegPeriodStr, "%u", SIPRegPeriod); + if (argc==3) { + SIPRegPeriod = strtol(argv[2],NULL,10); + sprintf(SIPRegPeriodStr, "%s", argv[2]); + } + if (!gConfig.isValidValue("SIP.RegistrationPeriod", SIPRegPeriodStr)) { + os << "valid SIP registration range is 6..2298 minutes" << endl; + return BAD_VALUE; + } + + // Set the values in the table and on the GSM beacon. + gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod); + gConfig.set("GSM.Timer.T3212",newT3212); + // Done. + return SUCCESS; +} + + +/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << */ +static CLIStatus alarms(int argc, char** argv, ostream& os) +{ + std::ostream_iterator output( os, "\n" ); + std::list alarms = gGetLoggerAlarms(); + std::copy( alarms.begin(), alarms.end(), output ); + return SUCCESS; +} + + +/** Version string. */ +static CLIStatus version(int argc, char **argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os << gVersionString << endl; + return SUCCESS; +} + +/** Show start-up notices. */ +static CLIStatus notices(int argc, char **argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os << endl << gOpenBTSWelcome << endl; + return SUCCESS; +} + +static CLIStatus page(int argc, char **argv, ostream& os) +{ + if (argc==1) { + 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; +} + +static GSM::ChannelType translateChannelType(string channelName) +{ + if (0==strcasecmp(channelName.c_str(),"sdcch")) { + return GSM::SDCCHType; + } else if (0==strcasecmp(channelName.c_str(),"tch")) { + return GSM::TCHFType; + } else { + throw CLIParseError(format("invalid channel -type argument: %s",channelName)); + } +} + + +// 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; +} + + +// TODO - This does not work for SDCCH calls. +static const char *endcallHelp = "IMSI or trans# -- terminate the given transaction by IMSI or by transaction number (from the chans command output.)"; +static CLIStatus endcall(int argc, char **argv, ostream& os) +{ + if (argc!=2) return BAD_NUM_ARGS; + char *id = argv[1]; + if (id[0] == 'T' || strlen(id) < 14) { + TranEntryId tid = transactionId(id); + if (tid == 0) { + os << id << " is not a valid transaction id" << endl; + return BAD_VALUE; + } + + // TODO: Get rid of this, use mmTerminateByImsi(), which will work for SDCCH also. + if (! gNewTransactionTable.ttTerminate(tid,L3Cause::Operator_Intervention)) { + os << id << " not found in transaction table" << endl; + return BAD_VALUE; + } + } else { + // Assume it is an IMSI + id = translateIMSI(id); + if (! gMMLayer.mmTerminateByImsi(string(id))) { + os << "IMSI " << id << " not found." << endl; + } + } + + return SUCCESS; +} + + +static void addGprsInfo(vector &row, const GSM::L2LogicalChannel* chan) +{ + // "CN TN chan transaction active recyc UPFER RSSI TXPWR TA_DL RXLEV_DL BER_DL 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->l1active()?"true":"false")); // active + row.push_back(string(Unconst(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->l1active()) { 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, // Result returned here. + TranEntryList tids, // Transaction IDs in case that is one of the requested fields. + const GSM::L2LogicalChannel* chan, // The main channel, not the SACCH. + const vector &fields) // Fields to put in the result row. +{ + row.clear(); + for (unsigned i = 0; i < fields.size(); i++) { + string field = fields[i]; + MSPhysReportInfo *phys = NULL; + GSM::L3MeasurementResults meas = chan->getSACCH()->measurementResults(); + if (chan->getSACCH()) { meas = chan->getSACCH()->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(Unconst(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(phys->isValid() ? format("%-4d",(int)round(phys->getRSSI())) : string("-")); + } else if (field == "RSSP") { + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(phys->isValid() ? format("%-4d",(int)round(phys->getRSSP())) : string("-")); + } else if (field == "Signal") { + int target = gConfig.getNum("GSM.Radio.RSSITarget"); // It is negative. + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(phys->isValid() ? format("%-4d",(int) round(phys->getRSSP()) - target) : string("-")); + } 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(phys->isValid() ? format("%-.1f",phys->timingError()) : string("-")); + } else if (field == "TXPWR") { + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(phys->isValid() ? format("%-5d",(int)round(phys->actualMSPower())) : string("-")); + } else if (field == "TA_DL") { // The TA reported by the MS. + if (phys == 0) { phys = chan->getPhysInfo(); } + row.push_back(phys->isValid() ? format("%-4d",(int)round(phys->actualMSTiming())) : string("-")); + } else if (field == "RXLEV_DL") { + if (chan->getSACCH() && meas.isServingCellValid()) { + row.push_back(format("%-5d", meas.RXLEV_FULL_SERVING_CELL_dBm())); + } else { + row.push_back("-"); + } + } else if (field == "BER_DL") { + if (chan->getSACCH() && meas.isServingCellValid()) { + 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 + bool singleNeighborColumn = true; + if (chan->getSACCH() && 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; + } + if (singleNeighborColumn) { + row.push_back(format("%-8u(%d)", ARFCNList[CN],meas.RXLEV_NCELL_dBm(0))); + } else { + row.push_back(format("%-8u", ARFCNList[CN])); + row.push_back(format("%-8d",meas.RXLEV_NCELL_dBm(0))); + } + } else { + skipneighbors: + row.push_back("-"); + if (! singleNeighborColumn) { row.push_back("-"); } + } + if (! singleNeighborColumn) { + 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 == "Time") { + long duration = (long)chan->chanGetDuration(); + row.push_back(format("%ld:%ld",duration/60,duration%60)); // minutes:seconds + } 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)); + } else if (field == "Handover") { + // TODO: This should be implemented for sdcch handover too, but we dont do those yet. + string handoverInfo; + RefCntPointer tran = Unconst(chan)->chanGetVoiceTran(); + if (! tran.isNULL()) { + HandoverEntry *hep = tran->getHandoverEntry(false); + Peering::NeighborEntry entry; + if (hep && gNeighborTable.ntFindByPeerAddr(&hep->mInboundPeer,&entry)) { + handoverInfo = entry.neC0PlusBSIC(); + } + } + row.push_back(handoverInfo); + } else if (field == "Timers") { + row.push_back(chan->displayTimers()); + } else if (field == "Layer1") { + row.push_back(format("%s/%s",chan->l1active() ? "on" : "off",chan->getSACCH()->l1active() ? "on" : "off")); + } + } +} + + // 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\n" + " -a -- report for all channels\n" + " -l -- for longer listing\n" + " -tab -- for tab-separated output format\n" + " CN - Channel Number;\n" + " TN - Timeslot Number;\n" + " 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" + " Signal - Uplink signal strength if MS were at full transmit power, relative to configured GSM.Radio.RSSITarget. 0 or less is bad;\n" + " RSSP - Uplink RSSI possible if MS were at full transmit power;\n" + " RSSI - Uplink signal level dB measured by BTS, should be above config parameter GSM.Radio.RSSITarget;\n" + " SNR - Uplink Signal to Noise Ratio measured by BTS, higher is better, less than 10 is probably unusable;\n" + " FER - Uplink voice frame loss rate as a percentage measured by BTS;\n" + " BER - Uplink Bit Error Rate before decoding measured by BTS, as a percentage;\n" + " TA - Timing advance in symbol periods measured by the BTS;\n" + " TXPWR - Uplink transmit power dB reported by MS;\n" + " TA_DL - Timing advance in symbol periods reported by MS;\n" + " RXLEV_DL - Downlink signal level dB reported by MS;\n" + " BER_DL - Downlink Bit Error rate percentage reported by MS;\n" + " IMSI - International Mobile Subscriber Id of the MS on this channel, reported only if known; may also be:\n" + " 'no-MMChannel' to indicate no Mobility Management channel, which happens when\n" + " the Layer 2 RR channel is open but has not yet sent any Layer 3 messages, or\n" + " 'no-MMUser' to indicate that Layer 3 is connected (an MMChannel exists) but the IMSI is not yet known.\n" + " Time - Channel connection duration in seconds;\n" + " Timers - Internal channel timers;\n" + " Frames - number of bad, stolen, and total frames sent, only for traffic channels;\n" + " Neighbor ARFCN and dBm - The best neighbor's channel and downlink RSSI reported by the MS;\n" + " Layer1 - state of layer1 for host (TCH or DCCH) and SACCH sub-channels.\n" + ; + +CLIStatus printChansV4(std::ostream& os,bool showAll, int verbosity, 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. + string 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 (verbosity) { + header1 = "CN TN chan transaction LAPDm recyc Signal RSSI RSSP SNR FER BER TA TXPWR TA_DL RXLEV_DL BER_DL Time IMSI Neighbor Handover"; + header2 = "_ _ type id state _ dB dB dB _ pct pct sym dBm sym dBm pct M:S _ ARFCN(dBm) C0:BSIC"; + if (verbosity == 2) { + header1 += " Frames Timers Layer1"; + header2 += " bad/st/tot _ Host/SACCH"; + } + } else { + header1 = "CN TN chan transaction Signal SNR FER TA TXPWR RXLEV_DL BER_DL Time IMSI"; + header2 = "_ _ type id dB _ pct sym dBm dBm pct _ _"; + // This is the original version 4 list: + //header1 = "CN TN chan transaction LAPDm recyc FER RSSI TXPWR TA_DL RXLEV_DL BER_DL 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.c_str())); + tab.push_back(stringSplit(vh2,header2.c_str())); + + + 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->l1active() || ! 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 -d -tab"); + bool showAll = options.count("-a"); + int longList = options.count("-d") ? 2 : options.count("-l") ? 1 : 0; + 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(); + os << " " << setw(12) << transID; + os << " " << setw(6) << chan->active(); + os << " " << setw(5) << chan->recyclable(); + char buffer[200]; + snprintf(buffer,199,"%5.2f %4d %5d %4d", + 100.0*chan->FER(), (int)round(chan->RSSI()), + chan->actualMSPower(), chan->actualMSTiming()); + os << " " << buffer; + + if (!chan->SACCH()) { + os << endl; + return; + } + + const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults(); + + if (meas.MEAS_VALID()) { + os << endl; + return; + } + + snprintf(buffer,199,"%5d %5.2f", + meas.RXLEV_FULL_SERVING_CELL_dBm(), + 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER()); + os << " " << buffer; + + if (meas.NO_NCELL()==0) { + os << endl; + return; + } + 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(); + os << endl; + return; + } + snprintf(buffer,199,"%8u %8d",ARFCNList[CN],meas.RXLEV_NCELL_dBm(0)); + os << " " << buffer; + os << endl; +} + + +CLIStatus printChansV4(std::ostream& os,bool showAll) +{ + using namespace GSM; + os << "CN TN chan transaction active recyc UPFER RSSI TXPWR TA_DL RXLEV_DL BER_DL 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; + + 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); + } + } + 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 + + + + + + +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; + if (argc!=2) return BAD_NUM_ARGS; + + if (!gConfig.isValidValue("GSM.Radio.RxGain", argv[1])) { + os << "Invalid new value for RX gain. It must be in range ("; + os << gConfig.mSchema["GSM.Radio.RxGain"].getValidValues() << ")" << endl; + return BAD_VALUE; + } + + int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1])); + os << "new RX gain is " << newGain << " dB" << endl; + + gConfig.set("GSM.Radio.RxGain",newGain); + + return SUCCESS; +} + +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; + if (argc!=2) return BAD_NUM_ARGS; + + if (!gConfig.isValidValue("TRX.TxAttenOffset", argv[1])) { + os << "Invalid new value for TX attenuation. It must be in range ("; + os << gConfig.mSchema["TRX.TxAttenOffset"].getValidValues() << ")" << endl; + return BAD_VALUE; + } + + int newAtten = gTRX.ARFCN(0)->setTxAtten(atoi(argv[1])); + os << "new TX attenuation is " << newAtten << " dB" << endl; + + gConfig.set("TRX.TxAttenOffset",newAtten); + + return SUCCESS; +} + + +static CLIStatus freqcorr(int argc, char** argv, ostream& os) +{ + os << "current freq. offset is " << gConfig.getNum("TRX.RadioFrequencyOffset") << endl; + if (argc==1) return SUCCESS; + if (argc!=2) return BAD_NUM_ARGS; + + if (!gConfig.isValidValue("TRX.RadioFrequencyOffset", argv[1])) { + os << "Invalid new value for freq. offset. It must be in range ("; + os << gConfig.mSchema["TRX.RadioFrequencyOffset"].getValidValues() << ")" << endl; + return BAD_VALUE; + } + + int newOffset = gTRX.ARFCN(0)->setFreqOffset(atoi(argv[1])); + os << "new freq. offset is " << newOffset << endl; + + gConfig.set("TRX.RadioFrequencyOffset",newOffset); + + return SUCCESS; +} + + + +static CLIStatus noise(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + + int noise = gTRX.ARFCN(0)->getNoiseLevel(); + int target = abs(gConfig.getNum("GSM.Radio.RSSITarget")); + int diff = noise - target; + + 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; +} + +static CLIStatus sysinfo(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + + const GSM::L3SystemInformationType1 *SI1 = gBTS.SI1(); + if (SI1) os << *SI1 << endl; + const GSM::L3SystemInformationType2 *SI2 = gBTS.SI2(); + if (SI2) os << *SI2 << endl; + const GSM::L3SystemInformationType3 *SI3 = gBTS.SI3(); + if (SI3) os << *SI3 << endl; + const GSM::L3SystemInformationType4 *SI4 = gBTS.SI4(); + if (SI4) os << *SI4 << endl; + const GSM::L3SystemInformationType5 *SI5 = gBTS.SI5(); + if (SI5) os << *SI5 << endl; + const GSM::L3SystemInformationType6 *SI6 = gBTS.SI6(); + if (SI6) os << *SI6 << endl; + const GSM::L3SystemInformationType13 *SI13 = gBTS.SI13(); + if (SI13) os << *SI13 << endl; + + return SUCCESS; +} + + + +static const char *neighborsHelp = + "[-a -f -imsi -tab] -- dump neighbor information.\n" + " -a -- print the neighbor information for all connected handsets.\n" + " -imsi -- print the neighbor information associated with this imsi.\n" + " -f -- with -a or -imsi, dump the entire raw accumulated neighbor data.\n" + " -tab -- for tab-separated output format\n" + "With no -imsi, dump the neighbor table, which specifies the ARFCN and BSCI of OpenBTS neighbors that responded.\n" + ; +static CLIStatus neighbors(int argc, char** argv, ostream& os) +{ + using namespace Peering; + bool tabSeparated = 0; + + + prettyTable_t tab; + vector vh; + // We only report the C0 of the neighbor, which is the ARFCN of the beacon; we dont know how many ARFCNs total it uses. + tab.push_back(stringSplit(vh,"host C0 BSIC FreqIndex Noise ARFCNs TCH-Avail Updated Holdoff")); vh.clear(); + tab.push_back(stringSplit(vh,"------------------ --- ---- --------- ----- ------ --------- ------------ -------")); vh.clear(); + // 127.xx.xx.xx:16002 + +#if NEIGHBOR_TABLE_ON_DISK + // This is obsolete the on-disk version. + // Dump the global neighbor table. + FILE *result = NULL; + char cmd[200]; + 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 (result) { + char line[202]; + while (fgets(line, 200, result)) { + vh.clear(); + stringSplit(vh,line); + // (pat) The line includes the host, CO, and BSIC. Now compute the freq index and append it to the data. + if (vh.size() >= 2) { + int arfcn = atoi(vh[1].c_str()); + int freqIndex = gNeighborTable.getFreqIndexForARFCN(arfcn); + vh.push_back(format("%d",freqIndex)); + } + tab.push_back(vh); + } + } + if (result) pclose(result); +#else + + vector nvec; + gNeighborTable.getNeighborVector(nvec); + for (vector::iterator it = nvec.begin(); it != nvec.end(); it++) { + NeighborEntry &entry = *it; + vh.clear(); + vh.push_back(entry.mIPAddress); + vh.push_back(format("%d",(int)entry.mC0)); + vh.push_back(format("%d",(int)entry.mBSIC)); + int freqIndex = gNeighborTable.getFreqIndexForARFCN(entry.mC0); + vh.push_back(format("%d",freqIndex)); + vh.push_back(format("%d",(int)entry.mNoise)); + vh.push_back(format("%d",(int)entry.mNumArfcns)); + vh.push_back(format("%d",(int)entry.mTchAvail)); + vh.push_back(format("%ld",entry.getUpdated())); + vh.push_back(format("%ld",entry.getHoldoff())); + tab.push_back(vh); + } + +#endif + printPrettyTable(tab,os,tabSeparated); + 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)) { + if (!fgets(line, 200, result)) break; + os << line; + } + free(line); + os << endl; + pclose(result); + return SUCCESS; +} + + +static CLIStatus memStat(int argc, char** argv, ostream& os) +{ + // These counters are always available. + os << "Count"< +#include +#include "CLI.h" +#include +#include + +using namespace CommandLine; + +void Parser::Prompt() const +{ + printf("%s> ",this->mCommandName.c_str()); fflush(stdout); +} + +int Parser::Execute(bool console, const char cmdbuf[], int outfd) +{ + // step 2 - execute + gReports.incr("OpenBTS.CLI.Command"); + const char *type = console ? "Console: " : "Socket: "; + LOG(INFO) << type << "received command \"" << cmdbuf << "\""; + std::ostringstream sout; + int res = this->process(cmdbuf,sout); + const std::string rspString= sout.str(); + const char* rsp = rspString.c_str(); + + // step 3 - respond, put length at start of response + LOG(INFO) << type << "sending " << strlen(rsp) << "-char result"; + int len = strlen(rsp); + if (console) + { + if (write(outfd,rsp,len) != len) { + LOG(ERR) << type << "can't send CLI response"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + } + this->Prompt(); + } else + { + len = htonl(len); + int sendLen = sizeof(len); + int sentLen = 0; + if ((sentLen = send(outfd, &len, sendLen, 0)) != sendLen) + { + LOG(ERR) << type << "can't send CLI response"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + } else + { + sendLen = strlen(rsp); + if ((sentLen = send(outfd,rsp,sendLen,0)) != sendLen) { + LOG(ERR) << type << "can't send CLI response"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + } + } + } + return res; +} + + +// (pat) 4-2014: Moved this code written by Dave G. from OpenBTS.cpp to this directory so that it can be shared by multiple apps. +// I did not bother to rename all the gReports variables. +void Parser::cliServer() +{ + assert(mCommandName.size()); // Dont leave this empty. + + int netSockFd = -1; + + // CLI Interface code + string CLIPortOpt = "CLI.Port", CLIInterfaceOpt = "CLI.Interface"; + if (mCommandName != "OpenBTS") { + CLIPortOpt = mCommandName + "." + CLIPortOpt; + CLIInterfaceOpt = mCommandName + "." + CLIInterfaceOpt; + } + string netPort = gConfig.getStr(CLIPortOpt); + string neInterface = gConfig.defines(CLIInterfaceOpt) ? gConfig.getStr(CLIInterfaceOpt) : string(""); // TODO: Future implementation + bool netTcp = false; + + if (netPort.size()) + { + in_addr_t iInterface = neInterface.size() == 0 ? inet_addr("127.0.0.1") : inet_addr(neInterface.c_str()); + // printf("Interface mask 0x%08X\n", iInterface); + struct sockaddr_in servAddr; + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = iInterface; + int port = 0; + netTcp = true; + port = atoi(netPort.c_str()); + servAddr.sin_port = htons(port); + + netSockFd = socket(AF_INET,SOCK_STREAM,0); + if (netSockFd<0) { + perror("creating CLI network stream socket"); + LOG(ALERT) << "cannot create network tcp socket for CLI"; + gReports.incr("OpenBTS.Exit.CLI.Socket"); + exit(1); + } + int optval = 1; + setsockopt(netSockFd, SOL_SOCKET,SO_REUSEADDR, &optval, sizeof(optval)); + if (bind(netSockFd, (struct sockaddr *) &servAddr, sizeof(struct sockaddr_in))) { + perror("binding name to network socket"); + LOG(ALERT) << "cannot bind socket for CLI at " << (netTcp ? "tcp:" : "udp:" ) << netPort; + gReports.incr("OpenBTS.Exit.CLI.Socket"); + exit(1); + } + if (netTcp) listen(netSockFd, 10); + } + + if (netSockFd != -1) + { + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf)-1, "OpenBTSCLI network socket support for %s:%s\n", ( netTcp ? "tcp" : "udp") , netPort.c_str()); + COUT(buf); + } + + fd_set rdFds, curFds; + FD_ZERO(&rdFds); + if (netSockFd != -1) FD_SET(netSockFd, &rdFds); + if (isatty(0)) // if not running from a terminal, don't bother + { + FD_SET(0, &rdFds); // console + this->Prompt(); + } + char cmdbuf[BUFSIZ]; + bool isRunning = true; + struct timeval tv; + while (isRunning) { + // First, build mask of possible input channels + curFds = rdFds; + tv.tv_sec = 60; tv.tv_usec = 0; + int selRet = select(FD_SETSIZE, &curFds, NULL, NULL, &tv); + if (selRet > 0) + { + for (int i = 0; i < FD_SETSIZE; i++) + { + if (!FD_ISSET(i, &curFds)) + continue; + if (i == 0) + { + if (NULL == fgets(cmdbuf, sizeof(cmdbuf)-1, stdin)) { continue; } + char *p = strchr(cmdbuf, '\n'); + if (p != NULL) + *p = '\0'; + else + cmdbuf[BUFSIZ-1] = '\0'; + int res = Execute(true, cmdbuf, 1); // and ignore the result + if (res < 0) { isRunning = false; } + } else if (i == netSockFd) // accept (tcp) + { + if (netTcp) + { + struct sockaddr peer; + socklen_t len = sizeof(peer); + // int fd = accept(i, NULL, NULL); // don't care who it's from + int fd = accept(i, &peer, &len); // (pat) On the other hand, might be interesting to know who it's from. + if (fd < 0) { + LOG(ERR) << "can't accept network stream connection"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + break; + } + // (pat) Could make an argument that we should always print this message as a security measure, + // since the peer is allowed complete access to OpenBTS. + if (1) { + // Lets print out who it was from: + char addrString[256]; + struct sockaddr_in *sp = (struct sockaddr_in*)&peer; + if (const char *ret = inet_ntop(AF_INET,&(sp->sin_addr),addrString,255)) { + LOG(INFO) << format("Accepting CLI connection from %s:%d", ret,(int)ntohs(sp->sin_port)); + } + } + FD_SET(fd, &rdFds); // so that we can select on it next time around + } + } else if (FD_ISSET(i, &curFds)) // a tcp socket that had previously been accepted + { + // step 1 - read the command. Format is data, because it may take several reads to get the full read + // along a stream. + int len; + int nread = recv(i, &len, sizeof(len), 0); + if (nread < 0) + { + LOG(ERR) << "can't read CLI request from stream"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + break; + } + if (len == 0) // clsoe + { + FD_CLR(i, &rdFds); + shutdown(i, SHUT_RDWR); + close(i); + continue; // go to next socket + } + if (len < (int) sizeof(len)) // should never get here + { + char buf[BUFSIZ]; + sprintf(buf, "Unable to read complete length, s.b. %d bytes, got %d bytes\n", sizeof(len), len); + LOG(ERR) << buf; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + break; + } + len = ntohl(len); + int off = 0; + while(len != 0) + { + nread = recv(i, &cmdbuf[off], len, 0); + if (nread < 0) + { + LOG(ERR) << "can't read CLI request from stream"; + gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); + break; + } + if (nread == 0) // close + { + FD_CLR(i, &rdFds); + shutdown(i, SHUT_RDWR); + close(i); + continue; // go to next socket + } + off += nread; + len -= nread; + } + cmdbuf[off] = '\0'; + + int res = Execute(false, cmdbuf, i); + // (comment by Dave G) NOTE: This change was made because we need to exit as + // quickly as possible, or we end up getting some other + // threads faulting as objects vanish from under them. + // (pat 3-2014) I dont think the above helped. But I had to fix up the exit routines in order to run gprof, + // so it is ok to exit normally now. + if (res < 0) { + isRunning = false; + break; + } + } + } + } else if (selRet < 0) { + // (pat) TEMPORARY! Watch this to try to figure out where the unexpected signal is coming from. + perror("system call during CLI select loop"); + } + } + + if (netSockFd != -1) close(netSockFd); // Why bother? +} diff --git a/CLI/Makefile.am b/CLI/Makefile.am index 9ed1b27..cbbd19b 100644 --- a/CLI/Makefile.am +++ b/CLI/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2009 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -29,6 +30,9 @@ AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) noinst_LTLIBRARIES = libcli.la libcli_la_SOURCES = \ + CLIServer.cpp \ + CLICommands.cpp \ + CLIBase.cpp \ CLI.cpp noinst_HEADERS = \ diff --git a/CommonLibs b/CommonLibs index 00d91e9..3ad343b 160000 --- a/CommonLibs +++ b/CommonLibs @@ -1 +1 @@ -Subproject commit 00d91e90bb9565376ace752d3fe22186bb1e74dd +Subproject commit 3ad343b97b137743283194a2d55622c26c6d4800 diff --git a/Control/CodecSet.h b/Control/CodecSet.h new file mode 100644 index 0000000..807e9e3 --- /dev/null +++ b/Control/CodecSet.h @@ -0,0 +1,75 @@ +/**@file Declarations for common-use control-layer functions. */ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#ifndef _CODECSET_H_ +#define _CODECSET_H_ 1 + +namespace GSM { class L3MobileIdentity; }; + +namespace Control { + +// 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&); +}; + +}; // namespace Control +#endif diff --git a/Control/ControlCommon.h b/Control/ControlCommon.h index 9157ecc..1754e29 100644 --- a/Control/ControlCommon.h +++ b/Control/ControlCommon.h @@ -2,11 +2,11 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/Control/ControlTransfer.cpp b/Control/ControlTransfer.cpp index 0ef3c1b..2e0c7c3 100644 --- a/Control/ControlTransfer.cpp +++ b/Control/ControlTransfer.cpp @@ -1,10 +1,10 @@ /**@file Declarations for common-use control-layer functions. */ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,15 +14,19 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #define LOG_GROUP LogGroup::Control +#include +#include +#include +#include +#include "Defines.h" #include "ControlTransfer.h" -//#include "TransactionTable.h" -#include "L3TranEntry.h" -#include -#include +#include "CodecSet.h" namespace Control { +using namespace GSM; #define CASENAME(x) case x: return #x; const char *CodecType2Name(CodecType ct) @@ -134,7 +138,7 @@ ostream& operator<<(ostream& os, CallState state) std::ostream& operator<<(std::ostream& os, const TMSI_t&tmsi) { if (tmsi.valid()) { - char buf[10]; sprintf(buf,"0x%x",tmsi.value()); os <text(); return os; } +std::ostream& operator<<(std::ostream& os, NeighborPenalty np) { os <text(); return os; } + }; diff --git a/Control/ControlTransfer.h b/Control/ControlTransfer.h index 12300eb..e52be46 100644 --- a/Control/ControlTransfer.h +++ b/Control/ControlTransfer.h @@ -1,10 +1,10 @@ /**@file Declarations for common-use control-layer functions. */ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,26 +14,28 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #ifndef CONTROLTRANSFER_H #define CONTROLTRANSFER_H #include #include #include #include -#include +#include // From CommonLibs +#include // From CommonLibs +#include namespace SIP { class DialogMessage; }; -namespace GSM { class L3Frame; class L2LogicalChannel; } - -extern int gCountTranEntry; +namespace GPRS { class MSInfo; class TBF; } namespace Control { using namespace std; +using GSM::L3Cause; class TranEntry; class HandoverEntry; class TransactionEntry; class L3LogicalChannel; -typedef unsigned TranEntryId; +typedef unsigned TranEntryId; // value 0 is reserved for an undefined value. typedef vector TranEntryList; extern bool l3rewrite(); @@ -41,55 +43,6 @@ 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; @@ -108,7 +61,7 @@ struct FullMobileId { TMSI_t mTmsi; string mImei; string fmidUsername() const; // "IMSI" or "TMSI" or "IMEI" + digits. - bool fmidMatch(const GSM::L3MobileIdentity &mobileId) const; + // moved to L3MobileIdentity: bool fmidMatch(const GSM::L3MobileIdentity &mobileId) const; void fmidSet(string value); FullMobileId() {} // Nothing needed. FullMobileId(const string wAnything) { fmidSet(wAnything); } // Default is an imsi. @@ -163,27 +116,27 @@ struct CCState { 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. +// This is the return result from neighborFindBest. +struct BestNeighbor { + Bool_z mValid; + unsigned mARFCN; // C0 of the neighbor. + unsigned mBSIC; // BSIC of the neighbor. + float mRxlev; // rxlev of this neighbor in dB. + string mHandoverCause; // String version of BSSMAP Cause. + string text() const; }; +std::ostream& operator<<(std::ostream& os, BestNeighbor best); + +// This penalty is applied to this neighbor. +struct NeighborPenalty { + int mARFCN; + unsigned mBSIC; + Timeval mPenaltyTime; // When the penalty expires. + NeighborPenalty() : mARFCN(-1), mBSIC(0) {} // Dont care about BSIC init but be neat. + bool match(int arfcn, unsigned bsic) const { return arfcn == mARFCN && bsic == mBSIC; } + string text() const; +}; +std::ostream& operator<<(std::ostream& os, NeighborPenalty np); /** Return a human-readable string for a GSM::CallState. */ @@ -214,8 +167,5 @@ class GenericL3Msg { }; #endif - -void NewTransactionTable_ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg); - }; #endif diff --git a/Control/DCCHDispatch.cpp b/Control/DCCHDispatch.cpp index 156a401..a3d1ba2 100644 --- a/Control/DCCHDispatch.cpp +++ b/Control/DCCHDispatch.cpp @@ -2,11 +2,11 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -26,15 +26,7 @@ #include "L3MobilityManagement.h" #include "L3StateMachine.h" #include "L3LogicalChannel.h" -//#include "TransactionTable.h" -#include "RadioResource.h" -//#include "MobilityManagement.h" -//#include -//#include -//#include -//#include -//#include -//#include +#include #include #undef WARNING @@ -52,10 +44,11 @@ using namespace Control; // (pat) DCCH is a TCHFACCHLogicalChannel or SDCCHLogicalChannel void Control::DCCHDispatcher(L3LogicalChannel *DCCH) { - while (1) { + while (! gBTS.btsShutdown()) { // 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); LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS"; L3Frame *frame = DCCH->waitForEstablishOrHandover(); LOG(DEBUG) << *DCCH << " received " << *frame; diff --git a/Control/L3CallControl.cpp b/Control/L3CallControl.cpp index 352a54b..8e8fc76 100644 --- a/Control/L3CallControl.cpp +++ b/Control/L3CallControl.cpp @@ -1,8 +1,9 @@ -/* Copyright 2013, 2014 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -10,10 +11,13 @@ 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 +// Written by Pat Thompson +#define LOG_GROUP LogGroup::Control +#include #include "ControlCommon.h" #include "L3CallControl.h" #include "L3StateMachine.h" @@ -44,9 +48,9 @@ class CCBase : public SSDBase { 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); + MachineStatus closeCall(TermCause cause); + MachineStatus sendReleaseComplete(TermCause cause, bool sendCause); + MachineStatus sendRelease(TermCause cause, bool sendCause); void handleTerminationRequest(); }; @@ -57,7 +61,7 @@ class MOCMachine : public CCBase { stateAssignTCHFSuccess, }; bool mIdentifyResult; - MachineStatus sendCMServiceReject(MMRejectCause rejectCause); + MachineStatus sendCMServiceReject(MMRejectCause rejectCause,bool fatal); MachineStatus handleSetupMsg(const GSM::L3Setup *setup); MachineStatus serviceAccept(); @@ -113,6 +117,7 @@ class MTCMachine : public CCBase { const char *debugName() const { return "MTCMachine"; } }; + class InboundHandoverMachine : public CCBase { bool mReceivedHandoverComplete; enum State { @@ -155,39 +160,49 @@ void startMOC(const GSM::L3MMMessage *l3msg, MMContext *dcch, CMServiceTypeCode } #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; -} +//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) +MachineStatus CCBase::sendReleaseComplete(TermCause cause, bool sendCause) { - tran()->teCloseCallNow(l3cause); - return MachineStatusQuitTran; + LOG(INFO) << "SIP term info sendReleaseComplete"<teCloseCallNow(cause,sendCause); + return MachineStatus::QuitTran(cause); } -MachineStatus CCBase::sendRelease(L3Cause l3cause) +MachineStatus CCBase::sendRelease(TermCause cause, bool sendCause) { - tran()->teCloseDialog(); // redundant, would happen soon anyway. + LOG(INFO) << "SIP term info sendRelease cause: " << cause; // SVGDBG + tran()->teCloseDialog(cause); // redundant, would happen soon anyway. if (isL3TIValid()) { unsigned l3ti = getL3TI(); if (tran()->clearingGSM()) { // Oops! Something went wrong. Clear immediately. - tran()->teCloseCallNow(l3cause); - return MachineStatusQuitTran; + LOG(INFO) << "SIP term info call teCloseCallNow cause: " << cause; + tran()->teCloseCallNow(cause,sendCause); + return MachineStatus::QuitTran(cause); } 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)); + if (sendCause) { + // If BTS initiates release, we must include the cause element. + channel()->l3sendm(GSM::L3Release(l3ti,cause.tcGetCCCause())); + } else { + // Handset sent disconnect; our reply L3Release does not include a Cause Element. GSM 4.08 9.3.18.1.1 + channel()->l3sendm(GSM::L3Release(l3ti)); + } setGSMState(CCState::ReleaseRequest); timerStart(T308,T308ms,TimerAbortTran); LOG(DEBUG) << gMMLayer.printMMInfo(); @@ -196,7 +211,7 @@ MachineStatus CCBase::sendRelease(L3Cause l3cause) } 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; + return MachineStatus::QuitTran(cause); } } @@ -204,10 +219,11 @@ MachineStatus CCBase::sendRelease(L3Cause l3cause) // 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) +MachineStatus CCBase::closeCall(TermCause cause) { - WATCHINFO("closeCall"<descriptiveString()); - tran()->teCloseDialog(); // Make sure; this is redundant because the call will be repeated when the transaction is killed, + LOG(INFO) << "SIP term info closeCall"<descriptiveString()); + tran()->teCloseDialog(cause); // 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(); @@ -215,23 +231,23 @@ MachineStatus CCBase::closeCall(L3Cause l3cause) CallState ccstate = tran()->getGSMState(); if (ccstate == CCState::Active) { if (1) { - channel()->l3sendm(GSM::L3Disconnect(l3ti,l3cause)); + channel()->l3sendm(GSM::L3Disconnect(l3ti,cause.tcGetCCCause())); 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)); + channel()->l3sendm(GSM::L3Release(l3ti,cause.tcGetCCCause())); 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. + channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,cause.tcGetCCCause())); // 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); + LOG(INFO) "SIP term info handleTerminationRequest call closeCallNow Preemption"; + // TODO: It may be pre-emption by emergency call. + //tran()->teCloseCallNow(TermCause::Local(TermCodeOperatorIntervention)); + //tran()->teCloseCallNow(TermCause::Local((L3Cause::AnyCause)L3Cause::Operator_Intervention)); + tran()->teCloseCallNow(TermCause::Local(L3Cause::Operator_Intervention),true); } @@ -257,11 +279,11 @@ void CCBase::handleTerminationRequest() 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())) { + 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)); + channel()->l3sendm(GSM::L3HoldReject(getL3TI(),L3Cause::Service_Or_Option_Not_Available)); return MachineStatusOK; // ignore bad message otherwise. } case L3CASE_MM(CMServiceAbort): { @@ -269,46 +291,61 @@ MachineStatus CCBase::defaultMessages(int state, const GSM::L3Message *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. + // 603 is only supposed to be used if we know there is no second choice like voice mail. + return closeCall(TermCause::Local(L3Cause::Call_Rejected)); // 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); + const L3Disconnect *dmsg = dynamic_cast(l3msg); + return sendRelease(TermCause::Local(dmsg->cause().cause()),false); // (pat) Preserve the cause the handset sent us. + //return sendRelease(TermCause::Local(L3Cause::Normal_Call_Clearing)); //svg change from CallRejected to NormalCallClearing 05/29/14 } case L3CASE_CC(Release): { // 24.008 5.4.3.3: In any state except ReleaseRequest send a ReleaseComplete, then kill the transaction, + timerStopAll(); const L3Release *dmsg = dynamic_cast(l3msg); if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG! - timerStopAll(); - return sendReleaseComplete(L3Cause::NormalCallClearing); + // (pat) The cause is optional; only included if the Release message is used to initiate call clearing. + L3Cause::CCCause cccause; + if (dmsg->haveCause()) { + cccause = dmsg->cause().cause(); + } else { + cccause = L3Cause::Normal_Call_Clearing; + } + return sendReleaseComplete(TermCause::Local(cccause),false); } 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. + //changed 10-24-13: return closeCall(L3Cause::Normal_Call_Clearing); // normal event. + // tran()->teCloseDialog(TermCause::Local(TermCodeNormalDisconnect)); // Redundant, and we dont know what initiated it so this error is not correct setGSMState(CCState::NullState); // redundant, we are deleting this transaction. - return MachineStatusQuitTran; + // (pat) The ReleaseComplete message may be sent by handset in response to our request for Release, + // in which case we dont want to change the termination cause from what it was previously, + // or it could be the handset informing us for the first time that it wants to delete this transaction. + TermCause cause = tran()->mFinalDisposition; + if (cause.tcIsEmpty()) { cause = TermCause::Local(L3Cause::Normal_Call_Clearing); } + return MachineStatus::QuitTran(cause); } 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); + LOG(INFO) << "SIP term info IMSIDetachIndication text: " << l3msg->text(); + // Must unregister. FIXME: We're going to do that first because the stupid layer2 may hang in l3sendm. + L3MobileIdentity mobid = detach->mobileID(); + imsiDetach(mobid,channel()); channel()->l3sendm(L3ChannelRelease()); // Many handsets never complete the transaction. // So force a shutdown of the channel. - channel()->chanRelease(HARDRELEASE); - return MachineStatusQuitChannel; + // (pat 5-2014) Changed from HARDRELEASE to RELEASE - we need to let the LAPDm shut down normally. + channel()->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::IMSI_Detached)); + return MachineStatus::QuitChannel(TermCause::Local(L3Cause::IMSI_Detached)); } case L3CASE_RR(ApplicationInformation): { const GSM::L3ApplicationInformation *aimsg = dynamic_cast(l3msg); @@ -341,15 +378,25 @@ MachineStatus CCBase::handleIncallCMServiceRequest(const GSM::L3Message *l3msg) // 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))); + // (pat) There is no termcause here because there is nothing to terminate. + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported)); 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) +MachineStatus MOCMachine::sendCMServiceReject(MMRejectCause rejectCause, bool fatal) { - channel()->l3sendm(L3CMServiceReject(L3RejectCause(rejectCause))); - return closeChannel(L3RRCause::NormalEvent,RELEASE); + channel()->l3sendm(L3CMServiceReject(rejectCause)); + LOG(INFO) << "SIP term info closeChannel called in sendCMServiceReject"; + if (fatal) { + // Authorization failure. It is an MM level failure, but a "normal event" at the RR level. + return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(rejectCause)); + } else { + // This would happen if the user is not authorized for the particular service requested. + // This case does not currently occur. + tran()->teCloseDialog(TermCause::Local(rejectCause)); + return MachineStatus::QuitTran(TermCause::Local(rejectCause)); + } } bool CCBase::isVeryEarly() { return (channel()->chtype()==GSM::FACCHType); } @@ -379,26 +426,14 @@ MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup) // 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); + // It is MOC, so we should not be sending an error to any dialogs, but we will fill in a SIP error anyway. + return closeCall(TermCause::Local(L3Cause::Missing_Called_Party_Number)); } 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. @@ -411,7 +446,8 @@ MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup) 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); + LOG(INFO) << "SIP term info closeChannel called in handlesetupMessage"; + return closeChannel(L3RRCause::Unspecified,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Sip_Internal_Error)); } //setDialog(dialog); Moved into newSipDialogMOC to eliminate a race. @@ -441,11 +477,13 @@ MachineStatus MOCMachine::serviceAccept() // TODO: This should be a function in MMContext. if (!isVeryEarly()) { if (! channel()->reassignAllocNextTCH()) { - channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); - tran()->teCloseDialog(CancelCauseCongestion); + TermCause cause = TermCause::Local(L3Cause::No_Channel_Available); + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Congestion)); + tran()->teCloseDialog(cause); // TODO: This will become redundant with closeChannel and should be removed later. // (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); + LOG(INFO) << "SIP term info closeChannel called in serviceAccept"; + return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause); } } @@ -489,7 +527,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg // 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); + return sendCMServiceReject(L3RejectCause::IMSI_Unknown_In_VLR,true); } } @@ -526,7 +564,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg string imsi(mobileID.digits()); tran()->setSubscriberImsi(string(mobileID.digits()),true); if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) { - return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true); } return serviceAccept(); } @@ -540,7 +578,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg // But for now, just accept it. tran()->setSubscriberImsi(imsi,true); if (!authorized) { - return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true); } return serviceAccept(); } @@ -551,6 +589,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg // 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. + // Or maybe the tmsi table was deleted. 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)); @@ -566,7 +605,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg string imsi(mobileID.digits()); tran()->setSubscriberImsi(imsi,true); if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) { - return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed); + return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true); } return serviceAccept(); } else { @@ -574,7 +613,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg 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 sendCMServiceReject(L3RejectCause::Message_Type_Not_Compatible_With_Protocol_State,false); } return something } @@ -614,12 +653,15 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg return MachineStatusOK; } case L3CASE_SIP(dialogRinging): { +#define ATTEMPT_TO_FIX_ZTE_PHONE 1 #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); + //L3ProgressIndicator progressIE(L3ProgressIndicator::InBandAvailable); + // To make the ZTE work I tried: Progress=Unspecified, NotISDN and Queuing. + L3ProgressIndicator progressIE(L3ProgressIndicator::Queuing,L3ProgressIndicator::User); channel()->l3sendm(L3Alerting(getL3TI(),progressIE)); #else channel()->l3sendm(L3Alerting(getL3TI())); @@ -629,6 +671,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg } case L3CASE_SIP(dialogActive): { // Success! The call is connected. + tran()->mConnectTime = time(NULL); if (gConfig.getBool("GSM.Cipher.Encrypt")) { int encryptionAlgorithm = gTMSITable.tmsiTabGetPreferredA5Algorithm(tran()->subscriberIMSI().c_str()); @@ -658,7 +701,8 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg return callMachStart(new InCallMachine(tran())); } else if (getDialog()->isFinished()) { // The SIP side hung up on us! - return closeCall(L3Cause::NormalCallClearing); + TermCause cause = dialog2TermCause(getDialog()); + return closeCall(cause); } else { // Not possible. PROCLOG(ERR) << "Connect Acknowledge received in incorrect SIP Dialog state:"<< getDialog()->getDialogState(); @@ -677,18 +721,16 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg case L3CASE_SIP(dialogBye): { // The other user hung up before we could finish. - return closeCall(L3Cause::NormalCallClearing); + return closeCall(dialog2ByeCause(getDialog())); } 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); - } + // (pat) Since this is MOC, the SIP code supplied in the cause should not be used, + // but we will be ultra cautious and preserve it. + TermCause cause = dialog2TermCause(getDialog()); + LOG(INFO) << "SIP dialogFail"<mSipDialogMessagesBlocked = false; - if (!modeOK) return closeCall(L3Cause::ChannelUnacceptable); + if (!modeOK) return closeCall(TermCause::Local(L3Cause::Channel_Unacceptable)); return MachineStatusPopMachine; } @@ -786,7 +828,7 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message channel()->reassignComplete(); PROCLOG(INFO) << "successful assignment"; PROCLOG(DEBUG) << gMMLayer.printMMInfo(); - if (IS_LOG_LEVEL(DEBUG)) { + if (IS_WATCH_LEVEL(DEBUG)) { cout << "AssignmentComplete:\n"; CommandLine::printChansV4(cout,false); } @@ -804,10 +846,26 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message sendReassignment(); return MachineStatusOK; } else { + // (pat) redundant: chanFreeContext(TermCause::Local(L3Cause::Channel_Assignment_Failure)); goto caseAssignTimeout; } } + case stateAssignTimeout: { + // This is the case where we received neither AssignmentComplete nor AssignmentFailure - it is loss of radio contact. + LOG(INFO) << "SIP term info stateAssignTimeout NoUserResponding"; + caseAssignTimeout: + channel()->reassignFailure(); + // TODO: This is not optimal - we should drop back to the MMLayer to see if it wants to do something else. + // Determine and pass cause SVGDBG + LOG(INFO) << "SIP term info dialogCancel called in AssignTCHMachine::machineRunState"; + TermCause cause = TermCause::Local(L3Cause::Channel_Assignment_Failure); + if (getDialog()) { getDialog()->dialogCancel(cause); } // Should never be NULL, but dont crash. + // We dont call closeCall because we already sent the specific RR message required for this situation. + LOG(INFO) << "SIP term info closeChannel called in AssignTCHMachine::machineRunState 1"; + return closeChannel(L3RRCause::No_Activity_On_The_Radio,L3_RELEASE_REQUEST,cause); + } + // 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): { @@ -816,15 +874,9 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message 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): + LOG(DEBUG) << "ignoring duplicate L3Setup"; + return MachineStatusOK; default: if (sipmsg) { @@ -843,20 +895,25 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg 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); + //formerly: return closeCall(L3Cause::Interworking_Unspecified); + return closeCall(dialog2TermCause(getDialog())); } // Allocate channel now, to be sure there is one. - if (!isVeryEarly()) { + // Formerly all we had to do was check the VEA flag, since that controlled the channel type, + // but it is better to test for TCHF directly - this works for testcall where the channel type was + // specified by the user, and also handles the rare case where the VEA option changed on us. + //if (!isVeryEarly()) + if (! channel()->isTCHF()) { if (! channel()->reassignAllocNextTCH()) { - channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); - tran()->teCloseDialog(CancelCauseCongestion); + channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Congestion)); + TermCause cause = TermCause::Local(L3Cause::No_Channel_Available); + tran()->teCloseDialog(cause); // (pat) TODO: We are supposed to go back to using SDCCH in case of an ongoing SMS. - return closeChannel(L3RRCause::NormalEvent,RELEASE); + LOG(INFO) << "SIP term info closeChannel called in AssignTCHMachine::machineRunState 2"; + return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause); } } @@ -876,7 +933,7 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg timerStart(T303,T303ms,TimerAbortTran); // Time state "Call Present"; start CMServiceRequest recv; stop CallProceeding recv. // And send trying message to SIP - getDialog()->MTCSendTrying(); + if (getDialog()) { getDialog()->MTCSendTrying(); } return MachineStatusOK; // Wait for L3CallConfirmed message. } @@ -920,7 +977,7 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* 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(); + if (getDialog()) { getDialog()->MTCSendRinging(); } return MachineStatusOK; // Waiting for L3Connect. } @@ -942,12 +999,13 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg // 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()); + if (getDialog()) { 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. + tran()->mConnectTime = time(NULL); // (pat) To doug: The place to move cipher starting is probably InCallMachine::machineRunState case stateStart. if (gConfig.getBool("GSM.Cipher.Encrypt")) { @@ -980,11 +1038,14 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg // SIP Dialog failure cases. case L3CASE_SIP(dialogBye): { // The other user hung up before we could finish. - return closeCall(L3Cause::NormalCallClearing); + return closeCall(dialog2ByeCause(getDialog())); } case L3CASE_SIP(dialogFail): { // It cannot be busy because it is a MTC. - return closeCall(L3Cause::InterworkingUnspecified); + // This most likely a CANCEL, ie, it is a Mobile Terminated Disconnect before the SIP dialog ACK. + TermCause cause = dialog2TermCause(getDialog()); + LOG(INFO) << "SIP dialogFail"<subscriber()); @@ -1017,8 +1079,10 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me 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); + TermCause cause = TermCause::Local(L3Cause::Invalid_Handover_Message); + closeCall(cause); + LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 1"; + return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause); } setDialog(dialog); setGSMState(CCState::HandoverProgress); @@ -1028,15 +1092,19 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me 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); + LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 2"; + return closeCall(dialog2TermCause(getDialog())); + //return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST); case L3CASE_SIP(dialogBye): // SIP end hung up. Just hang up the MS. - closeCall(L3Cause::NormalCallClearing); - return closeChannel(L3RRCause::NormalEvent,RELEASE); + LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 3"; + return closeCall(dialog2ByeCause(getDialog())); + //return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST); case L3CASE_SIP(dialogActive): { // Success! SIP side is active. + tran()->mConnectTime = time(NULL); timerStop(TSipHandover); getDialog()->MOCSendACK(); @@ -1071,7 +1139,8 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me // 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); + TermCause cause = TermCause::Local(L3Cause::Invalid_Handover_Message); + return closeChannel(L3RRCause::Message_Type_Not_Compapatible_With_Protocol_State,L3_RELEASE_REQUEST,cause); } 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"); @@ -1094,7 +1163,7 @@ void InCallMachine::acknowledgeDtmf() 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)); + channel()->l3sendm(GSM::L3StartDTMFReject(tran()->getL3TI(),L3Cause::Service_Or_Option_Not_Available)); } } @@ -1202,17 +1271,19 @@ MachineStatus InCallMachine::machineRunState(int state, const GSM::L3Message *l3 return MachineStatusOK; } case L3CASE_SIP(dialogBye): { - return closeCall(L3Cause::NormalCallClearing); + return closeCall(dialog2ByeCause(getDialog())); } 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); + TermCause cause = dialog2TermCause(getDialog()); + LOG(INFO) << "SIP dialogFail"< #include +#define LOG_GROUP LogGroup::Control #include #include "ControlCommon.h" -#include "RadioResource.h" +#include "L3Handover.h" #include "L3CallControl.h" #include "L3MMLayer.h" #include #include #include "../GPRS/GPRSExport.h" +#include +#include #include #include @@ -52,166 +55,10 @@ static void abortInboundHandover(RefCntPointer transaction, RRCause c 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! @@ -226,14 +73,14 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) RefCntPointer tran = chan->chanGetVoiceTran(); if (tran == NULL) { LOG(WARNING) << "handover access with no inbound transaction on " << chan; - chan->chanRelease(HARDRELEASE); + chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error)); return; } LOG(DEBUG) << *tran; if (!tran->getHandoverEntry(false)) { LOG(WARNING) << "handover access with no inbound handover on " << *chan; - chan->chanRelease(HARDRELEASE); + chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error)); return; } @@ -249,12 +96,12 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) // 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? + abortInboundHandover(tran,L3RRCause::Handover_Impossible,dynamic_cast(tran->channel())); + chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Distance)); // TODO: Is this right? Will the channel be immediately re-available? return; } - chan->getL2Channel()->setPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp); + chan->getL2Channel()->l1InitPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp); // Respond to handset with physical information until we get Handover Complete. int TA = (int)(hr.mhrTimingError + 0.5F); @@ -262,17 +109,17 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) 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. + Z100Timer T3105(gConfig.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--) { + for (unsigned sendCount = gConfig.getNum("GSM.Handover.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); + chan->l3sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::L3_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 @@ -286,7 +133,7 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) LOG(INFO) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; delete frame; continue; - case ESTABLISH: + case L3_ESTABLISH_INDICATION: delete frame; // Channel is established, so the MS is there. Finish up with a state machine. startInboundHandoverMachine(tran.self()); @@ -296,8 +143,8 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) 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? + abortInboundHandover(tran,L3RRCause::Message_Type_Not_Compapatible_With_Protocol_State,chan); + chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error)); // TODO: Is this right? Will the channel be immediately re-available? return; } } @@ -306,199 +153,90 @@ void ProcessHandoverAccess(L3LogicalChannel *chan) // 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? + abortInboundHandover(tran,L3RRCause::No_Activity_On_The_Radio,chan); + chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // TODO: Is this right? Will the channel be immediately re-available? return; +} +static BestNeighbor NoHandover(BestNeighbor np) +{ + np.mValid = false; + return np; +} -#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 +static BestNeighbor YesHandover(BestNeighbor &np, L3Cause::BSSCause why) +{ + assert(np.mValid); + np.mHandoverCause = string(L3Cause::BSSCause2Str(why)); + return np; } -// Warning: This runs in a separate thread. -void HandoverDetermination(const L3MeasurementResults& measurements, float myRxLevel, SACCHLogicalChannel* SACCH) +static BestNeighbor HandoverDecision(const L3MeasurementResults* measurements, 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; + ChannelHistory *chp = sacch->getChannelHistory(); + int myRXLEV_DL = chp->getAvgRxlev(); + NeighborPenalty penalty; + if (MMContext *mmc = sacch->hostChan()->chanGetContext(false)) { + penalty = mmc->mmcHandoverPenalty; } + LOG(DEBUG) << LOGVAR(penalty); + + BestNeighbor bestn = chp->neighborFindBest(penalty); + if (! bestn.mValid) { return NoHandover(bestn); } + + int margin = gConfig.GSM.Handover.Margin; + + Peering::NeighborEntry nentry; + if (!gNeighborTable.ntFindByArfcn(bestn.mARFCN, bestn.mBSIC, &nentry)) { + LOG(ERR) << "Could not find best neighbor entry from :"<= margin) { return YesHandover(bestn,L3Cause::Downlink_Strength); } + + return NoHandover(bestn); +} + +// Warning: This runs in a separate thread. +void HandoverDetermination(const L3MeasurementResults* measurements, SACCHLogicalChannel* sacch) +{ + //LOG(DEBUG) <text(); + // This is called from the SACCH service loop. + if (! sacch->neighborAddMeasurements(sacch,measurements)) 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()); + const L3LogicalChannel *mainChanConst = dynamic_cast(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; + LOG(ERR) << "active SACCH with no transaction record: " << *sacch; return; } if (tran->getGSMState() != CCState::Active) { @@ -526,8 +264,8 @@ void HandoverDetermination(const L3MeasurementResults& measurements, float myRxL LOG(DEBUG) << "skipping handover for transaction " << tran->tranID() << " because age "<tranID() - << " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm"; + LOG(INFO) << "preparing "<tranID() + << " to " << peerstr << " with downlink RXLEV=" << bestn.mRxlev << " 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. @@ -535,7 +273,7 @@ void HandoverDetermination(const L3MeasurementResults& measurements, float myRxL // 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())) { + if (resolveAddress(&peerAddr,peerstr.c_str())) { LOG(ALERT) "handover"<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. @@ -596,13 +327,16 @@ bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH) // 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.. + // 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; } + if (IS_LOG_LEVEL(NOTICE)) { + L3Message *msg = parseL3(*result); + // It is ok to pass a NULL L3Message pointer here. + LOG(NOTICE) << "failed handover, "<setGSMState(CCState::Active); @@ -618,12 +352,10 @@ bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH) // (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. + TermCause cause = TermCause::Local(L3Cause::Handover_Outbound); // There is no SIP code. The dialog was moved to another BTS via a SIP REFER. + transaction->teCancel(cause); // We need to do this immediately in case a reverse handover comes back soon. This destroys the dialog too. - // We need to immediately destroy the dialog. - - LOG(INFO) "timeout following outbound handover; exiting normally"; - //TCH->l2sendp(GSM::HARDRELEASE); now done by caller. + LOG(INFO) <<"timeout following outbound handover; exiting normally"; return true; } diff --git a/Control/L3Handover.h b/Control/L3Handover.h new file mode 100644 index 0000000..48b956b --- /dev/null +++ b/Control/L3Handover.h @@ -0,0 +1,33 @@ +/* +* Copyright 2014 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. +*/ + +#ifndef L3HANDOVER_H +#define L3HANDOVER_H + +namespace GSM { + class L3MeasurementResults; + class SACCHLogicalChannel; +}; + +namespace Control { + class L3LogicalChannel; + class TranEntry; + +void ProcessHandoverAccess(L3LogicalChannel *chan); +bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH); +void HandoverDetermination(const GSM::L3MeasurementResults* measurements, GSM::SACCHLogicalChannel* sacch); +}; +#endif diff --git a/Control/L3LogicalChannel.cpp b/Control/L3LogicalChannel.cpp index 1f0affc..750536c 100644 --- a/Control/L3LogicalChannel.cpp +++ b/Control/L3LogicalChannel.cpp @@ -3,7 +3,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +// Written by Pat Thompson #define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging @@ -29,7 +30,7 @@ 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. + chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // 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 @@ -39,7 +40,7 @@ void L3LogicalChannel::L3LogicalChannelReset() // This state indicates the other channel is idle, but it is dangerous to free // it from here because it could receive a primitive and become active at any time. // If that happens it will get a new MMContext and try to fire up, not sure what happens then. - mNextChan->chanFreeContext(); + mNextChan->chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); mNextChan = NULL; } LOG(DEBUG); @@ -59,10 +60,11 @@ void L3LogicalChannel::L3LogicalChannelInit() L3LogicalChannel::~L3LogicalChannel() { - chanFreeContext(); + chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); } -const char *L3LogicalChannel::descriptiveString() const { +// This virtual method should never be called; it is over-ridden by the sub-class for all channel types that matter. +const char * L3LogicalChannel::descriptiveString() const { return "undefined"; } @@ -79,7 +81,7 @@ const L2LogicalChannel * L3LogicalChannel::getL2Channel() const { } L3LogicalChannel *L3LogicalChannel::getSACCHL3() { - return dynamic_cast(this->getL2Channel()->SACCH()); + return dynamic_cast(this->getL2Channel()->getSACCH()); } void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/) @@ -89,13 +91,16 @@ void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& } // These days this is used only for the handover command, which was sent as a pre-formed L3-message from BTS2 to BTS1. +// Note that SAP is encoded in the L3Frame. void L3LogicalChannel::l3sendf(const GSM::L3Frame& frame) { - LOG(INFO) <primitive()==ESTABLISH) return req; + if (req->primitive()==L3_ESTABLISH_INDICATION) return req; if (req->primitive()==HANDOVER_ACCESS) return req; LOG(INFO) << "L3LogicalChannel: Ignored primitive:"<primitive(); delete req; @@ -127,20 +133,32 @@ L3Frame* L3LogicalChannel::waitForEstablishOrHandover() MMContext *L3LogicalChannel::chanGetContext(bool create) { - LOG(DEBUG); + //LOG(DEBUG); ScopedLock lock(gMMLock,__FILE__,__LINE__); - LOG(DEBUG); + //LOG(DEBUG); if (mChContext == NULL) { if (create) { mChContext = new MMContext(this); } } return mChContext; } +void L3LogicalChannel::chanSetHandoverPenalty(NeighborPenalty &penalty) +{ + chanGetContext(false)->chanSetHandoverPenalty(penalty); +} + // 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" : ""); + return mChContext ? mChContext->mmGetImsi(verbose) : string(verbose ? "no-MMChannel" : ""); +} + +// WARNING: This is called from the CLI thread. +time_t L3LogicalChannel::chanGetDuration() const +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + return mChContext ? mChContext->mmcDuration() : 0; } //void L3LogicalChannel::chanSetContext(MMContext* wContext) @@ -152,7 +170,7 @@ string L3LogicalChannel::chanGetImsi(bool verbose) const // 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() +void L3LogicalChannel::chanFreeContext(TermCause cause) { LOG(DEBUG); ScopedLock lock(gMMLock,__FILE__,__LINE__); @@ -161,7 +179,7 @@ void L3LogicalChannel::chanFreeContext() mChContext = NULL; if (save) { LOG(DEBUG) <setPhy(*getL2Channel()); + tch->lcstart(); + LOG(DEBUG) << LOGVAR2("curchan",this)<(tch); @@ -201,21 +223,21 @@ void L3LogicalChannel::reassignStart() LOG(ERR) <<"At start of channel reassignment target channel is not idle:" <chanFreeContext(); // This is supposed to be a no-op. + mNextChan->chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // 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 + // We set this state on nextChan in case of channel loss - see L3LogicalChannelReset 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->lcopen(); // 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. +// We need to release the nextChannel. Caller takes care of this chan. // 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() { @@ -228,7 +250,7 @@ void L3LogicalChannel::reassignFailure() // 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->chanFreeContext(TermCause::Local(L3Cause::Channel_Assignment_Failure)); mNextChan = NULL; } else { LOG(ERR) << "reassignment failure but no nextChan? "<mmSetChannel(mNextChan); LOG(INFO) <<"successful channel reassignment" < L3LogicalChannel::chanGetVoiceTran() return set->tsGetVoiceTran(); } -void L3LogicalChannel::chanEnqueueFrame(L3Frame *frame) -{ - ml3UplinkQ.write(frame); -} +//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. diff --git a/Control/L3LogicalChannel.h b/Control/L3LogicalChannel.h index ce42dcd..0207a36 100644 --- a/Control/L3LogicalChannel.h +++ b/Control/L3LogicalChannel.h @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -16,8 +16,10 @@ #ifndef _L3LOGICALCHANNEL_H_ #define _L3LOGICALCHANNEL_H_ 1 #include "ControlTransfer.h" +#include "L3TermCause.h" +//#include +#include #include -#include namespace Control { using namespace GSM; @@ -62,14 +64,14 @@ class L3LogicalChannel { // 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? + MMContext *mChContext; // When set we increment the use count in the MMContext. protected: L3LogicalChannel *mNextChan; // Used in GSM during channel reassignment. //L3LogicalChannel *mPrevChan; public: bool chanRunning(); - InterthreadQueue ml3UplinkQ; // uplink SACCH message are enqueued here. + //InterthreadQueue ml3UplinkQ; // uplink SACCH message are enqueued here. private: // This can be thought of as the RR state, as known from an L3 perspective. @@ -94,17 +96,17 @@ class L3LogicalChannel { 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; + virtual GSM::L3Frame * l2recv(unsigned timeout_ms = 15000) = 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 l2sendf(const GSM::L3Frame& frame) = 0; + virtual void l2sendm(const GSM::L3Message& msg, GSM::Primitive prim=GSM::L3_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 l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::L3_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; + //unused virtual unsigned N200() const = 0; + virtual bool multiframeMode(SAPI_t 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; @@ -118,9 +120,11 @@ class L3LogicalChannel { L3LogicalChannel* getSACCHL3(); MMContext *chanGetContext(bool create); + void chanSetHandoverPenalty(NeighborPenalty &penalty); 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. + time_t chanGetDuration() const; //void chanSetContext(MMContext* wTranSet); - void chanFreeContext(); + void chanFreeContext(TermCause cause); void reassignComplete(); void reassignFailure(); void reassignStart(); @@ -130,14 +134,16 @@ class L3LogicalChannel { //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 + void chanClose(GSM::RRCause cause, // cause sent to the handset. + GSM::Primitive prim, // prim is RELEASE or HARDRELEASE + TermCause upstreamCause); // All active transactions closed with this - sent upstram via SIP. // 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); + void chanRelease(Primitive prim,TermCause cause); RefCntPointer chanGetVoiceTran(); //void chanSetVoiceTran(TranEntry *trans); - void chanEnqueueFrame(L3Frame *frame); + //void chanEnqueueFrame(L3Frame *frame); /** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */ GSM::L3Frame* waitForEstablishOrHandover(); diff --git a/Control/L3MMLayer.cpp b/Control/L3MMLayer.cpp index 440fd43..6be6345 100644 --- a/Control/L3MMLayer.cpp +++ b/Control/L3MMLayer.cpp @@ -1,8 +1,9 @@ -/* Copyright 2013, 2014 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +// Written by Pat Thompson + #define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging #include "L3MMLayer.h" @@ -78,6 +81,7 @@ void MMContext::startSMSTran(TranEntry *tran) } // (pat) WARNING: If this routine returns true it has performed the gMMLock.unlock() corresponding to a lock() in the caller. +// Setting the lock in one function and releasing it in another sucks and should be fixed. bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel. { devassert(gMMLock.lockcnt()); // Caller locked it. @@ -96,7 +100,11 @@ bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel. // Did the SIP session give up while we were waiting? // That will be handled in the MTCMachine. - initMTC(tran); + switch (tran->servicetype()) { + default: + initMTC(tran); + break; + } gMMLock.unlock(); tran->lockAndStart(); return true; @@ -142,6 +150,8 @@ bool MMContext::mmInMobilityManagement() // Return true if anything happened. bool MMContext::mmCheckNewActivity() { + // Dont lock gMMLock up here - we may send a message later and we cant do that holding the lock. + // 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. @@ -155,20 +165,26 @@ bool MMContext::mmCheckNewActivity() // 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__); + gMMLock.lock(__FILE__,__LINE__); // Do not replace this one with a scoped lock! if (mmcMMU) { - if (mmcMMU->mmuServiceMTQueues()) { return true; } + if (mmcMMU->mmuServiceMTQueues()) { return true; } // If it returns true it unlocked the lock, gack. } gMMLock.unlock(); } // If there are no transactions, kill the channel. + // We wait 5 seconds to allow a transaction to start; otherwise there is a race because + // the channel service thread that calls this method is started by an ESTABLISH sent by layer 1, + // which is sent before the message that initiates the transaction is sent. + // The 5 seconds is kind of made up. It doesnt have to be very long because at channel initiation + // the signal should be good. // 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); + if (mmIsEmpty() && mmcDuration() > 5) { + LOG(DEBUG) <<"closing"<chanClose(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected)); return true; // This is new activity - the calling loop should skip back to the top } return false; @@ -190,8 +206,40 @@ MMUser::MMUser(string& wImsi) // LOG(DEBUG) << "MMUser ALLOC "<<(void*)this; //} + +//GSM::CMServiceTypeCode MMUser::mmuGetInitialServiceType() +//{ +// devassert(gMMLock.lockcnt()); // Caller locked it. +// if (mmuMTCq.size()) { +// TranEntry *front = this->mmuMTCq.front(); +// return front->servicetype(); +// } +// devassert(mmuMTSMSq.size()); +// // The purpose of this is to choose the channel type, so it doesnt really matter what the servicetype is as long as it is one that can use SDCCH. +// return L3CMServiceType::ShortMessage; +//} + +GSM::ChannelType MMUser::mmuGetInitialChanType() const +{ + devassert(gMMLock.lockcnt()); // Caller locked it. + if (mmuMTCq.size()) { + TranEntry *front = this->mmuMTCq.front(); + switch (front->servicetype()) { + case L3CMServiceType::MobileOriginatedCall: + devassert(0); + case L3CMServiceType::MobileTerminatedCall: + case L3CMServiceType::EmergencyCall: + return gConfig.getBool("Control.VEA") ? GSM::TCHFType : GSM::SDCCHType; + default: // There shouldnt be anything else in the MTCq. + return GSM::SDCCHType; + } + } + devassert(mmuMTSMSq.size()); + return GSM::SDCCHType; +} + // 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. +void MMUser::mmuFree(MMUserMap::iterator *piter, TermCause 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. @@ -266,7 +314,6 @@ bool MMContext::mmCheckTimers() // // 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); @@ -638,14 +685,9 @@ void MMUser::mmuAddMT(TranEntry *tran) { ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill. //ScopedLock lock(mmuLock,__FILE__,__LINE__); - mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113")); + mmuPageTimer.future(gConfig.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; @@ -683,16 +725,16 @@ std::ostream& operator<<(std::ostream& os, const MMUser*mmu) { if (mmu) mmu->mmu 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 + // (pat) 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)); + mmcOpenTime = time(NULL); LOG(DEBUG)<<"MMContext ALLOC "<<(void*)this; } @@ -719,7 +761,7 @@ string MMContext::mmGetImsi(bool verbose) void MMContext::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/) { - WATCHINFO("sendm "<l2sendm(msg,prim,SAPI); } @@ -730,6 +772,7 @@ void MMContext::mmcText(std::ostream&os) const os << " MMContext("; os < MMContext::mmGetTran(unsigned ati) const 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: + // When a primary transaction is deleted we may promote the secondary transaction, so keep trying to 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(); + // (pat) This is a bug somewhere. + mmcTE[ati]->teCancel(TermCause::Local(L3Cause::L3_Internal_Error)); } } - mmcTE[ati] = tran; - tran->teSetContext(this); + mmcTE[ati] = tran; // Takes charge of tran; increments the refcnt + tran->teSetContext(this); // And set the back pointer. } @@ -795,14 +839,11 @@ void MMContext::mmConnectTran(TranEntry *tran) 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; @@ -845,6 +886,7 @@ void MMContext::mmDisconnectTran(TranEntry *tran) LOG(ERR) << "Attempt to remove transaction "<tranID()<<" not found in MMContext"; } + // Does nothing if already unlinked. void MMContext::mmcUnlink() { @@ -860,11 +902,51 @@ void MMContext::mmcUnlink() } } + +// Move transactions from oldmmc to this, which is the current MMC serving the handset. +// We do this when we have positive knowledge that the oldmmc channel has been abandoned by the handset. +// This happens when the MS was on one MMC and has been moved or showed up on another MMC. +void MMContext::mmcMoveTransactions(MMContext *oldmmc) +{ + for (unsigned ati = TE_first; ati < TE_num; ati++) { + if (! oldmmc->mmcTE[ati].isNULL()) { + if (mmcTE[ati].isNULL()) { + // Disconnect the old tran but be careful not to delete it. + // To be sure we have to keep a pointer to it through this operation. + RefCntPointer oldtran = oldmmc->mmcTE[ati]; + oldmmc->mmcTE[ati] = NULL; + oldtran->teSetContext(NULL); // Not necessary, but be tidy. + mmConnectTran((ActiveTranIndex)ati, oldtran.self()); + } else { + // (pat) Disaster. There is a corresponding transaction already running on the new MMC, + // for example, old voice transaction and new voice transaction. + // I don't think this is possible for double paging responses (see comments at mmcLink and NewPagingResponseHandler) + // because we call mmcLink immediately when the second page is received, so the new MMC is empty. + // I'm not sure about other cases; the logic is too complicated. + // We will keep the new (more recent) transaction and the old transaction on the + // old MMC will be dropped when that channel is closed. + LOG(ERR) << "Handset has changed channels and has transactions running on the both channels. " + <tsChannel()) <mmcTE[ati].self()); + // We dont do anything. The transaction will be delete when the old channel is closed, + // which the caller should do immediately. We could call teCancel here but teCancel is tricky + // and I would like to reduce the number of calls to it. This probably doesnt happen anyway. + // If this does happen, we could be more clever, like a voice transaction could move to the secondary slot, etc. + } + } + } +} + +// This is called whenever we have positively identified an MS so we can connect the MMU to the MMC. +// That includes: +// 1. when we receive a PagingResponse message (which includes an IMSI or TMSI; note that PagingResponse +// is an L3 message which means the MS has already negotiated L2 LAPDm connection to send it.) +// 2. MOC call control when we identify the MS +// 3. Mobility Management after authorization. +// 4. From SMS somewhere too. 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) { @@ -872,27 +954,41 @@ void MMContext::mmcLink(MMUser *mmu) devassert(mmu->mmuContext == mmc); // We always maintain pointers both ways. return; } - if (mmu->mmuContext) { mmu->mmuContext->mmcUnlink(); } + // 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. + // (pat 4-2014) It also happens for paging response: Sometimes the MS sends two RACHes in a row, + // which allocates two channels (say A and B.) + // We send two immediate assignments, and the MS may respond to both! First it does an L1 LAPDm negotiation on A + // and sends a Paging Response there, which connects its MMU to the MMContext for A, then it + // does an L2 LAPDm negotiation on B and sends a second Paging Response there, so we get here with this == channel B + // but with the MMU attached to channel A. So we must disconnect the existing channel and move the MMU to the new channel. + // We have to move the transactions from the old MMContext to the new; for example if there was only one MTC transaction and + // it has already been moved from the MMU to the MMC, then the MMU is empty of transactions which will release channel B + // immmediately in mmCheckNewActivity. + if (MMContext *oldmmc = mmu->mmuContext) { + if (oldmmc != mmc) { + LOG(DEBUG) <<"reconnecting mmu"<mmcUnlink(); + } mmc->mmcUnlink(); mmc->mmcMMU = mmu; mmu->mmuContext = mmc; } -void MMContext::mmcFree() +void MMContext::mmcFree(TermCause cause) { 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 + if (mmcTE[i] != NULL) { mmcTE[i]->teCancel(cause); } // Removes the transaction from mmcTE via mmDisconnectTran } assert(mmcTE[i] == NULL); // teCancel removed it. } @@ -901,7 +997,12 @@ void MMContext::mmcFree() } // The logical channel no longer points to this Context, so release it. -void MMLayer::mmFreeContext(MMContext *mmc) +// The cause is used only for reporting purposes for any transactions still extent; +// the underlying channel has already been released so we cannot send a cause code downstream, +// and if there are any new SIP dialogs upstream we should normally start re-paging the handset +// to create a new mmcontext rather than cancelling them. +// TODO: We may want to cancel any SIP dialogs based on the cause. +void MMLayer::mmFreeContext(MMContext *mmc,TermCause cause) { // There can be multiple logical channels pointing to the same Context, so decrement // the channel use count and delete only when 0. @@ -921,19 +1022,19 @@ void MMLayer::mmFreeContext(MMContext *mmc) // 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"); + //SipCode sipcode(480,"Temporarily Unavailable"); if (mmu) { - // It is possible for new SIP dialogs to have started between the time we decided + // FIXME 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. + mmu->mmuFree(NULL,TermCause::Local(L3Cause::No_Transaction_Expected)); // TermCause is not used because there are no dialogs. } } - mmc->mmcFree(); + mmc->mmcFree(cause); } void MMLayer::mmMTRepage(const string imsi) @@ -944,7 +1045,7 @@ void MMLayer::mmMTRepage(const string imsi) MMUser *mmu = mmFindByImsi(imsi,false); if (mmu) { // This has no effect unless we are paging, ie, if the MMUser has not yet connected to an MMChannel. - mmu->mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113")); + mmu->mmuPageTimer.future(gConfig.GSM.Timer.T3113); } else { LOG(DEBUG) << "repeated INVITE/MESSAGE with no MMUser record"; } @@ -968,6 +1069,27 @@ void MMLayer::mmAttachByImsi(L3LogicalChannel *chan, string imsi) // So now we want to unblock any previously blocked imsi/tmsi } + +bool MMLayer::mmTerminateByImsi(string imsi) +{ + ScopedLock lock(gMMLock,__FILE__,__LINE__); + MMUser *mmu = mmFindByImsi(imsi,false); + if (!mmu) { return false; } // Not found. + MMContext* mmc = mmu->mmuContext; + if (!mmc) { + // There is no channel, just kill off the MMUser, which will stop paging and cancel the SIP dialogs. + mmu->mmuFree(NULL,TermCause::Local(L3Cause::Operator_Intervention)); + return true; + } + if (mmc->tsChannel()->chanRunning()) { + // Dont call chanClose from here because it sends a message which would block the calling thread. + //mmc->tsChannel()->chanClose(L3RRCause::PreemptiveRelease,RELEASE); DONT DO THIS! + // FIXME: We would like to send an RR Release message first. + mmc->mmcTerminationRequested = true; + } + return true; +} + // This is the way MMUsers are created from the SIP side. void MMLayer::mmAddMT(TranEntry *tran) { @@ -991,16 +1113,6 @@ MMUser *MMLayer::mmFindByImsi(string imsi, // Do not change this to a reference. { LOG(DEBUG) <mmuDidTmsiCheck) { + this->mmuDidTmsiCheck = true; + if (uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mmuImsi,true)) { this->mmuTmsi = tmsi; } + } + return this->mmuTmsi; +} + MMUser *MMLayer::mmFindByTmsi(uint32_t tmsi) { devassert(gMMLock.lockcnt()); // Caller locked it. @@ -1032,7 +1154,11 @@ MMUser *MMLayer::mmFindByTmsi(uint32_t tmsi) MMUser *result = NULL; for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ++it) { MMUser *mmu = it->second; - if (mmu->mmuTmsi.valid() && mmu->mmuTmsi.value() == tmsi) { result = mmu; break; } + // (pat) The handset we want could be simultaneously doing an MM procedure that is establishing a TMSI, + // so we should check the tmsi table every single time this happens. + // However, we are currently doing an expensive sql lookup so only check once. + TMSI_t mmutmsi = mmu->mmuGetTmsi(); + if (mmutmsi.valid() && mmutmsi.value() == tmsi) { result = mmu; break; } } LOG(DEBUG) << LOGVAR(result); return result; @@ -1050,17 +1176,15 @@ MMUser *MMLayer::mmFindByMobileId(L3MobileIdentity&mid) } } -// 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) +// When called from the paging thread loop this function is responsible for noticing expired MMUsers and deleting them. +void MMLayer::mmGetPages(NewPagingList_t &pages) { - LOG(DEBUG); - ScopedLock lock(gMMLock,__FILE__,__LINE__); + //LOG(DEBUG) <second; @@ -1074,48 +1198,48 @@ void MMLayer::mmGetPages(NewPagingList_t &pages, bool wait) // 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); + // Paul at Null Team recommended 504. + // FIXME URGENTLY: Dont do an mmFree within the gMMLock, although we need to make sure it does not disappear. + mmu->mmuFree(&thisone,TermCause::Local(L3Cause::No_Paging_Response)); 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)); + NewPagingEntry tmp(mmu->mmuGetInitialChanType(), mmu->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. } + if (pages.size()) LOG(DEBUG) < #include "ControlCommon.h" +#include "PagingEntry.h" #include "L3TranEntry.h" // Needed because InterthreadQueue deletes its elements on destruction. -#include "RadioResource.h" // For Paging +//#include "RadioResource.h" // For Paging #include "L3Utils.h" #include @@ -73,9 +74,18 @@ class MMUser : public MemCheckMMUser /*: public RefCntBase*/ { 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. + + protected: string mmuImsi; // Just the imsi, without "IMSI" + public: string mmuGetImsi(bool verbose) { return mmuImsi.empty() ? (verbose ? "no-imsi" : "") : mmuImsi; } + + protected: TMSI_t mmuTmsi; + Bool_z mmuDidTmsiCheck; // Have we looked up the IMSI in the TMSI table yet? + public: TMSI_t mmuGetTmsi(); + + protected: + void mmuFree(MMUserMap::iterator *it,TermCause cause /*= TermCauseUnknown*/); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the universe first. + + GSM::ChannelType mmuGetInitialChanType() const; void MMUserInit() { mmuState = MMStateUndefined; mmuContext = NULL; } public: @@ -84,7 +94,6 @@ class MMUser : public MemCheckMMUser /*: public RefCntBase*/ { 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(); @@ -111,13 +120,19 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { MMUser* mmcMMU; //L3Timer TChReassignment; + void mmcMoveTransactions(MMContext *oldmmc); protected: void mmcUnlink(); void mmcLink(MMUser *mmu); + time_t mmcOpenTime; // These are the Transactions/Procedures that may be active simultaneously: public: - UDPSocket *mmcFuzzPort; + Bool_z mmcTerminationRequested; + NeighborPenalty mmcHandoverPenalty; + void chanSetHandoverPenalty(NeighborPenalty &wPenalty) { mmcHandoverPenalty = wPenalty; } + + enum ActiveTranIndex { TE_first = 0, // Start of table. @@ -133,11 +148,11 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { }; RefCntPointer mmcTE[TE_num]; unsigned mNextTI; - InterthreadQueue mmcServiceRequests; + InterthreadQueue mmcServiceRequests; // Incoming CM Service Request messages. 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. + void mmcFree(TermCause cause); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the MMUserMap first. public: MMContext(L3LogicalChannel *chan); @@ -146,6 +161,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { L3LogicalChannel *tsChannel() { return mmcChan; } MMContext *tsDup(); void mmcPageReceived() const; + time_t mmcDuration() const { return time(NULL) - mmcOpenTime; } RefCntPointer mmGetTran(unsigned ati) const; void mmConnectTran(ActiveTranIndex ati, TranEntry *tran); @@ -156,7 +172,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { 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 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 "" @@ -166,7 +182,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ { 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 l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::L3_DATA, SAPI_t SAPI=SAPI0); void mmcText(std::ostream&os) const; }; std::ostream& operator<<(std::ostream& os, const MMContext&mmc); @@ -193,23 +209,25 @@ class MMLayer { Signal mmPageSignal; ///< signal to wake the paging loop MMUserMap MMUsers; public: - void mmGetPages(NewPagingList_t &pages, bool wait); + void mmGetPages(NewPagingList_t &pages); 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); + void mmFreeContext(MMContext *mmc,TermCause cause); // 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 mmTerminateByImsi(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 mmGetNeighborTextByImsi(string imsi, bool full); string printMMInfo(); // Is the single MTC slot busy? diff --git a/Control/L3MobilityManagement.cpp b/Control/L3MobilityManagement.cpp index 6ac5b81..982ecfc 100644 --- a/Control/L3MobilityManagement.cpp +++ b/Control/L3MobilityManagement.cpp @@ -1,8 +1,9 @@ -/* Copyright 2013, 2014 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +// Written by Pat Thompson + #define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging #include @@ -27,6 +30,7 @@ #include #include #include +#include #include @@ -41,7 +45,6 @@ //#include #include #include -#include "RRLPServer.h" using namespace GSM; @@ -79,7 +82,7 @@ void NewCMServiceResponder(const L3CMServiceRequest* cmsrq, MMContext* mmchan) default: gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.Unhandled"); LOG(NOTICE) << "service not supported for " << *cmsrq; - mmchan->l3sendm(L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported))); + mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported)); //mmchan->l3sendm(L3ChannelRelease(L3RRCause::Unspecified)); return; } @@ -99,8 +102,8 @@ void NewPagingResponseHandler(const L3PagingResponse* resp, MMContext* mmchan) 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)); + LOG(WARNING) << "Paging Reponse with no Mobility Management record (probably timed out) for " << mobileId; + mmchan->l3sendm(L3ChannelRelease(L3RRCause::Call_Already_Cleared)); return; // There is nothing more we can do about this because we dont know who it is. } } @@ -249,17 +252,8 @@ MMSharedData *LUBase::ludata() const 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. @@ -269,7 +263,7 @@ static MMRejectCause getRejectCause(unsigned sipCode) 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; + rejectCause = L3RejectCause::Network_Failure; break; case 401: { // SIP 401 "Unauthorized" // The sip nomenclature for 401 and 404 are exactly reversed: @@ -280,7 +274,7 @@ static MMRejectCause getRejectCause(unsigned sipCode) break; } case 403: { // SIP 403 "Forbidden" - rejectCause = L3RejectCause::LocationAreaNotAllowed; + rejectCause = L3RejectCause::Location_Area_Not_Allowed; break; } case 404: { // SIP 404 "Not Found" @@ -294,7 +288,7 @@ static MMRejectCause getRejectCause(unsigned sipCode) break; } case 424: { // SIP 424 "Bad Location Information" - rejectCause = L3RejectCause::RoamingNotAllowedInLA; + rejectCause = L3RejectCause::Roaming_Not_Allowed_In_LA; break; } case 504: { // SIP 504 "Servier Time-out" @@ -302,18 +296,19 @@ static MMRejectCause getRejectCause(unsigned sipCode) break; } case 603: { // SIP 603 "Decline" - rejectCause = L3RejectCause::IMSIUnknownInVLR; + rejectCause = L3RejectCause::IMSI_Unknown_In_VLR; break; } case 604: { // SIP 604 "Does Not Exist Anywhere" - rejectCause = L3RejectCause::IMSIUnknownInHLR; + rejectCause = L3RejectCause::IMSI_Unknown_In_HLR; break; } default: LOG(NOTICE) << "REGISTER unexpected response from Registrar" <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); + LOG(INFO) << "SIP term info closeChannel called in stateRecvIdentityResponse"; + return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(failCause)); } } @@ -689,8 +685,8 @@ MachineStatus LUAuthentication::machineRunState(int state, const GSM::L3Message* LOG(ALERT) << "Invalid RAND challenge returned by Registrar (RAND length=" <mRegistrationResult.regSetError(); - //channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::ServiceOptionTemporarilyOutOfOrder)); - //return closeChannel(L3RRCause::NormalEvent,RELEASE); + //channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Service_Option_Temporarily_Out_Of_Order)); + //return closeChannel(L3RRCause::Normal_Event,RELEASE); return callMachStart(new LUFinish(tran())); } @@ -921,6 +917,8 @@ MachineStatus LUFinish::stateSendLUResponse() } } + const char *openregistrationmsg = ""; + switch (ludata()->mRegistrationResult.mRegistrationStatus) { case RegistrationSuccess: authorization = AuthAuthorized; @@ -930,7 +928,7 @@ MachineStatus LUFinish::stateSendLUResponse() authorization = AuthFailOpen; //ludata()->mRegistrationResult.regSetSuccess(); } else { - failCause = L3RejectCause::NetworkFailure; + failCause = L3RejectCause::Network_Failure; } break; case RegistrationFail: @@ -939,6 +937,7 @@ MachineStatus LUFinish::stateSendLUResponse() if (openRegistration()) { //ludata()->mRegistrationResult.regSetSuccess(); authorization = AuthOpenRegistration; + openregistrationmsg = "(open registration)"; } else { failCause = ludata()->mRegistrationResult.mRejectCause; } @@ -948,7 +947,7 @@ MachineStatus LUFinish::stateSendLUResponse() if (authorization != AuthUnauthorized) { if (authorization == AuthAuthorized) { - LOG(INFO) << "registration SUCCESS: " << ludata()->mLUMobileId; + LOG(INFO) << "registration SUCCESS"<mLUMobileId; } else { LOG(INFO) << "registration ALLOWED: " << ludata()->mLUMobileId; } @@ -1023,7 +1022,7 @@ MachineStatus LUFinish::stateSendLUResponse() //gTMSITable.tmsiTabSetRejected(imsi,failCause); ludata()->store.setRejectCode(failCause); gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi); - channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause(failCause))); + channel()->l3sendm(L3LocationUpdatingReject(failCause)); sendWelcomeMessage(ludata(), "Control.LUR.FailedRegistration.Message", // Does nothing if the SQL var is not set. "Control.LUR.FailedRegistration.ShortCode",subscriber(),channel()); @@ -1031,8 +1030,8 @@ MachineStatus LUFinish::stateSendLUResponse() // tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field. gTMSITable.tmsiTabUpdate(imsi,&ludata()->store); - //return closeChannel(L3RRCause::NormalEvent,RELEASE); - return MachineStatusQuitTran; + //return closeChannel(L3RRCause::Normal_Event,RELEASE); + return MachineStatus::QuitTran(TermCause::Local(failCause)); } // (pat) We must NOT attach the MMContext to the MMUser during the Location Updating procedure; @@ -1078,14 +1077,6 @@ 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. @@ -1107,20 +1098,10 @@ MachineStatus LUFinish::statePostAccept() 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; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::MM_Success)); } // The l3msg is LocationUpdatingRequest @@ -1154,7 +1135,7 @@ MachineStatus LUNetworkFailure::machineRunState(int state, const GSM::L3Message* //onTimeout1(4000,stateAuthFail); timerStart(TMMCancel,4000,TimerAbortChan); // Mystery timer. // We dont unauthorize because it is not the MS fault. - channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::NetworkFailure)); + channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Network_Failure)); return MachineStatusOK; default: return unexpectedState(state,l3msg); @@ -1188,7 +1169,7 @@ MachineStatus L3RegisterMachine::machineRunState(int state, const GSM::L3Message { switch (state) { case stateStart: // Start state. - startRegister(tran()->subscriber(),mRResult->mRand,mSRES,channel()); + startRegister(tran()->tranID(),tran()->subscriber(),mRResult->mRand,mSRES,channel()); return MachineStatusOK; case L3CASE_SIP(dialogActive): { @@ -1265,4 +1246,31 @@ string RegistrationResult::text() return result; } +// (pat) 5-2014. We dont currently save the detach information in OpenBTS. I dont want to set the TMSI table AUTH or AUTH_EXPIRY to 0, +// because we may need those for a later authorization if backhaul is cut or central authorization entity needs to be +// refreshed from our database. I dont want to add a new TMSI table field at this time, since it outdates all existing TMSI tables. +// The best way would would be to make AUTH a bit field and add bits. +// However, we dont currently use that information; we could send an immediate final response code to an INVITE or MESSAGE +// for a handset that did an Imsi-Detach if the beacon AttachDetach flag is still set, however, that will not work when we support +// multiple BTS per LAC - the final response should be sent from the central authority, not the individual BTS. +// So I am going to do nothing else at this time. +// (pat) NOTE: Kazoo may send a dialog failure to the imsi detach, which will try to go to the transaction, and is currently ignored. +void imsiDetach(L3MobileIdentity mobid, L3LogicalChannel *chan) +{ + string imsi; + if (mobid.isIMSI()) { + imsi = string(mobid.digits()); + } else if (mobid.isTMSI()) { + imsi = gTMSITable.tmsiTabGetIMSI(mobid.TMSI(),NULL); + if (imsi.size() == 0) { + LOG(WARNING)< #include #include +#include #include @@ -35,8 +37,9 @@ 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; + void l3sendSms(const L3Message &msg); // Send an SMS message to the correct place. + //L3LogicalChannel *getSmsChannel() const; + SAPI_t getSmsSap() const; }; @@ -71,19 +74,29 @@ class MTSMSMachine : public SMSCommon }; -L3LogicalChannel *SMSCommon::getSmsChannel() const +//L3LogicalChannel *SMSCommon::getSmsChannel() const +//{ +// if (channel()->isSDCCH()) { +// return channel(); // Use main SDCCH. +// } else { +// assert(channel()->isTCHF()); +// return channel()->getSACCHL3(); // Use SACCH associated with TCH. +// } +//} + +SAPI_t SMSCommon::getSmsSap() const { if (channel()->isSDCCH()) { - return channel(); // Use main SDCCH. + return SAPI3; // The SDCCH is faster than SACCH. } else { assert(channel()->isTCHF()); - return channel()->getSACCHL3(); // Use SACCH associated with TCH. + return SAPI3Sacch; // Use SACCH associated with TCH. } } -void SMSCommon::l3sendSms(const L3Message &msg, SAPI_t sapi) +void SMSCommon::l3sendSms(const L3Message &msg) { - getSmsChannel()->l3sendm(msg,GSM::DATA,sapi); + channel()->l3sendm(msg,GSM::L3_DATA,getSmsSap()); } @@ -173,9 +186,12 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m } case stateIdentResult: { if (! mIdentifyResult) { - //const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation); - l3sendSms(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation),SAPI0); - return MachineStatusQuitTran; + //const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information); + // (pat 6-2014) I think this is wrong, based on comment below, so changing it to the main channel: + // l3sendSms(L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information),SAPI0); + MMRejectCause rejectCause = L3RejectCause::Invalid_Mandatory_Information; + channel()->l3sendm(L3CMServiceReject(rejectCause),L3_DATA,SAPI0); + return MachineStatus::QuitTran(TermCause::Local(rejectCause)); } // Let the phone know we're going ahead with the transaction. @@ -184,7 +200,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m // 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); + channel()->l3sendm(GSM::L3CMServiceAccept(),L3_DATA,SAPI0); gReports.incr("OpenBTS.GSM.SMS.MOSMS.Start"); return MachineStatusOK; @@ -195,7 +211,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m // (pat) TODO: Call this on parsel3 error... // TODO: Also send an error code to the sip side, if any. - l3sendSms(CPError(getL3TI()),SAPI3); + l3sendSms(CPError(getL3TI())); return MachineStatusQuitTran; } #endif @@ -214,14 +230,14 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m 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; + l3sendSms(CPError(L3TI)); + return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Error)); } // 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); + l3sendSms(CPAck(L3TI)); // (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, @@ -247,7 +263,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m catch (SMSReadError) { LOG(WARNING) << "SMS parsing failed (above L3)"; // Cause 95, "semantically incorrect message". - LCH->l3sendf(CPData(L3TI,RPError(95,ref)),3); + LCH->l3sendf(CPData(L3TI,RPError(95,ref)),3); if you ever use this, it should call l3sendSms delete CM; throw UnexpectedMessage(); } @@ -275,7 +291,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m if (! success) { PROCLOG(INFO) << "sending RPError in CPData"; // Cause 95 is "semantically incorrect message" - l3sendSms(CPData(L3TI,RPError(95,mRpduRef)),SAPI3); + l3sendSms(CPData(L3TI,RPError(95,mRpduRef))); } mSmsState = MoSmsWaitForAck; LOG(DEBUG) << "case DATA returning"; @@ -283,8 +299,8 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m } case L3CASE_SIP(dialogBye): { // SIPDialog sends this when the MESSAGE clears. - PROCLOG(INFO) << "sending RPAck in CPData"; - l3sendSms(CPData(getL3TI(),RPAck(mRpduRef)),SAPI3); + PROCLOG(INFO) << "SMS peer did not respond properly to dialog message; sending RPAck in CPData"; + l3sendSms(CPData(getL3TI(),RPAck(mRpduRef))); LOG(DEBUG) << "case dialogBye returning"; } case L3CASE_SIP(dialogFail): { @@ -292,7 +308,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m // 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); + l3sendSms(CPData(getL3TI(),RPError(127,mRpduRef))); LOG(DEBUG) << "case dialogFail returning"; } @@ -318,7 +334,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m LOG(DEBUG) << "case ACK returning"; // This attach causes any pending MT transactions to start now. gMMLayer.mmAttachByImsi(channel(),tran()->subscriberIMSI()); - return MachineStatusQuitTran; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Success)); } default: @@ -385,7 +401,7 @@ void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan) LOG(ERR) <<"Received third simultaneous MO-SMS, which is illegal:"<teCancel(); // Promotes TE_MOSMS2 to TE_MOSMS1 + prevMOSMS->teCancel(TermCause::Local(L3Cause::SMS_Error)); // Promotes TE_MOSMS2 to TE_MOSMS1 devassert(mmchan->mmGetTran(MMContext::TE_MOSMS2) == NULL); } //mmchan->setTran(MMContext::TE_MOSMS2,tran); @@ -402,20 +418,7 @@ void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan) // 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) { @@ -428,12 +431,12 @@ bool MTSMSMachine::createRPData(RPData &rp_data) } 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; + LOG(WARNING) << "Message is zero length which is valid"; + // This is valid continue + return true; } - try { + try { // I suspect this is here to catch the above FIXED crash when string is zero length RLFrame RPDU(RPDUbits); LOG(DEBUG) << "SMS RPDU: " << RPDU; @@ -443,14 +446,12 @@ bool MTSMSMachine::createRPData(RPData &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(); + //LCH->l2sendf(CPData(L3TI,RPError(95,this->mRpduRef)),3); if you ever use this, it should call l3sendSms return false; } catch (GSM::L3ReadError) { LOG(WARNING) << "SMS parsing failed (in L3)"; // TODO:: send error back to the phone - //throw UnsupportedMessage(); return false; } catch (...) { @@ -459,11 +460,9 @@ bool MTSMSMachine::createRPData(RPData &rp_data) } } 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) @@ -483,7 +482,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const // 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; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Timeout)); // could be a sip internal error? } timerStart(TR2M,TR2Mms,TimerAbortTran); @@ -496,12 +495,16 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const gReports.incr("OpenBTS.GSM.SMS.MTSMS.Start"); - L3LogicalChannel *smschan = getSmsChannel(); - - if (smschan->multiframeMode(3)) { goto step1; } // If already established. + // pat 6-2014. We just send the ESTABLISH_REQUEST no matter what now. + // The LAPDm will respond with ESTABLISH_INDICATION immediately if + SAPI_t sap = getSmsSap(); + //L3LogicalChannel *smschan = getSmsChannel(); + //if (smschan->multiframeMode(3)) { goto step1; } // If already established. + // if (channel()->multiframeMode(sap)) { goto step1; } // If already established. // Start ABM in SAP3. - smschan->l3sendp(GSM::ESTABLISH,SAPI3); + //smschan->l3sendp(GSM::L3_ESTABLISH_REQUEST,SAPI3); + channel()->l3sendp(GSM::L3_ESTABLISH_REQUEST,sap); // 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. @@ -512,8 +515,12 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const return MachineStatusOK; // Wait for the ESTABLISH on the uplink. } - case L3CASE_PRIMITIVE(ESTABLISH): { - step1: + // We use ESTABLISH_INDICATION instead of ESTABLISH_CONFIRM to indicate establishment. + // We would have to accept both ESTABLISH_CONFIRM and ESTABLISH_INDICATION here anyway in case + // SABM was started by us and handset simultaneously, so we just dont bother with making ESTABLISH_CONFIRM separate. + case L3CASE_PRIMITIVE(L3_ESTABLISH_INDICATION): + case L3CASE_PRIMITIVE(L3_ESTABLISH_CONFIRM): { + //step1: // Step 1 // Send the first message. @@ -521,19 +528,19 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const RPData rp_data; int l3ti = getL3TI(); - if (! createRPData(rp_data)) { - l3sendSms(CPData(l3ti,RPError(95,this->mRpduRef)),SAPI3); + if (! createRPData(rp_data)) { // NULL can be returned + l3sendSms(CPData(l3ti,RPError(95,this->mRpduRef))); // TODO: Is this correct? // TODO: Make sure MachineStatusQuitTran sends a failure code to SIP. if (getDialog()) getDialog()->MTSMSReply(400, "Bad Request"); - return MachineStatusQuitTran; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Error)); } 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); + // (pat) FIXME: The MS may send a DELIVER_REPORT which is discarded by parseTPDU. + l3sendSms(deliver); LOG(DEBUG) << "case ESTABLISH returning, after receiving ESTABLISH"; return MachineStatusOK; // Wait for CP-ACK message. } @@ -586,7 +593,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const // Step 4 // Send CP-ACK to the MS. PROCLOG(INFO) << "MTSMS: sending CPAck"; - l3sendSms(CPAck(getL3TI()),SAPI3); + l3sendSms(CPAck(getL3TI())); // Ack in SIP domain. if (!getDialog()) { @@ -598,7 +605,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const } LOG(DEBUG) << "case DATA returning"; - return MachineStatusQuitTran; // Finished. + return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Success)); // Finished. } default: return unexpectedState(state,l3msg); diff --git a/Control/L3SMSControl.h b/Control/L3SMSControl.h index e46a602..47b6717 100644 --- a/Control/L3SMSControl.h +++ b/Control/L3SMSControl.h @@ -1,8 +1,9 @@ -/* Copyright 2013 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/Control/L3StateMachine.cpp b/Control/L3StateMachine.cpp index 615468e..530801c 100644 --- a/Control/L3StateMachine.cpp +++ b/Control/L3StateMachine.cpp @@ -4,7 +4,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,12 +14,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +// Written by Pat Thompson #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 "L3Handover.h" #include #include #include @@ -27,7 +29,7 @@ #include #include #include -#include +#include #include using namespace GSM; @@ -36,8 +38,8 @@ 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 MachineStatusQuitTran = MachineStatus(MachineStatus::MachineCodeQuitTran); +//MachineStatus MachineStatusQuitChannel = MachineStatus(MachineStatus::MachineCodeQuitChannel); MachineStatus MachineStatusAuthorizationFail = MachineStatus(MachineStatus::MachineCodeQuitChannel); MachineStatus MachineStatusUnexpectedState = MachineStatus(MachineStatus::MachineCodeUnexpectedState); @@ -138,9 +140,9 @@ MachineStatus MachineBase::machPush( return this->callMachStart(wCalledProcedure); } -MachineStatus MachineBase::closeChannel(L3RRCause cause,Primitive prim) +MachineStatus MachineBase::closeChannel(RRCause rrcause,Primitive prim,TermCause upstreamCause) { - LOG(DEBUG); + LOG(INFO) << "SIP term info closeChannel L3RRCause: " << rrcause; // SVGDBG // We dont want to set to NullState because we want to differentiate the startup state from the closed state // so that if something new happens (like a SIP dialog message coming in) we wont advance, we'll stay dead. // TODO: Make sure the routines that handle incoming dialog messages check for channel already in a released state. @@ -148,8 +150,8 @@ MachineStatus MachineBase::closeChannel(L3RRCause cause,Primitive prim) setGSMState(CCState::ReleaseRequest); // The chanClose below will send the request. // Many handsets never complete the transaction. // So force a shutdown of the channel. - channel()->chanClose(cause,prim); - return MachineStatusQuitChannel; + channel()->chanClose(rrcause,prim,upstreamCause); // TODO: Remove, now redundant. + return MachineStatus::QuitChannel(upstreamCause); } #if UNUSED @@ -191,7 +193,11 @@ L3LogicalChannel* MachineBase::channel() const { CallState MachineBase::getGSMState() const { return tran()->getGSMState(); } -void MachineBase::setGSMState(CallState state) { tran()->setGSMState(state); } + +void MachineBase::setGSMState(CallState state) { + LOG(INFO) << "SIP term info setGSMState state: " << state; // SVGDBG + tran()->setGSMState(state); +} SIP::SipDialog * MachineBase::getDialog() const { return tran()->getDialog(); } void MachineBase::setDialog(SIP::SipDialog*dialog) { return tran()->setDialog(dialog); } @@ -255,8 +261,6 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool 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; @@ -266,10 +270,13 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool // TODO: Should we check that this an appropriate time to start it? LURInit(l3msg,mmchan); return true; - case L3CASE_MM(L3MMMessage::IMSIDetachIndication): + case L3CASE_MM(L3MMMessage::IMSIDetachIndication): { // (pat) TODO, but it is not very important. - //IMSIDetachController(dynamic_cast(l3msg),mmchan); + L3MobileIdentity mobid = dynamic_cast(l3msg)->mobileID(); + imsiDetach(mobid,mmchan->tsChannel()); + mmchan->tsChannel()->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::IMSI_Detached)); return true; + } case L3CASE_MM(L3MMMessage::CMServiceRequest): mmchan->mmcServiceRequests.write(l3msg); //NewCMServiceResponder(dynamic_cast(l3msg),dcch); @@ -278,11 +285,10 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool default: break; } - *deletemsg = false; return false; } -MachineStatus MachineBase::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) +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); @@ -306,7 +312,8 @@ MachineStatus MachineBase::machineRunL3Msg(int state, const GSM::L3Message *l3ms MachineStatus handlePrimitive(const L3Frame *frame, L3LogicalChannel *lch) { switch (frame->primitive()) { - case GSM::ESTABLISH: + case L3_ESTABLISH_CONFIRM: + case L3_ESTABLISH_INDICATION: // 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; @@ -420,36 +427,33 @@ MachineStatus MachineBase::dispatchTimeout(L3Timer*timer) // 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"; + // (pat) TODO: We should not use one error fits all here; the error should be set up when the timer was established. + TermCause cause = TermCause::Local(L3Cause::No_User_Responding); // SVG 5/20/14 changed this from InterworkingUnspecified to NoUserResponding // 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; + case L3CMServiceType::MobileTerminatedCall: { + LOG(INFO) << "SIP term info dispatchTimeout call teCloseCallNow servicetype: " << tran()->servicetype(); // SVGDBG + tran()->teCloseCallNow(cause,true); + } 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; + return MachineStatus::QuitTran(cause); } 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); + LOG(INFO) << "SIP term info closeChannel called in dispatchTimeout"; + // TODO: Error should be set up when timer started. + return closeChannel(L3RRCause::Timer_Expired,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_User_Responding)); } else { assert(0); + return MachineStatus::QuitTran(TermCause::Local(L3Cause::L3_Internal_Error)); } } @@ -495,7 +499,7 @@ static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch) // 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"<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. + + // FIXME: This prim needs to be passed to the state machines to abort procedures. + + lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Layer2_Error)); // 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 + //case HARDRELEASE: ///< forced release after an assignment + // if (sapi == 0) lch->chanRelease(L3_HARDRELEASE_REQUEST); // Release the channel. + // return false; + case L3_RELEASE_INDICATION: ///< 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. @@ -566,17 +574,21 @@ static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi) // // Just drop the channel. // lch->chanLost(); // Kill off all the transactions associated with this channel. //} - if (sapi == 0) lch->chanRelease(RELEASE); + + // FIXME: This prim needs to be passed to the state machines to abort procedures. + + if (sapi == 0) lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Normal_Call_Clearing)); return false; default: LOG(ERR) <chanRelease(RELEASE); // Kill off all the transactions associated with this channel. + lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::L3_Internal_Error)); // Kill off all the transactions associated with this channel. //lch->freeContext(); return false; } } +// Dont delete the frame; caller does that. static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch) { L3Message *l3msg = NULL; @@ -596,32 +608,13 @@ static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch) } bool deleteit; if (l3msg && handleCommonMessages(l3msg, mmchan, &deleteit)) { - LOG(DEBUG) << "message handled by handleCommonMessagse"<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 @@ -724,7 +717,9 @@ static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogica } if (numFlushed) { LOG(DEBUG) <recvTCH()) { + + + if (SIP::AudioFrame *ulFrame = TCH->recvTCH()) { activity += ulFrame->sizeBytes(); // Send on RTP. LOG(DEBUG) <rxFrame()) { + if (SIP::AudioFrame *dlFrame = transaction->rxFrame()) { activity += dlFrame->sizeBytes(); if (activity == 0) { activity++; } // Make sure we signal activity. LOG(DEBUG) <chanGetContext(true); + if (set->mmcTerminationRequested) { + set->mmcTerminationRequested = false; // Reset the flag superstitiously. + dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Operator_Intervention)); + return true; + } + + // All messages from all host chan saps and from sacch now come in l2recv now. if (GSM::L3Frame *l3frame = dcch->l2recv(delay)) { - LOG(DEBUG) <l2recv(0,3)) { - 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. - } + // // How about SACCH? These messages are supposed to be prioritized, but we're not bothering. + // // We need to pass the ESTABLISH primitive to higher layer, specifically, MTSMSMachine. + // if (L3Frame *aframe = dcch->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. + // } +#endif // 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 @@ -833,13 +837,13 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch) // 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()) { + while (dcch->chanRunning() && !gBTS.btsShutdown()) { 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 + dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel. + //tran->getDialog()->dialogCancel(TermCause::TermCodeUnknown, GSM::L3Cause::Unknown_L3_Cause); // was forceSIPClearing //tran->teRemove(); return; } @@ -856,21 +860,23 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch) // we should keep the channel open until that ends. LOG(NOTICE) << "attempting to use a defunct Transaction"<chanClose(L3RRCause::PreemptiveRelease,RELEASE); + dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected)); 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()) { + L3Cause::AnyCause termcause = tran->terminationRequested(); + if (termcause.value != 0) { LOG(DEBUG)<terminateHook(); // Gives the L3Procedure state machine a chance to do something first. - dcch->chanClose(L3RRCause::PreemptiveRelease,RELEASE); + // GSM 4.08 3.4.13.4.1: Use RR Cause PreemptiveRelease if terminated for a higher priority, ie, emergency, call + dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(termcause)); return; // We wont be back. } if (tran->getGSMState() == CCState::HandoverOutbound) { if (outboundHandoverTransfer(tran,dcch)) { LOG(DEBUG)<chanRelease(HARDRELEASE); + dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Outbound)); return; // We wont be back. } } @@ -879,10 +885,6 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch) // 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. } } @@ -936,6 +938,7 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch) // If nothing happened, set nextDelay so so we dont burn up the CPU cycles. nextDelay = 20; // Do not exceed the RTP frame size of 20ms. } + LOG(DEBUG) << "final return"; } // TODO: When a channel is first opened we should save the CMServiceRequest or LocationUpdateRequest or PagingResponse and initiate @@ -950,11 +953,13 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch) static void L3SDCCHLoop(L3LogicalChannel*dcch) { assert(dcch->chtype() == SDCCHType); - while (dcch->chanRunning()) { + while (dcch->chanRunning() && !gBTS.btsShutdown()) { 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. + // (pat) 5-2014: Changed to RELEASE from HARDRELEASE - even though we can no longer hear the handset, + // it might still hear us so we have to deactivate SACCH and wait T3109. + dcch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel. return; } @@ -980,7 +985,7 @@ void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame) dcch->chanSetState(L3LogicalChannel::chEstablished); switch (prim) { - case ESTABLISH: + case L3_ESTABLISH_INDICATION: break; case HANDOVER_ACCESS: ProcessHandoverAccess(dcch); @@ -1017,10 +1022,10 @@ void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame) // 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. + dcch->l3sendp(L3_RELEASE_REQUEST); // WARNING! This must be the only place in L3 that sends this primitive. break; case L3LogicalChannel::chRequestHardRelease: - dcch->l3sendp(HARDRELEASE); + dcch->l3sendp(L3_HARDRELEASE_REQUEST); break; default: break; } diff --git a/Control/L3StateMachine.h b/Control/L3StateMachine.h index a2f72a8..6388543 100644 --- a/Control/L3StateMachine.h +++ b/Control/L3StateMachine.h @@ -1,10 +1,10 @@ /**@file Declarations for Circuit Switched State Machine and related classes. */ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -36,6 +36,7 @@ #include #include "ControlCommon.h" #include "L3Utils.h" +#include "L3TermCause.h" //#include //#include //#include @@ -68,29 +69,34 @@ struct MachineStatus { 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. + MachineCodeQuitTran, // Pop all machines from stack and remove the transaction. This is the normal exit from a completed procedure. + MachineCodeQuitChannel, // Drop the channel, which kills all transactions on this channel. 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;*/ } + MachineStatusCode msCode; + TermCause msCause; // If it is QuitTran or QuitChannel. + bool operator==(MachineStatus &other) { return msCode == other.msCode; } + bool operator!=(MachineStatus &other) { return msCode != other.msCode; } + MachineStatus(MachineStatusCode code) { msCode = code; } + static MachineStatus QuitTran(TermCause wCause) { + MachineStatus result(MachineCodeQuitTran); + result.msCause = wCause; + return result; + } + static MachineStatus QuitChannel(TermCause wCause) { + MachineStatus result(MachineCodeQuitChannel); + result.msCause = wCause; + return result; + } }; 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 MachineStatusOK, MachineStatusPopMachine, MachineStatusAuthorizationFail; extern MachineStatus MachineStatusAuthorizationFail, MachineStatusUnexpectedState; +//extern MachineStatus MachineStatusQuitChannel; +//extern MachineStatus MachineStatusQuitTran; struct MachineStatusQuitTran : MachineStatus { @@ -148,7 +154,7 @@ class MachineBase : public MemCheckMachineBase bool isL3TIValid() const; virtual const char *debugName() const = 0; MachineStatus unexpectedState(int state, const L3Message*l3msg); - MachineStatus closeChannel(L3RRCause cause,Primitive prim); + MachineStatus closeChannel(RRCause rrcause,Primitive prim,TermCause cause); 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. diff --git a/Control/L3SupServ.cpp b/Control/L3SupServ.cpp index a8828a8..1134ab6 100644 --- a/Control/L3SupServ.cpp +++ b/Control/L3SupServ.cpp @@ -1,8 +1,9 @@ -/* Copyright 2013 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -11,6 +12,9 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + +// Written by Pat Thompson + #define LOG_GROUP LogGroup::Control #include @@ -23,6 +27,7 @@ #include #include #include +#include // Generic SS messages are transferred by the Facility IE, whose content is described by 24.080 4.61 @@ -247,7 +252,7 @@ class SupServCodes 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? + addSSCode(0x41,"43"); // WAIT 22.083 Call waiting? // I think HOLD and MultiParty are handled by L3 messages at the MS level, and these MAP-SS-Codes are used only at the MSC level. //addSSCode(0x42,??); // HOLD see section 4.5.5. Ha ha, there is no such section in either 22.030 or 22.083. It is in 2.30. //addSSCode(0x51, ??); // MPTTY see 4.5.5 22.084 @@ -637,7 +642,7 @@ void startMOSSD(const L3CMServiceRequest*cmsrq,MMContext *mmchan) string proxyUssd = gConfig.getStr("SIP.Proxy.USSD"); if (proxyUssd.size() == 0) { // Disabled. Reject USSD immediately. - mmchan->l3sendm(L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported))); + mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported)); return; } @@ -660,9 +665,9 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m } case stateSSIdentResult: { if (! mIdentifyResult) { - //const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation); - channel()->l3sendm(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation)); - return MachineStatusQuitTran; + //const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information); + channel()->l3sendm(L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information)); + return MachineStatus::QuitTran(TermCause::Local(L3RejectCause::Invalid_Mandatory_Information)); } PROCLOG(DEBUG) << "sending CMServiceAccept"; @@ -693,18 +698,18 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m case L3CASE_SS(L3SupServMessage::ReleaseComplete): { const L3SupServFacilityMessage *relp = dynamic_cast(l3msg); WATCH("SS ReleaseComplete" << relp); - return MachineStatusQuitTran; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success)); } case L3CASE_SIP(dialogBye): { if (sipmsg == NULL) { LOG(ERR) << "USSD client error: missing BYE message"; - return MachineStatusQuitTran; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Error)); } const DialogUssdMessage *umsg = dynamic_cast(sipmsg); if (umsg == NULL) { LOG(ERR) << "USSD client error: could not convert DialogMessage to DialogUssdMessage "<dmMsgPayload; // Send it to the MS. @@ -714,7 +719,7 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m L3SupServReleaseCompleteMessage ssrelease(getL3TI()); channel()->l3sendm(ssrelease); //sendUssdMsg(result, true); - return MachineStatusQuitTran; + return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success)); } default: diff --git a/Control/L3SupServ.h b/Control/L3SupServ.h index 5422b03..f10f88d 100644 --- a/Control/L3SupServ.h +++ b/Control/L3SupServ.h @@ -1,8 +1,9 @@ -/* Copyright 2013 Range Networks, Inc. +/* +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -11,6 +12,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #ifndef _L3SUPSERV_H_ #define _L3SUPSERV_H_ 1 diff --git a/Control/L3TermCause.cpp b/Control/L3TermCause.cpp new file mode 100644 index 0000000..d304028 --- /dev/null +++ b/Control/L3TermCause.cpp @@ -0,0 +1,486 @@ +/**@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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ +#define LOG_GROUP LogGroup::Control +#include // For snprintf + +#include +#include +#include +#include +#include + +namespace Control { +using std::string; +using namespace GSM; + +// The default SIP <-> L3-CC-Cause mapping an be overridden with config options. +// For this purpose the GSM Layer3 causes are identified by using the names of the causes +// as defined in GSM 4.08 with space replaced by underbar and any other special chars removed. See file L3Enums.h +// Config option Control.Termination.CauseToSIP. specifies the SIP code, and optionally, the SIP reason phrase, +// to be sent to the SIP peer when this L3 Cause occurs. +// Config option Control.Termination.SIPToCause. specifies the name of the layer3 cause to be used for a specific SIP code. +// The Layer3 cause is passed to the handset to indicate the message to display, and also may be saved in the CDR. +// To make this easier for the user we will use the L3 cause as the SIP reason phrase, so the reason phrase can +// be placed in the config option to control it. + +static int cause2SipCodeFromConfig(AnyCause acause, string &reason) +{ + // User is allowed to over-ride default sipcode for each cause in configuration options: + const char *causeName = L3Cause::AnyCause2Str(acause); + char configOptionName[100]; + snprintf(configOptionName,100,"Control.Termination.CauseToSIP.%s",causeName); + if (gConfig.defines(configOptionName)) { + string value = gConfig.getStr(configOptionName); + // Value must begin with a positive number; ignore leading space. + const char *vp = value.c_str(); + while (isspace(*vp)) { vp++; } + if (!isdigit(*vp)) { + LOG(ERR) << "Invalid config value for '"< commap)) return 0; + causep += strlen("cause"); + int result; + if (1 != sscanf(causep," = %d",&result)) return 0; + return result; +} + +// Search for a cause in the SIP message. +TermCause dialog2TermCause(SIP::SipDialog *dialog) +{ + if (! dialog) { // Be ultra-cautious. + // This is not supposed to happen. + return TermCause::Remote(AnyCause(L3Cause::No_User_Responding),480,"No_User_Responding"); + } + string sipreason; + int sipcode = dialog->getLastResponseCode(sipreason); + // Does the reason phrase look like one that was created on a peer OpenBTS? + int l3cause = CauseName2Cause(sipreason); + if (! l3cause) { + string reasonHeader = dialog->getLastResponseReasonHeader(); + // Was a Q.850 reason included? + int q850 = parseReasonHeaderQ850(reasonHeader.c_str()); + if (q850) { + l3cause = (q850 == 8) ? L3Cause::Preemption : (CCCause) q850; + } else { + bool alerted = dialog->mReceived180; + l3cause = sipCode2AnyCause(sipcode,alerted); + } + } + return TermCause::Remote(AnyCause(l3cause),sipcode,sipreason); +} + +std::ostream& operator<<(std::ostream& os, TermCause &cause) +{ + os < +#include + +#include +#include // for Int_z, From CommonLibs +#include + +namespace SIP { class SipDialog; } + +namespace Control { +using std::string; +using GSM::L3Cause; + + +// 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.) +// Why cant we just use CC or RR Cause? Because some of the reasons do not exist in any other single list, for example, NoAnswerToPage. +// 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 TermCauseUnknown, however, all such cases +// are distinguished from TermCauseNoAnswerToPage in that we know the MS is on the current system. + +// pat added 6-2014, so every client that closes a transaction is forced to explicitly specify all needed cancellation information. +class TermCause { + L3Cause::AnyCause mtcAnyCause; // The full extended cause. Self-inits to 0. + Int_z mtcSipCode; + string mtcSipReason; + public: + enum Side { SideLocal, SideRemote } mtcInstigator; // Which side closed the channel? + + int tcGetValue() { return mtcAnyCause.value; } + string tcGetStr(); + bool tcIsEmpty() { return mtcAnyCause.isEmpty(); } + + L3Cause::CCCause tcGetCCCause(); // Returns the nearest Call-Control-Cause as per GSM 4.08 10.5.4.11. + int tcGetSipCodeAndReason(string &sipreason); // Returns the nearest SIP code, and the sip reason phrase derived from the real cause. + string tcGetSipReasonHeader(); // Returns a SIP "Reason:" header string. + + // constructors: + TermCause() { assert(mtcAnyCause.value == 0 && mtcSipCode == 0); } + + // This constructor is used to terminate a transaction by this BTS. + // The TermCode is translated as needed to an L3Cause, SIP code, and SIP reason. + //TermCause(Side side,TermCode code); + static TermCause Local(L3Cause::AnyCause cause) { + TermCause self; + self.mtcInstigator = SideLocal; + self.mtcAnyCause = cause; + self.mtcSipCode = 0; + LOG(DEBUG)< +#include #include "ControlCommon.h" #include "L3TranEntry.h" @@ -29,10 +31,15 @@ #include #include #include +#include #include +#include #include +#include +#include + //#include //#include #include @@ -43,16 +50,20 @@ #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; +using namespace Peering; // for sockaddr2string - remove me +CdrService gCdrService; #if EXTERNAL_TRANSACTION_TABLE @@ -88,6 +99,8 @@ HandoverEntry::HandoverEntry(const TranEntry *tran) : mMyTranID(tran->tranID()), mHandoverOtherBSTransactionID(0) { + memset(&mInboundPeer,0,sizeof(mInboundPeer)); + memset(&mOutboundPeer,0,sizeof(mOutboundPeer)); }; HandoverEntry *TranEntry::getHandoverEntry(bool create) const // It is not const, but we want C++ to be a happy compiler. @@ -109,14 +122,11 @@ void TranEntry::TranEntryInit() //mChannel = NULL; //mNextChannel = NULL; mMMData = NULL; - //mRemoved = false; moved to TranEntryProtected //initTimers(); } +//#include -//DIG: Debug Start -#include -//DIG: Debug End TranEntry::TranEntry( SipDialog *wDialog, //const L3MobileIdentity& wSubscriber, @@ -127,13 +137,10 @@ TranEntry::TranEntry( if (wDialog) setDialog(wDialog); //mSubscriber = wSubscriber; mService = wService; + mTerminationRequested.value = 0; // redundant; it inits itself to 0 - //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); @@ -144,12 +151,12 @@ TranEntry::TranEntry( 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; + ***/ + + mStartTime = time(NULL); + mConnectTime = 0; // Means never connected. + //mEndTime = 0; //gNewTransactionTable.ttAdd(this); } @@ -160,15 +167,14 @@ TranEntry *TranEntry::newMO(MMContext *wChan, const GSM::L3CMServiceType& wServi //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. +void TranEntry::txFrame(SIP::AudioFrame* frame, unsigned numFlushed) { getDialog()->txFrame(frame,numFlushed); } +SIP::AudioFrame *TranEntry::rxFrame() { return getDialog()->rxFrame(); } // Crashes if rtp not established. unsigned TranEntry::getRTPPort() const { @@ -184,7 +190,7 @@ TranEntry *TranEntry::newMOSSD(MMContext* wChannel) TranEntry *TranEntry::newMOC(MMContext* wChannel, CMServiceTypeCode serviceType) { - assert(serviceType == L3CMServiceType::MobileOriginatedCall); + devassert(serviceType == L3CMServiceType::MobileOriginatedCall); return newMO(wChannel,serviceType); } @@ -202,7 +208,7 @@ TranEntry *TranEntry::newMOMM(MMContext* wChannel) TranEntry *TranEntry::newMTC( SipDialog *dialog, const FullMobileId& msid, - const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI. + const GSM::L3CMServiceType& wService, // MobileTerminatedCall or UndefinedType for generic page from CLI. const string wCallerId) //const L3CallingPartyBCDNumber& wCalling) { @@ -252,10 +258,6 @@ TranEntry *TranEntry::newHandover( // 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); @@ -312,7 +314,7 @@ void HandoverEntry::initHandoverEntry( mHandoverOtherBSTransactionID = wHandoverOtherBSTransactionID; // Save the peer address. - bcopy(peer,&mInboundPeer,sizeof(mInboundPeer)); + memcpy(&mInboundPeer,peer,sizeof(mInboundPeer)); const char* refer = params.get("REFER"); if (refer) { @@ -332,26 +334,6 @@ void HandoverEntry::initHandoverEntry( 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__); @@ -377,19 +359,9 @@ TranEntry::~TranEntry() #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); } @@ -397,7 +369,6 @@ bool TranEntryProtected::clearingGSM() const bool TranEntryProtected::isStuckOrRemoved() const { - //if (mRemoved) return true; unsigned age = mStateTimer.elapsed(); // 180-second tests @@ -434,134 +405,56 @@ bool TranEntry::deadOrRemoved() const //} +#if UNUSED +bool TranEntry::teDead() 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; + if (mDialog && mDialog->sipIsStuck()) return true; + //mLock.unlock(); + +#if 0 + // (pat) You cannot check the sip state here based on the transaction state-age because the state + // age is not updated for sip-side state changes. + // 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; +#endif + + // If we got here, the state-vs-timer relationship + // appears to be valid. + return false; +} +#endif + + 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 <descriptiveString(), tranID()); + } else { + sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL=NULL WHERE ID=%u", + (unsigned)time(NULL), tranID()); + } + + runQuery(query); +} +#endif + + void TranEntry::setSubscriberImsi(string imsi, bool andAttach) { mSubscriber.mImsi = imsi; @@ -708,16 +600,12 @@ void TranEntry::setSubscriberImsi(string imsi, bool andAttach) 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; } @@ -746,15 +634,11 @@ L3LogicalChannel* TranEntry::getTCHFACCH() { 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; } @@ -762,11 +646,21 @@ CallState TranEntryProtected::getGSMState() const void TranEntryProtected::setGSMState(CallState wState) { - //if (wState != CCState::NullState && isRemoved()) throw RemovedTransaction(tranID()); //ScopedLock lock(mLock,__FILE__,__LINE__); mStateTimer.now(); mGSMState = wState; +#if UNUSED // We are removing the transaction table, so I'm just taking this out. + const char* stateString = CCState::callStateString(wState); + assert(stateString); + + unsigned now = mStateTimer.sec(); + char query[150]; + sprintf(query, + "UPDATE TRANSACTION_TABLE SET GSMSTATE='%s',CHANGED=%u WHERE ID=%u", + stateString,now, tranID()); + runQuery(query); +#endif } SIP::SipState TranEntry::echoSipState(SIP::SipState state) const @@ -776,7 +670,7 @@ SIP::SipState TranEntry::echoSipState(SIP::SipState state) const mPrevSipState = state; const char* stateString = SIP::SipStateString(state); - assert(stateString); + devassert(stateString); #if EXTERNAL_TRANSACTION_TABLE unsigned now = time(NULL); @@ -794,7 +688,6 @@ SIP::SipState TranEntry::echoSipState(SIP::SipState state) const void TranEntry::setCalled(const L3CalledPartyBCDNumber& wCalled) { - //if (isRemoved()) throw RemovedTransaction(tranID()); //ScopedLock lock(mLock,__FILE__,__LINE__); mCalled = wCalled; @@ -823,7 +716,6 @@ bool TranEntry::matchL3TI(unsigned ti, bool fromMS) void TranEntry::setL3TI(unsigned wL3TI) { - //if (isRemoved()) throw RemovedTransaction(tranID()); //ScopedLock lock(mLock,__FILE__,__LINE__); mL3TI = wL3TI; @@ -837,12 +729,11 @@ void TranEntry::setL3TI(unsigned wL3TI) } -bool TranEntry::terminationRequested() +L3Cause::AnyCause TranEntry::terminationRequested() { ScopedLock lock(mAnotherLock,__FILE__,__LINE__); - //if (isRemoved()) throw RemovedTransaction(tranID()); - bool retVal = mTerminationRequested; - mTerminationRequested = false; + L3Cause::AnyCause retVal = mTerminationRequested; + mTerminationRequested.value = 0; return retVal; } @@ -859,7 +750,6 @@ string TranEntry::handoverString(string peer) const // 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(); @@ -867,7 +757,7 @@ string TranEntry::handoverString(string peer) const // 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(); + //if (getGSMState()==CCState::Handover_Outbound) 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(); @@ -931,17 +821,44 @@ void NewTransactionTable::ttInit() //mIDCounter = random(); mIDCounter = 100; // pat changed. 0 is reserved. Start it high enough so it cannot possibly be confused with an L3TI. +#if EXTERNAL_TRANSACTION_TABLE + // Connect to the database. + const char *path = gConfig.getStr("Control.Reporting.TransactionTable").c_str(); + 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,createNewTransactionTable)) { + LOG(ALERT) << "Cannot create Transaction Table"; + } + // Clear any previous entires. + if (!sqlite3_command(gNewTransactionTable.getDB(),"DELETE FROM TRANSACTION_TABLE")) + LOG(WARNING) << "cannot clear previous transaction table"; +#endif } +#if EXTERNAL_TRANSACTION_TABLE +NewTransactionTable::~NewTransactionTable() +{ + // Don't bother disposing of the memory, + // since this is only invoked when the application exits. + if (mDB) sqlite3_close(mDB); +} +#endif + + + + unsigned NewTransactionTable::ttNewID() { - unsigned iCntr; - rwLock.wlock(); - iCntr = mIDCounter++; - rwLock.unlock(); - return iCntr; + ScopedLock lock(mttLock,__FILE__,__LINE__); + return mIDCounter++; } @@ -949,34 +866,33 @@ void NewTransactionTable::ttAdd(TranEntry* value) { LOG(DEBUG); LOG(INFO) << "new transaction " << *value; - rwLock.wlock(); - //value->vGetRef(); - value->incRefCnt(); + ScopedLock lock(mttLock,__FILE__,__LINE__); + //clearDeadEntries(); // This the only call to clearDeadEntries that really matters. 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 } +bool TranEntry::teIsTalking() +{ + LOG(DEBUG) << LOGVAR2("GSMState",this->getGSMState()); + if (this->getGSMState() == CCState::Active) { + //WATCHINFO(LOGVAR2("GSMState",this->getGSMState()) <channel()->getL2Channel()->getSACCH()->measurementResults())); + if (L3LogicalChannel *l3chan = this->channel()) { + L2LogicalChannel *l2chan = l3chan->getL2Channel(); + GSM::L3MeasurementResults meas = l2chan->getSACCH()->measurementResults(); + if (meas.isServingCellValid()) { return true; } + } + } + return false; +} + +bool NewTransactionTable::ttIsTalking(TranEntryId tranid) +{ + bool result = false; + ScopedLock lock(mttLock,__FILE__,__LINE__); + if (TranEntry *tran = ttFindById(tranid)) { result = tran->teIsTalking(); } + return result; +} TranEntry* NewTransactionTable::ttFindById(TranEntryId key) { @@ -984,21 +900,15 @@ TranEntry* NewTransactionTable::ttFindById(TranEntryId key) // ID==0 is a non-valid special case. LOG(DEBUG) << "by key: " << key; - assert(key); - - TranEntry* poEntry = NULL; - rwLock.rlock(); + devassert(key); + ScopedLock lock(mttLock,__FILE__,__LINE__); NewTransactionMap::iterator itr = mTable.find(key); - if (itr!=mTable.end()) - if (!itr->second->deadOrRemoved()) - poEntry = itr->second; - rwLock.unlock(); - return poEntry; + if (itr==mTable.end()) return NULL; + if (itr->second->deadOrRemoved()) return NULL; + return (itr->second); } - // 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 + if (itr==mTable.end()) return false; + mTable.erase(itr); + return true; } // 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 NewTransactionTable::ttTerminate(TranEntryId tid, L3Cause::BSSCause cause) { - bool bRet = true; - rwLock.rlock(); + ScopedLock lock(mttLock,__FILE__,__LINE__); 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; + if (itr==mTable.end()) { return false; } + TranEntry *tran = itr->second; + ScopedLock lock2(tran->mAnotherLock,__FILE__,__LINE__); + tran->mTerminationRequested = cause; + return true; } // Does the TranEntry referenced by this id still pointer to its SipDialog? @@ -1093,98 +943,21 @@ bool NewTransactionTable::ttTerminate(TranEntryId tid) // 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(); + ScopedLock lock(mttLock,__FILE__,__LINE__); NewTransactionMap::iterator itr = mTable.find(tid); - if (itr==mTable.end()) bRet = false; - else bRet = (itr->second->mDialog == 0); - rwLock.unlock(); - return bRet; + if (itr==mTable.end()) { return true; } // TranEntry no longer exists. + return itr->second->mDialog == 0; } -// 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(); + ScopedLock lock(mttLock,__FILE__,__LINE__); NewTransactionMap::iterator itr = mTable.find(tid); - if (itr==mTable.end()) bRet = false; - else itr->second->setDialog(dialog); - rwLock.unlock(); - return bRet; + if (itr==mTable.end()) { return false; } // TranEntry no longer exists. + itr->second->setDialog(dialog); + return true; } -//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 @@ -1250,6 +1023,7 @@ bool NewTransactionTable::isBusy(const L3MobileIdentity& mobileID) if (itr->second->subscriber() != mobileID) continue; GSM::L3CMServiceType::TypeCode service = itr->second->servicetype(); bool speech = + service==GSM::L3CMServiceType::EmergencyCall || service==GSM::L3CMServiceType::MobileOriginatedCall || service==GSM::L3CMServiceType::MobileTerminatedCall; if (!speech) continue; @@ -1307,40 +1081,28 @@ TranEntry *NewTransactionTable::ttFindByL3Msg(GSM::L3Message *l3msg, L3LogicalCh #endif -#if UNUSED -// (pat added) By design, there is no back pointer from SIP to the TranEntry, so the TranEntry can be deleted -// independently, so we have to search for the Transaction if we need it. -TranEntry* NewTransactionTable::ttFindByDialog(SIP::SipDialog *pDialog) -{ - // Brute force search. - ScopedLock lock(mttLock,__FILE__,__LINE__); - for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { - TranEntry *tran = itr->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(); + ScopedLock lock(mttLock,__FILE__,__LINE__); 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"<getGSMState() : CCState::NullState; + return result; } // This is an external interface so we dont have to include L3TranEntry.h just to access this function. @@ -1350,29 +1112,6 @@ void NewTransactionTable_ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dms } -//TranEntry* NewTransactionTable::ttFindBySIPCallId(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. -// ScopedLock lock(mttLock,__FILE__,__LINE__); -// clearDeadEntries(); -// -// // Brute force search. -// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { -// TranEntry *tran = itr->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) { @@ -1397,52 +1134,79 @@ TranEntry* NewTransactionTable::ttFindHandoverOther(const L3MobileIdentity& mobi LOG(DEBUG) "no match "<mHandover->mHandoverOtherBSTransactionID<<"!="<subscriber().fmidMatch(mobileID)) { + if (! mobileID.fmidMatch(&tran->subscriber())) { LOG(DEBUG) "no match"<subscriber()) <second->deadOrRemoved()) continue; + if (! itr->second->subscriber().fmidMatch(mobileID)) continue; + L3LogicalChannel* chan = itr->second->channel(); + if (!chan) continue; + if (chan->chtype() == FACCHType) return chan; + if (chan->chtype() == SDCCHType) return chan; + // (pat) What other channel type could there be? The SACCH are not returned by channel(). + assert(0); // Alert pat if you get this assertion. + } + return NULL; +} +#endif + + +#if UNUSED +unsigned NewTransactionTable::countChan(const L3LogicalChannel* chan) +{ + ScopedLock lock(mttLock,__FILE__,__LINE__); + clearDeadEntries(); + unsigned count = 0; + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->channel() == chan) count++; + } + return count; +} +#endif + + size_t NewTransactionTable::dump(ostream& os, bool showAll) const { - rwLock.rlock(); + ScopedLock lock(mttLock,__FILE__,__LINE__); size_t sz = 0; for (NewTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { if ((!showAll) && itr->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(); + ScopedLock lock(mttLock,__FILE__,__LINE__); //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; @@ -1454,10 +1218,8 @@ TranEntryId NewTransactionTable::findLongestCall() longCall = itr; } } - if (longCall == mTable.end()) iRet = 0; - else iRet = longCall->second->tranID(); - rwLock.unlock(); - return iRet; + if (longCall == mTable.end()) return 0; + return longCall->second->tranID(); } /** @@ -1485,9 +1247,9 @@ unsigned allocateRTPPorts() /* 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) +bool NewTransactionTable::RTPAvailable(unsigned rtpPort) { - rwLock.rlock(); + ScopedLock lock(mttLock,__FILE__,__LINE__); //clearDeadEntries(); bool avail = true; for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { @@ -1497,44 +1259,34 @@ bool NewTransactionTable::RTPAvailable(short rtpPort) break; } } - rwLock.unlock(); return avail; } -void StaleTransactionTable::ttAdd(StaleTranEntry* value) + + + +#if 0 +bool NewTransactionTable::outboundReferenceUsed(unsigned ref) { - rwLock.wlock(); - if ((int)mTable.size() >= gConfig.getNum("Control.Reporting.TransactionMaxCompletedRecords")) { - mTable.erase(mTable.begin()); + // Called is expected to hold mttLock. + for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { + if (itr->second->deadOrRemoved()) continue; + if (itr->second->getGSMState() != GSM::Handover_Outbound) continue; + if (itr->second->handoverReference() == ref) return true; } - mTable[value->tranID()]=value; - rwLock.unlock(); + return false; } -void StaleTransactionTable::ttErase(StaleTransactionMap::iterator itr) +unsigned NewTransactionTable::generateHandoverReference(TranEntry *transaction) { - // 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(); + ScopedLock lock(mttLock,__FILE__,__LINE__); + clearDeadEntries(); + unsigned ref = random() % 256; + while (outboundReferenceUsed(ref)) { ref = (ref+1) % 256; } + transaction->handoverReference(ref); + return ref; } +#endif MachineBase *TranEntry::tePopMachine() { @@ -1580,12 +1332,12 @@ MachineStatus TranEntry::handleRecursion(MachineStatus status) bool TranEntry::handleMachineStatus(MachineStatus status) { //MMContext *set = teGetContext(); - OBJLOG(DEBUG) <isReleased()) { + // (pat) Update: Now the cause is passed to us in the transaction result MachineStatus. + // If the caller already called chanRelease then channel will already be null. + if (channel() && ! channel()->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); + channel()->chanRelease(L3_RELEASE_REQUEST,status.msCause); } 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. + // In all cases the caller was supposed to already send termination messages toward both layer2 (the handset) + // and SIP Dialog (the network) however, we will perform additional termination to make sure. + teRemove(status.msCause); // 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. @@ -1636,6 +1392,11 @@ bool TranEntry::handleMachineStatus(MachineStatus status) // The 'lockAnd...' methods are used to initially start or restart a Procedure. // Update: This locking is no longer needed or relevant. +// (pat 5-13-2014) Update update: Yes the locks appear to be used - I got the "waiting more than one second" emssage from lockAndInvokeFrame; +// the situation was starting then cancelling a call on the same handset. This may have been in the midst of a reassignment procedure +// so messages were arriving simultaneously on both channels. Looks like the CC Disconnect was sent on the SDCCH right before the +// reassign procedure, then a dialog cancel came in too. +// The phone misbehaved - it looked like it stayed connected after pressing disconnect? check again. // 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. @@ -1644,10 +1405,10 @@ bool TranEntry::lockAndStart(MachineBase *wProc) ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); if (wProc) { teSetProcedure(wProc,false); - assert(wProc == currentProcedure()); + devassert(wProc == currentProcedure()); } else { wProc = currentProcedure(); - assert(wProc); // Someone set the currentProcedure before calling this method. + devassert(wProc); // Someone set the currentProcedure before calling this method. } return handleMachineStatus(wProc->callMachStart(wProc)); } @@ -1658,10 +1419,37 @@ bool TranEntry::lockAndStart(MachineBase *wProc, GSM::L3Message *l3msg) { ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); teSetProcedure(wProc,false); - assert(wProc == currentProcedure()); + devassert(wProc == currentProcedure()); return handleMachineStatus(wProc->dispatchL3Msg(l3msg)); } +#if UNUSED +// 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. +bool TranEntry::lockAndInvokeL3Msg(const GSM::L3Message *l3msg /*, const L3LogicalChannel *lch*/) +{ + LOG(DEBUG); + ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__); +#if ORIGINAL + // Look up the message in the Procedure message table. + int state = mCurrentProcedure->findMsgMap(l3msg->PD(),l3msg->MTI()); + if (state == -1) { + // If state is -1, message is not mapped and is ignored by us. + return false; // Message was not wanted by this Procedure. + } + teProcInvoke(state,l3msg,NULL); +#endif + if (MachineBase *proc = currentProcedure()) { + LOG(DEBUG) <<"sending l3msg to"<dispatchL3Msg(l3msg)); + } + return false; +} +#endif // l3msg may be NULL for primitives or unparseable messages. bool TranEntry::lockAndInvokeFrame(const L3Frame *frame, const L3Message *l3msg) @@ -1719,15 +1507,18 @@ bool TranEntry::lockAndInvokeTimeout(L3Timer *timer) void TranEntry::terminateHook() { + LOG(INFO) "SIP term info terminateHook"; if (MachineBase *proc = currentProcedure()) { proc->handleTerminationRequest(); } } -void TranEntry::teCloseDialog(CancelCause cause) + +void TranEntry::teCloseDialog(TermCause cause) { CallState state = getGSMState(); + LOG(INFO) << "SIP term info teCloseDialog cancel cause: " << cause /*<< " l3Cause : " << l3Cause*/ ; // SVGDBG // 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(); @@ -1736,211 +1527,76 @@ void TranEntry::teCloseDialog(CancelCause cause) // 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; + cause = TermCause::Local(L3Cause::Handover_Outbound); } + LOG(INFO) << "SIP term info dialogCancel called in teCloseDialog"; 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) +void TranEntry::teRemove(TermCause cause) { - CallState state = getGSMState(); - + if (mFinalDisposition.tcIsEmpty()) { + mFinalDisposition = cause; + } SipDialog *dialog = getDialog(); - mDialog = 0; - + CallState state = getGSMState(); 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; + // FIXME: Sigh, it could be NoUserResponding from the default timer handler. + devassert(cause.tcGetValue() == L3Cause::Handover_Outbound || cause.tcIsEmpty()); + cause = TermCause::Local(L3Cause::Handover_Outbound); } + if (cause.tcIsEmpty() && dialog->getLastResponseCode()) { + // Both transaction and dialog are already cancelled, so this cause is for reporting purposes. + cause = dialog2TermCause(dialog); + } + LOG(INFO) << "SIP term info dialogCancel called in teRemove cancel cause: " << cause; dialog->dialogCancel(cause); // Does nothing if dialog not yet started. } + //GSM::CCCause l3Cause = GSM::L3Cause::UnknownL3Cause; + //if (dialog) { + // int SIPerror = dialog->getLastResponseCode(); + // // 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. + // if (state == CCState::Handover_Outbound) { + // devassert(cause == TermCauseHandoverOutbound); + // cause = TermCauseHandoverOutbound; + // } + + // if (cause == TermCauseNoAnswerToPage) + // l3Cause = GSM::L3Cause::NoUserResponding; + // else if (cause == TermCauseBusy) + // l3Cause = GSM::L3Cause::UserBusy; + // else if (cause == TermCauseCongestion) + // l3Cause = GSM::L3Cause::SwitchingEquipmentCongestion; + + // if (l3Cause == GSM::L3Cause::UnknownL3Cause) { + // // Translate SIP error to L3Cause + // switch (SIPerror) { + // case 408: l3Cause = L3Cause::NoUserResponding; break; + // case 480: l3Cause = L3Cause::UserAlertingNoAnswer; break; + // case 404: l3Cause = L3Cause::UnassignedNumber; break; + // default: break; + // } + // } + + // LOG(INFO) << "SIP term info dialogCancel called in teRemove cancel cause: " << cause << " L3Cause: " << l3Cause << " SIPerror: " << SIPerror; + // dialog->dialogCancel(cause, l3Cause); // Does nothing if dialog not yet started. + //} + + if (L3CDR *cdr = this->createCDR(false,cause)) { + gCdrService.cdrServiceStart(); + gCdrService.cdrAdd(cdr); + } + // 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. @@ -1958,19 +1614,258 @@ void TranEntry::teRemove(CancelCause cause) // 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) +void TranEntry::teCloseCallNow(TermCause cause, bool sendCause) { - WATCHINFO("CloseCallNow"<descriptiveString()); - LOG(DEBUG) <descriptiveString()); + LOG(DEBUG) <teCloseDialog(cause); // Redundant with teRemove, but I want to be sure we terminate the dialog regardless of bugs. if (isL3TIValid() && tran()->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. + if (sendCause) { + channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,cause.tcGetCCCause())); // This is a CC message that releases this Transaction immediately. + } else { + channel()->l3sendm(GSM::L3ReleaseComplete(l3ti)); // This is a reply that confirms transaction was released. + } } setGSMState(CCState::NullState); // redundant, we are deleting this transaction. } -}; +void writePrivateHeaders(SipMessage *msg, const L3LogicalChannel *l3chan) +{ + // P-PHY-Info + // This is a non-standard private header in OpenBTS. + // TODO: If we add the MSC params to this, especially L3TI, the SIP message will completely encapsulate handover. + // TA= 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(); + // (pat 5-2014) Adding the imsi to the info, since it is sometimes not possible to know which of the + // two handsets involved in the dialog initiated the SIP message. + string imsi = l3chan->chanGetImsi(true); + snprintf(phy_info,200,"OpenBTS; IMSI=%s TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d time=%9.3lf", + imsi.c_str(), + phys->actualMSTiming(), phys->timingError(), + phys->getRSSI(), 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. +} + +void TranInit() +{ + SipCallbacks::setcallback_ttAddMessage( & NewTransactionTable_ttAddMessage); + SipCallbacks::setcallback_writePrivateHeaders( & writePrivateHeaders); +} + +// Look up the phone number, if any, passed to us from the Registrar when this imsi registered. +static string lookupPhoneNumber(string imsi) +{ + string number, unused; + gTMSITable.getSipIdentities(imsi,number,unused); + int len = number.size(); + if (len >= 2 && number[0] == '<') { number = number.substr(1,len-2); } + if (0 == strncasecmp(number.c_str(),"tel:",4)) { number = number.substr(4); } + return number; +} + +L3CDR *TranEntry::createCDR(bool makeCMR, TermCause cause) // If true, make a CMR instead of a CDR. CMRs have more info. +{ + L3CDR *cdrp = new L3CDR(), &cdr = *cdrp; // Gotta love this language. + cdr.cdrToNumber = string(this->called().digits()); + cdr.cdrFromNumber = string(this->calling().digits()); + + switch (this->service().type()) { + case L3CMServiceType::MobileOriginatedCall: + cdr.cdrType = "MOC"; + labelmoc: + if (mConnectTime == 0 && ! makeCMR) { goto labelIgnore; } + cdr.cdrFromImsi = this->subscriber().mImsi; + if (SipDialog *dialog = this->getDialog()) { + // MOC do not know the IMSI of the phone number they are calling. + //cdr.cdrToImsi = dialog->sipRemoteUsername(); // This is the phone number, not the imsi. + if (dialog->dsPeer()) cdr.cdrPeer = dialog->dsPeer()->mipName; + } + if (cdr.cdrFromNumber.empty()) { + cdr.cdrFromNumber = lookupPhoneNumber(cdr.cdrFromImsi); + } + break; + case L3CMServiceType::EmergencyCall: + cdr.cdrType = "Emergency"; + goto labelmoc; + break; + case L3CMServiceType::ShortMessage: + if (this->mMessage.size() == 0 && ! makeCMR) { goto labelIgnore; } + cdr.cdrType = "MOSMS"; + goto labelmoc; + break; + case L3CMServiceType::MobileTerminatedShortMessage: + if (this->mMessage.size() == 0 && ! makeCMR) { goto labelIgnore; } + cdr.cdrType = "MTSMS"; + goto labelmtc; + break; + + case L3CMServiceType::UndefinedType: + case L3CMServiceType::SupplementaryService: + case L3CMServiceType::VoiceCallGroup: + case L3CMServiceType::VoiceBroadcast: + case L3CMServiceType::LocationService: + case L3CMServiceType::LocationUpdateRequest: + case L3CMServiceType::HandoverCall: // The handover code sets the type to MobileOriginatedCall or MobileTerminatedCall so we should not see this. + break; + + case L3CMServiceType::MobileTerminatedCall: + cdr.cdrType = "MTC"; + labelmtc: + if (mConnectTime == 0 && ! makeCMR) { goto labelIgnore; } + cdr.cdrToImsi = this->subscriber().mImsi; + if (SipDialog *dialog2 = this->getDialog()) { + cdr.cdrFromImsi = dialog2->sipRemoteUsername(); + if (0 == strncasecmp(cdr.cdrFromImsi.c_str(),"IMSI",4)) { + cdr.cdrFromImsi = cdr.cdrFromImsi.substr(4); + } + if (dialog2->dsPeer()) cdr.cdrPeer = dialog2->dsPeer()->mipName; + } + if (cdr.cdrToNumber.empty()) { + cdr.cdrToNumber = lookupPhoneNumber(cdr.cdrToImsi); + } + break; + }; + + if (cdr.cdrType == "") { + LOG(ERR) << "Unrecognized service, no CDR generated:"<service(); + labelIgnore: + delete cdrp; + return NULL; + } + + cdr.cdrTid = this->tranID(); + cdr.cdrConnectTime = this->mConnectTime; + cdr.cdrDuration = cdr.cdrConnectTime ? time(NULL) - cdr.cdrConnectTime : 0; + cdr.cdrMessageSize = this->mMessage.size(); + cdr.cdrCause = this->mFinalDisposition; + if (HandoverEntry *hp = this->getHandoverEntry(false)) { + cdr.cdrFromHandover = sockaddr2string(&hp->mInboundPeer,false); + cdr.cdrToHandover = sockaddr2string(&hp->mOutboundPeer,false); + } + return cdrp; +} + +void L3CDR::cdrWriteHeader(FILE *pf) +{ + // Fields must match type and order in cdrWriteEntry() + typedef struct {const char *name; const char *type;} Field; + static Field fields[] = { + {"Type","string"}, + {"Transaction-id","integer"}, + {"To-IMSI","string"}, // usually empty for MOC. + {"From-IMSI","string"}, + {"To-number","string"}, + {"From-number","string"}, + {"Peer","string"}, + {"Start-time","integer"}, // standard unix time. + // Connect duration, as opposed to total time, in seconds. 0 means call never successfully connected. + {"Connect-Duration","integer"}, + {"Message-size","integer"}, // Only for SMS. + {"To-Handover","string"}, // TODO + {"From-Handover","string"}, + {"Termination-Side","char"}, // R or L for remote or local side. + {"Termination-Cause","integer"}, + {NULL,NULL} + }; + // Print the field names. + for (Field *field = fields; field->name; field++) { + if (field != fields) fputc(',',pf); + fprintf(pf,"%s",field->name); + } + fputc('\n',pf); + // Print the field types. + for (Field *field = fields; field->type; field++) { + if (field != fields) fputc(',',pf); + fprintf(pf,"%s",field->type); + } + fputc('\n',pf); + fflush(pf); +} + +void L3CDR::cdrWriteEntry(FILE *pf) +{ + fprintf(pf,"%s,%u,",cdrType.c_str(),cdrTid); + fprintf(pf,"%s,%s,",cdrToImsi.c_str(),cdrFromImsi.c_str()); + fprintf(pf,"%s,%s,",cdrToNumber.c_str(),cdrFromNumber.c_str()); + fprintf(pf,"%s,",cdrPeer.c_str()); + fprintf(pf,"%lu,%lu,",cdrConnectTime,cdrDuration); + fprintf(pf,"%d,",cdrMessageSize); + fprintf(pf,"%s,%s,",cdrToHandover.c_str(),cdrFromHandover.c_str()); // handover to, from + fprintf(pf,"%c",(cdrCause.mtcInstigator == TermCause::SideLocal) ? 'L' : 'R'); // termination side + fprintf(pf,"%d",cdrCause.tcGetValue()); // termination cause + fprintf(pf,"\n"); + fflush(pf); // Write to disk in case OpenBTS crashes. +} + +// Open a new file whenever the date changes. +void CdrService::cdrOpenFile() +{ + time_t now = time(NULL); + struct tm tm; + localtime_r(&now,&tm); + if (tm.tm_yday == cdrCurrentDay) { + // We already tried to open the file. Either we succeeded or not, but we wont try to open it on every transaction. + return; + } + if (mpf) { fclose(mpf); mpf = NULL; } + cdrCurrentDay = tm.tm_yday; + string date = format("%04d-%02d-%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + const char *dirname = "/var/log/OpenBTS"; // TODO: make this a config option. + mkdir(dirname,0777); // Doesnt hurt to do this even if unnecessary. + + string btsid = gConfig.getStr("SIP.Local.IP"); // Default bts id to the local IP address. + string filename = format("%s/OpenBTS_%s_%s.cdr",dirname,btsid,date); + mpf = fopen(filename.c_str(),"a"); + // Dont re-write the header if we are appending to an existing file. + if (mpf && 0 == ftell(mpf)) { L3CDR::cdrWriteHeader(mpf); } +} + +void*CdrService::cdrServiceLoop(void*arg) +{ + CdrService *self = static_cast(arg); + while (!gBTS.btsShutdown()) { + L3CDR *cdr = self->mCdrQueue.read(); // Blocking read. + self->cdrOpenFile(); // We may have to open a new file if the date changed. + if (self->mpf) { cdr->cdrWriteEntry(self->mpf); } + delete cdr; + } + return NULL; +} + +void CdrService::cdrServiceStart() +{ + if (!cdrServiceRunning) { + cdrServiceRunning = true; + cdrServiceThread.start(cdrServiceLoop,this); + } +} + +}; // namespace // vim: ts=4 sw=4 diff --git a/Control/L3TranEntry.h b/Control/L3TranEntry.h index bd99356..05ae744 100644 --- a/Control/L3TranEntry.h +++ b/Control/L3TranEntry.h @@ -6,7 +6,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -43,6 +43,10 @@ #include "L3MobilityManagement.h" #include "L3Utils.h" +extern int gCountTranEntry; + +struct sqlite3; + /**@namespace Control This namepace is for use by the control layer. */ namespace Control { @@ -75,14 +79,7 @@ class HandoverEntry { 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; + struct ::sockaddr_in mOutboundPeer; protected: void initHandoverEntry( @@ -191,35 +188,26 @@ class HandoverEntry { // 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() : mGSMState(CCState::NullState) { mStateTimer.now(); } + // (pat 6-2014) It looks like Dave added this. It would be needed to preserve the state timer + // if the TranEntry were ever copied. Is it? This may have been part of the staletranentry that was deleted. TranEntryProtected(TranEntryProtected &old) { mGSMState = old.mGSMState; @@ -227,12 +215,46 @@ class TranEntryProtected } }; +// pat added 6-2014. +// Call Data Record, created from a TranEntry. +// The information we dont know is left empty. +struct L3CDR { + string cdrType; // MOC, MTC, Emergency + TranEntryId cdrTid; + string cdrToImsi, cdrFromImsi; + string cdrToNumber, cdrFromNumber; + string cdrPeer; + time_t cdrConnectTime; + long cdrDuration; // Connect duration, as distinct from total duration. + int cdrMessageSize; // For SMS + string cdrToHandover, cdrFromHandover; + TermCause cdrCause; + + static void cdrWriteHeader(FILE *pf); + void cdrWriteEntry(FILE *pf); +}; + +// pat added 6-2014. +// This is sent L3CDR records. It writes them to a file. +class CdrService { + InterthreadQueue mCdrQueue; + Thread cdrServiceThread; + FILE *mpf; + int cdrCurrentDay; + Bool_z cdrServiceRunning; + public: + CdrService() : mpf(NULL), cdrCurrentDay(-1) {} + void cdrServiceStart(); + void cdrOpenFile(); + static void *cdrServiceLoop(void*); + void cdrAdd(L3CDR*cdrp) { mCdrQueue.write(cdrp); } +}; +extern CdrService gCdrService; 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 @@ -284,6 +306,12 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP std::string mContentType; ///< text message payload content type //@} + TermCause mFinalDisposition; // How transaction ended. + + + + + // (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. @@ -296,14 +324,14 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP // 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::SipDialogRef mDialog; // (pat) post-l3-rewrite only. - SIP::SipDialog *getDialog() { return mDialog; } - SIP::SipDialog *getDialog() const { return mDialog; } // grrr + SIP::SipDialog *getDialog() { return mDialog.self(); } + SIP::SipDialog *getDialog() const { return mDialog.self(); } // grrr public: void setDialog(SIP::SipDialog *dialog); // Also passed through to here from MachineBase - void teCloseDialog(CancelCause cause=CancelCauseUnknown); + void teCloseDialog(TermCause cause); private: mutable SIP::SipState mPrevSipState; ///< previous SIP state, prior to most recent transactions @@ -319,7 +347,7 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP private: - Bool_z mTerminationRequested; + L3Cause::AnyCause mTerminationRequested; public: // But only used by MobilityManagement routines. // TODO: Maybe this should move into the MMContext. @@ -350,7 +378,7 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP static TranEntry *newMTC( SIP::SipDialog *wDialog, const FullMobileId& msid, - const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI. + const GSM::L3CMServiceType& wService, // MobileTerminatedCall or UndefinedType for generic page from CLI. string wCallerId); static TranEntry *newMTSMS( @@ -388,15 +416,11 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP ); /** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */ - void setInboundHandover( - float wRSSI, - float wTimingError - ); + //void setInboundHandover(float wRSSI, float wTimingError); + -private: /** Delete the database entry upon destruction. */ ~TranEntry(); -public: /**@name Accessors. */ //@{ @@ -442,7 +466,7 @@ public: //@} - bool terminationRequested(); + L3Cause::AnyCause terminationRequested(); /**@name SIP-side operations */ //@{ @@ -452,13 +476,8 @@ public: // 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(); } + void txFrame(SIP::AudioFrame* frame, unsigned numFlushed); + SIP::AudioFrame *rxFrame(); //@} @@ -469,16 +488,17 @@ public: 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: + /** Set up a new entry in gTransactionTable's sqlite3 database. */ + //void insertIntoDatabase(); + /** Run a database query. */ void runQuery(const char* query) const; @@ -488,12 +508,11 @@ public: // (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. + private: void teRemove(TermCause 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. + void teCancel(TermCause cause) { teRemove(cause); } // Used from MM. Cancel dialog and delete transaction, do not notify MM layer. Used within MMLayer. + void teCloseCallNow(TermCause cause, bool sendCause); // 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: @@ -537,47 +556,16 @@ public: HandoverEntry *getHandoverEntry(bool create) const; // Creates if necessary. TranEntry *unconst() const { return const_cast(this); } + // Is this handset ever communicated with us? + bool teIsTalking(); + // 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; } + time_t mStartTime; // transaction creation time. + time_t mConnectTime; // When call is connected, or 0 if never. + L3CDR *createCDR(bool makeCMR, TermCause cause); }; -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*); @@ -586,10 +574,6 @@ 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. */ @@ -599,17 +583,26 @@ class NewTransactionTable { private: +#if EXTERNAL_TRANSACTION_TABLE + sqlite3 *mDB; ///< database connection +#endif + NewTransactionMap mTable; - mutable RWLock rwLock; + mutable Mutex mttLock; unsigned mIDCounter; public: - /** Initialize a transaction table. + @param path Path fto sqlite3 database file. */ void ttInit(); +#if EXTERNAL_TRANSACTION_TABLE + NewTransactionTable() : mDB(0) {} // (pat) Make sure no garbage creeps in. + ~NewTransactionTable(); +#endif + /** Return a new ID for use in the table. */ @@ -627,6 +620,10 @@ class NewTransactionTable { @return NULL if ID is not found or was dead */ TranEntry* ttFindById(TranEntryId wID); + bool ttIsTalking(TranEntryId tranid); + // (pat) This is a temporary routine to return the state asynchronously. + // Should be replaced by using a RefCntPointer in the NewTransactionTable and returning that. + CallState ttGetGSMStateById(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. @@ -646,7 +643,7 @@ class NewTransactionTable { Return the availability of this particular RTP port @return True if Port is available, False otherwise */ - bool RTPAvailable(short rtpPort); + bool RTPAvailable(unsigned rtpPort); /** Fand an entry by its handover reference. @@ -660,12 +657,9 @@ class NewTransactionTable { @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 + bool ttRemove(unsigned wID); - // Use this ONLY when deleting from the Stale table in the main process - void ttErase(NewTransactionMap::iterator itr); - - bool ttTerminate(TranEntryId tid); + bool ttTerminate(TranEntryId tid, L3Cause::BSSCause cause); //bool remove(TranEntry* transaction) { return remove(transaction->tranID()); } @@ -743,20 +737,21 @@ class NewTransactionTable { /** 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; } + size_t size() { ScopedLock lock(mttLock); return mTable.size(); } - // 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: +#if EXTERNAL_TRANSACTION_TABLE + /** Accessor to database connection. */ + sqlite3* getDB() { return mDB; } +#endif + /** Remove "dead" entries from the table. A "dead" entry is a transaction that is no longer active. @@ -782,105 +777,9 @@ class NewTransactionTable { //} }; -/** - A table for tracking the states of stale transactions. -*/ -class StaleTransactionTable { - private: - StaleTransactionMap mTable; - mutable RWLock rwLock; +extern void TranInit(); - 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 @@ -888,3 +787,5 @@ class StaleTranEntry #endif // vim: ts=4 sw=4 + + diff --git a/Control/L3Utils.cpp b/Control/L3Utils.cpp index bdf9810..2afbb0e 100644 --- a/Control/L3Utils.cpp +++ b/Control/L3Utils.cpp @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -13,6 +13,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + +// Written by Pat Thompson + #define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging #include "L3Utils.h" #include "L3StateMachine.h" diff --git a/Control/L3Utils.h b/Control/L3Utils.h index e933392..1853c41 100644 --- a/Control/L3Utils.h +++ b/Control/L3Utils.h @@ -1,10 +1,10 @@ /**@file Declarations for Circuit Switched State Machine and related classes. */ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #ifndef _L3UTILS_H_ #define _L3UTILS_H_ #include // Where Z100Timer lives. diff --git a/Control/Makefile.am b/Control/Makefile.am index 99d1fbb..b58ac64 100644 --- a/Control/Makefile.am +++ b/Control/Makefile.am @@ -1,7 +1,7 @@ # # Copyright 2008 Free Software Foundation, Inc. # Copyright 2010 Kestrel Signal Processing, Inc. -# Copyright 2011 Range Networks, Inc. +# Copyright 2011, 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -32,27 +32,31 @@ noinst_LTLIBRARIES = libcontrol.la # L3CallControl.cpp libcontrol_la_SOURCES = \ + L3TermCause.cpp \ + L3CallControl.cpp \ + PagingEntry.cpp \ + L3Handover.cpp \ L3SMSControl.cpp \ L3MobilityManagement.cpp \ - L3CallControl.cpp \ L3StateMachine.cpp \ L3SupServ.cpp \ L3MMLayer.cpp \ L3TranEntry.cpp \ L3LogicalChannel.cpp \ L3Utils.cpp \ - L3Handover.cpp \ TMSITable.cpp \ ControlTransfer.cpp \ - RadioResource.cpp \ DCCHDispatch.cpp \ - SMSCB.cpp \ - RRLPServer.cpp + SMSCB.cpp # TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory. noinst_HEADERS = \ + L3TermCause.h \ + CodecSet.h \ + PagingEntry.h \ + L3Handover.h \ L3SMSControl.h \ L3CallControl.h \ L3MobilityManagement.h \ @@ -64,7 +68,4 @@ noinst_HEADERS = \ L3Utils.h \ ControlCommon.h \ ControlTransfer.h \ - TMSITable.h \ - RadioResource.h \ - TMSITable.h \ - RRLPServer.h + TMSITable.h diff --git a/Control/PagingEntry.cpp b/Control/PagingEntry.cpp new file mode 100644 index 0000000..7957d15 --- /dev/null +++ b/Control/PagingEntry.cpp @@ -0,0 +1,85 @@ +/**@file Declarations for common-use control-layer functions. */ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#define LOG_GROUP LogGroup::Control +#include "PagingEntry.h" +#include "L3MMLayer.h" +#include + +namespace Control { + +NewPagingEntry::~NewPagingEntry() +{ + if (mImmAssign) { delete mImmAssign; } +} + +GSM::ChannelType NewPagingEntry::getGsmChanType() const +{ + return mInitialChanType; +} + +// This does a lot of mallocing. +L3MobileIdentity NewPagingEntry::getMobileId() +{ + if (! mCheckedForTmsi) { + mCheckedForTmsi = true; + if (! mTmsi.valid()) { + uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mImsi,true); + LOG(DEBUG)<<"tmsiTabGetTMSI imsi="<text(); return os; } + +}; diff --git a/Control/PagingEntry.h b/Control/PagingEntry.h new file mode 100644 index 0000000..86af6b5 --- /dev/null +++ b/Control/PagingEntry.h @@ -0,0 +1,81 @@ +/**@file Declarations for common-use control-layer functions. */ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#ifndef _PAGING_H_ +#define _PAGING_H_ + +#include +#include "ControlTransfer.h" +namespace GSM { class L3Frame; class L2LogicalChannel; class L3ImmediateAssignment; class L3MobileIdentity; } + +namespace Control { + + +/** An entry in the paging list. */ +struct NewPagingEntry { + GSM::ChannelType mInitialChanType; + // Each page has to be sent twice to make sure the handset hears it. + Int_z mSendCount; // Number of times this page has been sent. + + // These fields are needed for all pages: + std::string mImsi; // Always provided. + + // For GSM pages we need the following fields: + TMSI_t mTmsi; // A place for the pager to cache the tmsi if it finds one. + Bool_z mCheckedForTmsi; // A flag used by the pager to see if the imsi has been checked for tmsi. + + // For GPRS pages we need the following fields: + GPRS::TBF *mGprsClient; // If non-NULL this is a GPRS client that needs service. + GSM::L3ImmediateAssignment *mImmAssign; + unsigned mDrxBegin; // GSM Frame number when DRX mode begins. + + // Such a clever language. + NewPagingEntry(GSM::ChannelType wChanType, std::string &wImsi) : + /*mNpeType(wType),*/ + mInitialChanType(wChanType), + mImsi(wImsi), + mGprsClient(NULL), mImmAssign(NULL), mDrxBegin(0) + {} + NewPagingEntry(const NewPagingEntry &other) : /*mNpeType(other.mNpeType),*/ + mInitialChanType(other.mInitialChanType), + mImsi(other.mImsi), + mTmsi(other.mTmsi), mCheckedForTmsi(other.mCheckedForTmsi), + mGprsClient(NULL), mImmAssign(NULL), mDrxBegin(0) + { + //mTmsi = other.mTmsi; + //mCheckedForTmsi = other.mCheckedForTmsi + assert(other.mGprsClient == NULL); + assert(other.mImmAssign == NULL); // Since it is deleted when NewPagingEntry is deleted, it would be double deleted. + } + // { *this = other; } + ~NewPagingEntry(); + + // Return the GSM channel type, assuming it is not a GPRS paging type. + GSM::ChannelType getGsmChanType() const; + GSM::L3MobileIdentity getMobileId(); + unsigned getImsiMod1000() const; + string text() const; +}; +std::ostream& operator<<(std::ostream& os, const NewPagingEntry&npe); +std::ostream& operator<<(std::ostream& os, const NewPagingEntry*npe); + +typedef std::vector NewPagingList_t; +void MMGetPages(NewPagingList_t &pages, bool wait); + +}; + +#endif diff --git a/Control/RRLPServer.cpp b/Control/RRLPServer.cpp deleted file mode 100644 index b7b1ac0..0000000 --- a/Control/RRLPServer.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/**@file RRLPServer */ -/* -* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, 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. -* -* 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 . - -*/ -#define LOG_GROUP LogGroup::Control -#include - -using namespace std; - -#include "ControlCommon.h" -#include "L3MMLayer.h" -#include "RRLPServer.h" - -#include -#include -#include // for L3ApplicationInformation - -#include -#include - -namespace Control { -using namespace GSM; - -// (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) -{ - char *p = line + strlen(line) - 1; - while (p > line && *p <= ' ') *p-- = 0; -} - -string getConfig() -{ - const char *configs[] = { - "GSM.RRLP.ACCURACY", - "GSM.RRLP.RESPONSETIME", - "GSM.RRLP.ALMANAC.URL", - "GSM.RRLP.EPHEMERIS.URL", - "GSM.RRLP.ALMANAC.REFRESH.TIME", - "GSM.RRLP.EPHEMERIS.REFRESH.TIME", - "GSM.RRLP.SEED.LATITUDE", - "GSM.RRLP.SEED.LONGITUDE", - "GSM.RRLP.SEED.ALTITUDE", - "GSM.RRLP.EPHEMERIS.ASSIST.COUNT", - "GSM.RRLP.ALMANAC.ASSIST.PRESENT", - 0 - }; - string config = ""; - for (const char **p = configs; *p; p++) { - string configValue = gConfig.getStr(*p); - if (configValue.length() == 0) return ""; - config.append("&"); - config.append(*p); - config.append("="); - if (0 == strcmp((*p) + strlen(*p) - 3, "URL")) { - // TODO - better to have urlencode and decode, but then I'd have to look for them - char buf[3]; - buf[2] = 0; - for (const char *q = configValue.c_str(); *q; q++) { - sprintf(buf, "%02x", *q); - config.append(buf); - } - } else { - config.append(configValue); - } - } - return config; -} - -RRLPServer::RRLPServer(string wSubscriber) -{ - mTrouble = false; - mURL = gConfig.getStr("GSM.RRLP.SERVER.URL"); - if (mURL.length() == 0) { - LOG(INFO) << "RRLP not configured"; - mTrouble = true; - return; - } - //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 - // 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; - //} - } -} - -// send a location request -void RRLPServer::rrlpSend(L3LogicalChannel *chan) -{ - mQuery = "query=loc"; - // server encodes location request into apdu - serve(); - if (mAPDUs.size() == 0) { - LOG(ERR) << "no apdu to send to mobile"; - return; - } - 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)); -} - -// decode a RRLP response from the mobile -void RRLPServer::rrlpRecv(const GSM::L3Message *resp) -{ - 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; - } - } - 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) <. - -*/ - -#ifndef RRLPSERVER_H -#define RRLPSERVER_H -#include // For L3MobileIdentity -#include "ControlCommon.h" - -namespace Control { - -class RRLPServer -{ - public: - RRLPServer(string wSubscriber); - bool trouble() { return mTrouble; } - void rrlpSend(L3LogicalChannel *mmchan); - void rrlpRecv(const GSM::L3Message*); - private: - RRLPServer(); // not allowed - 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 wMobileID, L3LogicalChannel *wLCH); -void recvRRLP(MMContext *wLCH, const GSM::L3Message *wMsg); - -}; - -#endif diff --git a/Control/RRLP_PDU_Test.cpp b/Control/RRLP_PDU_Test.cpp index 3694eb1..40e8989 100644 --- a/Control/RRLP_PDU_Test.cpp +++ b/Control/RRLP_PDU_Test.cpp @@ -1,3 +1,18 @@ +/* +* Copyright 2014 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. +*/ + #include "Configuration.h" #include "RRLPQueryController.h" diff --git a/Control/SMSCB.cpp b/Control/SMSCB.cpp index b1a8ef0..81916a8 100644 --- a/Control/SMSCB.cpp +++ b/Control/SMSCB.cpp @@ -1,10 +1,11 @@ /**@file SMSCB Control (L3), GSM 03.41. */ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #define LOG_GROUP LogGroup::Control #include "ControlCommon.h" diff --git a/Control/TMSITable.cpp b/Control/TMSITable.cpp index fffc3d0..10a56c4 100644 --- a/Control/TMSITable.cpp +++ b/Control/TMSITable.cpp @@ -1,10 +1,11 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,7 +16,6 @@ */ - #define LOG_GROUP LogGroup::Control #include #include @@ -284,7 +284,9 @@ int TMSITable::tmsiTabOpen(const char* wPath) LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mTmsiDB); } LOG(INFO) << "Opened TMSI table version "<auth_changed)<auth)<assigned_changed)<assigned); + LOG(INFO) << "update entry 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; @@ -534,7 +536,7 @@ uint32_t TMSITable::tmsiTabCreateOrUpdate( LOG(ALERT) << "TMSI creation failed for"< > TMSITable::tmsiTabView(int verbosity, bool rawFlag, uns } -void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll) const +void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll, bool taboption) const { // Dump the TMSI table. unsigned maxrows = showAll ? 2^31 : 100; @@ -861,7 +863,7 @@ void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAl view.push_back(tmp); } - printPrettyTable(view,os); + printPrettyTable(view,os,taboption); #if 0 // previous tmsi table dumping code. sqlite3_stmt *stmt; diff --git a/Control/TMSITable.h b/Control/TMSITable.h index 4208b9b..e784bf5 100644 --- a/Control/TMSITable.h +++ b/Control/TMSITable.h @@ -1,10 +1,11 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,8 +16,6 @@ */ - - #ifndef TMSITABLE_H #define TMSITABLE_H @@ -160,7 +159,7 @@ class TMSITable { std::vector< std::vector > tmsiTabView(int verbosity, bool rawFlag, unsigned maxrows) const; /** Write entries as text to a stream. */ - void tmsiTabDump(int verbosity,bool rawFlag,std::ostream&, bool ShowAll = false) const; + void tmsiTabDump(int verbosity,bool rawFlag,std::ostream&, bool ShowAll = false, bool taboption = false) const; /** Clear the table completely. */ void tmsiTabClear(); diff --git a/GPRS/BSSG.cpp b/GPRS/BSSG.cpp index 71301b3..c33007a 100644 --- a/GPRS/BSSG.cpp +++ b/GPRS/BSSG.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -13,6 +12,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging #include "Defines.h" #include "GPRSInternal.h" // For GPRSLOG() @@ -26,6 +26,7 @@ #include #include + #if INTERNAL_SGSN == 0 namespace BSSG { diff --git a/GPRS/BSSG.h b/GPRS/BSSG.h index 480a6f1..9367e28 100644 --- a/GPRS/BSSG.h +++ b/GPRS/BSSG.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -27,7 +26,7 @@ namespace BSSG { -// GSM 08.16 describes tne NS layer. +// GSM 08.16 describes the NS layer. // GSM 08.18 sec 10 describes the PDU messages that the SGSN can send to the BSS. // GSM 48.018 is the updated spec with PS-HANDOVER related commands. // Definitions: diff --git a/GPRS/BSSGMessages.cpp b/GPRS/BSSGMessages.cpp index fa7519f..adbfe99 100644 --- a/GPRS/BSSGMessages.cpp +++ b/GPRS/BSSGMessages.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "BSSG.h" #include "BSSGMessages.h" #include "GPRSInternal.h" diff --git a/GPRS/BSSGMessages.h b/GPRS/BSSGMessages.h index dcec925..8fc61e7 100644 --- a/GPRS/BSSGMessages.h +++ b/GPRS/BSSGMessages.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/ByteVector.cpp b/GPRS/ByteVector.cpp index fbc0f47..5d9f526 100644 --- a/GPRS/ByteVector.cpp +++ b/GPRS/ByteVector.cpp @@ -1,5 +1,5 @@ /* -* Copyright 2011 Range Networks, Inc. +* Copyright 2011, 2014 Range Networks, Inc. * * * This software is distributed under the terms of the GNU Affero Public License. @@ -22,6 +22,9 @@ along with this program. If not, see . */ + +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "ByteVector.h" // Set the char[2] array at ip to a 16-bit int value, swizzling bytes as needed for network order. diff --git a/GPRS/ByteVector.h b/GPRS/ByteVector.h index 06ce78e..fb51b3e 100644 --- a/GPRS/ByteVector.h +++ b/GPRS/ByteVector.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/FEC.cpp b/GPRS/FEC.cpp index 0826d5b..e597032 100644 --- a/GPRS/FEC.cpp +++ b/GPRS/FEC.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "GPRSInternal.h" #include "RLCMessages.h" #include "RLCEngine.h" @@ -278,7 +279,7 @@ void PDCHL1Downlink::send1Frame(BitVector& frame,ChannelCodingType encoding, boo // Return true if we send a block on the downlink. -bool PDCHL1Downlink::send1DataFrame( +bool PDCHL1Downlink::send1DataFrame( //SVGDBG RLCDownEngine *engdown, RLCDownlinkDataBlock *block, // block to send. int makeres, // 0 = no res, 1 = optional res, 2 = required res. @@ -751,7 +752,7 @@ void PDCHL1Downlink::dlService() static int debugCntTotal = 0, debugCntDummy = 0; debugCntTotal++; if ((GPRSDebug&512) || debugCntTotal % 1024 == 0) { - GPRSLOG(2) << "dlService sent total="<parent(); } // If gFixIdleFrame only send blocks on the even BSNs, diff --git a/GPRS/FEC.h b/GPRS/FEC.h index c43013a..f8f10f5 100644 --- a/GPRS/FEC.h +++ b/GPRS/FEC.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -145,6 +144,7 @@ class GprsDecoder : public SharedL1Decoder mD_CS4(mDP_CS4.head(424)) {} bool decodeCS4(); + const char* descriptiveString() const { return "GprsDecoder"; } // not very useful. }; // CS-4 has 431 input data bits, which are always 424 real data bits (53 bytes) @@ -169,6 +169,8 @@ class GprsEncoder : public SharedL1Encoder {} void encodeCS4(const BitVector&src); void encodeCS1(const BitVector &src); + // would be nice to add "GPRS"; should be at init. + const char* descriptiveString() const { return "GprsEncoder"; } }; diff --git a/GPRS/GPRSCLI.cpp b/GPRS/GPRSCLI.cpp index b756959..0d7a86d 100644 --- a/GPRS/GPRSCLI.cpp +++ b/GPRS/GPRSCLI.cpp @@ -1,10 +1,9 @@ /* * Copyright 2011, 2014 Range Networks, Inc. -* All Rights Reserved. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "GPRSInternal.h" #include "TBF.h" #include "RLCEngine.h" @@ -251,6 +252,7 @@ static CLIStatus gprsStop(int argc, char **argv, int argi, ostream&os) return SUCCESS; } +#if NO_LONGER_WORKING // (pat 2-2014) This code has eroded so I just disabled it. static CLIStatus gprsTestRach(int argc, char **argv, int argi, ostream&os) { GSM::Time now = gBTS.time(); @@ -258,6 +260,7 @@ static CLIStatus gprsTestRach(int argc, char **argv, int argi, ostream&os) GPRSProcessRACH(RA,now,-20,0.5); return SUCCESS; } +#endif extern "C" { int gprs_llc_fcs(uint8_t *data, unsigned int len); @@ -615,7 +618,7 @@ static struct GprsSubCmds { { "stop",gprsStop, "stop [-c] # stop gprs thread and if -c release channels" }, { "step",gprsStep, "step # single step the MAC service loop (requires 'start step')." }, { "set",gprsSet, "set name [val] # print and optionally set a variable - see source for names" }, - { "rach",gprsTestRach, "rach # Simulate a RACH, which starts gprs service" }, + //{ "rach",gprsTestRach, "rach # Simulate a RACH, which starts gprs service" }, { "testmsg",gprsTestMsg, "testmsg # Test message functions" }, { "testbsn",gprsTestBSN, "testbsn # Test bsn<->frame number functions" }, #if INTERNAL_SGSN==0 diff --git a/GPRS/GPRSExport.h b/GPRS/GPRSExport.h index 057003c..f85636a 100644 --- a/GPRS/GPRSExport.h +++ b/GPRS/GPRSExport.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -29,9 +28,11 @@ // so they must be defined first. namespace GSM { class RxBurst; + class RachInfo; class L3RRMessage; class CCCHLogicalChannel; class L3RequestReference; + class L3ImmediateAssignment; class Time; }; @@ -84,22 +85,18 @@ extern int GetPowerGamma(); extern unsigned GPRSDebug; extern void GPRSSetDebug(int value); extern void GPRSNotifyGsmActivity(const char *imsi); +class TBF; +extern bool gprsPageCcchSetTime(TBF *tbf, GSM::L3ImmediateAssignment *iap, unsigned afterFrame); +extern GSM::L3ImmediateAssignment *makeSingleBlockImmediateAssign(GSM::RachInfo *rip, unsigned afterFrame); // Hook into CLI/CLI.cpp:Parser class for GPRS sub-command. CommandLine::CLIStatus gprsCLI(int,char**,std::ostream&); int configGprsChannelsMin(); void gprsStart(); // External entry point to start gprs service. +void gprsStop(); }; // namespace GPRS -// GPRSLOG is no longer used outside the GPRS directory. -/**** - * #ifndef GPRSLOG - * #include "Logger.h" - * #define GPRSLOG(level) if (GPRS::GPRSDebug & (level)) \ - * Log(LOG_DEBUG).get() <<"GPRS,"<<(level)<<":" - * #endif -***/ #endif diff --git a/GPRS/GPRSInternal.h b/GPRS/GPRSInternal.h index bde8c90..9321249 100644 --- a/GPRS/GPRSInternal.h +++ b/GPRS/GPRSInternal.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -115,7 +114,9 @@ namespace GPRS { #undef GPRSLOG #endif // 6-18-2012: If someone sets Log.Level to DEBUG, show everything. -#define GPRSLOG(level) if (GPRS::GPRSDebug & (level) || IS_LOG_LEVEL(DEBUG)) \ +// #define GPRSLOG(level) if (GPRS::GPRSDebug & (level) || IS_LOG_LEVEL(DEBUG)) +// Don't show all GPRS logging in normal debug level. Log output is so high calls can't be processed 05/09/14 SVG +#define GPRSLOG(level) if (GPRS::GPRSDebug & (level))\ _LOG(DEBUG) <<"GPRS"<<(level)<<","< #include "GPRSInternal.h" @@ -92,6 +92,7 @@ struct ExtDyn { } } extDyn; + // Get the number quietly. // Use this for debug options we dont need the user to see. int configGetNumQ(const char *name, int defaultvalue) @@ -131,11 +132,13 @@ void GPRSSetDebug(int value) bool GPRSConfig::IsEnabled() { - // BEGINCONFIG - // 'GPRS.Enable',1,0,0,'Enable GPRS service: 0 or 1. If enabled, GPRS service is advertised in the C0T0 beacon, and GPRS service may be started on demand. See also GPRS.Channels.*' + // 'GPRS.Enable',1,0,0,'Enable GPRS service: 0 or 1. If enabled, GPRS service is advertised in the C0T0 beacon, + // and GPRS service may be started on demand. See also GPRS.Channels.*' // ENDCONFIG - if (gConfig.getNum("GPRS.Enable")) return true; + if (gConfig.getNum("GPRS.Enable") && GPRS::configGprsChannelsMin() > 0) { + return true; + } return false; // nope } @@ -189,7 +192,7 @@ static unsigned T3192Codes[8] = { GPRSCellOptions_t::GPRSCellOptions_t() : mNMO(gConfig.getNum("GPRS.NMO")-1), // IE value is spec NMO - 1. //mNMO(1), // Dont let customers change this. - mT3168Code(gConfig.getNum("GPRS.CellOptions.T3168Code")), // T3168Code 5 => 5000msec + mT3168Code(gConfig.getNum("GPRS.CellOptions.T3168Code")), // T3168Code 5 => 3000msec mT3192Code(gConfig.getNum("GPRS.CellOptions.T3192Code")), // T3192Code 0 => 500msec. // Took this out of the gConfig options. // mDRX_TIMER_MAX(gConfig.getNum("GPRS.CellOptions.DRX_TIMER_MAX",0)), @@ -403,7 +406,7 @@ void L2MAC::macConfigInit() macChCongestionMax = gConfig.getNum("GPRS.Channels.Congestion.Timer") * RLCBlocksPerSecond; // database number specified in percent: macChCongestionThreshold = gConfig.getNum("GPRS.Channels.Congestion.Threshold") / 100.0; - macDownlinkPersist = gConfig.getNum("GPRS.Downlink.Persist"); + macDownlinkPersist = gConfig.getNum("GPRS.Downlink.Persist"); // (pat) We dont use this. static bool thisMessageHasBeenPrinted = false; if (macDownlinkPersist && !thisMessageHasBeenPrinted) { thisMessageHasBeenPrinted = true; @@ -416,8 +419,8 @@ void L2MAC::macConfigInit() if (macSingleStepMode) { // Set these to maximum values so we can single step the service loop // without these timers going off. - macMSIdleMax = macChIdleMax = 0x7fffffff; - macT3191Value = macT3193Value = 0x7fffffff; + macMSIdleMax = macChIdleMax = 0x7fffffff; // For single step mode + macT3191Value = macT3193Value = 0x7fffffff; // For single step mode } gFixIdleFrame = configGetNumQ("GPRS.FixIdleFrame",gFixIdleFrame); @@ -600,6 +603,7 @@ bool L2MAC::macFreeChannel() // The channel better not be in use. // Delete any tbfs using the channel. Detach any MSs using the channel. // Remove the channel from the list in use by GPRS. +// SVGDBG What about existing data void L2MAC::macForgetCh(PDCHL1FEC*pch) { pch->mchStop(); // TODO: This should set a timer before the channel goes back to RR use. @@ -870,6 +874,9 @@ PDCHL1FEC *L2MAC::macPickChannel() // We return success only if we have allocated at least one GSM channel // for GPRS service and started all the service threads. static void *macThreadFunc(void *arg); // forward decl + + +// Starts macThreadFunc bool L2MAC::macStart() { if (! GPRSConfig::IsEnabled()) return false; @@ -921,7 +928,7 @@ bool L2MAC::macStart() if (! macSingleStepMode) { macRunning = true; // set this first to avoid an unlikely race condition since // the lock above is released before thread starts. - macThread.start(macThreadFunc,this); + macThread.start(macThreadFunc,this); // macThreadFunc started here } GLOG(INFO) << "GPRS service thread started"; return true; @@ -934,7 +941,8 @@ void gprsStart() { gL2MAC.macStart(); } // in fact, we probably want to leave those alone for post-mortem examination. void L2MAC::macStop(bool channelstoo) { - ScopedLock lock(macLock); // prevents a RACH from interrupting us. + // (pat) We must not set the lock or the serviceloop thread may not complete, creating deadlock. + //ScopedLock lock(macLock); // prevents a RACH from interrupting us. if (macRunning) { macStopFlag = true; macThread.join(); @@ -949,6 +957,8 @@ void L2MAC::macStop(bool channelstoo) GPRSLOG(1) << "macStop successful\n"; } +// External entry point. +void gprsStop() { gL2MAC.macStop(false); } L1UplinkReservation::L1UplinkReservation() @@ -1053,83 +1063,6 @@ RLCBSN_t L1UplinkReservation::makeReservationInt( return -1; // Abject failure. } - -// We need to make a block uplink reservation on CCCH, so we need to know exactly -// when the AGCH message is going to be transmitted. -// DAB GPRS - That will happen if the CCCH just came out of an idle period. -// DAB GPRS - A better way to fix this is to call AGCH->resync() -// DAB GPRS - inside of getNextMsgSendTime(). -// pat - Done, and it worked. - -// 12 blocks is one 52-multiframe. For now, add an extra multframe -// to the AGCH load to make sure it is in the future. -// Update: This seems to be too far in the future for the Multitech Modem. -// TODO: This could be reduced down to a few blocks in the future. -// Update: 12 blocks in the future did not work, trying 24. Now trying 36. -// Return the reservation time -// TODO: The DRX mode needs to know which channel the MS is on, based on IMSI. see GSM05.02 6.5 -RLCBSN_t L1UplinkReservation::makeCCCHReservation( - CCCHLogicalChannel *AGCH, - RLCBlockReservation::type type, TBF *tbf, RadData *rd, - bool forDRX, - MsgTransactionType mttype) // The sub-state that this reservation is for. -{ - mac_debug(); - // TODO: Add a separate GPRS qmax, since it seems like MS cant handle much delay. - int qmax = gConfig.getNum("GSM.CCCH.AGCH.QMax"); - if (qmax > 0 && AGCH->load()>(unsigned)qmax) { - if (type == RLCBlockReservation::ForRACH) { - GPRSLOG(1) << "RACH dropped due to AGCH congestion.\n"; - } else if (type == RLCBlockReservation::ForPoll) { - GLOG(INFO) << "CCCH congestion prevented Packet Downlink Assignment Message"; - } - return RLCBSN_t(-1); // invalid. - } - - RLCBSN_t resbsn; // BSN of the immediate assignment uplink reservation. - // advanceblocks is time between MS receiving reservation and reacting. - int advanceblocks = 4; // We will default to 4. - Time sendTime = AGCH->getNextMsgSendTime(); - unsigned fn = sendTime.FN(); - - if (1) { - // new way: - // Converting to an RLC block rounds down: - resbsn = FrameNumber2BSN(fn); - // We have to give the MS a little time to respond. - // GSM05.10 sec 6.11.1, and I quote: - // "If the MS is required to transmit a PACKET CONTROL ACKNOWLEDGEMENT subsequent - // to an assignment message (see 3GPP TS 04.60), the MS shall be ready to - // transmit and receive on the new assignment no later than the - // next occurrence of block B((x+2) mod 12) where block B(x) is - // radio block containing the PACKET CONTROL ACKNOWLEDGEMENT." - if (gConfig.defines("GPRS.MS.ResponseTime")) { - advanceblocks = gConfig.getNum("GPRS.MS.ResponseTime") + ExtraClockDelay; - } - - // We also have to add one to compensate for FrameNumber2BSN rounding down. - resbsn = resbsn + advanceblocks + 1; - if (forDRX) { - // FIXME TODO: Fix this total hack. - // We dont know which paging channel, so make sure the reservation is beyond any of them. - // The BS_PA_MFRMS number of 51-multiframes used for paging is - // advertised in the beacon as 2. - // TODO: This magic number should not be hard-coded here. - resbsn = resbsn + 22; // Should be enough. Note: Must be even for gFixIdleFrame. - } - } else { - // old way that worked: - - // For debugging, add a variable advance amount. - //int advanceframes = gConfig.getNum("GPRS.advanceframes",0); - advanceblocks = gConfig.getNum("GPRS.advanceblocks"); // This worked! - - resbsn = gBSNNext + (int32_t)(12 * AGCH->load() + advanceblocks); // This is in blocks. - } - mac_debug(); - return makeReservationInt(type,resbsn,tbf,rd,NULL,mttype); -} - // Make an RRBP reservation if possible. Return the rrbp (range 0 to 3), or -1 if failed. RLCBSN_t L1UplinkReservation::makeRRBPReservation(TBF *tbf, int *prrbp, // The RRBP result, 0..3 @@ -1217,7 +1150,7 @@ static int findNeedyUSF(PDCHL1FEC *pdch) { int usf; - GPRSLOG(512) << "findNeedyUSF start for "< 0 && AGCH->load()>(unsigned)qmax) { - // GPRSLOG(1) << "RACH dropped due to AGCH congestion.\n"; - //} - ***/ - // 5-24-2012: Each MS must have a single requestCh assigned. - // Since we dont know what MS rached us at this point, that - // implies we either have to use only one channel as the PAACH - // for all MS, or we would have to change the MS channel once - // we get a response back from it. For now, the former. - // When this was a random channel, all kinds of problems occurred. +// 2-2014: No longer uses makeCCCHReservation because we are called on demand and we know exactly when the CCCH is going out. +// WARNING: This runs in a different thread than the rest of GPRS. That means that makeReservationInt must be mutex protected. +L3ImmediateAssignment *makeSingleBlockImmediateAssign(RachInfo *rip, unsigned afterFrame) +{ + if (! GPRSConfig::IsEnabled()) { return NULL; } + LOG(DEBUG) <makeReservationInt(RLCBlockReservation::ForRACH, + afterBSN, // Reservation must be after this time. + NULL, // No TBF for this + &rip->mRadData, // We remember the timing advance. + NULL, // No RRBP + MsgTransNone); // Dont inform anyone else. - RLCBSN_t RBN; // check .valid for error. - switch (mRA & 0xf8) { - case 0x78: { // MS requests an uplink TBF. - // GSM04.18 sec3.5.2.1.3.1 says: if one phase access is requested, - // the network may grant either a one phase access or a single block - // packet access, which forces the MS to do a two phase access. - // In order to implement single-phase access, we would have to implement code - // to identify the MS using one of the contention resolution methods - // in GSM04.60 sec7.1.2.3, and detect failures via timeouts, making - // all the state machines more complicated, so we wont. - // So fall through to Single Phase Access... - - /*** - int TFI = chan->downlink()->allocateTFI(); - if (TFI >= 0) { - const GSM::L3RequestReference &reqref, // Specifies when the RACH burst occurred. - result = new L3ImmediateAssignment( - GSM::L3RequestReference(rachinfo->RA,rachinfo->when) - chan->uplink->packetChannelDescription(), - GSM::L3TimingAdvance(GetTimingAdvance(timingError)), true); - result->packetAssign()->setPacketUplinkAssignDynamic(TFI,CSNum); - return result; - } - // Else, unlikely case of all TFIs in use. Fall through to single phase access. - ****/ + if (! RBN.valid()) { + // Abject failure. This should never happen because the reservation controller can make + // reservations as far in advance as necessary. + GPRSLOG(1) << "serviceRACH failed to make a reservation at" <makeCCCHReservation(AGCH,RLCBlockReservation::ForRACH,NULL,&mRadData,0,MsgTransNone); - - Time now = gBTS.time(); - - if (! RBN.valid()) { - // Abject failure. This is probably due to AGCH congestion, - // and we printed one message already. - GPRSLOG(1) << "serviceRACH failed to make a reservation at" - <packetChannelDescription(), - GSM::L3TimingAdvance(GetTimingAdvance(mRadData.mTimingError)), - true,false); // tbf, downlink + // GSM 4.08 3.5.2.1.3.1: The immediate assignment message includes + // the frame when the RACH was received (in reqref) + // 11-22 NOTE! The downlink must be true. If you set it to false, + // the multitech modem simply fails to respond. + GSM::L3ImmediateAssignment *result = new L3ImmediateAssignment( + GSM::L3RequestReference(rip->mRA,rip->mWhen), + chan->packetChannelDescription(), + GSM::L3TimingAdvance(GetTimingAdvance(rip->mRadData.mTimingError)), + true,false); // tbf, downlink - // The immediate assignment has a TFI, but we do not set it for a single block assignment. - L3IAPacketAssignment *pa = result.packetAssign(); - pa->setPacketUplinkAssignSingleBlock(RBN.FN()); + // The immediate assignment has a TFI, but we do not set it for a single block assignment. + L3IAPacketAssignment *pa = result->packetAssign(); + pa->setPacketUplinkAssignSingleBlock(RBN.FN()); - // We dont know what MS we are talking to, so set the power params to global defaults. - // We could fiddle with the power gamma based on RSSI, but not implemented, - // and probably unnecessary. It is not really necessary to change these at all here. - pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); - - GPRSLOG(1) << "GPRS serviceRACH sending L3ImmediateAssignment:" << result; - AGCH->l2sendm(result); - break; - } - default: - devassert(0); - } -} - -void L2MAC::macServiceRachQ() -{ - RachInfo *rip; - while ((rip = macRachQ.readNoBlock())) { - GPRSLOG(1) << "GPRS: servicing RACHQ"; - LOGWATCHF("RACH %s\n",strrchr(timestr().c_str(),':')+1); - // TODO: Check the burst age again, in case start of GPRS service was delayed. - // If the RACH is too old, discard it. - - if (!macActiveChannels()) { - // No GPRS channels allocated; attempt to get one. - macAddChannel(); - } - if (!macActiveChannels()) { - GPRSLOG(1) << "GPRS: RACHQ failed to allocate channels"; - // Failed to allocate any channels at all. - // Toss the RACH. The MS will just have to try again later. - //macRachQ.write(rip); // We reordered the RACHs, but should not matter. - } else { - rip->serviceRach(); - } - delete rip; - } + // We dont know what MS we are talking to, so set the power params to global defaults. + // We could fiddle with the power gamma based on RSSI, but not implemented, + // and probably unnecessary. It is not really necessary to change these at all here. + pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); + GPRSLOG(1) << "GPRS serviceRACH sending L3ImmediateAssignment:" << *result; + return result; } // The RLC block just arrived on pdch. Figure out to which TBF it belongs and @@ -1835,7 +1678,6 @@ static void processResourceRequest( RLCBlockReservation::type restype, RadData &rd) { int accessType = rmsg->mAccessTypePresent ? (int) rmsg->mAccessType : 0; - //GPRSLOG(1) << "processResourceRequest"<str(); // TODO: This switch is not very interesting... switch (accessType) { // GSM04.60 table 11.2.16.2 @@ -1909,7 +1751,7 @@ static void processUplinkDummy(PDCHL1FEC *pdch, RLCMsgPacketUplinkDummyControlBl // The src block is the decoded uplink BitVector from the radio. // The BitVector we are passed was allocated; delete when we are finished. -static void processUplinkBlock(PDCHL1FEC *pdch, RLCRawBlock *src) +static void processUplinkBlock(PDCHL1FEC *pdch, RLCRawBlock *src) // SVGDBG process blocks { // Possibly handle timers. See: XCCHL1Decoder:handleGoodFrame(); // TODO ... @@ -2368,7 +2210,7 @@ static void serviceLoopSynchronize(bool firsttime) if (GPRSDebug) timeprev = timef(); } - +// SVGDBG // (pat) This is the main service loop for the entire GPRS subsystem. // It processes in time increments of one radio block time unit (4 or 5 GSM frames), about 18 ms. // We only use one thread, so no additional locks needed in the entire GPRS code @@ -2390,7 +2232,7 @@ static void serviceLoopSynchronize(bool firsttime) void L2MAC::macServiceLoop() { double starttime = 0; // useless initialization to shut up gcc. - GPRSLOG(16) << "macServiceLoop:" << LOGVAR(gBSNNext); + GPRSLOG(16) << "macServiceLoop:" << LOGVAR(gBSNNext); // SVGDBG if (GPRSDebug) { starttime = timef(); } mac_debug(); @@ -2398,7 +2240,6 @@ void L2MAC::macServiceLoop() // Step: Each incoming RACH will need a single block assignment. // We do this first because if no channels are assigned to GPRS, // this will allocate the first GPRS channel as a side effect. - macServiceRachQ(); GPRSLOG(16) << "macServiceLoop: after serviceRachQ"; // Step: Maybe add or free some radio channels. @@ -2469,6 +2310,8 @@ void L2MAC::macServiceLoop() } } + +// The main processing thread static void *macThreadFunc(void *arg) { gL2MAC.macRunning = true; @@ -2482,6 +2325,7 @@ static void *macThreadFunc(void *arg) ChIdleCounter = ChCongestionCounter = 0; // Lets start synced up on a 52-multiframe boundary. + // Remove or is this used for testing if (0) { // why bother? int offset = fnstart % 52; if (offset) { diff --git a/GPRS/MAC.h b/GPRS/MAC.h index 6806f0f..9684e88 100644 --- a/GPRS/MAC.h +++ b/GPRS/MAC.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -216,19 +215,7 @@ class USFList void usfDump(std::ostream&os); }; -struct RachInfo -{ - unsigned mRA; - const GSM::Time mWhen; - RadData mRadData; - - // Gotta love this language. - RachInfo(unsigned wRA, const GSM::Time &wWhen, RadData wRD) - : mRA(wRA), mWhen(wWhen), mRadData(wRD) - { RN_MEMCHKNEW(RachInfo) } - ~RachInfo() { RN_MEMCHKDEL(RachInfo) } - void serviceRach(); -}; +extern void serviceRach(); // There is only one of these. @@ -251,7 +238,7 @@ class L2MAC // are in use for RR connections, the as-yet-unserviced RACHs may queue up here. // When GPRS service resumes, we should disard RACHs that are too old. // Note that there could be multiple RACH for the same MS, a case we cannot detect. - InterthreadQueue macRachQ; + InterthreadQueue macRachQ; // We are doing a linear search through these lists, but there should only be a few of them. PDCHL1FECList_t macPDCHs; // channels assigned to GPRS. @@ -390,7 +377,7 @@ struct RLCBlockReservation : public Text2Str { RLCBSN_t mrBSN; // The block number that has been reserved. TBF *mrTBF; // tbf if applicable. (Not known for MS initiated RACH.) // TODO: Is it stupid to save mrRadData? We will get new data when the MS responds. - RadData mrRadData; // Saved from a RACH to be put in MS when it responds. + GSM::RadData mrRadData; // Saved from a RACH to be put in MS when it responds. static const char *name(RLCBlockReservation::type type); void text(std::ostream&) const; }; @@ -478,13 +465,12 @@ class L1UplinkReservation L1UplinkReservation(); - private: - RLCBSN_t makeReservationInt(RLCBlockReservation::type restype, RLCBSN_t afterBSN, - TBF *tbf, RadData *rd, int *prrbp, MsgTransactionType mttype); - public: + RLCBSN_t makeReservationInt(RLCBlockReservation::type restype, RLCBSN_t afterBSN, + TBF *tbf, GSM::RadData *rd, int *prrbp, MsgTransactionType mttype); + RLCBSN_t makeCCCHReservation(GSM::CCCHLogicalChannel *AGCH, - RLCBlockReservation::type type, TBF *tbf, RadData *rd, bool forDRX, MsgTransactionType mttype); + RLCBlockReservation::type type, TBF *tbf, GSM::RadData *rd, bool forDRX, MsgTransactionType mttype); RLCBSN_t makeRRBPReservation(TBF *tbf, int *prrbp, MsgTransactionType ttype); // Get the reservation for the specified block timeslot. @@ -493,7 +479,7 @@ class L1UplinkReservation RLCBlockReservation *getReservation(RLCBSN_t bsn); void clearReservation(RLCBSN_t bsn, TBF *tbf); - RLCBlockReservation::type recvReservation(RLCBSN_t bsn, TBF**restbf, RadData *prd,PDCHL1FEC *ch); + RLCBlockReservation::type recvReservation(RLCBSN_t bsn, TBF**restbf, GSM::RadData *prd,PDCHL1FEC *ch); void dumpReservations(std::ostream&os); }; diff --git a/GPRS/MSInfo.cpp b/GPRS/MSInfo.cpp index b12d530..ba112b1 100644 --- a/GPRS/MSInfo.cpp +++ b/GPRS/MSInfo.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "MSInfo.h" #include "TBF.h" #include "FEC.h" @@ -510,6 +511,8 @@ bool MSInfo::msAssignChannels2(int maxdown, int maxup, int sum) // and do 3x2 or 2x3 instead of 3x1 or 1x3? // The way the user does that is ask for 4x2 or 2x4, and they'll get // 4x1 or 1x4 if avail, else 3x2 or 2x3. + // (pat) Update: We settled on using only 3-down/2-up, or 2-down/2-up if we dont have 3 adjacent ch; + // for these cases the additional bidir channel can be on either side of the pacch. (Which fact is encapsulated in the msTrySlots code.) int up21 = maxup >= 2 ? 2 : 1; int down21 = maxdown >= 2 ? 2 : 1; if (maxdown >= 4) { @@ -692,6 +695,17 @@ RROperatingMode::type MSInfo::getRROperatingMode() } } +// This is used from the CCCH service code to find the TBF that requested service. +// It is possible that the TBF was destroyed while waiting for the paging opportunity, in which case return NULL. +//TBF *findTBFForCCCH() +//{ +// RN_MS_FOR_ALL_TBF(this,tbf) { +// // Gosh, the tbf could be destroyed right now. +// if (tbf->mtDir == RLCDir::Down && tbf->mtGetState() == TBFState::DataWaiting1) { return tbf; } +// } +// return NULL; +//} + // Count how many TBFs exist in the specified direction (which may be Either) // are in the specified TbfMacroState. diff --git a/GPRS/MSInfo.h b/GPRS/MSInfo.h index 4d9ec11..35c375b 100644 --- a/GPRS/MSInfo.h +++ b/GPRS/MSInfo.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -29,8 +28,8 @@ #define CASENAME(x) case x: return #x; +namespace GSM { struct RadData; } namespace GPRS { -struct RadData; typedef RList PDCHL1DownlinkList_t; typedef RList PDCHL1UplinkList_t; @@ -140,7 +139,7 @@ struct SignalQuality { Statistic msILevel; Statistic msRXQual; Statistic msSigVar; - void setRadData(RadData &rd); + void setRadData(GSM::RadData &rd); void setRadData(float wRSSI,float wTimingError); void dumpSignalQuality(std::ostream &os) const; }; @@ -218,12 +217,16 @@ struct MSStat { GprsTimer msTalkUpTime; // When the MS last talked to us. GprsTimer msTalkDownTime; // When we last talked to the MS. + // pat 3-2014: Use msDownlinkAttempts to detect that a high-side initiated downlink failed. + UInt_z msDownlinkAttempts; + // Called at every uplink/downlink communication from MS. // These are not strictly statistics because they are also used to kill a non-responsive MS. // These timers differ from the persistent mode timers in that those // count only data, and these count anything. - void talkedUp(bool doubleCount=false) { msTalkUpTime.setNow(); if (!doubleCount) {msTrafficMetric++;} } - void talkedDown() { msTalkDownTime.setNow(); msTrafficMetric++; } + // SVGDBG this means we are got something from the phone + void talkedUp(bool doubleCount=false) { msDownlinkAttempts=0; msTalkUpTime.setNow(); if (!doubleCount) {msTrafficMetric++;} } + void talkedDown() { msDownlinkAttempts=0; msTalkDownTime.setNow(); msTrafficMetric++; } // Dump all except traffic metric. void msStatDump(const char *indent,std::ostream &os); @@ -495,7 +498,7 @@ class MSInfo : public SGSN::MSUEAdapter, public SignalQuality, public MSStat RLCBSN_t msLastUsfGrant; // Called when a USF is granted for this MS. - // If penalize, if the MS does not answer we kill of the tbf. + // If penalize, if the MS does not answer we kill off the tbf. void msCountUSFGrant(bool penalize); // Incoming downlink data queue. diff --git a/GPRS/Makefile.am b/GPRS/Makefile.am index 4fe2e25..14fb5e2 100644 --- a/GPRS/Makefile.am +++ b/GPRS/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -23,7 +24,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) #AM_CXXFLAGS = -O0 -g -CXXFLAGS = -g -O0 +CXXFLAGS = -g -O2 noinst_LTLIBRARIES = libGPRS.la diff --git a/GPRS/MsgBase.cpp b/GPRS/MsgBase.cpp index ae7cf25..fe90b97 100644 --- a/GPRS/MsgBase.cpp +++ b/GPRS/MsgBase.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include #include "MsgBase.h" diff --git a/GPRS/MsgBase.h b/GPRS/MsgBase.h index 1aa15b7..b37eefb 100644 --- a/GPRS/MsgBase.h +++ b/GPRS/MsgBase.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/RLC.cpp b/GPRS/RLC.cpp index 5931ab8..e3e7149 100644 --- a/GPRS/RLC.cpp +++ b/GPRS/RLC.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #include "GPRSRLC.h" #include "GSMCommon.h" diff --git a/GPRS/RLCEngine.cpp b/GPRS/RLCEngine.cpp index 35ba9f8..0cbd78c 100644 --- a/GPRS/RLCEngine.cpp +++ b/GPRS/RLCEngine.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + // File contains RLCUpEngine and RLCDownEngine, which are classes that extend // TBF (Temporary Block Flow) to do the data block uplink/downlink movement functions for a TBF. diff --git a/GPRS/RLCEngine.h b/GPRS/RLCEngine.h index 811e90c..f50bfc7 100644 --- a/GPRS/RLCEngine.h +++ b/GPRS/RLCEngine.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/RLCHdr.h b/GPRS/RLCHdr.h index a92beea..a56f35e 100644 --- a/GPRS/RLCHdr.h +++ b/GPRS/RLCHdr.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -27,6 +26,7 @@ #include "MsgBase.h" #include "GPRSInternal.h" #include "MemoryLeak.h" +#include "GSMTransfer.h" // For RadData #define CASENAME(x) case x: return #x; @@ -161,19 +161,11 @@ struct MACUplinkHeader // 8 bits. See GSM04.60sec10.3.2 } #endif -struct RadData { - bool mValid; - float mRSSI; - float mTimingError; - RadData() { mValid = false; } - RadData(float wRSSI, float wTimingError) : mValid(true),mRSSI(wRSSI),mTimingError(wTimingError) {} -}; - // An incoming block straight from the decoder. struct RLCRawBlock { RLCBSN_t mBSN; // The BSN corresponding to the GSM FN of the first received burst. - RadData mRD; + GSM::RadData mRD; BitVector mData; MACUplinkHeader mmac; ChannelCodingType mUpCC; @@ -190,7 +182,7 @@ struct RLCRawBlock { mmac.parseMAC(mData); // Pull the MAC header out of the BitVector. mUpCC = cc; assert(mData.isOwner()); - mRD = RadData(wRSSI,wTimingError); + mRD = GSM::RadData(wRSSI,wTimingError); } #endif diff --git a/GPRS/RLCMessages.cpp b/GPRS/RLCMessages.cpp index f2eca45..d9720e7 100644 --- a/GPRS/RLCMessages.cpp +++ b/GPRS/RLCMessages.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -16,6 +15,8 @@ /**@file GPRS L2 RLC Messages, from GSM 04.60 Section 11 */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + //#include #include "Defines.h" //#include "GSMCommon.h" @@ -153,7 +154,9 @@ void RLCMsgPowerControlParametersIE::setFrom(TBF *tbf) } -/** GSM 04.60 11.2 */ +/** GSM 04.60 11.2 + * Process/parse all uplink messages + * */ RLCUplinkMessage* RLCUplinkMessageParse(RLCRawBlock *src) { RLCUplinkMessage *result = NULL; diff --git a/GPRS/RLCMessages.h b/GPRS/RLCMessages.h index e8547ce..46c3542 100644 --- a/GPRS/RLCMessages.h +++ b/GPRS/RLCMessages.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -84,7 +83,7 @@ class RLCMsgDownlinkIE } }; -// The RLCDownlink and RLCUplink Messagesc are RLC Control messages - +// The RLCDownlink and RLCUplink Messages are RLC Control messages - // they are not L3 Messages as defined in GSM 04.18. // These messages are used at the RLC/MAC layer to control the PDCH channels // assigned for packet (GPRS) use. Their primary use is to transfer diff --git a/GPRS/RList.h b/GPRS/RList.h index b3e6cf1..8f6672c 100644 --- a/GPRS/RList.h +++ b/GPRS/RList.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/ScalarTypes.h b/GPRS/ScalarTypes.h index dafab45..48fa2f9 100644 --- a/GPRS/ScalarTypes.h +++ b/GPRS/ScalarTypes.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GPRS/TBF.cpp b/GPRS/TBF.cpp index f9cf7c9..330138b 100644 --- a/GPRS/TBF.cpp +++ b/GPRS/TBF.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging + #define TBF_IMPLEMENTATION 1 #include "MSInfo.h" #include "TBF.h" @@ -21,6 +22,9 @@ #include "RLCMessages.h" #include "BSSG.h" #include "RLCEngine.h" +#include +#include +using namespace Control; namespace GPRS { @@ -38,6 +42,7 @@ static int configTbfRetry() { bool MsgTransaction::mtMsgPending(MsgTransactionType mttype) { + ScopedLock lock(mtLock); // The multiple tests here are overkill. return mtMsgExpectedBits.isSet(mttype) && mtExpectedAckBSN[mttype].valid() && @@ -47,6 +52,7 @@ bool MsgTransaction::mtMsgPending(MsgTransactionType mttype) // Is any message pending? bool MsgTransaction::mtMsgPending() { + ScopedLock lock(mtLock); // We are not 'waiting' until the expectedAckBSN is valid, because: // 1. the first time through the TBF state machine's logic, // it tests this function before the msgAckTime has been set. @@ -344,6 +350,7 @@ uint32_t TBF::mtGetTlli() return tlli; } +#if 0 // 7-25: Neither the Blackberry nor Multitech like this message. // Maybe because I include the downlink assignment when there is no downlink TBF? static bool sendTimeslotReconfigure( @@ -370,6 +377,7 @@ static bool sendTimeslotReconfigure( send1MsgFrame(tbf,msg,2,MsgTransReassign,&tbf->mtReassignCounter); } } +#endif static bool sendAssignmentPacch( PDCHL1FEC *pacch, // The PACCH channel. @@ -432,121 +440,114 @@ static bool sendAssignmentPacch( } } -void sendAssignmentCcch( +// 2-14-2014: Create the L3ImmediateAssignment to assign a downlink TBF to this MS. +// We create the L3ImmediateAssignment in the main gprs thread so we dont have to worry about locking anything, +// then just before it is sent by the CCCH controller, gprsPageCcchSetTime is called to set the time. +// This routine does not care whether the MS is in DRX mode or not; the caller tracks the drx timer +// and sends the result on either PCH or generic CCCH depending on drx mode, but we dont yet track it very well. +// GSM 44.60 5.5.1.5 Describes the conditions under which DRX should be applied. +// The MS sends the DRX info to the SGSN in the Attach message. +// None of that implemented yet. +L3ImmediateAssignment *gprsPageCcchStart( PDCHL1FEC *pacch, // The PACCH channel. - TBF *tbf, - std::ostream *os) // for testing - if set, print out the message instead of sending it. + TBF *tbf) { - static GSM::CCCHLogicalChannel *ourAGCH = 0; + assert(tbf->mtDir == RLCDir::Down); MSInfo *ms = tbf->mtMS; - // Send a message on CCCH. - // TODO FIXME: - // GSM 44.60 5.5.1.5 Describes the conditions under which DRX should be applied. - // The MS sends the DRX info to the SGSN in the Attach message. - // None of that implemented yet. - bool drxmode = false; - // BUG: The returned AGCH time is not monotonically increasing. The bug traces all - // the way back down that the getAGCH call is not, in fact, returning the next one. - // I am guessing that the choice of CCCH has a bug that does not really - // return the next CCCH slot because of the way the idle frames are handled. - // Anyway, this screws up sendAssignment; see todo.txt. - // To try to fix this, we will use just one AGCH, ever, to try to get our - // messages delivered in order. - // TODO: We need to use ourAGCH for unattached MS, - // but we could select a different AGCH for every attached MS. - if (ourAGCH == 0) { ourAGCH = gBTS.getAGCH(); } + // The RequestReference is not used for this type of downlink assignment, + // which contains TLLI instead; we have to set RequestReference to a number that + // cannot possibly be confused with any valid value, ie, somewhere in the future, + // at the time this is sent. + RLCBSN_t impossibleBSN = gBSNNext + 10000; + //RLCBSN_t impossibleBSN = resBSN + 1000; + GSM::Time impossible(BSN2FrameNumber(impossibleBSN),0); // TN not used. + // The downlink bit documentation is goofed up in GSM 4.08/4.18: They clarified it + // in GSM 44.018 sec 10.5.2.25b: This bit is 1 only for downlink TBFs. + L3ImmediateAssignment *result = new L3ImmediateAssignment( + L3RequestReference(0,impossible), + pacch->packetChannelDescription(), + GSM::L3TimingAdvance(ms->msGetTA()), + true,true); // for_tbf, downlink - // Make a reservation for the poll response. - RLCBSN_t resBSN = 0; - // TODO: Fix this. The DRX mode is sent to us by the MS I think in the - // initial attach message which went to the SGSN. - if (gFixDRX && tbf->mtCcchAssignCounter >= (unsigned)gFixDRX) { - // We may have lost contact with the MS because it is in DRX mode. - // Send the message on all 3 AGCH channels twice - we need - // to saturate both 51 multi-frames because there - // are 6 paging channels. And we just hope no other - // AGCH messages are in the way. - // We need to increase the reservation to make sure it is beyond - // the CCCH in the second 51 multiframe. - drxmode = true; - } - GSM::CCCHLogicalChannel *currentAGCH = drxmode ? gBTS.getPCH(0) : ourAGCH; + L3IAPacketAssignment *pa = result->packetAssign(); + // DEBUG: I tried taking out power: + // 12-16: Put power back in: + pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); - resBSN = pacch->makeCCCHReservation(currentAGCH,RLCBlockReservation::ForPoll,tbf,NULL,drxmode,MsgTransAssign1); - if (! resBSN.valid()) { - // We will try again later. - return /*false*/; // We did not use the packet channel downlink. - } - // TODO: mtSetAckExpected function should be moved into the makeReservation code. - // We have to wait for the time whether we sent the poll or not. - tbf->mtSetAckExpected(resBSN,MsgTransAssign1); + pa->setPacketDownlinkAssign(tbf->mtGetTlli(), tbf->mtTFI, + tbf->mtChannelCoding(), tbf->mtUnAckMode, 1); - // The RequestReference is not used for this type of downlink assignment, - // which contains TLLI instead; we have to set RequestReference to a number that - // cannot possibly be confused with any valid value, ie, somewhere in the future, - // at the time this is sent. - RLCBSN_t impossibleBSN = resBSN + 1000; - GSM::Time impossible(BSN2FrameNumber(impossibleBSN),0); // TN not used. - // The downlink bit documentation is goofed up in GSM 4.18: They clarified it - // in GSM 44.018 sec 10.5.2.25b: This bit is 1 only for downlink TBFs. - L3ImmediateAssignment amsg( - L3RequestReference(0,impossible), - pacch->packetChannelDescription(), - GSM::L3TimingAdvance(ms->msGetTA()), - true,tbf->mtDir == RLCDir::Down); // tbf, downlink - L3IAPacketAssignment *pa = amsg.packetAssign(); - // DEBUG: I tried taking out power: - // 12-16: Put power back in: - pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); - if (gFixIAUsePoll) { - pa->setPacketPollTime(resBSN.FN()); - } - if (tbf->mtDir == RLCDir::Up) { - // This assignment type is only for 1-phase access, which we do not support. - // An uplink immediate assignment can only be in response to a RACH - // and is identified by the request reference. Instead we send a - // one-block uplink assignment to get a packet resource request, - // then do the uplink TBF on PACCH based on that. - devassert(0); - // There is only one USF (ie, no multi-slot). - //int usf = ms->msUSFs[pacch->uplink()->TN()]; - int usf = ms->msUSFs[pacch->TN()]; - devassert(USF_VALID(usf)); - pa->setPacketUplinkAssignDynamic(tbf->mtTFI, tbf->mtChannelCoding(), usf); - } else { - pa->setPacketDownlinkAssign(tbf->mtGetTlli(), tbf->mtTFI, - tbf->mtChannelCoding(), tbf->mtUnAckMode, 1); - } - - if (os) { - amsg.text(*os); - } else { - GPRSLOG(1) << "GPRS sendAssignment "<load()) << amsg; - LOGWATCHF("%s CCCH load=%d\n", tbf->tbfid(1), currentAGCH->load()); - tbfDumpAll(); - tbf->mtAssignCounter++; - - // FIXME TODO: - if (drxmode) { - // Send the message on a paging channel. - // This code assumes the paging channel setup described - // in the class L3ControlChannelDescription. - // 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->l2sendm(amsg); - currentAGCH->l2sendm(amsg); - } else { - currentAGCH->l2sendm(amsg); // send() takes care of converting it to a BitVector. - } - tbf->mtCcchAssignCounter++; - } + //GPRSLOG(1) << "GPRS sendAssignment "<load()) << *result; + LOGWATCHF("%s\n", tbf->tbfid(1)); + tbfDumpAll(); + tbf->mtAssignCounter++; + tbf->mtCcchAssignCounter++; + return result; } +void sendAssignmentCcch( + PDCHL1FEC *pacch, // The PACCH channel where the MS will be directed to send its answer. + TBF *tbf, // The TBF that wants to initiate a downlink transfer to the MS. + std::ostream *os) // not supported in C_RELEASE. +{ + MSInfo *ms = tbf->mtMS; + string imsi = ms->sgsnFindImsiByHandle(ms->msGetHandle()); + if (imsi.empty()) { + LOG(ERR) << "Attempt to send TBF assignment on CCCH to MS without an IMSI?"; + return; // This is a disaster. I dont know what is going to happen to this TBF; will it ever timeout? Just hope this never happens. + } + // Set an ack time far far in the future to keep the caller (mtServiceDownlink) from trying + // to start another assignment. This ack time will be updated to the real value by gprsPageCcchSetTime(). + tbf->mtSetAckExpected(gBSNNext + 1000,MsgTransAssign1); + // We allocate a NewPagingEntry for convenience of queuing this request, but since this is not a real page the first argument (ChannelType) is unused. + NewPagingEntry *npe = new NewPagingEntry(GSM::PSingleBlock1PhaseType,imsi); + npe->mGprsClient = tbf; + npe->mImmAssign = gprsPageCcchStart(pacch,tbf); + // We dont support DRX yet, so just set the DRX time to the current frame, ie, assume all MS are in DRX if they are on CCCH. + npe->mDrxBegin = gBTS.time().FN(); + pagerAddCcchMessageForGprs(npe); +} + +// WARNING: This is called from the CCCH thread. +// Make the reservation and set the poll time in the L3ImmediateAssignment. +// Return true on success or false on failure, in which case the caller should try again later. +bool gprsPageCcchSetTime(TBF *tbf, L3ImmediateAssignment *iap, unsigned afterFrame) +{ + LOG(DEBUG) <makeReservationInt(RLCBlockReservation::ForPoll, // We are requesting a confirming poll from the MS. + afterBSN, // Reservation must be after this time. + tbf, // Tells which tbf to inform when the poll arrives. Also used for statistics gathering. + NULL, // RSSI, TimingAdvance not needed. + NULL, // No RRBP + MsgTransAssign1); // Dont inform anyone else. + + if (! resBSN.valid()) { + // Abject failure. This should never happen because the reservation controller can make + // reservations as far in advance as necessary. + GPRSLOG(1) << "serviceRACH failed to make a reservation at" <mtSetAckExpected(resBSN,MsgTransAssign1); + + if (gFixIAUsePoll) { + L3IAPacketAssignment *pa = iap->packetAssign(); + pa->setPacketPollTime(resBSN.FN()); + } + return true; +} + + // Return true if we send a block on the downlink. // NOTE: The L3ImmediateAssignment message can not assign multislot assignments. // NOTE: If MS T3204 (GSM04.08) 1 second, started when MS sends RACH, expires before @@ -618,6 +619,8 @@ bool sendAssignment( } if (tbf->mtAssignmentOnCCCH) { + // (pat) 2-2014: Changing the CCCH from push to pull, so we send a message to the CCCH code and + // it will call us back when the CCCH is available. sendAssignmentCcch(pacch,tbf,os); return false; // We did not use the packet channel downlink. } else { @@ -626,6 +629,7 @@ bool sendAssignment( } } +# static bool sendTA(PDCHL1Downlink *down,TBF *tbf) { RLCMsgPacketPowerControlTimingAdvance *tamsg = new RLCMsgPacketPowerControlTimingAdvance(tbf); @@ -638,6 +642,7 @@ static bool sendTA(PDCHL1Downlink *down,TBF *tbf) return down->send1MsgFrame(tbf,msg,2,MsgTransTA,NULL); } + bool MSInfo::msCanUseDownlinkTn(unsigned tn) { PDCHL1Downlink *down; @@ -820,6 +825,8 @@ void MSInfo::msRestart() } #endif + +// Writes a block of data to the MS static RLCDownEngine *createDownlinkTbf(MSInfo *ms, DownlinkQPdu *dlmsg, bool isRetry, ChannelCodingType codingMax) { ms->msStalled = 0; @@ -828,7 +835,7 @@ static RLCDownEngine *createDownlinkTbf(MSInfo *ms, DownlinkQPdu *dlmsg, bool is // Wrong! Removed 4-24 : ms->msT3193.reset(); // At this point the RLCEngine takes charge of the dlmsg memory. TBF *tbf = engine->getTBF(); - tbf->mtTlli = dlmsg->mTlli; // Dont think mtTlli is used in a downlink TBF. + tbf->mtTlli = dlmsg->mTlli; // Don't think mtTlli is used in a downlink TBF. tbf->mtChannelCodingMax = codingMax; //tbf->mtIsRetry = isRetry; engine->engineWriteHighSide(dlmsg); @@ -838,6 +845,7 @@ static RLCDownEngine *createDownlinkTbf(MSInfo *ms, DownlinkQPdu *dlmsg, bool is // Service this MS, called from the service loop every RLCBSN time. // Counters and Timers defined in GSM04.60 sec 13. +// Goes through msDownlinkQueue messages void MSInfo::msService() { // After the last downlink TBF, the MS waits until this timer expires @@ -853,10 +861,12 @@ void MSInfo::msService() if (msIsSuspended()) {return;} if (msTBFs.size()) { - msIdleCounter = 0; + msIdleCounter = 0; // Not idle } else { - if (++msIdleCounter > gL2MAC.macMSIdleMax) { - msDelete(); + // List empty + if (++msIdleCounter > gL2MAC.macMSIdleMax) { // default 600 seconds * blocks per second + // LOG(DEBUG) << "Exceeded macMSIdleMax: " << gL2MAC.macMSIdleMax; + msDelete(); // SVGDBG This removes an MS from gL2MAC return; } } @@ -909,27 +919,43 @@ void MSInfo::msService() // If there is a downlink message and this MS does not have any downlink TBFs running, // create a new TBF. - while (msDownlinkQueue.size()) { + //LOG(DEBUG) << "msDownlinkAttempts: " << msDownlinkAttempts; SVGDBG + while (msDownlinkQueue.size()) { // We have something to send + //LOG(DEBUG) << "Processing msDownlinkQueue message attempt: " << msDownlinkAttempts; SVGDBG // We will not start a new downlink TBF as long as there is any kind // of downlink TBF. // Formerly, (if 0==StallOnlyForActiveTBF) we also stalled for dead TBFS // (indicating the MS is probably unreachable) but that tended to kill off // active TBFs when any one died for mysterious reasons, so I turned it off. - // 6-24-2012 UPDATE: I am going to reset StallOnlyForActive because we dont + // 6-24-2012 UPDATE: I am going to reset StallOnlyForActive because we don't // have bugs and we now use dead tbfs to legitimately block downlinks until expiry. bool stallOnlyForActiveTBF = configGetNumQ("GPRS.TBF.StallOnlyForActive",0); TBF *blockingtbf; - if (! msCountTBF2(RLCDir::Down,stallOnlyForActiveTBF?TbfMActive:TbfMAny,&blockingtbf)) { - DownlinkQPdu *dlmsg = msDownlinkQueue.read(); + // Make sure there is something to process + if (! msCountTBF2(RLCDir::Down,stallOnlyForActiveTBF?TbfMActive:TbfMAny,&blockingtbf)) { // Look in list of TBF's + + // (pat 3-2014) The 3 is just made up; allows the MS to ride out a simple bad connection. + // The MS may also be non-responsive due to temporary suspension, which is not supported yet. + if (msDownlinkAttempts >= 3) { + // We already tried this and failed. + msStop(RLCDir::Either,MSStopCause::NonResponsive,TbfNoRetry,gL2MAC.macT3169Value); + msDelete(); + // LOG(DEBUG) << "Greater than 3 attempts sending TBF count: " << msDownlinkAttempts; SVGDBG + // SVGDBG should msDownlinkAttempts be reset here + return; + } + // Send downlink message + DownlinkQPdu *dlmsg = msDownlinkQueue.read(); // Gets entry from top of the queue // Because the message is queued for this MS, it means the tlli // is equal to either msTlli or msOldTlli. The SGSN tells us which // one to use. Make sure it is the current one. // The tlli is changed on the next message after an attach procedure. msChangeTlli(dlmsg->mTlli); - createDownlinkTbf(this,dlmsg,false,ChannelCodingMax); + createDownlinkTbf(this,dlmsg,false,ChannelCodingMax); // Write a block in msService + msDownlinkAttempts++; } else { // This code just prints a nice message: - devassert(blockingtbf); + devassert(blockingtbf); // SVGDBG fix if this is a crash // stalltype is 1 for stalled by active, 2 for stalled by dead tbf. unsigned stalltype = blockingtbf->isActive() ? 1 : 2; if (stalltype != msStalled) { @@ -938,7 +964,7 @@ void MSInfo::msService() msStalled = stalltype; } } - break; + break; // SVGDBG msDownlinkAttempts may not work because msDownlinkAttempts can get reset in macServiceLoop } // If the MS has a delayed request an uplink TBF, start it up. @@ -960,6 +986,7 @@ void MSInfo::msService() // TODO: Should be TBF.NonResponsivve. int timerVal = gConfig.getNum("GPRS.Timers.MS.NonResponsive"); // value of 0 disables. if (timerVal > 0 && msTalkUpTime.elapsed() > timerVal) { + // LOG(DEBUG) << "GPRS.Timers.MS.NonResponsive exceeded"; // SVGDBG msStop(RLCDir::Either,MSStopCause::NonResponsive,TbfNoRetry,gL2MAC.macT3169Value); } } @@ -970,6 +997,7 @@ void MSInfo::msService() // The TBF ends either in mtFinishSuccess or mtCancel. void TBF::mtFinishSuccess() { + // LOG(DEBUG) << "mtFinishSuccess"; // SVGDBG mtMS->msT3191.reset(); //GPRSLOG(1) << "@@@ok" << this <<" dir="<tbfDump(false)<msDeprecated) { // No retry for MSInfo that has been replaced by some other TLLI. // We check again because deprecated may have changed between the time this TBF @@ -1018,13 +1047,14 @@ void TBF::mtRetry() dlmsg = oldengine->mDownlinkPdu; oldengine->mDownlinkPdu = NULL; } else { - dlmsg = mtMS->msDownlinkQueue.readNoBlock(); + dlmsg = mtMS->msDownlinkQueue.readNoBlock(); // Aka pop_front } if (dlmsg) { // Not possible to be NULL, but be safe. if (dlmsg->mDlTime.elapsed() < gConfig.getNum("GPRS.TBF.Expire")) { - createDownlinkTbf(mtMS, dlmsg, true, chCoding); + createDownlinkTbf(mtMS, dlmsg, true, chCoding); // In mtRetry } else { // Too old. Give up. + // LOG(DEBUG) << "Exceeded GPRS.TBF.Expire time"; //SVGDBG delete dlmsg; } } @@ -1039,6 +1069,7 @@ void TBF::mtRetry() void TBF::mtCancel(MSStopCause::type cause, TbfCancelMode release) // When to release and whether to retry. { + // LOG(DEBUG) << "mtCancel mtState: " << mtState; //SVGDBG // Clear out any reservation, in case the reservation does try to notify // this tbf, which will no longer exist. This is probably overkill, // because either the reservations were answered and cleaned up and they @@ -1129,6 +1160,7 @@ void TBF::mtCancel(MSStopCause::type cause, // assigned to its MS yet. void TBF::mtServiceUnattached() { + LOG(INFO) << "mtServiceUnattached mtState: " << mtState; // SVGDBG Make DEBUG // Dead tbfs are attached, so dont test this flag. //if (mtAttached) return; switch (mtState) { @@ -1138,11 +1170,13 @@ void TBF::mtServiceUnattached() // Fix it so we wont see this message again. mtCancel(MSStopCause::CauseUnknown, TbfNoRetry); //mtState = TBFState::Dead; + LOG(INFO) << "mtServiceUnattached Unused"; // SVGDBG Make DEBUG return; case TBFState::DataReadyToConnect: if (mtAttach()) { mtSetState(TBFState::DataWaiting1); } + LOG(INFO) << "mtServiceUnattached DataReadyToConnect"; // SVGDBG Make DEBUG return; case TBFState::Deleting: casedeleting: @@ -1150,19 +1184,28 @@ void TBF::mtServiceUnattached() // Wait until the response must have been received, // to make sure it gets delivered to the right TBF. // This is overkill - whoever put this in Deleting state already did this. + LOG(INFO) << "mtServiceUnattached Deleting mtMsgPending"; // SVGDBG Make DEBUG return; } mtDelete(); // Cleans up and deletes. + LOG(INFO) << "mtServiceUnattached Deleting mtDelete"; // SVGDBG Make DEBUG return; case TBFState::Dead: // A dead TBF is normally still "attached", ie, hanging onto its resources // to prevent someone else from using them, until its killtime expires. // But the MS may lose its channel assignment (eg, due to RACH) // so this case may not be handled by the attached tbf code. + LOG(INFO) << "mtServiceUnattached Dead"; // SVGDBG Make DEBUG devassert(mtDeadTime.valid()); - if (mtDeadTime.expired()) { mtDetach(); goto casedeleting; } + if (mtDeadTime.expired()) { + mtDetach(); + LOG(INFO) << "mtServiceUnattached expired"; // SVGDBG Make DEBUG + goto casedeleting; + } + return; + default: + LOG(INFO) << "mtServiceUnattached default"; // SVGDBG Make DEBUG return; - default:return; } } @@ -1206,6 +1249,7 @@ bool TBF::wantsMultislot() // We depend on setState resetting the msgAck flag. bool TBF::mtSendTbfRelease(PDCHL1Downlink *down) { + LOG(INFO) << "mtSendTbfRelease"; // SVGDBG Make DEBUG if (mtMsgPending()) { return false; } // Wait for message in progress. if (mtGotAck(MsgTransTbfRelease,true)) { mtDetach(); @@ -1214,6 +1258,7 @@ bool TBF::mtSendTbfRelease(PDCHL1Downlink *down) return false; } if ((int)mtTbfReleaseCounter > gConfig.getNum("GPRS.Counters.TbfRelease")) { + LOG(INFO) << "GPRS.Counters.TbfRelease count exceeded"; // SVGDBG Make DEBUG mtCancel(MSStopCause::ReleaseCounter,TbfRetryAfterWait); return false; } @@ -1224,6 +1269,7 @@ bool TBF::mtSendTbfRelease(PDCHL1Downlink *down) // See if the TBF can send anything on this downlink, and return true if it sent a block. bool TBF::mtServiceDownlink(PDCHL1Downlink *down) { + LOG(INFO) << "mtServiceDownlink mtState: " << mtState; // SVGDBG Make DEBUG // Only send messages on PACCH. while (1) { mac_debug(); @@ -1261,7 +1307,7 @@ bool TBF::mtServiceDownlink(PDCHL1Downlink *down) } if (mtAssignmentOnCCCH && ! gFixIAUsePoll) { // We will never get a response since we didnt poll. - // The second time through this loop, // just go to the next state. + // The second time through this loop, just go to the next state. // The ExpectedAckBSN is valid even though we are not polling because we are // using it basically as a timer to wait until the message is sent on AGCH. //if (mtExpectedAckBSN.valid()) diff --git a/GPRS/TBF.h b/GPRS/TBF.h index 7d6e2ac..6ecd615 100644 --- a/GPRS/TBF.h +++ b/GPRS/TBF.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -274,6 +273,7 @@ class MsgTransaction { private: BitSet mtMsgAckBits; // Type of acks received. BitSet mtMsgExpectedBits; // The types messages we are waiting for. + Mutex mtLock; // The CCCH handler calls mtSetAckExpected from another thread, so we have to lock appropriately. public: RLCBSN_t mtExpectedAckBSN[MsgTransMax]; // When we expect to get the acknowledgement from the MS. @@ -289,9 +289,11 @@ class MsgTransaction // Wait for the next message. void mtMsgSetWait(MsgTransactionType mttype) { - devassert(mtExpectedAckBSN[mttype].valid()); - mtMsgAckBits.clearBit(mttype); - mtMsgExpectedBits.setBit(mttype); + { ScopedLock lock(mtLock); + devassert(mtExpectedAckBSN[mttype].valid()); + mtMsgAckBits.clearBit(mttype); + mtMsgExpectedBits.setBit(mttype); + } GPRSLOG(4) << "mtMsgSetWait"< + <- Status: 100 Trying + <- Status: 180 Ringing + <- Status: 200 OK + ACK -> + <- BYE + Status: 200 OK -> + +SIP MTC: + <- INVITE + (page the MS) + MTCSendTrying trying -> + +This is the INVITE sent to asterisk: + BTS->asterisk: + Via: SIP/2.0/UDP 127.0.0.1:5062;branch=z9hG4bKobts28492195eb36b5dc03^M + From: IMSI310260520943554 ;tag=zilubvciueqbadda^M + To: ^M + Call-ID: 265130645@127.0.0.1^M + CSeq: 355 INVITE^M + Contact: ;expires=3600^M + Content-Type: application/sdp^M + User-Agent: OpenBTS 3.0TRUNK Build Date Oct 16 2012^M + Max-Forwards: 5^M + P-PHY-Info: OpenBTS; TA=1 TE=0.449219 UpRSSI=-39.000000 TxPwr=33 DnRSSIdBm=-87^M + P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ec000a^M + P-Preferred-Identity: ^M + Content-Length: 135^M + ^M + v=0^M + o=IMSI310260520943554 0 0 IN IP4 127.0.0.1^M + s=Talk Time^M + t=0 0^M + m=audio 16518 RTP/AVP 3^M + c=IN IP4 127.0.0.1^M + a=rtpmap:3 GSM/8000^M + + asterisk sends + INVITE (for call) + MESSAGE (for SMS?) + +CODECS: + Codec Selection is in 24.008 5.2.11. Uses Bearer Capability (old IE for GSM) and Supported Codec List IE: + For GSM: If no Supported Codec List or Bearer Capability, select GSM_FR. + For UMTS: If any GSM codecs are listed use UMTS_AMR_2, which seems to be AMR with uplink codec selection limited + to 1 every 40ms so it can interoperate with GSM. UMTS_AMR_2 is 'backward compatible' with UMTS_AMR. 25.413. + The NAS Synchroniszation IE specifies the selected codec type. + + AMR = Adaptive Mult-Rate + + GSM_FR GSM Full Rate 13.0kb/s + GSM_HR GSM Half Rate 5.60kb/s + GSM_EFR GSM Enhanced Full Rate 12.2Kb/s (same as AMR) + FR_AMR GSM Full rate amr + preferred configs defined in 28.062 table 7.11.3.1.3-2. + Config-NB-1 includes codecs: 12.2, 7.4, 5.9, 4.75 for FR_AMR. + HR_AMR same codecs but excluding 12.2. + AMR 7.4 identical to TDMS_EFR codec. + "This Configuration is especially recommended because it leads in all cases + to TFO/TrFO compatible connections with optimal voice quality." + QUESTION: How does 7.4 fit in the channel? + + 7-13, uplink.persist=3000, 1 channel: speed 11.7 latency 1.8 uplink.persist=3000 3-down/2-up: speed 46.2 latency 1.3 uplink.persist=3000 4-down/1-up: speed 41.5 latency 1.4 diff --git a/GSM/AppInfTest.cpp b/GSM/AppInfTest.cpp index 829427d..7d004d0 100644 --- a/GSM/AppInfTest.cpp +++ b/GSM/AppInfTest.cpp @@ -1,3 +1,20 @@ +/* +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include #include diff --git a/GSM/GSM610Tables.cpp b/GSM/GSM610Tables.cpp index 52cf394..26fb830 100644 --- a/GSM/GSM610Tables.cpp +++ b/GSM/GSM610Tables.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,6 +13,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include "GSM610Tables.h" diff --git a/GSM/GSM610Tables.h b/GSM/GSM610Tables.h index 98b1894..2cd807b 100644 --- a/GSM/GSM610Tables.h +++ b/GSM/GSM610Tables.h @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSM/GSMCCCH.cpp b/GSM/GSMCCCH.cpp new file mode 100644 index 0000000..a394f6a --- /dev/null +++ b/GSM/GSMCCCH.cpp @@ -0,0 +1,576 @@ +/* +* Copyright 2014 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + +*/ + +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + +#include "GSMLogicalChannel.h" +#include "GSMCCCH.h" +#include "GSMRadioResource.h" +#include +#include // For NewTransactionTable. +#include +#include + +using namespace Control; +using namespace GPRS; + +namespace GSM { + + +static int sMaxAge; ///< Maximum allowed age of RACH in frames, a constant computed from config options. + + +CCCHLogicalChannel::CCCHLogicalChannel(unsigned wCcchGroup, const TDMAMapping& wMapping) + :mCcchGroup(wCcchGroup), mRunning(false) +{ + unsigned TN = wCcchGroup * 2; // *2 to convert from CCCH_GROUP + this->mL1 = new CCCHL1FEC(wMapping,TN); + this->connect(this->mL1); +} + + +static void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel* chan) +{ + chan->ccchServiceLoop(); + return NULL; +} + +void CCCHLogicalChannel::ccchOpen() +{ + L2LogicalChannelBase::startl1(); + if (!mRunning) { + mRunning=true; + mServiceThread.start((void*(*)(void*))CCCHLogicalChannelServiceLoopAdapter,this); + } +} + +// This is a specialized thread-safe queue to be used only for the paging and related queues. +// The writer thread(s) can only write to the end of the queue. +// The single reader thread can do a protected iteration using a single captive iterator in the class. +// It does not support blocking wait so it is much simpler than InterthreadQueue. +template // Entry Type, which is a Pointer. +class IterableQueue { + typedef typename std::list::iterator itr_t; + Mutex miqLock; + std::list miqList; + typename std::list::iterator miqIterator; + ETP iqResult() { return miqIterator == miqList.end() ? NULL : *miqIterator; } + + public: + int *miqLoadPointer; // If the descendent class sets this, it will be maintained with the number of elements in the queue. + IterableQueue() : miqLoadPointer(NULL) {} + + void push_back(ETP elt) { + ScopedLock lock(miqLock); + miqList.push_back(elt); + if (miqLoadPointer) (*miqLoadPointer)++; + } + //ETP pop_frontr() { + // ScopedLock lock(miqLock); + // if (miqList.empty()) { return 0; } // Hopefully 0 has some distinct meaning for type ET. + // ETP result = miqList.front(); + // miqList.pop_front(); + // if (miqLoadPointer) (*miqLoadPointer)++; + // return result; + //} + + ETP itBegin() { + ScopedLock lock(miqLock); + miqIterator = miqList.begin(); + return iqResult(); + } + // It is the caller's responsibility to delete the element, or enqueue it elsewhere. + ETP itRemove(ETP thisone) { // The argument is redundant but helps check compliance with list semantics. + LOG(DEBUG); + ScopedLock lock(miqLock); + assert(*miqIterator == thisone); + miqIterator = miqList.erase(miqIterator); + if (miqLoadPointer) (*miqLoadPointer)--; + return iqResult(); + } + ETP itSkip(ETP thisone) { // The argument is redundant but helps check compliance with list semantics. + ScopedLock lock(miqLock); + assert(*miqIterator == thisone); + miqIterator++; + return iqResult(); + } + + int getSize() { + ScopedLock lock(miqLock); + return miqList.size(); + } +}; + +class PagingQ { + public: + InterthreadQueue mPageQ; + void addPage(NewPagingEntry *npe) { mPageQ.write(npe); } + unsigned getPagingLoad() { return mPageQ.size(); } +} gPagingQ; + +// Global linkage: +int getPCHLoad() { + return gPagingQ.getPagingLoad(); +} + + +// Welcome to wonderful C++. +struct RachCompareAdapter { + /** Compare the objects pointed to, not the pointers themselves. */ + // (pat) This is used when a RachInfo is placed in a priority_queue. + // Return true if rach1 should appear before rach2 in the priority_queue, + // meaning that rach1 will be serviced before rach2. + bool operator()(const RachInfo *rach1, const RachInfo *rach2) { + assert(!rach1->mChan == !rach2->mChan); // In any given queue, all raches have mChan, or none. + if (rach1->mChan) { + return rach1->mReadyTime < rach2->mReadyTime; + } else { + return rach1->mWhen < rach2->mWhen; + } + } +}; + + +// This queue holds RACHes of all types waiting to be serviced. +InterthreadQueue gRachq; + + +// The result is 3-state (LCH, !LCH and deleteMe==true, !LCH && deleteMe==false.) +// If it is a TCH or SDCCH return an allocated channel, or NULL if not possible, +// in which case the handset may ultimately be sent an immediate assignment reject. +// On return, if deleteMe, the caller will delete the rach. +static L2LogicalChannel *preallocateChForRach(RachInfo *rach, bool *deleteMe) +{ + *deleteMe = false; + + Time now = gBTS.time(); + int age = now - rach->mWhen; // The result is number of frames and could be negative. + if (age>sMaxAge) { + LOG(WARNING) << "ignoring RACH burst with age " << age; + *deleteMe = true; + return NULL; + } + + L2LogicalChannel *LCH = NULL; + ChannelType chtype = decodeChannelNeeded(rach->mRA); + if (chtype == PSingleBlock1PhaseType || chtype == PSingleBlock2PhaseType) { + return NULL; // this routine does nothing for this. + } else if (chtype == TCHFType) { + LCH = gBTS.getTCH(); + } else if (chtype == SDCCHType) { + LCH = gBTS.getSDCCH(); + } else { + LOG(NOTICE) << "RACH burst for unsupported service RA=" << rach->mRA; + // (pat) 4-2014: Stop allocating channels for this. We get a lot of false RACHes so lets ignore those with unrecognized RA. + // LCH = gBTS.getSDCCH(); + *deleteMe = true; + return NULL; + } + + if (!LCH) { + LOG(DEBUG) << "No channels availablable, RACH discarded"; + *deleteMe = true; + return NULL; + } + + int initialTA = rach->initialTA(); + assert(initialTA >= 0 && initialTA <= 62); // enforced by AccessGrantResponder. + LCH->l1InitPhy(rach->RSSI(),initialTA,gBTS.clock().systime(rach->mWhen.FN())); + LCH->lcstart(); + rach->mChan = LCH; + Time sacchStart = LCH->getSACCH()->getNextWriteTime(); + // There is a race whether the thread that runs SACCH can run before sacchStart time, and if not + // the SACCH will not be transmitted at this time. + // Therefore, if sacchStart time is close, add a whole sacch frame, 104 frames == 480ms. + now = gBTS.time(); // Must update this because getTCH blocked. + int diff = sacchStart - now; // Returns number of 4.8ms frames. + if (diff < 26) { // very conservative. + sacchStart += 104; + } + if (sacchStart - now < 0) { + // Should never happen. + sacchStart = now + 104; + } + rach->mReadyTime = sacchStart; + LOG(DEBUG) < gGprsCcchMessageQ; + +// Global linkage: +int getAGCHLoad() +{ + int result = 0; + result += gRachq.size(); + return result + gGprsCcchMessageQ.getSize(); +} + +// This is called from the GPRS directory. +void pagerAddCcchMessageForGprs(NewPagingEntry *npe) +{ + LOG(DEBUG) <mGprsClient,gprsMsg->mImmAssign,frameTime.FN())) { return false; } + L2LogicalChannelBase::l2sendm(*(gprsMsg->mImmAssign),L3_UNIT_DATA); + return true; +} + + +int getAGCHPending() { return 0; } + + +// Return true if the CCCH frame was used. +bool CCCHLogicalChannel::processRaches() +{ + while (RachInfo *rach = gRachq.readNoBlock()) + { + Time now = gBTS.time(); + int age = now - rach->mWhen; // The result is number of frames and could be negative. + if (age>sMaxAge) { + LOG(WARNING) << "ignoring RACH burst with age " << age; + if (rach->mChan) { + LOG(INFO) << "BTS congestion: unable to process RACH for pre-allocated channel within expiration time"; + rach->mChan->l2sendp(L3_HARDRELEASE_REQUEST); // (pat) added 9-6-2013 + } + delete rach; + continue; // Did not use the CCCH frame yet. + } + + + ChannelType chtype = decodeChannelNeeded(rach->mRA); + LOG(DEBUG) <mChan == NULL); + if (0 == gConfig.getNum("GPRS.Enable")) { + // GPRS service request when the beacon advertises no beacon support. + // This was a spurious RACH message or a stupid handset. Ignore it. + assert(rach->mChan == NULL); + delete rach; + continue; + } + // Regardless of the type of GPRS request, we will send a single-block uplink, + // which the MS will (most likely) use to send us a PacketResourceRequest. + // First request a single-block reservation from GPRS. If GPRS resources are busy, + // this will return NULL and we will send reject the RACH. + L3ImmediateAssignment *iap = GPRS::makeSingleBlockImmediateAssign(rach, mCcchNextWriteTime.FN() + 4); + if (iap) { + L2LogicalChannelBase::l2sendm(*iap,L3_UNIT_DATA); + delete iap; + delete rach; + } else { + } + return true; // We processed this rach and used the CCCH, one way or another. + } + +// L2LogicalChannel *LCH; +// if (chtype == TCHFType) { +// // FIXME: This blocks at L2LAPDm::sendIdle! +// LCH = gBTS.getTCH(); +// } else if (chtype == SDCCHType) { +// // We may reserve some SDCCH for CC and SMS. +// if (requestingLUR(rach->mRA)) { +// int SDCCHAvailable = gBTS.SDCCHAvailable(); +// int SDCCHReserve = gConfig.getNum("GSM.Channels.SDCCHReserve"); +// if (requestingLUR(rach->mRA) && SDCCHAvailable <= SDCCHReserve) { +// // (pat 2-2014) Changed this message and downgraded from CRIT. +// LOG(CRIT) << "LUR [Location Update Request] congestion, insufficient "<mChan; + + // TODO: Update T3101. + + L3ImmediateAssignment assign( + L3RequestReference(rach->mRA,rach->mWhen), + LCH->channelDescription(), + L3TimingAdvance(initialTA) + ); + //assign.setStartFrame(rach->mReadyTime.FN() + 104); + + if (0) { // This was for debugging. Adding this delay made the layer1 connection reliable. + // Delay the channel assignment until the SACCH is known to be transmitting... + Time sacchStart = LCH->getSACCH()->getNextWriteTime(); + now = gBTS.time(); // Must update this because getTCH blocked. + int msecsDelay = ((sacchStart - now.FN()).FN() * gFrameMicroseconds) / 1000; + // Add an extra gratuitous 250ms delay for testing. + // msecsDelay += 250; + if (msecsDelay < 0) { msecsDelay = 0; } + if (msecsDelay) { + LCH->addT3101(msecsDelay); + //assign.setStartFrame(now.FN() + (1000 * msecsDelay) / gFrameMicroseconds); + usleep(msecsDelay * 1000); + } + LOG(INFO) << "sending L3ImmediateAssignment " << LCH->descriptiveString() <mGprsClient) { // Is it a GPRS page? + // Add 51 to the frame time because the message because the MS may be on the other 51-multiframe. + Time future(mCcchNextWriteTime + 52); + if (! sendGprsCcchMessage(npe1,future)) { + delete npe1; // In the incredibly unlikely event that the above failed, just give up. + continue; + } + } else { + const L3MobileIdentity& id1 = npe1->getMobileId(); + ChannelType type1 = npe1->getGsmChanType(); + L3PagingRequestType1 page1(id1,type1); + L2LogicalChannelBase::l2sendm(page1,L3_UNIT_DATA); + } + if (++npe1->mSendCount < 2) { // Send each page twice. + gPagingQ.mPageQ.write_front(npe1); // Put it back for resend in the next multiframe. + } else { + delete npe1; + } + return true; + } + return false; // CCCH unused. +} + + + +// Return true if the current CCCH was used. +bool CCCHLogicalChannel::ccchServiceQueue() +{ + // Update the time when the next CCCH frame will be sent. + // All the l2sendm/l2sendp calls below block using waitToSend() + // on the mPrevWriteTime set by the getNextWriteTime call. + mCcchNextWriteTime = getNextWriteTime(); + if (gBTS.btsHold()) { return false; } + + // Is this CCCH used for pages? We determine this by looking at the frame number within the 51-multiframe. + int paging_block_index = mRevPCH[mCcchNextWriteTime.FN() % 51]; + + + if (paging_block_index >= 0 && processPages()) { return true; } + + // We did not use this CCCH for a page, so lets look for something else to send. + if (processRaches()) { + return true; // We used this CCCH. + } + + // GPRS messages may be in DRX mode or not. + // If they are in DRX mode, move them to a paging queue and set foundSomeNewPages. + bool foundSomeNewPages = false; + + for (NewPagingEntry *gprsIt = gGprsCcchMessageQ.itBegin(); gprsIt; ) { + NewPagingEntry *gprsMsg = gprsIt; // This is redundant; the items are not currently modified by iteration. + LOG(DEBUG) << "processing"<mDrxBegin); + if (mCcchNextWriteTime >= drxBeginTime) { + // This MS is now in DRX mode. Move the message to the paging queue. + gprsIt = gGprsCcchMessageQ.itRemove(gprsIt); + gPagingQ.addPage(gprsMsg); + foundSomeNewPages = true; + continue; + } + // Woo hoo! Send out the message. + if (sendGprsCcchMessage(gprsMsg,mCcchNextWriteTime)) { + gprsIt = gGprsCcchMessageQ.itRemove(gprsIt); + delete gprsMsg; + return true; // We used this CCCH. + } else { + // Failed. That means we were unable to make a reservation for some reason. + // While unlikely, it is possible that a reservation on one channel failed while reservations on other + // channels would succeed, so dont block the entire queue due to this one failure, keep going. + gprsIt = gGprsCcchMessageQ.itSkip(gprsIt); + } + } + + if (foundSomeNewPages); // shut up gcc + + return false; // We have not used this CCCH. +} + +// pats TODO: Send the transceiver an idle frame rather than doing it here. +// There is one of these for each timeslot, ie, for ccch_group. +void CCCHLogicalChannel::ccchServiceLoop() +{ + // build the idle frame + static const L3PagingRequestType1 filler; + static const L3Frame idleFrame(filler,L3_UNIT_DATA); + + bool isCCCHCombined = gControlChannelDescription->isCCCHCombined(); + // TODO: Send idle frame to transceiver. + + // Calculate maximum number of frames of delay. + // See GSM 04.08 3.3.1.1.2 for the logic here. + static const unsigned txInteger = gConfig.getNum("GSM.RACH.TxInteger"); + assert(txInteger <= 15); + // RACH slot definition: we see in GSM 5.02 clause 7 table 3 of 9 that a RACH can appear on timeslots 0,2,4, or 6, and in table 5 of 9 we + // see that for a non-combined CCCH-CONF any of the 51 frames can be used, and for combined CCCH-CONF the available (51*4=204 slots/51-multiframe) + // frames are: B4, B5, B14, B15 ... B36, B45, B46 (27*4=108 slots/51-multiframe.) + // GSM 4.08 11.1.1, And I quote: "The minimum value of this timer is equal to the time taken by T+2S slots of the mobile station's + // RACH. S and T are defined in sub-clause 3.3.1.2. The maximum value of this timer is 5 seconds." + static const int stval = GSM::RACHSpreadSlots[txInteger] + 2*(isCCCHCombined ? GSM::RACHWaitSParamCombined[txInteger] : GSM::RACHWaitSParam[txInteger]); + // Subtract some frames from the maxAge to make sure we still have time left to send it; the amount should be 4 frames + // plus some time for the MS to receive and decode it plus the slack induced by the OpenBTS tranceiver interface, which we do not know. + sMaxAge = min(stval, (int)(5 * 51 * 4.2)) - 6; + + for (int i = 0; i < 51; i++) { mRevPCH[i] = -1; } // -1 means not used for paging. + + mRevPCH[16] = 0; + + int cnt = 0; + while (! gBTS.btsShutdown()) { + + if (! ccchServiceQueue()) { + // Nothing to send on CCCH frame, so just send an idle frame. + //LOG(DEBUG)<<"sending page as idleframe"; + l2sendf(idleFrame); + } + + } + if (cnt); // shut up gcc. +} + +static unsigned newPageAll() +{ + LOG(DEBUG); + Control::NewPagingList_t pages; + Control::MMGetPages(pages,false); + + LOG(INFO) << "paging " << pages.size() << " mobile(s)"; + + // Move the pages from the single incoming queue to the individual PCH paging queues. + for (Control::NewPagingList_t::iterator lp = pages.begin(); lp != pages.end(); lp++) { + gPagingQ.addPage(new NewPagingEntry(*lp)); + } + return pages.size(); +} + + +size_t NewPager::pagingEntryListSize() +{ + Control::NewPagingList_t pages; + MMGetPages(pages,false); + return pages.size(); +} + +void NewPager::serviceLoop() +{ + Timeval nextTime; + while (! gBTS.btsShutdown()) { + // Wait for pending activity to clear PCH. + if (unsigned load = gPagingQ.getPagingLoad()) { + LOG(DEBUG) << "Pager waiting with load " << load; + sleepFrames(51); // There could be multiple paging channels, in which case this is longer than necessary, but no matter. + continue; + } + // nextTime controls how quickly we resend pages. + if (! nextTime.passed()) { + sleepFrames(51); + continue; + } + newPageAll(); + nextTime.future(5000); // Wait 5 seconds between paging sets. + } +} + + +void* NewPager::PagerServiceLoopAdapter(NewPager *pager) +{ + pager->serviceLoop(); + return NULL; +} + +void NewPager::start() +{ + if (mRunning) return; + mRunning=true; + mPagingThread.start((void* (*)(void*))PagerServiceLoopAdapter, (void*)this); +} + +NewPager gPager; + +void PagerStart() +{ + gPager.start(); +} + +}; // namespace GSM diff --git a/GSM/GSMCCCH.h b/GSM/GSMCCCH.h new file mode 100644 index 0000000..84ca4e6 --- /dev/null +++ b/GSM/GSMCCCH.h @@ -0,0 +1,96 @@ +/* +* Copyright 2014 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + +*/ +// Written 3-2014 by Pat Thompson +#ifndef GSMCCCH_H +#define GSMCCCH_H 1 + + +#include "GSMLogicalChannel.h" +#include "GSMCommon.h" +#include "GSMTDMA.h" +#include "PagingEntry.h" + +namespace GSM { + +class NewPager { + Thread mPagingThread; ///< Thread for the paging loop. + volatile bool mRunning; + + public: + + NewPager() :mRunning(false) {} + + /** Set the output FIFO and start the paging loop. */ + void start(); + + /** A loop that repeatedly calls pageAll. */ + void serviceLoop(); + + /** C-style adapter. */ + static void *PagerServiceLoopAdapter(NewPager*); + +public: + /** return size of PagingEntryList */ + size_t pagingEntryListSize(); +}; +extern NewPager gPager; +extern void PagerStart(); + +struct FrontOrBack { + enum Dir { Front, Back }; +}; + +class CCCHLogicalChannel : public NDCCHLogicalChannel +{ + private: + friend class GSMConfig; + + unsigned mCcchGroup; ///< CCCH group: 0,1,2,3 corresponding to timeslots 0,2,4,6 on ARFCN 0. + bool mRunning; ///< a flag to indication that the service loop is running + Thread mServiceThread; ///< a thread for the service loop + GSM::Time mCcchNextWriteTime; ///< Indicates frame currently being serviced. + + int mRevPCH[51]; // Reverse index of frame number to paging block number, B0 .. B8. + + public: + + CCCHLogicalChannel(unsigned wCcchGroup, const TDMAMapping& wMapping); + + void ccchOpen(); + void ccchServiceLoop(); + bool ccchServiceQueue(); + void sendReject(RachInfo *rach, int priority); + void sendRawReject(RachInfo *rach, int delaysecs); // Testing routine. + bool processRaches(); + bool processPages( + ); + bool sendGprsCcchMessage(Control::NewPagingEntry *gprsMsg, GSM::Time &frameTime); + + ChannelType chtype() const { return CCCHType; } +}; + +extern int gCcchTestIAReject; // If non-zero, send an ImmediateAssignmentReject to every RACH with a delay time set from this var. + +extern void pagerAddCcchMessageForGprs(Control::NewPagingEntry *npe); +extern int getAGCHLoad(); +extern int getPCHLoad(); +extern int getAGCHPending(); +extern unsigned getT3122(); +extern void enqueueRach(RachInfo *rip); + +}; // namespace +#endif // GSMCCCH_H diff --git a/GSM/GSMChannelHistory.cpp b/GSM/GSMChannelHistory.cpp new file mode 100644 index 0000000..ac7482a --- /dev/null +++ b/GSM/GSMChannelHistory.cpp @@ -0,0 +1,210 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ +// Written 3-2014 by Pat Thompson + +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + +#include + +#include "GSMLogicalChannel.h" +#include "NeighborTable.h" +#include "GSMChannelHistory.h" +#include "GSML1FEC.h" + +namespace GSM { + +// Return the averaged RXLEV from the serving BTS as reported by the handset. +int ChannelHistory::getAvgRxlev() +{ + ScopedLock lock(mSCellLock); + FrameNum now = gBTS.time().FN(); + return round(ComputeTrend(mSCellData,now,&SCellPoint::getFrameNum,&SCellPoint::getRxlev,gConfig.GSM.Handover.RXLEV_DL.History)); +} + + +bool NeighborHistory::nhGetAvgRxlev(FrameNum fn, float &avg, int algorithm) +{ + ScopedLock lock(nhLock); + if (nhList.size() == 0) { return false; } + avg = ComputeTrend(nhList,fn,&NCellPoint::getFrameNum,&NCellPoint::getRxlev,gConfig.GSM.Handover.RXLEV_DL.History,algorithm); + return true; +} + +NeighborHistory& ChannelHistory::getNeighborData(unsigned arfcn, unsigned BSIC) +{ + unsigned key = makeKey(arfcn,BSIC); + NeighborMap::iterator it; + if ((it = mNeighborData.find(key)) == mNeighborData.end()) { + // Create a new entry. You would think this could be easier, but not if you want to avoid the extra unnecessary construction. + // C++11 adds emplace but we cant use that yet. + //std::pair mapentry(key,NeighborHistory(arfcn,BSIC)); + //std::pair blah = mNeighborData.insert(mapentry); + //return blah.first->second; + //std::pair blah = mNeighborData.insert( NeighborMap::value_type(key,newone) ); + NeighborHistory newone((unsigned)arfcn,BSIC); + it = mNeighborData.insert( NeighborMap::value_type(key,newone) ).first; + } + return it->second; +} + +// Clear all neighbor data. +void ChannelHistory::neighborClearMeasurements() +{ + LOG(DEBUG); + mNeighborData.clear(); +} + +void NeighborHistory::nhAddPoint(NCellPoint &npt, FrameNum now) +{ + ScopedLock lock(nhLock); + nhList.push_front(npt); // This makes a copy of npt. + int maxlen = gConfig.GSM.Handover.History.Max; + while ((int)nhList.size() > maxlen) { nhList.pop_back(); } + // Throw away points that are too old. + int maxage = (1+maxlen) * 2*52; // Each report requires 2 * 52-multiframes, 480ms. + while (nhList.size()) { + NCellPoint &bk = nhList.back(); + if (FNDelta(now,bk.ncpFrame) > maxage) { nhList.pop_back(); continue; } + break; + } +} + +void ChannelHistory::chAddPoint(SCellPoint &spt, FrameNum now) +{ + LOG(DEBUG) << LOGVAR(spt.scpFrame) << LOGVAR(now); + ScopedLock lock(mSCellLock); + mSCellData.push_front(spt); + int maxlen = gConfig.GSM.Handover.History.Max; + while ((int)mSCellData.size() > maxlen) { mSCellData.pop_back(); } + // Throw away points that are too old. + int maxage = (1+maxlen) * 2*52; // Each report requires 2 * 52-multiframes, 480ms. + while (mSCellData.size()) { + SCellPoint &bk = mSCellData.back(); + if (FNDelta(now,bk.scpFrame) > maxage) { mSCellData.pop_back(); continue; } + break; + } +} + +// Find the neighbor with the highest RXLEV. +// If none, the mValid in the result will be false. +Control::BestNeighbor ChannelHistory::neighborFindBest(Control::NeighborPenalty penalty) +{ + Control::BestNeighbor best; + FrameNum sampleFN = gBTS.time().FN(); + LOG(DEBUG) <second; + LOG(DEBUG) < best.mRxlev) { + // Check for congestion in the neighbor. + if (gNeighborTable.neighborCongestion(nh.nhARFCN,nh.nhBSIC)) { + LOG(DEBUG) << "skipping, neighborCongestion"; + continue; + } + best.mValid = true; + best.mRxlev = thisRxlev; + unsigned bestkey = it->first; + crackKey(bestkey,&best.mARFCN,&best.mBSIC); + LOG(DEBUG) <<"found:"<getNeighborData(arfcn,BSIC); // creates an entry if necessary. + + { NCellPoint npt; + npt.ncpFrame = when; + npt.ncpRxlev = rxlevdb; + nh.nhAddPoint(npt,when); + LOG(DEBUG) <mReportTimestamp-1; + LOG(DEBUG) <mReportTimestamp) <mReportTimestamp; + +} + +bool ChannelHistory::neighborAddMeasurements(SACCHLogicalChannel* SACCH,const L3MeasurementResults* measurements) +{ + + Time sampleTime = gBTS.time(); // This thread could be running behind the clock, but it is close enough for government work. + + this->mReportTimestamp++; + + { SCellPoint spt; + spt.scpFrame = sampleTime.FN(); + // These are reported by the handset. + spt.scpValid = measurements->isServingCellValid(); + if (spt.scpValid) { + spt.scpRxlev = measurements->RXLEV_FULL_SERVING_CELL_dBm(); + spt.scpRxqual = measurements->RXQUAL_FULL_SERVING_CELL(); + } else { + spt.scpRxlev = -1000; // Impossible value. + spt.scpRxqual = 8; // Impossible value. + } + } + + // Save the RXLEV for the neighbors. + if (7 == measurements->NO_NCELL()) { + // This special value means neighbor cell information is not avaliable. Gotta love that. + return false; + } + for (unsigned int i=0; iNO_NCELL(); i++) { + int thisRxLevel = measurements->RXLEV_NCELL_dBm(i); + int thisFreq = measurements->BCCH_FREQ_NCELL(i); + if (thisFreq == 31) { + // (pat) This is reserved for 3G in some weird way. + // We support only 31 instead of 32 neighbors to avoid any confusion here. + continue; + } + int thisBSCI = measurements->BSIC_NCELL(i); + int arfcn = gNeighborTable.getARFCN(thisFreq); + if (arfcn < 0) { + LOG(INFO) << "Measurement report with invalid freq index:" << thisFreq << " arfcn:" << arfcn; // SVGDBG seeing this error + continue; + } + this->neighborAddMeasurement(sampleTime.FN(),(unsigned)arfcn,thisBSCI,thisRxLevel); + } + return measurements->isServingCellValid() && measurements->NO_NCELL() > 0; +} + + +}; diff --git a/GSM/GSMChannelHistory.h b/GSM/GSMChannelHistory.h new file mode 100644 index 0000000..c3ca5d0 --- /dev/null +++ b/GSM/GSMChannelHistory.h @@ -0,0 +1,172 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ +// Written 3-2014 by Pat Thompson +#ifndef _GSMNEIGHBORS_H_ +#define _GSMNEIGHBORS_H_ 1 + +#include +#include "GSMCommon.h" +#include "GSMConfig.h" +#include + +namespace GSM { +class SACCHLogicalChannel; + +// GSM FN values must use modulo logic. +// Using this type is just a comment that the value is a frame number, use is not enforced. +typedef int FrameNum; + + +template + float inline ComputeTrend( + PointListType &data, // currently it is always: std::deque &data, + FrameNum x0, // this doesnt need to be an argument; it is always the current frame number. + FrameNum (PointType::*xmethod)(), // method pointer to retrieve X values from data. + YValueType (PointType::*ymethod)(bool &valid), // method pointer to retrieve Y values from data. + int maxpoints, + int algorithm=0 // Which algorithm to use. + ) +{ + // Init accumulator variables. + float sumy = 0; + int npoints = 0; + // Process the points. Run backwards so exponential decay weights the first point most heavily. + // Running backwards we have to skip points until we get to maxpoints. + int skip = data.size() - maxpoints; // may already be negative, which means skip no points. + for (typename PointListType::reverse_iterator it = data.rbegin(); it != data.rend(); it++) { + if (skip-- > 0) continue; + PointType &pt = *it; + bool valid = true; // useless init to quiet g++. + YValueType y = (pt.*ymethod)(valid); + if (!valid) { continue; } + sumy += y; + npoints++; + } + + return (float) sumy / npoints; +} + + +// Neighbor Cell Data Point +struct NCellPoint { + + // The measurement report scales rxlev; this is post scaled, ie, actual db. + int ncpRxlev; // Value is negative in dB. + FrameNum ncpFrame; // When the report was received in GSM frame numbers. + public: + NCellPoint() {} + //NCellPoint(int wRxlev, Time wTime) : ncpRxlev(wRxlev), ncpFrame(wTime.FN()) {} + int getRxlev(bool &valid) { valid=true; return ncpRxlev; } + FrameNum getFrameNum() { return ncpFrame; } +}; +typedef std::deque NCellPointList; + +class ChannelHistory; +class NeighborHistory { + // The most recent point is on the front of the vector. + NCellPointList nhList; + Mutex nhLock; + + friend class ChannelHistory; + // These are the identifying information for this neighbor, used for the key in the map. + // It does not absolutely need to be in this struct, it could be passed around everywhere, but this is simpler. + unsigned nhARFCN; + unsigned nhBSIC; + + public: + // No special destructor is needed. + NeighborHistory(unsigned wARFCN,unsigned wBSIC) : nhARFCN(wARFCN), nhBSIC(wBSIC) {} + + // timestamp of most recent report; + Int_z nhTimestamp; + // Number of consecutive reports. + Int_z nhConsecutiveCount; + + + void nhAddPoint(NCellPoint &npt, FrameNum now); + + void nhClear(); + + // Return true if there was data available and compute and return the averaged RXLEV, where 'averaged' is computed by one of several algorithms. + bool nhGetAvgRxlev(FrameNum fn, float &avg, int algorithm=0); + + // Return the latest point, which is the front. + bool nhGetLatest(NCellPoint &latest); // not used. + void nhText(string &result,bool full); +}; + +// Serving Cell Data Point +struct SCellPoint { + // Max age: 32 reports, each SACCH frame requiring 2 * 52-multiframes each, approximately 16 seconds, plus slop. + //const int cMaxAgeFrames = 32 * 2 * 52 + (2*52-1); + + // The measurement report scales rxlev; this is post scaled, ie, actual db. + FrameNum scpFrame; // When the report was received in GSM frame numbers. + + bool scpValid; // Measurements from MS were valid in this report. + int scpRxlev; // serving cell RXLEV reported by MS. Value is negative in dB. + int scpRxqual; // value is 0-7. GSM5.08 8.2.24: 0 is good, 7 is BER > 12.8% + public: + FrameNum getFrameNum() { return scpFrame; } + int getRxlev(bool &valid) { valid=scpValid; return scpRxlev; } + +}; +typedef std::deque SCellPointList; + +// GSM 5.08 A3.1 specifies BSS processing of measurement reports and recommended. operator control parameters. +// We are required to save 32 samples. + +// (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 ChannelHistory { + // The unsigned key is the frequency index combined with the BSIC. + // The Peering code creates the frequency index; it is sent in one of several sysinfo messages, for us probably type2. + typedef std::map NeighborMap; + NeighborMap mNeighborData; + + // The most recent point is on the front of the vector. + SCellPointList mSCellData; + mutable Mutex mSCellLock; + + void chAddPoint(SCellPoint &spt,FrameNum now); + + //int cNumReports; // Neighbor must appear in 2 of last cNumReports measurement reports. + Int_z mReportTimestamp; // Incremented each time a report arrives. + public: + unsigned makeKey(unsigned arfcn, unsigned BSIC) { return (arfcn<<6) + BSIC; } + void crackKey(unsigned key, unsigned *arfcn, unsigned *BSIC) { *BSIC = key & 0x3f; *arfcn = key>>6; } + + NeighborHistory &getNeighborData(unsigned arfcn, unsigned BSIC); + + // Argument is current RSSI, and return is the averaged RSSI to use for handover determination purposes. + void neighborAddMeasurement(FrameNum when, unsigned freq, unsigned BSIC, int RSSI); + bool neighborAddMeasurements(SACCHLogicalChannel* SACCH,const L3MeasurementResults* measurements); + void neighborClearMeasurements(); // Call to clear everything. + Control::BestNeighbor neighborFindBest(Control::NeighborPenalty penalty); + + // Routines to return the accumulated data. + int getAvgRxlev(); // Doesnt hurt to round the return to an int. +}; + +}; +#endif diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp index 08b76fc..821f4d6 100644 --- a/GSM/GSMCommon.cpp +++ b/GSM/GSMCommon.cpp @@ -1,10 +1,10 @@ /* * Copyright 2008 Free Software Foundation, Inc. -* Copyright 2011, 2013 Range Networks, Inc. +* Copyright 2011, 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,8 +15,10 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging -#include +#include +#include #include "GSMCommon.h" using namespace GSM; @@ -46,6 +48,13 @@ const BitVector2 GSM::gTrainingSequence[] = { BitVector2("11101111000100101110111100"), }; +// (pat) Dummy Burst defined in GSM 5.02 5.2.6 +// From 5.02 6.5.1: A base transceiver station must transmit a burst in every timeslot of every TDMA frame in the downlink of +// radio frequency channel C0 of the cell allocation (to allow mobiles to make power measurements of the radio +// frequency channels supporting the BCCH, see GSM 05.08). In order to achieve this requirement a dummy +// burst is defined in clause 5.2.6 which shall be transmitted by the base transceiver station on all timeslots of all +// TDMA frames of radio frequency channel C0 for which no other channel requires a burst to be transmitted. +// (pat) But this is probably not correct for an idle SACCH, where we should be delivering L2 idle frames. const BitVector2 GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000"); const BitVector2 GSM::gRACHSynchSequence("01001011011111111001100110101010001111000"); @@ -149,7 +158,8 @@ unsigned GSM::downlinkFreqKHz(GSMBand band, unsigned ARFCN) -// See GSM 04.08 Table 10.5.68. +// Number of slots used to spread RACH transmission as a function of broadcast Tx-integer. +// See GSM 04.08 Table 10.5.68 in section 10.5.2.29. const unsigned GSM::RACHSpreadSlots[16] = { 3,4,5,6, @@ -159,6 +169,7 @@ const unsigned GSM::RACHSpreadSlots[16] = }; // See GSM 04.08 Table 3.1 +// Value of parameter S as a function of broadcast Tx-integer for non-combined CCCH. const unsigned GSM::RACHWaitSParam[16] = { 55,76,109,163,217, @@ -167,9 +178,19 @@ const unsigned GSM::RACHWaitSParam[16] = 55 }; +// See GSM 04.08 Table 3.1. S parameter used in 3.3.1.1.2 +// Value of parameter S as a function of broadcast Tx-integer for combined CCCH, ie, any type except combination V. +const unsigned GSM::RACHWaitSParamCombined[16] = +{ + 41,52,58,86,115, + 41,52,58,86,115, + 41,52,58,86,115, + 41 +}; +/** Get a clock difference, within the modulus, v1-v2. */ int32_t GSM::FNDelta(int32_t v1, int32_t v2) { static const int32_t halfModulus = gHyperframe/2; @@ -199,25 +220,24 @@ ostream& GSM::operator<<(ostream& os, const Time& t) -void Clock::set(const Time& when) +void Clock::clockSet(const Time& when) { - mLock.lock(); + ScopedLock lock(mLock); mBaseTime = Timeval(0); mBaseFN = when.FN(); - mLock.unlock(); + isValid = true; } int32_t Clock::FN() const { - mLock.lock(); + ScopedLock lock(mLock); Timeval now; int32_t deltaSec = now.sec() - mBaseTime.sec(); int32_t deltaUSec = now.usec() - mBaseTime.usec(); int64_t elapsedUSec = 1000000LL*deltaSec + deltaUSec; int64_t elapsedFrames = elapsedUSec / gFrameMicroseconds; int32_t currentFN = (mBaseFN + elapsedFrames) % gHyperframe; - mLock.unlock(); return currentFN; } @@ -234,6 +254,14 @@ double Clock::systime(const GSM::Time& when) const return st; } +Timeval Clock::systime2(const GSM::Time& when) const +{ + double ftime = systime(when); + unsigned sec = floor(ftime); + unsigned usec = (ftime - sec) * 1e6; + return Timeval(sec,usec); +} + void Clock::wait(const Time& when) const { @@ -372,6 +400,17 @@ void Z100Timer::set() mActive=true; } +void Z100Timer::addTime(int msecs) // Can be positive or negative +{ + mLimitTime += msecs; + if (mLimitTime < 0) { mLimitTime = 0; } + if (mActive) { + long remaining = mEndTime.remaining() + msecs; + if (remaining < 0) { remaining = 0; } + mEndTime.future(remaining); + } +} + void Z100Timer::expire() { mEndTime = Timeval(0); diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h index 7c2afbb..cde6f24 100644 --- a/GSM/GSMCommon.h +++ b/GSM/GSMCommon.h @@ -2,11 +2,11 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -31,6 +31,7 @@ #include #include #include +#include namespace GSM { @@ -107,9 +108,11 @@ 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 (pat) Started on Immediate Assignment, stopped when MS siezes channel. +//const unsigned T3101ms = 4000; ///< L1 timeout for SDCCH assignment (pat) Started on Immediate Assignment, stopped when MS seizes channel. +// (pat 4-2014) Increase T3101 to allow time for a SACCH init first, and additionally the old value seemed too low anyway, so add 2 secs. +const unsigned T3101ms = 6000; ///< L1 timeout for SDCCH assignment (pat) Started on Immediate Assignment, stopped when MS seizes 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 +// (pat) moved to config const unsigned T3109ms = 30000; ///< L1 timeout for an existing channel const unsigned T3111ms = 2*T200ms; ///< L1 timeout for reassignment of a channel //@} /**@name GSM timeouts for mobility management, GSM 04.08 11.2. */ @@ -301,9 +304,10 @@ std::ostream& operator<<(std::ostream& os, L3PD val); /**@name Tables related to Tx-integer; GSM 04.08 3.3.1.1.2 and 10.5.2.29. */ //@{ /** "T" parameter, from GSM 04.08 10.5.2.29. Index is TxInteger. */ -extern const unsigned RACHSpreadSlots[]; +extern const unsigned RACHSpreadSlots[16]; /** "S" parameter, from GSM 04.08 3.3.1.1.2. Index is TxInteger. */ -extern const unsigned RACHWaitSParam[]; +extern const unsigned RACHWaitSParam[16]; +extern const unsigned RACHWaitSParamCombined[16]; //@} @@ -414,6 +418,7 @@ class Time { return newVal; } + // (pat) Notice that + and - are different. Time operator+(const Time& other) const { unsigned newTN = (mTN + other.mTN) % 8; @@ -511,9 +516,10 @@ class Clock { private: + Bool_z isValid; mutable Mutex mLock; int32_t mBaseFN; - Timeval mBaseTime; + Timeval mBaseTime; // Defaults to now. public: @@ -522,19 +528,27 @@ class Clock { {} /** Set the clock to a value. */ - void set(const Time&); + void clockSet(const Time&); + bool isClockValid() { return isValid; } // Dont need a semaphore for POD. /** Read the clock. */ int32_t FN() const; /** Read the clock. */ - Time get() const { return Time(FN()); } + Time clockGet() const { return Time(FN()); } /** Block until the clock passes a given time. */ void wait(const Time&) const; /** Return the system time associated with a given timestamp. */ - double systime(const Time&) const; + // (pat) in secs with microsec resolution. + // (pat) This is updated at every CLOCK IND from the transceiver, so it is possible + // for this time to skip either forward or backward, either as a result of the FN being + // changed forward or backward by the radio, or jitter in when the CLOCK IND is processed. + // If the when argument was retrieved during the same CLOCK IND interval then it is ok, + // but we cannot guarantee that. + double systime(const Time& when) const; + Timeval systime2(const Time& when) const; }; @@ -579,8 +593,12 @@ class Z100Timer { /** Start or restart the timer, possibly specifying a new limit. */ void set(long wLimitTime); + // Change the limit time, and if active, the remaining time as well. + void addTime(int msecs); + /** Stop the timer. */ void reset() { assert(mLimitTime!=0); mActive = false; } + void reset(long wLimitTime) { mLimitTime=wLimitTime; reset(); } /** Returns true if the timer is active. */ bool active() const { return mActive; } @@ -599,6 +617,22 @@ class Z100Timer { }; std::ostream& operator<<(std::ostream& os, const Z100Timer&); +class Z100TimerThreadSafe : public Z100Timer { + mutable Mutex mtLock; + + public: + bool expired() const { ScopedLock lock(mtLock); return Z100Timer::expired(); } + void expire() { ScopedLock lock(mtLock); Z100Timer::expire(); } + void set() { ScopedLock lock(mtLock); Z100Timer::set(); } + void set(long wLimitTime) { ScopedLock lock(mtLock); Z100Timer::set(wLimitTime); } + void addTime(int msecs) { ScopedLock lock(mtLock); Z100Timer::addTime(msecs); } + void reset() { ScopedLock lock(mtLock); Z100Timer::reset(); } + void reset(long wLimitTime) { ScopedLock lock(mtLock); Z100Timer::reset(wLimitTime); } + // bool active() const; // No need to protect. + long remaining() const { ScopedLock lock(mtLock); return Z100Timer::remaining(); } + void wait() const { ScopedLock lock(mtLock); Z100Timer::wait(); } +}; + 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 10219ee..501e8b0 100644 --- a/GSM/GSMConfig.cpp +++ b/GSM/GSMConfig.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008, 2009, 2010, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,10 +13,13 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include "GSMConfig.h" #include "GSMTransfer.h" #include "GSMLogicalChannel.h" +#include "GSMCCCH.h" #include "GPRSExport.h" #include #include @@ -27,25 +31,42 @@ using namespace std; using namespace GSM; +using Control::L3LogicalChannel; +extern void PagerStart(); + +int GSM::gNumC7s, GSM::gNumC1s; // Number of C7 and C1 timelots created. +static bool testStart = false; // Set this to try testing without the channels started. GSMConfig::GSMConfig() :mCBCH(NULL), - mSI5Frame(UNIT_DATA),mSI6Frame(UNIT_DATA), + mSI5Frame(SAPI0Sacch,L3_UNIT_DATA),mSI6Frame(SAPI0Sacch,L3_UNIT_DATA), mSI1(NULL),mSI2(NULL),mSI3(NULL),mSI4(NULL), - mSI5(NULL),mSI6(NULL), + mSI5(NULL),mSI6(NULL), mSI13(NULL), mStartTime(::time(NULL)), mChangemark(0) { } -void GSMConfig::init() +void GSMConfig::gsmInit() { + // (pat 3-2014) Because the changemark was always inited to 0, if you kill OpenBTS, change something that affects the beacon, + // and restart, the handsets do not know the beacon has changed. We need the changemark to be persistent across restarts. + // As a sneaky dirty way to do this, I am putting it in the config database. + const char *GsmChangeMark = "GSM.ChangeMark"; + if (gConfig.defines(GsmChangeMark)) { + mChangemark = gConfig.getNum(GsmChangeMark) % 8; + } + long changed = 0; long band = gConfig.getNum("GSM.Radio.Band"); long c0 = gConfig.getNum("GSM.Radio.C0"); + // We do not init this statically because it must be done after config options are inited. + gControlChannelDescription = new L3ControlChannelDescription(); + gControlChannelDescription->validate(); + // adjust to an appropriate band if C0 is bogus if (c0 >= 128 && c0 <= 251 && band != 850) { changed = band; @@ -73,23 +94,19 @@ void GSMConfig::init() } mBand = (GSMBand)gConfig.getNum("GSM.Radio.Band"); - mT3122 = gConfig.getNum("GSM.Timer.T3122Min"); regenerateBeacon(); } -void GSMConfig::start() +void GSMConfig::gsmStart() { - mPowerManager.start(); - // Do not call this until the paging channels are installed. - mPager.start(); // If requested, start gprs to allocate channels at startup. // Otherwise, channels are allocated on demand, if possible. - if (GPRS::configGprsChannelsMin() > 0) { + if (GPRS::GPRSConfig::IsEnabled()) { // Start gprs. GPRS::gprsStart(); } - // Do not call this until AGCHs are installed. - mAccessGrantThread.start(Control::AccessGrantServiceLoop,NULL); + // Do not call this until the paging channels are installed. + PagerStart(); Control::l3start(); // (pat) For the L3 rewrite: start the L3 state machine dispatcher. } @@ -99,10 +116,16 @@ void GSMConfig::start() void GSMConfig::regenerateBeacon() { - // FIXME -- Need to implement BCCH_CHANGE_MARK + // (pat) regenerateBeacon is called whenever there is a change to the config database. + // When we call gConfig.set() it would cause infinite recursion; this prevents that, albeit goofily. + // This is not safe in case someone later installs throws that jump over this. + static int noRecursionPlease = 0; + if (noRecursionPlease) return; + noRecursionPlease++; gReports.incr("OpenBTS.GSM.RR.BeaconRegenerated"); mChangemark++; + gConfig.set("GSM.ChangeMark",mChangemark); // Update everything from the configuration. LOG(NOTICE) << "regenerating system information messages, changemark " << mChangemark; @@ -127,7 +150,7 @@ void GSMConfig::regenerateBeacon() if (mSI1) delete mSI1; mSI1 = SI1; LOG(INFO) << *SI1; - L3Frame SI1L3(UNIT_DATA,0); + L3Frame SI1L3(L3_UNIT_DATA,0); SI1->write(SI1L3); L2Header SI1Header(L2Length(SI1L3.L2Length())); mSI1Frame = L2Frame(SI1Header,SI1L3); @@ -138,7 +161,7 @@ void GSMConfig::regenerateBeacon() if (mSI2) delete mSI2; mSI2 = SI2; LOG(INFO) << *SI2; - L3Frame SI2L3(UNIT_DATA,0); + L3Frame SI2L3(L3_UNIT_DATA,0); SI2->write(SI2L3); L2Header SI2Header(L2Length(SI2L3.L2Length())); mSI2Frame = L2Frame(SI2Header,SI2L3); @@ -149,7 +172,7 @@ void GSMConfig::regenerateBeacon() if (mSI3) delete mSI3; mSI3 = SI3; LOG(INFO) << *SI3; - L3Frame SI3L3(UNIT_DATA,0); + L3Frame SI3L3(L3_UNIT_DATA,0); SI3->write(SI3L3); L2Header SI3Header(L2Length(SI3L3.L2Length())); mSI3Frame = L2Frame(SI3Header,SI3L3,true); @@ -161,7 +184,7 @@ void GSMConfig::regenerateBeacon() mSI4 = SI4; LOG(INFO) << *SI4; LOG(INFO) << SI4; - L3Frame SI4L3(UNIT_DATA,0); + L3Frame SI4L3(L3_UNIT_DATA,0); SI4->write(SI4L3); //printf("SI4 bodylength=%d l2len=%d\n",SI4.l2BodyLength(),SI4L3.L2Length()); //printf("SI4L3.size=%d\n",SI4L3.size()); @@ -171,17 +194,20 @@ void GSMConfig::regenerateBeacon() #if GPRS_PAT | GPRS_TEST // SI13. pat added 8-2011 to advertise GPRS support. - L3SystemInformationType13 *SI13 = new L3SystemInformationType13; - LOG(INFO) << *SI13; - L3Frame SI13L3(UNIT_DATA,0); - //printf("start=%d\n",SI13L3.size()); - SI13->write(SI13L3); - //printf("end=%d\n",SI13L3.size()); - //printf("SI13 bodylength=%d l2len=%d\n",SI13.l2BodyLength(),SI13L3.L2Length()); - //printf("SI13L3.size=%d\n",SI13L3.size()); - L2Header SI13Header(L2Length(SI13L3.L2Length())); - mSI13Frame = L2Frame(SI13Header,SI13L3,true); - LOG(DEBUG) << "mSI13Frame " << mSI13Frame; + if (mSI13) { delete mSI13; mSI13 = NULL; } + if (GPRS::GPRSConfig::IsEnabled()) { + mSI13 = new L3SystemInformationType13; + LOG(INFO) << *mSI13; + L3Frame SI13L3(L3_UNIT_DATA,0); + //printf("start=%d\n",SI13L3.size()); + mSI13->write(SI13L3); + //printf("end=%d\n",SI13L3.size()); + //printf("SI13 bodylength=%d l2len=%d\n",SI13.l2BodyLength(),SI13L3.L2Length()); + //printf("SI13L3.size=%d\n",SI13L3.size()); + L2Header SI13Header(L2Length(SI13L3.L2Length())); + mSI13Frame = L2Frame(SI13Header,SI13L3,true); + LOG(DEBUG) << "mSI13Frame " << mSI13Frame; + } #endif // SI5 @@ -195,6 +221,7 @@ void GSMConfig::regenerateBeacon() SI6->write(mSI6Frame); LOG(DEBUG) "mSI6Frame " << mSI6Frame; + noRecursionPlease--; } @@ -211,27 +238,8 @@ void GSMConfig::regenerateSI5() LOG(DEBUG) << "mSI5Frame " << mSI5Frame; } -CCCHLogicalChannel* GSMConfig::minimumLoad(CCCHList &chanList) -{ - if (chanList.size()==0) return NULL; - CCCHList::iterator chan = chanList.begin(); - CCCHLogicalChannel *retVal = *chan; - unsigned minLoad = (*chan)->load(); - ++chan; - while (chan!=chanList.end()) { - unsigned thisLoad = (*chan)->load(); - if (thisLoad ChanType* getChan(vector& chanList, bool forGprs) @@ -273,7 +281,7 @@ bool testAdjacent(ChanType *ch1, ChanType *ch2) return (ch1->CN() == ch2->CN() && ch1->TN() == ch2->TN()-1); } -// Return the goodness of this possible match of gprs channels. +// (pat) Return the goodness of this possible match of gprs channels. // Higher numbers are gooder. template int testGoodness(vector& chanList, int lo, int hi) @@ -355,12 +363,12 @@ static unsigned getChanGroup(vector& chanList, ChanType **results) // Allocate a group of channels for gprs. // See comments at getChanGroup. -int GSMConfig::getTCHGroup(int groupSize,TCHFACCHLogicalChannel **results) +int GSMConfig::getTCHGroup(int /* unused groupSize*/,TCHFACCHLogicalChannel **results) { ScopedLock lock(mLock); int nfound = getChanGroup(mTCHPool,results); for (int i = 0; i < nfound; i++) { - results[i]->debugGetL1()->setGPRS(true,NULL); + results[i]->lcGetL1()->setGPRS(true,NULL); } return nfound; } @@ -371,19 +379,16 @@ int GSMConfig::getTCHGroup(int groupSize,TCHFACCHLogicalChannel **results) SDCCHLogicalChannel *GSMConfig::getSDCCH() { - LOG(DEBUG); ScopedLock lock(mLock); - LOG(DEBUG); SDCCHLogicalChannel *chan = getChan(mSDCCHPool,0); - LOG(DEBUG); - if (chan) chan->open(); - LOG(DEBUG); + if (chan) chan->lcinit(); + LOG(DEBUG) <open() calls L1Encoder::open() and L1Decoder::open(), -// which sets mActive in both and resets the timers. +// 6-2014 pat: The channel is now returned with T3101 running but un-started, which means it is not yet transmitting. +// The caller is responsible for setting the Timing Advance and then starting it. TCHFACCHLogicalChannel *GSMConfig::getTCH( bool forGPRS, // If true, allocate the channel to gprs, else to RR use. bool onlyCN0) // If true, allocate only channels on the lowest ARFCN. @@ -409,11 +414,11 @@ TCHFACCHLogicalChannel *GSMConfig::getTCH( if (onlyCN0 && chan->CN()) { return NULL; } if (forGPRS) { // (pat) Reserves channel for GPRS, but does not start delivering bursts yet. - chan->debugGetL1()->setGPRS(true,NULL); + chan->lcGetL1()->setGPRS(true,NULL); return chan; } - chan->open(); // (pat) LogicalChannel::open(); Opens mSACCH also. Starts T3101. gReports.incr("OpenBTS.GSM.RR.ChannelAssignment"); + chan->lcinit(); } else { //LOG(DEBUG)<<"getTCH returns NULL"; } @@ -448,16 +453,6 @@ size_t GSMConfig::TCHAvailable() const } -size_t GSMConfig::totalLoad(const CCCHList& chanList) const -{ - size_t total = 0; - for (unsigned i=0; iload(); - } - return total; -} - - unsigned countActive(const SDCCHList& chanList) { @@ -511,37 +506,6 @@ unsigned GSMConfig::TCHTotal() const } - - -unsigned GSMConfig::T3122() const -{ - ScopedLock lock(mLock); - return mT3122; -} - -unsigned GSMConfig::growT3122() -{ - unsigned max = gConfig.getNum("GSM.Timer.T3122Max"); - ScopedLock lock(mLock); - unsigned retVal = mT3122; - mT3122 += (random() % mT3122) / 2; - if (mT3122>(int)max) mT3122=max; - return retVal; -} - - -unsigned GSMConfig::shrinkT3122() -{ - unsigned min = gConfig.getNum("GSM.Timer.T3122Min"); - ScopedLock lock(mLock); - unsigned retVal = mT3122; - mT3122 -= (random() % mT3122) / 2; - if (mT3122<(int)min) mT3122=min; - return retVal; -} - - - void GSMConfig::createCombination0(TransceiverManager& TRX, unsigned TN) { // This channel is a dummy burst generator. @@ -562,11 +526,82 @@ void GSMConfig::createCombinationI(TransceiverManager& TRX, unsigned CN, unsigne TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(CN,TN,gTCHF_T[TN]); chan->downstream(radio); Thread* thread = new Thread; - thread->start((void*(*)(void*))Control::DCCHDispatcher,chan); - chan->open(); + thread->start((void*(*)(void*))Control::DCCHDispatcher,dynamic_cast(chan)); + chan->lcinit(); + if (CN == 0 && !testStart) chan->lcstart(); // Everything on C0 must broadcast continually. gBTS.addTCH(chan); } +class Beacon {}; + + +// Combination-5 beacon has 3 x CCCH + 4 x SDCCH. +// There can be only one Combination 5 beacon, always on timeslot 0. +class BeaconC5 : public Beacon { + SCHL1FEC SCH; + FCCHL1FEC FCCH; + BCCHL1FEC BCCH; + RACHL1FEC *RACH; + CCCHLogicalChannel *CCCH; + CBCHLogicalChannel *CBCH; + Thread CBCHControlThread; + SDCCHLogicalChannel *C0T0SDCCH[4]; + Thread C0T0SDCCHControlThread[4]; + + public: + BeaconC5(ARFCNManager *radio) + { + LOG(DEBUG); + unsigned TN = 0; + radio->setSlot(TN,5); + SCH.downstream(radio); + SCH.l1open(); + FCCH.downstream(radio); + FCCH.l1open(); + BCCH.downstream(radio); + BCCH.l1open(); + RACH = new RACHL1FEC(gRACHC5Mapping,0); + RACH->downstream(radio); + RACH->l1open(); + CCCH = new CCCHLogicalChannel(0, gCCCH_C5Mapping); // Always ccch_grooup == 0 + CCCH->downstream(radio); + CCCH->ccchOpen(); + // C-V C0T0 SDCCHs + C0T0SDCCH[0] = new SDCCHLogicalChannel(0,0,gSDCCH_4_0); + C0T0SDCCH[1] = new SDCCHLogicalChannel(0,0,gSDCCH_4_1); + C0T0SDCCH[2] = new SDCCHLogicalChannel(0,0,gSDCCH_4_2); + C0T0SDCCH[3] = new SDCCHLogicalChannel(0,0,gSDCCH_4_3); + + // Subchannel 2 used for CBCH if SMSCB enabled. + bool SMSCB = (gConfig.getStr("Control.SMSCB.Table").length() != 0); + for (int i=0; i<4; i++) { + if (SMSCB && (i==2)) continue; + C0T0SDCCH[i]->downstream(radio); + C0T0SDCCHControlThread[i].start((void*(*)(void*))Control::DCCHDispatcher,dynamic_cast(C0T0SDCCH[i])); + C0T0SDCCH[i]->lcinit(); + C0T0SDCCH[i]->lcstart(); // Everything on channel 0 needs to broadcast constantly. + gBTS.addSDCCH(C0T0SDCCH[i]); + } + // Install CBCH if used. + // It writes on SDCCH4. + if (SMSCB) { + LOG(INFO) << "creating CBCH for SMSCB"; + CBCH = new CBCHLogicalChannel(gSDCCH_4_2); + CBCH->downstream(radio); + CBCH->lcopen(); + gBTS.addCBCH(CBCH); + CBCHControlThread.start((void*(*)(void*))Control::SMSCBSender,NULL); + } + } +}; + + + +void GSMConfig::createBeacon(ARFCNManager *radio) +{ + new BeaconC5(radio); +} + void GSMConfig::createCombinationVII(TransceiverManager& TRX, unsigned CN, unsigned TN) { @@ -578,117 +613,27 @@ void GSMConfig::createCombinationVII(TransceiverManager& TRX, unsigned CN, unsig SDCCHLogicalChannel* chan = new SDCCHLogicalChannel(CN,TN,gSDCCH8[i]); chan->downstream(radio); Thread* thread = new Thread; - thread->start((void*(*)(void*))Control::DCCHDispatcher,chan); - chan->open(); + thread->start((void*(*)(void*))Control::DCCHDispatcher,dynamic_cast(chan)); + chan->lcinit(); + if (CN == 0 && !testStart) chan->lcstart(); // Everything on C0 must broadcast continually. gBTS.addSDCCH(chan); } } -void GSMConfig::hold(bool val) +void GSMConfig::setBtsHold(bool val) { ScopedLock lock(mLock); mHold = val; } -bool GSMConfig::hold() const +bool GSMConfig::btsHold() const { ScopedLock lock(mLock); return mHold; } - -#if ENABLE_PAGING_CHANNELS - -// 5-27-2012 pat added: -// Routines for CCCH messages to add real paging channels. -// Added in the simplest possible way to avoid destabilizing anything. -// GPRS still needs a pretty major rewrite of the underlying CCCHLogicalChannel class -// to reduce the latency, but paging queues at least relieve the congestion on CCCH. -// In DRX [Discontinuous Reception] mode the MS listens only to a subset of CCCH based on its IMSI. -// This is a GPRS thing but dependent on the configuration of CCCH in our system. -// See: GSM 05.02 6.5.2: Determination of CCCH_GROUP and PAGING_GROUP for MS in idle mode. -void GSMConfig::crackPagingFromImsi( - unsigned imsiMod1000 // The phones imsi mod 1000, so just atoi the last 3 digits. - unsigned &paging_block_index, // Returns which of the paging ccchs to use. - unsigned &multiframe_index // Returns which 51-multiframe to use. - ) -{ - L3ControlChannelDescription mCC; - - // BS_CCCH_SDCCH_COMB is defined in GSM 05.02 3.3.2.3; - int bs_cc_chans; // The number of ccch timeslots per 51-multiframe. - bool bs_ccch_sdcch_comb; // temp var indicates if sdcch is on same TS as ccch. - switch (mCC.mCCCH_CONF) { - case 0: bs_cc_chans=1; bs_ccch_sdcch_comb=false; break; - case 1: bs_cc_chans=1; bs_ccch_sdcch_comb=true; break; - case 2: bs_cc_chans=2; bs_ccch_sdcch_comb=false; break; - case 4: bs_cc_chans=3; bs_ccch_sdcch_comb=false; break; - case 6: bs_cc_chans=4; bs_ccch_sdcch_comb=false; break; - default: - LOG(ERR) << "Invalid GSM.CCCH.CCCH-CONF value:"< mPCC.mBS_AG_BLKS_RES); - - // GSM 05.02 6.5.2: N is number of paging blocks "available" on one CCCH. - // The "available" is in quotes and not specifically defined, but I believe - // they mean after subtracting out BS_AG_BLKS_RES, as per 6.5.1 paragraph v). - unsigned pch_avail = agch_avail - mPCC.mBS_AG_BLKS_RES; - unsigned Ntotal = pch_avail * bs_pa_mfrms; - unsigned tmp = (imsiMod1000 % (bs_cc_chans * Ntotal)) % Ntotal; - unsigned paging_group = tmp % Ntotal; - paging_block_index = paging_group / (Ntotal / bs_pa_mfrms); - // And I quote: The required 51-multiframe occurs when: - // PAGING_GROUP div (N div BS_PA_MFRMS) = (FN div 51) mod (BS_PA_MFRMS) - multiframe_index = paging_group / (Ntotal % bs_pa_mfrms); -} - -void GSMConfig::sendPCH(const L3RRMessage& msg,unsigned imsiMod1000) -{ - unsigned paging_block_index; // which of the paging ccchs to use. - unsigned multiframe_index; // which 51-multiframe to use. - 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,0)); -} - -Time GSMConfig::getPchSendTime(imsiMod1000) -{ - unsigned paging_block_index; // which of the paging ccchs to use. - unsigned multiframe_index; // which 51-multiframe to use. - crackPagingFromImsi(imsiMod1000,paging_block_index, multiframe_index); - assert(multiframe_index < sMax_BS_PA_MFRMS); - CCCHLogicalChannel* ch = getPCH(paging_block_index); - return ch->getNextPchSendTime(multiframe_index); -} -#endif - // (pat) Return a vector of the available channels. // Use to avoid publishing these iterators to the universe. void GSMConfig::getChanVector(L2ChanList &result) diff --git a/GSM/GSMConfig.h b/GSM/GSMConfig.h index 00ea30b..0e941b4 100644 --- a/GSM/GSMConfig.h +++ b/GSM/GSMConfig.h @@ -1,11 +1,11 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -24,11 +24,8 @@ #include "Defines.h" #include #include - -//#include -#include #include - +#include "GSMRadioResource.h" #include "GSML3RRElements.h" #include "GSML3CommonElements.h" #include "GSML3RRMessages.h" @@ -42,6 +39,7 @@ namespace GSM { const unsigned sMax_BS_PA_MFRMS = 9; + class CCCHLogicalChannel; class SDCCHLogicalChannel; class CBCHLogicalChannel; @@ -61,9 +59,7 @@ class GSMConfig { private: /** The paging mechanism is built-in. */ - Control::Pager mPager; - - PowerManager mPowerManager; + //Control::Pager mPager; mutable Mutex mLock; ///< multithread access control @@ -114,36 +110,29 @@ class GSMConfig { L3SystemInformationType4* mSI4; L3SystemInformationType5* mSI5; L3SystemInformationType6* mSI6; + L3SystemInformationType13* mSI13; //@} - int mT3122; - time_t mStartTime; L3LocationAreaIdentity mLAI; bool mHold; ///< If true, do not respond to RACH bursts. - - InterthreadQueue mChannelRequestQueue; - Thread mAccessGrantThread; + Bool_z mShutdown; unsigned mChangemark; - - - void crackPagingFromImsi(unsigned imsiMod1000,unsigned &ccch_group,unsigned &paging_Index);; + void crackPagingFromImsi(unsigned imsiMod1000,unsigned &ccch_group,unsigned &paging_Index); public: - - GSMConfig(); /** Initialize with parameters from gConfig. */ - void init(); + void gsmInit(); /** Start the internal control loops. */ - void start(); + void gsmStart(); /**@name Get references to L2 frames for BCCH SI messages. */ //@{ @@ -166,14 +155,15 @@ class GSMConfig { const L3SystemInformationType4* SI4() const { return mSI4; } const L3SystemInformationType5* SI5() const { return mSI5; } const L3SystemInformationType6* SI6() const { return mSI6; } + const L3SystemInformationType13* SI13() const { return mSI13; } //@} /** Get the current master clock value. */ - Time time() const { return mClock.get(); } + Time time() const { return mClock.clockGet(); } /**@name Accessors. */ //@{ - Control::Pager& pager() { return mPager; } + //Control::Pager& pager() { return mPager; } GSMBand band() const { return mBand; } unsigned BCC() const { return mBCC; } unsigned NCC() const { return mNCC; } @@ -201,12 +191,14 @@ class GSMConfig { Hold off on channel allocations; don't answer RACH. @param val true to hold, false to clear hold */ - void hold(bool val); + void setBtsHold(bool val); /** Return true if we are holding off channel allocation. */ - bool hold() const; + bool btsHold() const; + bool btsShutdown() const { return mShutdown; } + void setBtsShutdown() { mShutdown = true; } protected: @@ -218,63 +210,8 @@ class GSMConfig { public: - size_t AGCHLoad() { return totalLoad(mAGCHPool); } - size_t PCHLoad() { return totalLoad(mPCHPool); } - - /**@name Manage CCCH subchannels. */ - //@{ - - /** The add method is not mutex protected and should only be used during initialization. */ - void addAGCH(CCCHLogicalChannel* wCCCH) { mAGCHPool.push_back(wCCCH); } - - /** The add method is not mutex protected and should only be used during initialization. */ - void addPCH(CCCHLogicalChannel* wCCCH) { mPCHPool.push_back(wCCCH); } - - /** Return a minimum-load AGCH. */ - // (pat) TODO: This strategy needs to change. - // There needs to be a common message queue for all CCCH timeslots from which the - // FEC can pull the next AGCH message if there is no paging message at that paging slot. - // And if someone besides pat works on this, note that gprs also wants - // to be able cancel messages after sending them in case conditions have changed, - // and also needs to know, a-priori, the exact frame number when the message - // is going to be sent, none of which works properly at the moment. - CCCHLogicalChannel* getAGCH() { return minimumLoad(mAGCHPool); } - -#if ENABLE_PAGING_CHANNELS - ///< (pat) Send a paging message for the specified imsi. - // This function should be used instead of getPCH(), etc. which should then be made private. - void sendPCH(const L3RRMessage& msg,unsigned imsiMod1000); - - ///< (pat) Return the approximate time of the next PCH message for this imsi. - // This routine should be elided after DRX mode in GPRS is fixed. - Time getPchSendTime(imsiMod1000); - - ///< (pat) Send a message on the avail AGCH. - void sendAGCH(const L3RRMessage& msg); -#endif - - /** Return a minimum-load PCH. */ - CCCHLogicalChannel* getPCH() { return minimumLoad(mPCHPool); } - - /** Return a specific PCH. */ - CCCHLogicalChannel* getPCH(size_t index) - { - assert(index &result); }; +extern int gNumC7s, gNumC1s; + }; // GSM diff --git a/GSM/GSML1FEC.cpp b/GSM/GSML1FEC.cpp index 047cd68..2491d4c 100644 --- a/GSM/GSML1FEC.cpp +++ b/GSM/GSML1FEC.cpp @@ -5,7 +5,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -16,19 +16,21 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #define TESTTCHL1FEC #include "GSML1FEC.h" #include "GSMCommon.h" -#include "GSMTransfer.cpp" -#include "GSMSAPMux.h" +#include "GSMTransfer.h" +//#include "GSMSAPMux.h" #include "GSMConfig.h" #include "GSMTDMA.h" #include "GSMTAPDump.h" +#include "GSMLogicalChannel.h" #include -#include -#include +#include #include #include #include @@ -42,6 +44,11 @@ namespace GSM { using namespace std; +using namespace SIP; // For AudioFrame + +#undef OBJLOG +#define OBJLOG(level) LOG(level) <descriptiveString() : "NULL"); + return os; +} + void L1Encoder::rollForward() { - // Calculate the TDMA paramters for the next transmission. + // Calculate the TDMA parameters for the next transmission. // This implements GSM 05.02 Clause 7 for the transmit side. mPrevWriteTime = mNextWriteTime; - mTotalBursts++; - mNextWriteTime.rollForward(mMapping.frameMapping(mTotalBursts),mMapping.repeatLength()); + mTotalFrames++; + mNextWriteTime.rollForward(mMapping.frameMapping(mTotalFrames),mMapping.repeatLength()); } @@ -244,42 +255,51 @@ TypeAndOffset L1Encoder::typeAndOffset() const } -void L1Encoder::open() +void L1Encoder::encInit() { - OBJLOG(INFO) << "L1Encoder"; + OBJLOG(BLATHER) << "L1Encoder"; handoverPending(false); - ScopedLock lock(mLock); - if (!mRunning) start(); - mTotalBursts=0; - mActive = true; - resync(); - mPrevWriteTime = gBTS.time(); - // Turning off encryption when the channel closes would be a nightmare + ScopedLock lock(mEncLock,__FILE__,__LINE__); + mTotalFrames=0; + resync(true); // (pat 4-2014) Force mNextWriteTime to be recalculated at channel initiation. + mPrevWriteTime = gBTS.time(); // (pat) Prevents the first write after opening the channel from blocking in waitToSend called from transmit. + // (doug) Turning off encryption when the channel closes would be a nightmare // (catching all the ways, and performing the handshake under less than // ideal conditions), so we leave encryption on to the bitter end, // then clear the encryption flag here, when the channel gets reused. mEncrypted = ENCRYPT_NO; mEncryptionAlgorithm = 0; + // (pat) On very first initialization, start sending the dummy bursts; + // this allows us to get rid of the dopey 'starting' of all the channels when the BTS is turned on. + if (mCN == 0 && !mEncEverActive) { sendDummyFill(); } +} + +void L1Encoder::encStart() +{ + if (!mRunning) { mRunning = true; serviceStart(); } + mEncActive = true; + mEncEverActive = true; } +// (pat) sendDummyFill does not block, but it advances the mNextWriteTime. void L1Encoder::close() { - // Don't return until the channel is fully closed. - OBJLOG(INFO) << "L1Encoder"; - ScopedLock lock(mLock); - mActive = false; - sendIdleFill(); + OBJLOG(BLATHER) << "L1Encoder"; + ScopedLock lock(mEncLock,__FILE__,__LINE__); + if (mEncActive) { sendDummyFill(); } + mEncActive = false; } -bool L1Encoder::active() const +bool L1Encoder::encActive() const { - ScopedLock lock(mLock); - bool retVal = mActive; const L1Decoder *sib = sibling(); - if (sib) retVal = mActive && (!sib->recyclable()); - return retVal; + if (sib) { + return mEncActive && (sib->decActive()); + } else { + return mEncActive; + } } @@ -297,18 +317,19 @@ const L1Decoder* L1Encoder::sibling() const } -void L1Encoder::resync() +void L1Encoder::resync(bool force) { // If the encoder's clock is far from the current BTS clock, // get it caught up to something reasonable. Time now = gBTS.time(); int32_t delta = mNextWriteTime-now; OBJLOG(DEBUG) << "L1Encoder next=" << mNextWriteTime << " now=" << now << " delta=" << delta; - if ((delta<0) || (delta>(51*26))) { + if (force || (delta<0) || (delta>(51*26))) { mNextWriteTime = now; mNextWriteTime.TN(mTN); - mNextWriteTime.rollForward(mMapping.frameMapping(mTotalBursts),mMapping.repeatLength()); - OBJLOG(DEBUG) <<"L1Encoder RESYNC "<writeHighSideTx(mFillerBurst,"idle"); - rollForward(); + // (pat) FIXME: On other ARFCNs we need to disable the transceiver auto-filling. See wiki ticket 1141. + // (pat) In the meantime, we must send the dummy burst to inform the MS that this channel is disabled; + // this is required specifically for SACCH. + // To preserve the old behavior, we will leave other arfcns non-transmitting until the first time they are used, + // but ever after we have to send the filler pattern. + if (mCN==0 || mEncEverActive) { + for (unsigned i=0; iwriteHighSideTx(mFillerBurst,"dummy"); + rollForward(); + } + mFillerSendTime = gBTS.clock().systime2(mNextWriteTime); // (pat) The time when the last burst of the filler will be delivered. } } +bool L1Encoder::l1IsIdle() const +{ + return ! mEncActive && mFillerSendTime.passed(); +} + unsigned L1Encoder::ARFCN() const { assert(mDownstream); @@ -402,53 +436,63 @@ void DecoderStats::decoderStatsInit() mStatBadFrames = 0; } -void L1Decoder::open() +ostream& operator<<(std::ostream& os, const L1Decoder *decp) +{ + os <<"L1Decoder "<<(decp ? decp->descriptiveString() : "NULL"); + return os; +} + +string L1Decoder::displayTimers() const +{ + ostringstream ss; + // No point in showing T3103 for handover - its too fast. + //ss <setHandover(mTN); - if (!ok) LOG(ALERT) << "handover setup failed"; + if (!ok) OBJLOG(ALERT) << "handover setup failed"; } else { bool ok = mDownstream->clearHandover(mTN); - if (!ok) LOG(ALERT) << "handover clear failed"; + if (!ok) OBJLOG(ALERT) << "handover clear failed"; } } @@ -538,69 +595,43 @@ void L1FEC::downstream(ARFCNManager* radio) if (mDecoder) radio->installDecoder(mDecoder); } - -void L1FEC::open() +void L1FEC::l1start() { - if (mEncoder) mEncoder->open(); - if (mDecoder) mDecoder->open(); + LOG(DEBUG); + if (mDecoder) mDecoder->decStart(); + if (mEncoder) mEncoder->encStart(); } -void L1FEC::close() + +void L1FEC::l1init() { - if (mEncoder) mEncoder->close(); + LOG(DEBUG); + if (mDecoder) mDecoder->decInit(); + if (mEncoder) mEncoder->encInit(); +} + +void L1FEC::l1close() +{ + LOG(DEBUG) <close(); // Does the sendDummyFill. 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 +// Active means it is currently sending and receiving. +// recyclable is used to tell when the channel is reusable. +bool L1FEC::l1active() const { - // Encode-only channels are always active. + // non-sacch encode-only channels are always active. // Otherwise, the decoder is the better indicator. - if (mDecoder) return mDecoder->active(); - else return (mEncoder!=NULL); -} - - - - -void RACHL1Decoder::serviceLoop() -{ - // The service loop pulls RACH bursts from a FIFO - // and sends them to the decoder. - // This loop is in its own thread because - // the allocator can potentially block and we don't - // want the whole receive thread to block. - - while (true) { - RxBurst *rx = mQ.read(); - // Yes, if we wait long enough that read will timeout. - if (rx==NULL) continue; - writeLowSideRx(*rx); - delete rx; + if (mDecoder) { + return mDecoder->decActive(); + } else { + return (mEncoder!=NULL); // (pat) The send-only channels are always active. } } -void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder* obj) -{ - obj->serviceLoop(); - return NULL; -} - - -void RACHL1Decoder::start() -{ - // Start the processing thread. - L1Decoder::start(); - mServiceThread.start((void*(*)(void*))RACHL1DecoderServiceLoopAdapter,this); -} - - // (pat) Note that GPRS has an option to use a PRACH burst identical to RACH bursts. // (pat) This routine is fed immediately from the radio in TRXManager; @@ -646,9 +677,10 @@ void RACHL1Decoder::writeLowSideRx(const RxBurst& burst) countBER(mVCoder.getBEC(),36); mD.LSB8MSB(); unsigned RA = mD.peekField(0,8); - OBJLOG(INFO) <<"RACHL1Decoder received RA=" << RA << " at time " << burst.time() - << " with RSSI=" << burst.RSSI() << " timingError=" << burst.timingError(); - gBTS.channelRequest(new Control::ChannelRequestRecord(RA,burst.time(),burst.RSSI(),burst.timingError())); + OBJLOG(INFO) <<"RACHL1Decoder received RA=" << RA << " at time " << burst.time() \ + << " with RSSI=" << burst.RSSI() << " timingError=" << burst.timingError() <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(); - if (sibling()) sibling()->close(); - break; - case HARDRELEASE: - // This means that a higher layer released the link, - // with certainty that is has been cleared of activity, for example, handover. - close(); - if (sibling()) sibling()->close(true); - break; - case ERROR: - // If we got here, it means the link failed in L2 after several ack timeouts. - // Close the tx side and just let the receiver L1 time out on its own. - // Otherwise, we risk recycling the channel while the phone's still active. - close(); - break; - default: - LOG(ERR) << "unhandled primitive " << frame.primitive() << " in L2->L1"; - assert(0); - } + //assert(frame.primitive() == DATA); + if (!encActive()) { OBJLOG(INFO) << "XCCHL1Encoder::writeHighSide sending on non-active channel "; } + resync(); + sendFrame(frame); } void XCCHL1Encoder::sendFrame(const L2Frame& frame) { - OBJLOG(DEBUG) << "XCCHL1Encoder " << frame; + OBJLOG(DEBUG) << frame; // Make sure there's something down there to take the bursts. if (mDownstream==NULL) { LOG(WARNING) << "XCCHL1Encoder with no downstream"; @@ -1120,12 +1125,12 @@ void SharedL1Encoder::encode41() // GSM 05.03 4.1.2 // Generate the parity bits. mBlockCoder.writeParityWord(mD,mP); - OBJLOG(DEBUG) << "XCCHL1Encoder u[]=" << mU; + OBJLOG(DEBUG) << "u[]=" << mU; // GSM 05.03 4.1.3 // Apply the convolutional encoder. //mU.encode(mVCoder,mC); mVCoder.encode(mU,mC); - OBJLOG(DEBUG) << "XCCHL1Encoder c[]=" << mC; + OBJLOG(DEBUG) << "c[]=" << mC; } @@ -1211,7 +1216,6 @@ void L1Encoder::transmit(BitVector2 *mI, BitVector2 *mE, const int *qbits) mI[B].segment(0,57).copyToSegment(mBurst,3); mI[B].segment(57,57).copyToSegment(mBurst,88); } - // Copy in the "encrypted" bits, GSM 05.03 4.1.5, 05.02 5.2.3. mBurst.Hl(qbits[qi++]); mBurst.Hu(qbits[qi++]); // Send it to the radio. @@ -1223,9 +1227,9 @@ void L1Encoder::transmit(BitVector2 *mI, BitVector2 *mE, const int *qbits) -void GeneratorL1Encoder::start() +void GeneratorL1Encoder::serviceStart() { - L1Encoder::start(); + //L1Encoder::encStart(); mSendThread.start((void*(*)(void*))GeneratorL1EncoderServiceLoopAdapter,(void*)this); } @@ -1240,7 +1244,7 @@ void *GeneratorL1EncoderServiceLoopAdapter(GeneratorL1Encoder* gen) void GeneratorL1Encoder::serviceLoop() { - while (mRunning) { + while (mRunning && !gBTS.btsShutdown()) { resync(); waitToSend(); generate(); @@ -1250,8 +1254,8 @@ void GeneratorL1Encoder::serviceLoop() -SCHL1Encoder::SCHL1Encoder(L1FEC* wParent) - :GeneratorL1Encoder(0,0,gSCHMapping,wParent), +SCHL1Encoder::SCHL1Encoder(L1FEC* wParent, unsigned wTN) + :GeneratorL1Encoder(0,wTN,gSCHMapping,wParent), mBlockCoder(0x0575,10,25), mU(25+10+4), mE(78), mD(mU.head(25)),mP(mU.segment(25,10)), @@ -1298,8 +1302,8 @@ void SCHL1Encoder::generate() -FCCHL1Encoder::FCCHL1Encoder(L1FEC *wParent) - :GeneratorL1Encoder(0,0,gFCCHMapping,wParent) +FCCHL1Encoder::FCCHL1Encoder(L1FEC *wParent, unsigned wTN) + :GeneratorL1Encoder(0,wTN,gFCCHMapping,wParent) { mBurst.zero(); mFillerBurst.zero(); @@ -1322,9 +1326,9 @@ void FCCHL1Encoder::generate() -void NDCCHL1Encoder::start() +void NDCCHL1Encoder::serviceStart() { - L1Encoder::start(); + //L1Encoder::encStart(); mSendThread.start((void*(*)(void*))NDCCHL1EncoderServiceLoopAdapter,(void*)this); } @@ -1339,7 +1343,7 @@ void *NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder* gen) void NDCCHL1Encoder::serviceLoop() { - while (mRunning) { + while (mRunning && !gBTS.btsShutdown()) { generate(); } } @@ -1360,7 +1364,8 @@ void BCCHL1Encoder::generate() case 1: writeHighSide(gBTS.SI2Frame()); return; case 2: writeHighSide(gBTS.SI3Frame()); return; case 3: writeHighSide(gBTS.SI4Frame()); return; - case 4: writeHighSide(GPRS::GPRSConfig::IsEnabled() ? gBTS.SI13Frame() : gBTS.SI3Frame()); + //case 4: writeHighSide(GPRS::GPRSConfig::IsEnabled() ? gBTS.SI13Frame() : gBTS.SI3Frame()); + case 4: writeHighSide(gBTS.SI13() ? gBTS.SI13Frame() : gBTS.SI3Frame()); return; case 5: writeHighSide(gBTS.SI2Frame()); return; case 6: writeHighSide(gBTS.SI3Frame()); return; @@ -1445,12 +1450,13 @@ void TCHFACCHL1Decoder::writeLowSideRx(const RxBurst& inBurst) } return; // done } - OBJLOG(DEBUG) << "TCHFACCHL1Decoder " << inBurst <writeLowSide(HANDOVER_ACCESS); if (ref == mHandoverRef) { - LOG(NOTICE) << "queuing HANDOVER_ACCESS ref=" << ref; + OBJLOG(NOTICE) << "queuing HANDOVER_ACCESS ref=" << ref; mT3103.reset(); double when = gBTS.clock().systime(inBurst.time()); mHandoverRecord = HandoverRecord(inBurst.RSSI(),inBurst.timingError(),when); - mUpstream->writeLowSide(L2Frame(HANDOVER_ACCESS)); + mUpstream->writeLowSide(L2Message(HANDOVER_ACCESS)); + // (pat) FIXME: We need to set the PHY params from the handover burst so that SACCH will be transmitting the correct TA. } else { - LOG(ERR) << "no inbound handover with reference " << ref; + OBJLOG(ERR) << "no inbound handover with reference " << ref; } return; @@ -1608,18 +1615,21 @@ bool TCHFACCHL1Decoder::processBurst( const RxBurst& inBurst) // 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); + bool traffic = decodeTCH(okFACCH || stolenbits > 5,&mC); // Now keep statistics... if (okFACCH) { - countStolenFrame(4); + countStolenFrame(1); // was 4, why? We are counting frames, which occur every 4 bursts (even though they are spread over 8 bursts.) countBER(mVCoder.getBEC(),378); } else if (traffic) { OBJLOG(DEBUG) <<"TCHFACCHL1Decoder good TCH frame"; - countGoodFrame(4); + countGoodFrame(1); // was 4, why? countBER(mVCoder.getBEC(),378); // Don't let the channel timeout. - ScopedLock lock(mLock); - mT3109.set(); + //ScopedLock lock(mDecLock,__FILE__,__LINE__); + // (pat 4-2014) There are only 3 parity bits on the speech frame so the false-positive detection probability is high, + // resulting in setting T3109 and preventing the channel from being recycled. Not sure what to do, because + // there may not be any FACCH frames to set T3109. + //mT3109.set(); } else countBadFrame(4); @@ -1825,7 +1835,7 @@ bool TCHFRL1Decoder::decodeTCH_GSM(bool stolen,const SoftVector *wC) // Simulate high FER for testing? if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Uplink")) { - LOG(DEBUG) << "simulating dropped uplink vocoder frame at " << mReadTime; + OBJLOG(DEBUG) << "simulating dropped uplink vocoder frame at " << mReadTime; stolen = true; } @@ -1872,7 +1882,7 @@ bool TCHFRL1Decoder::decodeTCH_GSM(bool stolen,const SoftVector *wC) //OBJLOG(DEBUG) <<"TCHFACCHL1Decoder mclass1_c[]=" << mClass1_c.begin()<< "="<dispatch(); } } @@ -2124,9 +2134,9 @@ TCHFACCHL1Encoder::TCHFACCHL1Encoder( } -void TCHFACCHL1Encoder::start() +void TCHFACCHL1Encoder::serviceStart() { - L1Encoder::start(); + //L1Encoder::encStart(); OBJLOG(DEBUG) <<"TCHFACCHL1Encoder"; mEncoderThread.start((void*(*)(void*))TCHFACCHL1EncoderRoutine,(void*)this); } @@ -2134,11 +2144,11 @@ void TCHFACCHL1Encoder::start() -void TCHFACCHL1Encoder::open() +void TCHFACCHL1Encoder::encInit() { // There was more stuff here at one time to justify overriding the default. // But it's gone now. - XCCHL1Encoder::open(); + XCCHL1Encoder::encInit(); mPreviousFACCH = true; } @@ -2238,7 +2248,7 @@ void TCHFACCHL1Encoder::sendFrame( const L2Frame& frame ) OBJLOG(DEBUG) << "TCHFACCHL1Encoder " << frame; // Simulate high FER for testing. if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Downlink")) { - LOG(NOTICE) << "simulating dropped downlink frame at " << mNextWriteTime; + OBJLOG(NOTICE) << "simulating dropped downlink frame at " << mNextWriteTime; return; } mL2Q.write(new L2Frame(frame)); @@ -2258,7 +2268,7 @@ void TCHFACCHL1Encoder::dispatch() // If the channel is not active, wait for a multiframe and return. // Most channels do not need this, becuase they are entirely data-driven // from above. TCH/FACCH, however, must feed the interleaver on time. - if (!active()) { + if (!encActive()) { mNextWriteTime += 26; gBTS.clock().wait(mNextWriteTime); return; @@ -2381,7 +2391,7 @@ void TCHFACCHL1Encoder::dispatch() rollForward(); } - // Updaet the offset for the next transmission. + // Update the offset for the next transmission. if (mOffset==0) mOffset=4; else mOffset=0; @@ -2402,13 +2412,6 @@ void TCHFACCHL1Encoder::interleave31(int blockOffset) } -bool L1Decoder::uplinkLost() const -{ - ScopedLock lock(mLock); - return mT3109.expired(); -} - - void SACCHL1FEC::setPhy(const SACCHL1FEC& other) { @@ -2416,39 +2419,64 @@ void SACCHL1FEC::setPhy(const SACCHL1FEC& other) mSACCHEncoder->setPhy(*other.mSACCHEncoder); } -void SACCHL1FEC::setPhy(float RSSI, float timingError, double wTimestamp) +void SACCHL1FEC::l1InitPhy(float RSSI, float timingError, double wTimestamp) { - mSACCHDecoder->setPhy(RSSI,timingError,wTimestamp); - mSACCHEncoder->setPhy(RSSI,timingError); + mSACCHDecoder->initPhy(RSSI,timingError,wTimestamp); + mSACCHEncoder->initPhy(RSSI,timingError); } - -void MSPhysReportInfo::sacchInit() +void MSPhysReportInfo::sacchInit1() { 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; + mRSSI = gConfig.getNum("GSM.Radio.RSSITarget"); + // The RACH was sent at full power, but full power probably depends on the MS power class. We just use a constant. + mActualMSPower = cInitialPower; + mReportCount = 0; mTimingError = 0; mTimestamp = 0; } +//void MSPhysReportInfo::sacchInit2(float wRSSI, float wTimingError, double wTimestamp) +//{ +// // Used to initialize L1 phy parameters. +// // This is similar to the code for the closed loop tracking, +// // except that there's no damping. +// sacchInit1(); // first make sure everything is inited. +// // Do NOT set mReportCount - we use that to tell when the first measurement report arrives. +// // FIXME: We may want to set the initial power based on the RACH power instead of just using a constant cInitialPower. +// mTimestamp=wTimestamp; +// mTimingError = wTimingError; +// mRSSI = wRSSI; +// OBJLOG(BLATHER) << "SACCHL1Encoder init" <maxPower) mOrderedMSPower=maxPower; - else if (mOrderedMSPowermaxTiming) mOrderedMSTiming=maxTiming; - OBJLOG(INFO) << "SACCHL1Encoder timingError=" << timingError << - " actual=" << actualTiming << " ordered=" << mOrderedMSTiming; +} + + +void SACCHL1Encoder::initPhy(float wRSSI, float wTimingError) +{ + // Used to initialize L1 phy parameters. + // This is similar to the code for the closed loop tracking, except that there's no damping. + //SACCHL1Decoder &sib = *SACCHSibling(); + // RSSI + // (pat 4-2014) This is used only on channel initialization. The RSSI comes from the RACH burst which is + // always delivered at full power. So we want to ignore the actual MS power, which is not known yet. + // Arbitrarily goose the initial power up a little (10) by adjusting RSSITarget, just to make sure we get an ok initialization, + // in case the RSSI measured power was inaccurate, or there is noise in the channel. + //float RSSI = sib.getRSSI(); + float RSSITarget = gConfig.getNum("GSM.Radio.RSSITarget") + 10; + float deltaP = wRSSI - RSSITarget; + //float actualPower = sib.actualMSPower(); // This is just set to cInitialPower. + //setMSPower(actualPower - deltaP); + setMSPower(cInitialPower - deltaP); + //OBJLOG(INFO) <<"SACCHL1Encoder RSSI=" << wRSSI << " target=" << RSSITarget + // << " deltaP=" << deltaP << " actual=" << actualPower << " order=" << mOrderedMSPower; + // Timing Advance + // (pat) This is called at channel init so we have not received any measurement reports from the MS yet, + // so the actualMSTiming must be the initialized value, that is, 0, unless some stray measurement report + // comes in on the channel, in which case we should ignore it. + // float timingError = sib.timingError(); + // float actualTiming = sib.actualMSTiming(); + // mOrderedMSTiming = actualTiming + timingError; + setMSTiming(wTimingError); + //OBJLOG(INFO) << "SACCHL1Encoder init timingError=" << sib.timingError() << " actual=" << sib.actualMSTiming() << " ordered=" << mOrderedMSTiming; + OBJLOG(INFO) << "SACCHL1Encoder init" < 0 && SNR < SNRTarget) { // If RSSITarget is met but SNR looks bad... + // How do we decide what the target power should be from SNR? And I dont want to call log(). + // We only change upward based on SNR - we rely on RSSITarget to keep the power down. + deltaP = SNR - SNRTarget; // So how about something simple like this? eg: SNR == 10 is ok, SNR==6 would be bad, so add 4dB? + } + } float actualPower = sib.actualMSPower(); + int configPowerDamping = gConfig.getNum("GSM.MS.Power.Damping"); + // Use the power damping algorithm. float targetMSPower = actualPower - deltaP; - float powerDamping = gConfig.getNum("GSM.MS.Power.Damping")*0.01F; - mOrderedMSPower = powerDamping*mOrderedMSPower + (1.0F-powerDamping)*targetMSPower; - float maxPower = gConfig.getNum("GSM.MS.Power.Max"); - float minPower = gConfig.getNum("GSM.MS.Power.Min"); - if (mOrderedMSPower>maxPower) mOrderedMSPower=maxPower; - else if (mOrderedMSPowermaxTiming) mOrderedMSTiming=maxTiming; + setMSTiming(TADamping*mOrderedMSTiming + (1.0F-TADamping)*targetMSTiming); OBJLOG(DEBUG) << "SACCHL1Encoder timingError=" << timingError << " actualTA=" << actualTiming << " orderedTA=" << mOrderedMSTiming << " targetTA=" << targetMSTiming; -// } + } // Write physical header into mU and then call base class. // SACCH physical header, GSM 04.04 6.1, 7.1. @@ -2630,7 +2698,7 @@ void TestTCHL1FEC() 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. + int modeii = modei == 0 ? (int)TCH_FS : modei-1; // test full rate GSM first. AMRMode mode = (AMRMode)modeii; unsigned inSize = gAMRKd[mode]; bool ok = true; @@ -2647,7 +2715,7 @@ void TestTCHL1FEC() AudioFrame *aFrame = new AudioFrameRtp(mode); aFrame->append(r); encoder.encodeTCH(aFrame); // Leaves the result in mC - LOG(INFO) < +#include #include "../GPRS/GPRSExport.h" @@ -53,7 +53,7 @@ ViterbiBase *newViterbi(AMRMode mode); /* forward refs */ class GSMConfig; -class SAPMux; +//class SAPMux; class L1FEC; class L1Encoder; @@ -63,6 +63,7 @@ class SACCHL1Encoder; class SACCHL1Decoder; class SACCHL1FEC; class TrafficTranscoder; +class L2LogicalChannelBase; /* Naming convention for bit vectors follows GSM 05.03 Section 2.2. @@ -91,10 +92,13 @@ enum EncryptionType { class L1Encoder { protected: + friend class L1FEC; ARFCNManager *mDownstream; TxBurst mBurst; ///< a preformatted burst template TxBurst mFillerBurst; ///< the filler burst for this channel + // We cannot use a Time object for this because the channel could be idle longer than a hyperframe. + Timeval mFillerSendTime; // When the filler burst was last sent. /**@name Config items that don't change. */ //@{ @@ -107,7 +111,7 @@ class L1Encoder { /**@name Multithread access control and data shared across threads. */ //@{ - mutable Mutex mLock; + mutable Mutex mEncLock; //@} /**@ Internal state. */ @@ -119,12 +123,13 @@ class L1Encoder { // This is totally unlike decoders, for which AFCNManager:receiveBurst uses // the encoder mapping (which it has cached) to send incoming bursts directly // to the mapped L1Decoder::writeLowSideRx() for each frame. - unsigned mTotalBursts; ///< total bursts sent since last open() - GSM::Time mPrevWriteTime; ///< timestamp of pervious generated burst + unsigned mTotalFrames; ///< total frames sent since last open() + GSM::Time mPrevWriteTime; ///< timestamp of previous generated burst GSM::Time mNextWriteTime; ///< timestamp of next generated burst volatile bool mRunning; ///< true while the service loop is running - bool mActive; ///< true between open() and close() + Bool_z mEncActive; ///< true between open() and close() + Bool_z mEncEverActive; // true if the encoder has ever been active. //@} // (pat) Moved to classes that need the convolutional coder. @@ -176,7 +181,11 @@ class L1Encoder { virtual void close(); /** Open the channel for a new transaction. */ - virtual void open(); + virtual void encInit(); + void encStart(); + virtual void serviceStart() {} + + public: /** Set mDownstream handover correlator mode. */ void handoverPending(bool flag); @@ -186,7 +195,7 @@ class L1Encoder { For broadcast and unicast channels this is always true. For dedicated channels, this is taken from the sibling deocder. */ - virtual bool active() const; + virtual bool encActive() const; /** Process pending L2 frames and/or generate filler and enqueue the resulting timeslots. @@ -195,11 +204,10 @@ class L1Encoder { */ virtual void writeHighSide(const L2Frame&) { assert(0); } - /** Start the service loop thread, if there is one. */ - virtual void start() { mRunning=true; } - virtual const char* descriptiveString() const { return mDescriptiveString.c_str(); } + //virtual string debugId() const { static string id; return id.size() ? id : (id=format("%s %s ",typeid(this).name(),descriptiveString())); } + const L1FEC* parent() const { return mParent; } L1FEC* parent() { return mParent; } GSM::Time getNextWriteTime() { resync(); return mNextWriteTime; } @@ -216,18 +224,22 @@ class L1Encoder { virtual const L1Decoder* sibling() const; /** Make sure we're consistent with the current clock. */ - void resync(); + void resync(bool force=false); /** Block until the BTS clock catches up to mPrevWriteTime. */ void waitToSend() const; /** - Send the idle filling pattern, if any. + Send the dummy filling pattern, if any. + (pat) This is not the L2 idle fill pattern! The default is a dummy burst. */ - virtual void sendIdleFill(); - + void sendDummyFill(); + public: + bool l1IsIdle() const; }; +extern ostream& operator<<(std::ostream& os, const L1Encoder *); + struct HandoverRecord { bool mValid; @@ -242,6 +254,8 @@ struct DecoderStats { float mAveFER; float mAveBER; float mAveSNR; + float mLastSNR; // Most recent SNR + float mLastBER; // Most recent BER unsigned mSNRCount; // Number of SNR samples taken. int mStatTotalFrames; int mStatStolenFrames; @@ -262,28 +276,38 @@ struct DecoderStats { class L1Decoder { protected: + friend class L1FEC; // (pat) Not used for GPRS. Or for RACCH. It should be in XCCHL1Decoder. - SAPMux * mUpstream; + L2LogicalChannelBase * mUpstream; // Points to either L2LogicalChannel or SACCHLogicalChannel. /**@name Mutex-controlled state information. */ //@{ - mutable Mutex mLock; ///< access control + mutable Mutex mDecLock; ///< access control /**@name Timers from GSM 04.08 11.1.2 */ - // pat believes these timers should be in XCCHL1Decoder. + // pat thinks these timers should be in XCCHL1Decoder or L2LogicalChannel. + // (pat) GSM 4.08 3.4.13.1.1 is confusing, lets define some terms: + // "main signalling disconnected" means at layer 2, which can be either by the normal release procedure + // that sends a LAPDm DISC and waits for a response, or by "local end release" which means drop the channel immediately. + // T3109 is either: 3.4.13.1.1 the time between deactivation of the SACCH and when the channel is considered recyclable, in which + // case we are relying on the MS to release the channel after RADIO-LINK-TIMEOUT missing SACCH messages, + // or 3.4.13.2.2: the time after detecting a radio failure (by SACCH loss or bad RXLEV as per 5.08 5.2) and when + // the channel is considered recylcable. + // T3111 is shorter than T3109, used when a normal RELEASE is successfully acknoledged by the handset, + // so we dont have to wait the entire T3109 time. + // //@{ - Z100Timer mT3101; ///< timer for new channels - Z100Timer mT3109; ///< timer for existing uplink channels, uplinkLost on expiry - Z100Timer mT3111; ///< timer for reuse of a closed channel + //Z100Timer mT3101; ///< timer for new channels + //Z100Timer mT3109; ///< timer for loss of uplink signal. Need to check both host and SACCH timers. + //Z100Timer mT3111; ///< timer for reuse of a normally closed channel Z100Timer mT3103; ///< timer for handover //@} - bool mActive; ///< true between open() and close() + bool mDecActive; ///< true between open() and close() //@} /**@name Atomic volatiles, no mutex. */ // 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=208; ///< FER decay time, in frames // (pat) Uh, no. It is in frames for control frames, but in frames/4 for TCH frames. @@ -300,6 +324,8 @@ class L1Decoder { unsigned mTN; ///< timeslot number const TDMAMapping& mMapping; ///< demux parameters L1FEC* mParent; ///< a containing L1 processor, if any + /** The channel type. */ + virtual ChannelType channelType() const = 0; //@} // (pat) Moved to classes that use the convolutional coder. @@ -322,10 +348,12 @@ class L1Decoder { */ L1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC* wParent) :mUpstream(NULL), - mT3101(T3101ms),mT3109(T3109ms),mT3111(T3111ms), - mT3103(gConfig.getNum("GSM.Timer.T3103")), - mActive(false), - mRunning(false), + //mT3101(T3101ms), + //mT3109(gConfig.GSM.Timer.T3109), + //mT3111(T3111ms), + mT3103(gConfig.GSM.Timer.T3103), + mDecActive(false), + //mRunning(false), //mFER(0.0F), mCN(wCN),mTN(wTN), mMapping(wMapping),mParent(wParent), @@ -334,7 +362,7 @@ class L1Decoder { { // Start T3101 so that the channel will // become recyclable soon. - mT3101.set(); + //mT3101.set(); } @@ -345,26 +373,24 @@ class L1Decoder { Clear the decoder for a new transaction. Start T3101, stop the others. */ - virtual void open(); + virtual void decInit(); + void decStart(); /** Call this at the end of a tranaction. - Stop timers. If !hardRelase, start T3111. + Stop timers. If releaseType is not hardRelase, start T3111. */ - virtual void close(bool hardRelease=false); + virtual void close(); // (pat) why virtual? + public: /** Returns true if the channel is in use for a transaction. 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; + bool decActive() const; /** Connect the upstream SAPMux and L2. */ - virtual void upstream(SAPMux * wUpstream) + virtual void upstream(L2LogicalChannelBase * wUpstream) { assert(mUpstream==NULL); // Only call this once. mUpstream=wUpstream; @@ -387,22 +413,24 @@ class L1Decoder { TypeAndOffset typeAndOffset() const; ///< this comes from mMapping //@} - /** Control the processing of handover access busts. */ + /** Control the processing of handover access bursts. */ + // flag is true if handover is pending, false otherwise. // OK to pass reference to HandoverRecord since this struct is immortal. HandoverRecord &handoverPending(bool flag, unsigned handoverRef) { LOG(INFO) << LOGVAR(flag); - if (flag) { mT3103.set(); } else { mT3103.reset(); } + if (flag) { mT3103.set(gConfig.GSM.Timer.T3103); } else { mT3103.reset(); } mHandoverPending=flag; mHandoverRef=handoverRef; return mHandoverRecord; } public: - L1FEC* parent() { return mParent; } // pat thinks it is not used virtual. + const L1FEC* parent() const { return mParent; } + L1FEC* parent() { return mParent; } /** How much time left in T3101? */ - long debug3101remaining() { return mT3101.remaining(); } + //long debug3101remaining() { return mT3101.remaining(); } protected: @@ -412,21 +440,29 @@ class L1Decoder { /** Return pointer to paired L1 encoder, if any. */ virtual const L1Encoder* sibling() const; - /** Mark the decoder as started. */ - virtual void start() { mRunning=true; } - public: + // (pat 5-2014) Count bad frames in the BTS the same way as documented for RADIO-LINK-TIMEOUT in the MS, namely, + // increment by one for each bad frame, decrement by two for each good frame. This allows detecting marginal + // conditions, whereas the old way of just using a timer that was reset only required one good frame every timer period, + // which really only detected total channel loss, not a marginal channel. This var is only used on SACCH, + // so we dont bother modifying it in countStolenFrame, since stolen frames are only on TCH/FACCH. + // Note: Spec says frames are always sent on SACCH in both directions as long as channel is up, + // which is not necessarily required on the associated SDCCH or TCH/FACCH. + // Note: Unlike the counts in DecoderStats we are counting message periods, which are 4-frame groups, not individual frames. + int mBadFrameTracker; void countGoodFrame(unsigned nframes=1); - void countBadFrame(unsigned nframes = 1); + virtual void countBadFrame(unsigned nframes = 1); // over-ridden by SACCHL1Decoder 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?"; } + // 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() : "RACH?"; } + //virtual string debugId() const { static string id; return id.size() ? id : (id=format("%s %s ",typeid(this).name(),descriptiveString())); } + std::string displayTimers() const; bool decrypt_maybe(string wIMSI, int wA5Alg); unsigned char *kc() { return mKc; } }; +extern ostream& operator<<(std::ostream& os, const L1Decoder *decp); @@ -526,7 +562,6 @@ class L1Decoder { we implement continuous timing advance. */ class L1FEC { - protected: L1Encoder* mEncoder; @@ -544,7 +579,8 @@ class L1FEC { The L1FEC constructor is over-ridden for different channel types. But the default has no encoder or decoder. */ - L1FEC():mEncoder(NULL),mDecoder(NULL) + L1FEC(): + mEncoder(NULL),mDecoder(NULL) , mGprsReserved(0) , mGPRSFEC(0) {} @@ -572,17 +608,17 @@ class L1FEC { /** Attach L1 to an upstream SAPI mux and L2. */ // (pat) not used for GPRS. - virtual void upstream(SAPMux* mux) + virtual void upstream(L2LogicalChannelBase* mux) { if (mDecoder) mDecoder->upstream(mux); } /** set encoder and decoder handover pending mode. */ HandoverRecord& handoverPending(bool flag, unsigned handoverRef); - /**@name Ganged actions. */ - //@{ - void open(); - void close(); - //@} + void l1init(); // Called from lcopen when channel is created or from getTCH/getSDCCH. + void l1start(); // Called from lcopen when channel is created or from getTCH/getSDCCH. + void l1open() { l1init(); l1start(); } + + void l1close(); // Called from lcopen when channel is created or from getTCH/getSDCCH. /**@name Pass-through actions that concern the physical channel. */ @@ -605,11 +641,12 @@ class L1FEC { float FER() const // Frame Error Rate { assert(mDecoder); return mDecoder->FER(); } - bool recyclable() const // Can we reuse this channel yet? - { assert(mDecoder); return mDecoder->recyclable(); } - - bool active() const; // Channel in use? See L1Encoder - //bool reallyActive() const; // Channel really in use? See L1Decoder. + // Is the channel active? This tests both the mActive flag, which follows the L2 LAPDm state, + // as known by tracking ESTABLISH/RELEASE/ERROR primitives + // and recyclable, which tracks RR failure. + // The testonly flag is used from recyclable to prevent an infinite loop. + bool l1active() const; // Channel in use? See L1Encoder + //L2LogicalChannel *ownerChannel() const; // (pat) This lovely function is unsed. // TRXManager.cpp:installDecoder uses L1Decoder::mapping() directly. @@ -622,6 +659,7 @@ class L1FEC { virtual const char* descriptiveString() const { assert(mEncoder); return mEncoder->descriptiveString(); } + //virtual string debugId() const { static string id; return id.size() ? id : (id=format("%s %s ",typeid(this).name(),descriptiveString())); } //@} @@ -630,12 +668,12 @@ class L1FEC { ARFCNManager *getRadio() { return mEncoder->getRadio(); } bool inUseByGPRS() const { return mGprsReserved; } void setGPRS(bool reserved, GPRS::PDCHL1FEC *pch) { mGprsReserved = reserved; mGPRSFEC = pch; } + std::string displayTimers() const { return mDecoder ? mDecoder->displayTimers() : ""; } L1Decoder* decoder() { return mDecoder; } L1Encoder* encoder() { return mEncoder; } - bool radioFailure() const - { assert(mDecoder); return mDecoder->uplinkLost(); } }; +extern ostream& operator<<(std::ostream& os, const L1FEC *); /** @@ -645,7 +683,7 @@ class TestL1FEC : public L1FEC { private: - SAPMux * mUpstream; + L2LogicalChannelBase * mUpstream; ARFCNManager* mDownstream; public: @@ -654,7 +692,7 @@ class TestL1FEC : public L1FEC { void writeHighSide(const L2Frame&); void downstream(ARFCNManager *wDownstream) { mDownstream=wDownstream; } - void upstream(SAPMux * wUpstream){ mUpstream=wUpstream;} + void upstream(L2LogicalChannelBase * wUpstream){ mUpstream=wUpstream;} }; @@ -671,40 +709,19 @@ class RACHL1Decoder : public L1Decoder 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, - // because the channel allocation process might block - // and we don't want to block the radio receive thread. - // (pat) I dont think this is used. I think TRXManager calls writeLowSideRx directly. - // The serviceLoop is still started, and watches mQ forever, hopefully - // waiting for a burst that never comes. - RxBurstFIFO mQ; ///< a FIFO to decouple the rx thread - - Thread mServiceThread; ///< a thread to process the FIFO - + ChannelType channelType() const { return RACHType; } public: - RACHL1Decoder(const TDMAMapping &wMapping, - L1FEC *wParent) - :L1Decoder(0,0,wMapping,wParent), + RACHL1Decoder(const TDMAMapping &wMapping, L1FEC *wParent, unsigned wTN) + :L1Decoder(0,wTN,wMapping,wParent), mParity(0x06f,6,8),mU(18),mD(mU.head(8)) { } - /** Start the service thread. */ - void start(); - /** Decode the burst and call the channel allocator. */ void writeLowSideRx(const RxBurst&); - - /** A loop to watch the FIFO. */ - void serviceLoop(); - - /** A "C" calling interface for pthreads. */ - friend void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder*); }; -void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder*); @@ -757,6 +774,8 @@ class SharedL1Encoder //void encodeFrame41(const L2Frame &frame, int offset); void encodeFrame41(const BitVector2 &frame, int offset, bool copy=true); void initInterleave(int); + virtual const char* descriptiveString() const = 0; + //string debugId() const { static string id; return id.size() ? id : (id=format("SharedL1Encoder %s ",descriptiveString())); } }; // Shared by RR and GPRS @@ -790,6 +809,8 @@ class SharedL1Decoder public: SharedL1Decoder(); + virtual const char* descriptiveString() const = 0; + //string debugId() const { static string id; return id.size() ? id : (id=format("SharedL1Decoder %s ",descriptiveString())); } void deinterleave(); bool decode(); @@ -820,9 +841,6 @@ class XCCHL1Decoder : /** Offset to the start of the L2 header. */ virtual unsigned headerOffset() const { return 0; } - /** The channel type. */ - virtual ChannelType channelType() const = 0; - /** Accept a timeslot for processing and drive data up the chain. */ virtual void writeLowSideRx(const RxBurst&); @@ -836,6 +854,8 @@ class XCCHL1Decoder : /** Finish off a properly-received L2Frame in mU and send it up to L2. */ virtual void handleGoodFrame(); + const char* descriptiveString() const { return L1Decoder::descriptiveString(); } + //string debugId() const { return L1Decoder::debugId(); } }; @@ -854,42 +874,47 @@ class SDCCHL1Decoder : public XCCHL1Decoder { { } ChannelType channelType() const { return SDCCHType; } + const char* descriptiveString() const { return L1Decoder::descriptiveString(); } }; // 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. + public: // dont know why we bother + unsigned mReportCount; // Number of measurement reports received so far (not including the initial RACH); 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 double mTimestamp; ///< system time of most recent received burst, including the initial RACH burst. 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(); + void sacchInit1(); + void sacchInit2(float wRSSI, float wTimingError, double wTimestamp); public: - MSPhysReportInfo() : - mRSSICount(0), // irrelevant, see sacchInit - mRSSI(0.0F), // irrelevant, see sacchInit - mTimingError(0.0F), // irrelevant, see sacchInit - mTimestamp(0.0) // irrelevant, see sacchInit - {} + // constructor is irrelevant, should call sacchInit via open() before each use, but be tidy. + MSPhysReportInfo() { sacchInit1(); } // Unused init + + bool isValid() const { return mReportCount > 0; } int actualMSPower() const { return mActualMSPower; } int actualMSTiming() const { return mActualMSTiming; } - /** Set pyshical parameters for initialization. */ - void setPhy(float wRSSI, float wTimingError, double wTimestamp); + /** Set physical parameters for initialization. */ + void initPhy(float wRSSI, float wTimingError, double wTimestamp); void setPhy(const SACCHL1Decoder& other); /** RSSI of most recent received burst, in dB wrt full scale. */ - float RSSI() const { return mRSSI; } + float getRSSI() const { return mRSSI; } + float getRSSP() const { + return mRSSI + gConfig.GSM.MS.Power.Max - mActualMSPower; + } /** Artificially push down RSSI to induce the handset to push more power. */ void RSSIBumpDown(float dB) { mRSSI -= dB; } /** Timestamp of most recent received burst. */ + // (pat) Since BTS started, in seconds, with uSec resolution. + // Includes the initial rach burst, which is significant because that one provided only TA, did not have any useful power data. double timestamp() const { return mTimestamp; } /** @@ -899,6 +924,8 @@ class MSPhysReportInfo { float timingError() const { return mTimingError; } void processPhysInfo(const RxBurst &inBurst); MSPhysReportInfo * getPhysInfo() { return this; } + virtual const char* descriptiveString() const = 0; + //string debugId() const { static string id; return id.size() ? id : (id=format("MSPhysReportInfo %s ",descriptiveString())); } }; @@ -932,18 +959,21 @@ class SACCHL1Decoder : public XCCHL1Decoder, public MSPhysReportInfo { ChannelType channelType() const { return SACCHType; } - /** Override open() to set physical parameters with reasonable defaults. */ - void open(); + /** Override decInit() to set physical parameters with reasonable defaults. */ + void decInit(); /** Override processBurst to catch the physical parameters. */ bool processBurst(const RxBurst&); + void countBadFrame(unsigned nframes = 1); // over-ridden by SACCHL1Decoder + // (pat) TODO: We should just keep SNR for the SACCH, it is overkill computing it for the main channel. + float getAveSNR() { return getDecoderStats().mAveSNR; } protected: - SACCHL1FEC *SACCHParent() { return mSACCHParent; } + //unused: SACCHL1FEC *SACCHParent() { return mSACCHParent; } SACCHL1Encoder* SACCHSibling(); @@ -953,7 +983,8 @@ class SACCHL1Decoder : public XCCHL1Decoder, public MSPhysReportInfo { void handleGoodFrame(); unsigned headerOffset() const { return 16; } - + const char* descriptiveString() const { return L1Decoder::descriptiveString(); } + //string debugId() const { return L1Decoder::debugId(); } }; @@ -983,6 +1014,8 @@ class XCCHL1Encoder : /** Send a single L2 frame. */ virtual void sendFrame(const L2Frame&); + const char* descriptiveString() const { return L1Encoder::descriptiveString(); } + //string debugId() const { return L1Encoder::debugId(); } // Moved to SharedL1Encoder //virtual void transmit(BitVector2 *mI); }; @@ -1027,8 +1060,8 @@ class TCHFRL1Encoder : virtual public SharedL1Encoder const unsigned *mPuncture; unsigned mPunctureLth; - void encodeTCH_AFS(const AudioFrame* vFrame); - void encodeTCH_GSM(const AudioFrame* vFrame); + void encodeTCH_AFS(const SIP::AudioFrame* vFrame); + void encodeTCH_GSM(const SIP::AudioFrame* vFrame); void setViterbi(AMRMode wMode) { if (mViterbi) { delete mViterbi; } mViterbi = newViterbi(wMode); @@ -1037,11 +1070,12 @@ class TCHFRL1Encoder : virtual public SharedL1Encoder 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. + void encodeTCH(const SIP::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); } + //string debugId() const { static string id; return id.size() ? id : (id=format("TCHFRL1Encoder %s ",descriptiveString())); } }; @@ -1074,10 +1108,10 @@ public: L1FEC* wParent); /** Enqueue a traffic frame for transmission by the FEC to be sent to the radio. */ - void sendTCH(AudioFrame *frame) { mSpeechQ.write(frame); } + void sendTCH(SIP::AudioFrame *frame) { mSpeechQ.write(frame); } /** Extend open() to set up semaphores. */ - void open(); + void encInit(); protected: @@ -1096,8 +1130,9 @@ protected: void dispatch(); /** Will start the dispatch thread. */ - void start(); + void serviceStart(); + //string debugId() const { static string id; return id.size() ? id : (id=format("TCHFACCHL1Encoder %s ",descriptiveString())); } }; @@ -1133,7 +1168,7 @@ class TCHFRL1Decoder : virtual public SharedL1Decoder 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. + virtual void addToSpeechQ(SIP::AudioFrame *newFrame) = 0; // Where the upstream result from decodeTCH goes. void setViterbi(AMRMode wMode) { if (mViterbi) { delete mViterbi; } mViterbi = newViterbi(wMode); @@ -1149,6 +1184,7 @@ class TCHFRL1Decoder : virtual public SharedL1Decoder // (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); } + //string debugId() const { static string id; return id.size() ? id : (id=format("TCHFRL1Decoder %s ",descriptiveString())); } }; /** L1 decoder used for full rate TCH and FACCH -- mostly from GSM 05.03 3.1 and 4.2 */ @@ -1206,13 +1242,14 @@ class TCHFACCHL1Decoder : Non-blocking. Returns NULL if queue is dry. Caller is responsible for deleting the returned array. */ - AudioFrame *recvTCH() { return mSpeechQ.read(0); } - void addToSpeechQ(AudioFrame *newFrame); // write the audio frame into the mSpeechQ + SIP::AudioFrame *recvTCH() { return mSpeechQ.read(0); } + void addToSpeechQ(SIP::AudioFrame *newFrame); // write the audio frame into the mSpeechQ /** Return count of internally-queued traffic frames. */ unsigned queueSize() const { return mSpeechQ.size(); } + const char* descriptiveString() const { return L1Decoder::descriptiveString(); } + //string debugId() const { static string id; return id.size() ? id : (id=format("TCHFACCHL1Decoder %s ",descriptiveString())); } - /** Return true if the uplink is dead. */ // (pat) 3-29: Moved this higher in the hierarchy so it can be shared with SDCCH. //bool uplinkLost() const; }; @@ -1241,7 +1278,7 @@ class GeneratorL1Encoder : :L1Encoder(wCN,wTN,wMapping,wParent) { } - void start(); + void serviceStart(); protected: @@ -1279,7 +1316,7 @@ class SCHL1Encoder : public GeneratorL1Encoder { public: - SCHL1Encoder(L1FEC* wParent); + SCHL1Encoder(L1FEC* wParent,unsigned wTN); const char* descriptiveString() const { return "SCH"; } @@ -1301,8 +1338,9 @@ class FCCHL1Encoder : public GeneratorL1Encoder { public: - FCCHL1Encoder(L1FEC *wParent); + FCCHL1Encoder(L1FEC *wParent, unsigned wTN); const char* descriptiveString() const { return "FCCH"; } + //string debugId() const { static string id; return id.size() ? id : (id=format("FCCHL1Encoder %s ",descriptiveString())); } protected: @@ -1333,7 +1371,7 @@ class NDCCHL1Encoder : public XCCHL1Encoder { :XCCHL1Encoder(wCN, wTN, wMapping, wParent) { } - void start(); + void serviceStart(); protected: @@ -1343,6 +1381,7 @@ class NDCCHL1Encoder : public XCCHL1Encoder { void serviceLoop(); friend void *NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder*); + //string debugId() const { static string id; return id.size() ? id : (id=format("NDCCHL1Encoder %s ",descriptiveString())); } }; void *NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder*); @@ -1356,10 +1395,11 @@ class BCCHL1Encoder : public NDCCHL1Encoder { public: - BCCHL1Encoder(L1FEC *wParent) - :NDCCHL1Encoder(0,0,gBCCHMapping,wParent) + BCCHL1Encoder(L1FEC *wParent, unsigned wTN) + :NDCCHL1Encoder(0,wTN,gBCCHMapping,wParent) {} const char* descriptiveString() const { return "BCCH"; } + //string debugId() const { static string id; return id.size() ? id : (id=format("BCCHL1Encoder %s ",descriptiveString())); } private: @@ -1368,7 +1408,7 @@ class BCCHL1Encoder : public NDCCHL1Encoder { /** - L1 decoder for the SACCH. + L1 encoder for the SACCH. Like any other control channel, but with hooks for power/timing control. The SI5 and SI6 generation will be handled in higher layers. */ @@ -1392,27 +1432,30 @@ class SACCHL1Encoder : public XCCHL1Encoder { // (pat) commented out: never used. //void orderedMSPower(int power) { mOrderedMSPower = power; } //void orderedMSTiming(int timing) { mOrderedMSTiming = timing; } + void setMSPower(float orderedMSPower); + void setMSTiming(float orderedTiming); void setPhy(const SACCHL1Encoder&); - void setPhy(float RSSI, float timingError); + void initPhy(float RSSI, float timingError); virtual const char* descriptiveString() const; /** Override open() to initialize power and timing. */ - void open(); - - //bool active() const { return true; } + void encInit(); protected: + // (pat 5-2014) bool encActive() const { return true; } // (pat) WRONG! The SACCH is deactivated before releasing the channel. - SACCHL1FEC *SACCHParent() { return mSACCHParent; } + //unused: SACCHL1FEC *SACCHParent() { return mSACCHParent; } SACCHL1Decoder *SACCHSibling(); unsigned headerOffset() const { return 16; } - /** A warpper to send an L2 frame with a physical header. */ + /** A warpper to send an L2 frame with a physical header. (pat) really really fast. */ virtual void sendFrame(const L2Frame&); + //string debugId() const { static string id; return id.size() ? id : (id=format("SACCHL1Encoder %s ",descriptiveString())); } + }; @@ -1426,9 +1469,10 @@ class CCCHL1Encoder : public XCCHL1Encoder { public: CCCHL1Encoder(const TDMAMapping& wMapping, - L1FEC* wParent) - :XCCHL1Encoder(0,0,wMapping,wParent) + L1FEC* wParent, unsigned wTN) + :XCCHL1Encoder(0,wTN,wMapping,wParent) {} + //string debugId() const { static string id; return id.size() ? id : (id=format("CCCHL1Encoder %s ",descriptiveString())); } }; @@ -1445,6 +1489,7 @@ class CBCHL1Encoder : public XCCHL1Encoder { /** Override sendFrame to meet sync requirements of GSM 05.02 6.5.4. */ virtual void sendFrame(const L2Frame&); + //string debugId() const { static string id; return id.size() ? id : (id=format("CBCHL1Encoder %s ",descriptiveString())); } }; @@ -1466,6 +1511,7 @@ class SDCCHL1FEC : public L1FEC { mEncoder = new SDCCHL1Encoder(wCN,wTN,wMapping.downlink(),this); mDecoder = new SDCCHL1Decoder(wCN,wTN,wMapping.uplink(),this); } + //string debugId() const { static string id; return id.size() ? id : (id=format("SDCCHL1FEC %s ",descriptiveString())); } }; @@ -1482,6 +1528,7 @@ class CBCHL1FEC : public L1FEC { { mEncoder = new CBCHL1Encoder(wMapping.downlink(),this); } + //string debugId() const { static string id; return id.size() ? id : (id=format("CBCHL1FEC %s ",descriptiveString())); } }; @@ -1516,7 +1563,7 @@ public: } /** Send a traffic frame. */ - void sendTCH(AudioFrame * frame) + void sendTCH(SIP::AudioFrame * frame) { assert(mTCHEncoder); mTCHEncoder->sendTCH(frame); } /** @@ -1526,15 +1573,13 @@ public: Returns NULL is no data available. */ //unsigned char* recvTCH() - AudioFrame* recvTCH() + SIP::AudioFrame* recvTCH() { assert(mTCHDecoder); return mTCHDecoder->recvTCH(); } unsigned queueSize() const { assert(mTCHDecoder); return mTCHDecoder->queueSize(); } - // (pat) 3-29: Moved this higher in the hierarchy so it can be shared with SDCCH. - //bool radioFailure() const - //{ assert(mTCHDecoder); return mTCHDecoder->uplinkLost(); } + //string debugId() const { static string id; return id.size() ? id : (id=format("TCHFACCHL1FEC %s ",descriptiveString())); } }; @@ -1562,6 +1607,8 @@ class SACCHL1FEC : public L1FEC { SACCHL1Decoder *decoder() { return mSACCHDecoder; } SACCHL1Encoder *encoder() { return mSACCHEncoder; } + //void sacchl1open1(); + //void sacchl1open2(float wRSSI, float wTimingError, double wTimestamp); virtual const char* descriptiveString() const { return mSACCHEncoder->descriptiveString(); } @@ -1571,7 +1618,7 @@ class SACCHL1FEC : public L1FEC { 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); + /*virtual*/ void l1InitPhy(float RSSI, float timingError, double wTimestamp); //@} }; @@ -1601,10 +1648,10 @@ class CCCHL1FEC : public L1FEC { public: - CCCHL1FEC(const TDMAMapping& wMapping) + CCCHL1FEC(const TDMAMapping& wMapping, unsigned wTN) :L1FEC() { - mEncoder = new CCCHL1Encoder(wMapping,this); + mEncoder = new CCCHL1Encoder(wMapping,this,wTN); } }; @@ -1622,7 +1669,7 @@ class NDCCHL1FEC : public L1FEC { NDCCHL1FEC():L1FEC() {} - void upstream(SAPMux*){ assert(0);} + void upstream(L2LogicalChannelBase*){ assert(0);} }; @@ -1630,9 +1677,9 @@ class FCCHL1FEC : public NDCCHL1FEC { public: - FCCHL1FEC():NDCCHL1FEC() + FCCHL1FEC(unsigned wTN=0):NDCCHL1FEC() { - mEncoder = new FCCHL1Encoder(this); + mEncoder = new FCCHL1Encoder(this,wTN); } }; @@ -1642,10 +1689,10 @@ class RACHL1FEC : public NDCCHL1FEC { public: - RACHL1FEC(const TDMAMapping& wMapping) + RACHL1FEC(const TDMAMapping& wMapping, unsigned wTN) :NDCCHL1FEC() { - mDecoder = new RACHL1Decoder(wMapping,this); + mDecoder = new RACHL1Decoder(wMapping,this,wTN); } }; @@ -1654,9 +1701,9 @@ class SCHL1FEC : public NDCCHL1FEC { public: - SCHL1FEC():NDCCHL1FEC() + SCHL1FEC(unsigned wTN=0):NDCCHL1FEC() { - mEncoder = new SCHL1Encoder(this); + mEncoder = new SCHL1Encoder(this,wTN); } }; @@ -1665,9 +1712,9 @@ class BCCHL1FEC : public NDCCHL1FEC { public: - BCCHL1FEC():NDCCHL1FEC() + BCCHL1FEC(unsigned wTN=0):NDCCHL1FEC() { - mEncoder = new BCCHL1Encoder(this); + mEncoder = new BCCHL1Encoder(this,wTN); } }; diff --git a/GSM/GSML2LAPDm.cpp b/GSM/GSML2LAPDm.cpp index 496d6da..7221de2 100644 --- a/GSM/GSML2LAPDm.cpp +++ b/GSM/GSML2LAPDm.cpp @@ -1,9 +1,10 @@ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 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. +* licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,9 +15,12 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + /* Many elements follow Daniele Orlandi's vISDN/Q.921 implementation, although no code is copied directly. +6-2014: Pat Thompson rewrote the Layer1 and layer3 connections, primitives and messages. */ @@ -33,7 +37,8 @@ implementation, although no code is copied directly. */ #include "GSML2LAPDm.h" -#include "GSMSAPMux.h" +//#include "GSMSAPMux.h" +#include "GSMLogicalChannel.h" #include #include #include @@ -60,31 +65,21 @@ ostream& GSM::operator<<(ostream& os, LAPDState state) -void CCCHL2::l2WriteHighSide(const GSM::L3Frame& l3) -{ - OBJLOG(DEBUG) <<"CCCHL2::writeHighSide " << l3; - assert(mL2Downstream); - assert(l3.primitive()==UNIT_DATA); - L2Header header(L2Length(l3.L2Length())); - mL2Downstream->writeHighSide(L2Frame(header,l3,true)); -} - - - L2LAPDm::L2LAPDm(unsigned wC, SAPI_t wSAPI) :mRunning(false), mC(wC),mR(1-wC),mSAPI(wSAPI), mMaster(NULL), mState(LAPDStateUnused), mT200(T200ms), - mIdleFrame(DATA) + mIdleFrame(/*DATA*/) { // sanity checks assert(mC<2); //assert(mSAPI<4); assert(mSAPI == SAPI0 || mSAPI == SAPI3); // SAPI 1 and 2 are never used in the spec. - clearState(); + //releaseLink(true,L3_RELEASE_INDICATION); // (pat) This sends release in both directions, which is undesirable at initialization. + clearCounters(); // Set the idle frame as per GSM 04.06 5.4.2.3. mIdleFrame.fillField(8*0,(mC<<1)|1,8); // address @@ -97,12 +92,12 @@ L2LAPDm::L2LAPDm(unsigned wC, SAPI_t wSAPI) void L2LAPDm::writeL1(const L2Frame& frame) { OBJLOG(DEBUG) <<"L2LAPDm::writeL1 " << frame; - //assert(mL2Downstream); - if (!mL2Downstream) return; + if (!mL2Downstream) return; // only possible during initialization, and maybe not even then. // It is tempting not to lock this, but if we don't, // the ::open operation can result in contention in L1. ScopedLock lock(mL1Lock); - mL2Downstream->writeHighSide(frame); + //mL2Downstream->sapWriteHighSide(frame); // (pat) This blocks in L1Encoder::transmit until the time has passed. + mL2Downstream->writeToL1(frame); // (pat) This blocks in L1Encoder::transmit until the start time has passed. } @@ -110,21 +105,18 @@ void L2LAPDm::writeL1(const L2Frame& frame) // 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); + OBJLOG(DEBUG) << LOGVAR(frame->size())<primitive()); + //mL3Out.write(frame); + mL2Downstream->writeToL3(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); -} +// If SACCHLogicalChannel::serviceLoop() does not handle the message, then it will come here and go up to Layer3. +//void SACCHL2::writeL3(L3Frame *frame) +//{ +// OBJLOG(DEBUG) << LOGVAR(frame->size()); +// mL3Out.write(frame); +//} void L2LAPDm::writeL1NoAck(const L2Frame& frame) @@ -138,7 +130,8 @@ 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. + // Caller should hold mLock. (pat) Because we are modifying mSentFrame. + // (pat) Lock is held when called from sendMultiFrameData and sendUFrameDISC, but not sendUFrameSABM. Why? I'm fixing it. // GSM 04.06 5.4.4.2 OBJLOG(DEBUG) <<"L2LAPDm::writeL1Ack " << frame; frame.copyTo(mSentFrame); @@ -170,13 +163,21 @@ void L2LAPDm::waitForAck() -void L2LAPDm::releaseLink(Primitive releaseType) +// (pat 4-2014) The high side sends a L3ChannelRelease and then a RELEASE or HARDRELEASE. +// The handset may send the UFrameDISC before we send ours, in which case this sends a RELEASE +// down to L1FEC which does a close() on the main channel, and subsequently we get the RELEASE from above +// and send the UFrameDISC resulting in a "writeHighSide sending on non-active channel" message. +// Also note that the handset may continue to send up measurement reports for some time after the main channel is closed. +// I dont see where we make the SACCH non active; I think we just wait for valid messages to stop coming and it times out via T3109. +void L2LAPDm::releaseLink(bool notifyL3, Primitive releaseType) { - OBJLOG(DEBUG);//<< "mState=" << mState; + OBJLOG(DEBUG) <L2 " << frame; - assert(0); + devassert(0); } } @@ -418,7 +434,7 @@ void L2LAPDm::l2WriteHighSide(const L3Frame& frame) -void L2LAPDm::writeLowSide(const L2Frame& frame) +void L2LAPDm::l2dlWriteLowSide(const L2Frame& frame) { OBJLOG(DEBUG) << frame; mL1In.write(new L2Frame(frame)); @@ -426,7 +442,7 @@ void L2LAPDm::writeLowSide(const L2Frame& frame) -void L2LAPDm::serviceLoop() +void L2LAPDm::lapServiceLoop() { mLock.lock(); while (mRunning) { @@ -435,20 +451,27 @@ void L2LAPDm::serviceLoop() // Add 2 ms to prevent race condition due to roundoff error. unsigned timeout = mT200.remaining() + 2; // If SAP0 is released, other SAPs need to release also. - if (mMaster) { - if (mMaster->mState==LinkReleased) { - OBJLOG(DEBUG) << "master release"; - mState=LinkReleased; - } - } + + // (pat 6-2014) This seems to be a bug fix to compensate for improper initialization, + // and it depended on the mMaster only being set on the host channel, + // since if you set it on sacch it will keep anything from ever going through since sap0 is always in idle (LinkReleased) state. + // if (mMaster) { + // if (mMaster->mState==LinkReleased) { + // OBJLOG(DEBUG) << "master release"; + // mState=LinkReleased; + // } + // } + if (!mT200.active()) { if (mState==LinkReleased) timeout=3600000; - else timeout = T200(); // currently 3.6sec + else timeout = T200(); // currently 3.6sec (pat) Looks like .9 secs on TCH,SDCCH and 4*.9 on SACCH. Why? + // (pat) FIXME: T3111 must be longer that this, right? } 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); + LOG(DEBUG); mLock.lock(); if (frame!=NULL) { //OBJLOG(DEBUG) << "state=" << mState << " received " << *frame; @@ -472,13 +495,16 @@ void L2LAPDm::T200Expiration() OBJLOG(INFO) << "state=" << mState << " RC=" << mRC; mT200.reset(); switch (mState) { - case AwaitingRelease: - releaseLink(); - break; + case AwaitingRelease: // GSM 4.06 5.4.4.3 + // (pat) 5.4.4.3 says retransmit the DISC. + // old: releaseLink(MDL_ERROR_INDICATION); break; + // Fall Through case ContentionResolution: case LinkEstablished: - case AwaitingEstablish: - if (mRC>N200()) abnormalRelease(); + case AwaitingEstablish: // (pat) SABM sent. Should only happen on SAPI 3 since MS establishes link SAPI 0 + // (pat) 4.06 5.5.7: L2 is supposed to query L3 whether to release link or attempt re-establishment, but we just release. + // It either does nothing ("local end release") or starts a normal release procedure which would send a DISC. + if (mRC>N200()) abnormalRelease(false); // (pat) do not send DM. If anything, we would send DISC. else retransmissionProcedure(); break; default: @@ -494,6 +520,7 @@ void L2LAPDm::T200Expiration() +// (pat) Receive a frame from layer 1. void L2LAPDm::receiveFrame(const GSM::L2Frame& frame) { OBJLOG(DEBUG) << frame; @@ -516,11 +543,14 @@ void L2LAPDm::receiveFrame(const GSM::L2Frame& frame) */ switch (frame.primitive()) { - case ESTABLISH: + //case ESTABLISH: + case PH_CONNECT: // This primitive means the first L2 frame is on the way. + // (pat) We dont send an ESTABLISH primitive upstream to L3 until we get the SABM command. clearCounters(); + mState = LinkReleased; // Idle state. break; - case DATA: + case L2_DATA: // Dispatch on the frame type. switch (frame.controlFormat()) { case L2Control::IFormat: receiveIFrame(frame); break; @@ -529,6 +559,7 @@ void L2LAPDm::receiveFrame(const GSM::L2Frame& frame) } break; case HANDOVER_ACCESS: + devassert(0); // This primitive should now be handled in L2. writeL3(new L3Frame(mSAPI,HANDOVER_ACCESS)); break; default: @@ -550,7 +581,7 @@ void L2LAPDm::receiveUFrame(const L2Frame& frame) case L2Control::UAFrame: receiveUFrameUA(frame); break; default: OBJLOG(NOTICE) << " could not parse U-Bits " << frame; - unexpectedMessage(); + unexpectedMessage(1); return; } } @@ -578,13 +609,13 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) clearCounters(); mEstablishmentInProgress = true; // Tell L3 what happened. - writeL3(new L3Frame(mSAPI,ESTABLISH)); + writeL3(new L3Frame(mSAPI,L3_ESTABLISH_INDICATION)); if (frame.L()) { // Presence of an L3 payload indicates contention resolution. // GSM 04.06 5.4.1.4. mState=ContentionResolution; mContentionCheck = frame.sum(); - writeL3(new L3Frame(mSAPI,frame.L3Part(),DATA)); + writeL3(new L3Frame(mSAPI,frame.L3Part(),L3_DATA)); // Echo back payload. sendUFrameUA(frame); } else { @@ -593,6 +624,7 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) } break; case ContentionResolution: + // (pat) FIXME: This is wrong... Read the spec. // GSM 04.06 5.4.1.4 // This guards against the remote possibility that two handsets // are sending on the same channel at the same time. @@ -620,7 +652,8 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) break; } if (frame.L()) { - abnormalRelease(); + // (pat) Means SABM includes a command, which would attempt to re-establish contention resolution. + abnormalRelease(true); // (pat) send DM break; } // Re-establishment procedure, GSM 04.06 5.6.3. @@ -630,7 +663,7 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame) clearCounters(); break; default: - unexpectedMessage(); + unexpectedMessage(2); return; } } @@ -644,27 +677,28 @@ void L2LAPDm::receiveUFrameDISC(const L2Frame& frame) mEstablishmentInProgress = false; switch (mState) { case AwaitingEstablish: - clearState(); + releaseLink(true,L3_RELEASE_INDICATION); break; case LinkReleased: // GSM 04.06 5.4.5 sendUFrameDM(frame.PF()); - clearState(); + releaseLink(false,(Primitive)0); break; case ContentionResolution: case LinkEstablished: // Shut down the link and ack with UA. // GSM 04.06 5.4.4.2. sendUFrameUA(frame.PF()); - clearState(); + releaseLink(true,L3_RELEASE_INDICATION); break; case AwaitingRelease: // We can arrive here if both ends sent DISC at the same time. // GSM 04.06 5.4.6.1. sendUFrameUA(frame.PF()); + releaseLink(true,L3_RELEASE_CONFIRM); break; default: - unexpectedMessage(); + unexpectedMessage(3); return; } } @@ -679,7 +713,7 @@ void L2LAPDm::receiveUFrameUA(const L2Frame& 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(); + unexpectedMessage(4); return; } @@ -689,29 +723,36 @@ void L2LAPDm::receiveUFrameUA(const L2Frame& frame) clearCounters(); mState = LinkEstablished; mAckSignal.signal(); - writeL3(new L3Frame(mSAPI,ESTABLISH)); + // We used to use L3_ESTABLISH_INDICATION instead of L3_ESTABLISH_CONFIRM since there is no reason to distinguish between them. + writeL3(new L3Frame(mSAPI,L3_ESTABLISH_CONFIRM)); break; case AwaitingRelease: // We sent DISC and the peer responded. - clearState(); + releaseLink(true,L3_RELEASE_CONFIRM); break; case LinkEstablished: // Pat added: This is probably just a duplicate SABM establishment acknowledgement. // We could check more carefully if that is true, but who cares... break; + case ContentionResolution: + // (pat 3-13-2014) It is a bug to get a UA in ContentionResolution, but the Blackberry does this sometimes + // if you do two testcalls back to back, so dont abort the link because of it... + // (pat 4-7-2014) Update: The problem occurs when you do an endcall followed by a testcall in less than 30 seconds. + // (pat 6-2014) and that may be because we did not deactivate SACCH properly at the end of the call? + LOG(NOTICE) << "Ignoring unexpected LAPDm UA frame in ContentionResolution mode, which may be a bug in the MS."; + return; default: - unexpectedMessage(); + unexpectedMessage(5); return; } } +// (pat) Disconnect Mode response. Indicates some error in the peer. Per 5.4.2 table 7 we give up. void L2LAPDm::receiveUFrameDM(const L2Frame& frame) { // Caller should hold mLock. OBJLOG(INFO) << frame; - // GSM 04.06 5.4.5 - if (mState==LinkReleased) return; - // GSM 04.06 5.4.6.3 + // GSM 04.06 5.4.6.3: "A received DM with F=0 colliding with SABM or DISC is ignored." if (!frame.PF()) return; // Because we do not support multiple TEIs in LAPDm, @@ -721,7 +762,24 @@ void L2LAPDm::receiveUFrameDM(const L2Frame& frame) // able to establish ABM, so if we get this message // we know the channel is screwed up. - clearState(); + // 5.4.6.2: + switch (mState) { + case LAPDStateUnused: + case LinkReleased: + // GSM 04.06 5.4.5: In idle state all "other" frame types ignored. + return; + case AwaitingRelease: + // GSM 4.06 5.4.4.2 + releaseLink(true,L3_RELEASE_CONFIRM); + return; + case AwaitingEstablish: + case LinkEstablished: + case ContentionResolution: + // GSM 4.06 4.1.3.5: Unsolicited DM response is an error. + releaseLink(true,MDL_ERROR_INDICATION); + return; + } + } @@ -731,7 +789,7 @@ void L2LAPDm::receiveUFrameUI(const L2Frame& frame) // The zero-length frame is the idle frame. if (frame.L()==0) return; OBJLOG(INFO) << frame; - writeL3(new L3Frame(mSAPI,frame.alias().tail(24),UNIT_DATA)); + writeL3(new L3Frame(mSAPI,frame.alias().tail(24),L3_UNIT_DATA)); } @@ -747,7 +805,7 @@ void L2LAPDm::receiveSFrame(const L2Frame& frame) switch (frame.SFrameType()) { case L2Control::RRFrame: receiveSFrameRR(frame); break; case L2Control::REJFrame: receiveSFrameREJ(frame); break; - default: unexpectedMessage(); return; + default: unexpectedMessage(6); return; } } @@ -806,12 +864,12 @@ void L2LAPDm::receiveSFrameREJ(const L2Frame& frame) if (frame.PF()) { if (frame.CR()!=mC) sendSFrameRR(true); else { - unexpectedMessage(); + unexpectedMessage(7); return; } } // Since k=1, there's really nothing to retransmit, - // other than what was just rejected, so kust stop sending it. + // other than what was just rejected, so just stop sending it. sendIdle(); break; default: @@ -819,6 +877,8 @@ void L2LAPDm::receiveSFrameREJ(const L2Frame& frame) break; } // Send an idle frame to clear any repeating junk on the channel. + // (pat 5-2014) these sendIdle are unnecessary and possibly wrong. On SAPI 3 we send SIB5/SIB6 and on SAPI0 + // the idle frame is sent by layer1 dispatch only if necessary. sendIdle(); } @@ -961,18 +1021,25 @@ void L2LAPDm::sendUFrameUI(const L3Frame& l3) // GSM 04.06 3.8.4, 5.3.2, not supporting the short header format. // The caller need not hold mLock. OBJLOG(INFO) << "payload=" << l3; + devassert(mC == 1); // C/R is always 1 for commands except for testing where we could make a second dummy LAPDm to emulate a handset. L2Address address(mC,mSAPI); - L2Control control(L2Control::UFormat,1,0x00); + // (pat 6-2014) The U format is used for both control frames (SABM, DISC, DM, UA) and for unacknowledged information transfer. + // The P/F bit is used for the former, but not for the latter. + // GSM 4.06 5.2.1: "For unacknowledged information transfer with normal L2 header, the P/F bit is not used and shall be set to "0". + // Also GSM 4.06 5.3.2 says P is set to 0. + // This formerly set P to 1, which was wrong, and doug corrected it below for one message type, but it is wrong for all. + bool PFPollBit = false; + L2Control control(L2Control::UFormat,PFPollBit,0x00); L2Length length(l3.L2Length()); L2Header header(address,control,length); L2Frame l2f = L2Frame(header, l3); // (pat) Switches primitive to DATA even if l3 primitive was UNIT_DATA - // FIXME - - // The correct solution is to build an L2 frame in RadioResource.cpp and control the bits explicitly up there. - // But I don't know if the LogcialChannel class has a method for sending frames directly into L2. - if (l3.PD() == L3RadioResourcePD && l3.MTI() == L3RRMessage::PhysicalInformation) { - l2f.CR(true); - l2f.PF(false); - } + // doug? FIXME - + /**** pat 6-2014 corrected above. + * if (l3.PD() == L3RadioResourcePD && l3.MTI() == L3RRMessage::PhysicalInformation) { + * l2f.CR(true); // (pat) It already is 1; it is always 1 for commands from the BTS. That is the mC above in L2Address above. + * l2f.PF(false); + * } + */ writeL1NoAck(l2f); } @@ -980,7 +1047,7 @@ void L2LAPDm::sendUFrameUI(const L3Frame& l3) -void L2LAPDm::sendMultiframeData(const L3Frame& l3) +bool L2LAPDm::sendMultiframeData(const L3Frame& l3) { // See GSM 04.06 5.8.5 OBJLOG(INFO) << "payload=" << l3; @@ -992,12 +1059,12 @@ void L2LAPDm::sendMultiframeData(const L3Frame& l3) // in SACCH L3 during release. if (mState==LinkReleased) { OBJLOG(ERR) << "attempt to send DATA on released LAPm channel"; - abnormalRelease(); + abnormalRelease(false); // (pat) Do not send DM, just send (probably a repeat) RELEASE to L3 to tell it to stop sending us data. // pat 5-2013: Vastly reducing the delays here and in L2LogicalChannel to try to reduce // random failures of handover and channel reassignment from SDCCH to TCHF. // sleepFrames(51); - sleepFrames(4); - return; + sleepFrames(4); // FIXME - this is just dopey. + return false; } mDiscardIQueue = false; size_t bitsRemaining = l3.size(); @@ -1017,11 +1084,11 @@ void L2LAPDm::sendMultiframeData(const L3Frame& l3) // Did we abort multiframe mode while waiting? if (mDiscardIQueue) { OBJLOG(DEBUG) <<"aborting (discard)"; - break; + return false; } if ((mState!=LinkEstablished) && (mState!=ContentionResolution)) { OBJLOG(DEBUG) << "aborting, state=" << mState; - break; + return false; } OBJLOG(DEBUG) << " sendIndex=" << sendIndex << " thisChunkSize=" << thisChunkSize @@ -1030,6 +1097,7 @@ void L2LAPDm::sendMultiframeData(const L3Frame& l3) sendIndex += thisChunkSize; bitsRemaining -= thisChunkSize; } + return true; // success } @@ -1064,13 +1132,13 @@ bool L2LAPDm::stuckChannel(const L2Frame& frame) -void CBCHL2::l2WriteHighSide(const GSM::L3Frame& l3) +void CBCHL2::l2dlWriteHighSide(const GSM::L3Frame& l3) { OBJLOG(DEBUG) <<"CBCHL2 incoming L3 frame: " << l3; assert(mL2Downstream); - assert(l3.primitive()==UNIT_DATA); + assert(l3.primitive()==L3_UNIT_DATA); assert(l3.size()==88*8); - L2Frame outFrame(DATA); + L2Frame outFrame(L2_DATA); // (pat) TODO: get rid of the redundant idle fill in the constructor here. // Chop the L3 frame into 4 L2 frames. for (unsigned i=0; i<4; i++) { outFrame.fillField(0,0x02,4); @@ -1078,7 +1146,8 @@ void CBCHL2::l2WriteHighSide(const GSM::L3Frame& l3) const BitVector2 thisSeg = l3.cloneSegment(i*22*8,22*8); thisSeg.copyToSegment(outFrame,8); OBJLOG(DEBUG) << "CBCHL2 outgoing L2 frame: " << outFrame; - mL2Downstream->writeHighSide(outFrame); + //mL2Downstream->sapWriteHighSide(outFrame); + mL2Downstream->writeToL1(outFrame); } } @@ -1087,13 +1156,11 @@ void L2LAPDm::text(std::ostream&os) const { os <<" LAPDm(" <mState); } diff --git a/GSM/GSML2LAPDm.h b/GSM/GSML2LAPDm.h index 74db6ea..7555f9c 100644 --- a/GSM/GSML2LAPDm.h +++ b/GSM/GSML2LAPDm.h @@ -4,7 +4,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -32,7 +32,8 @@ implementation, although no code is copied directly. namespace GSM { // Forward refs. -class SAPMux; +class L2SAPMux; +class L2LogicalChannelBase; /**@name L2 Processing Errors */ //@{ @@ -42,8 +43,8 @@ class SAPMux; //unused: #define L2_READ_ERROR {throw L2ReadError();} /** L2 Write Error is thrown if there is an error in the data on the output side. */ -//unused: class L2WriteError : public GSMError { }; -//unused: #define L2_WRITE_ERROR {throw L2WriteError();} +//unused: class l2dlWriteError : public GSMError { }; +//unused: #define L2_WRITE_ERROR {throw l2dlWriteError();} //@} /** @@ -52,7 +53,7 @@ class SAPMux; */ enum LAPDState { LAPDStateUnused, - LinkReleased, + LinkReleased, // (pat) a.k.a. Idle state 4.06 5.4.5. AwaitingEstablish, ///< note that the BTS should never be in this state (pat) Incorrect, state is used during link establishment. AwaitingRelease, LinkEstablished, @@ -74,28 +75,25 @@ class L2DL { protected: - SAPMux *mL2Downstream; ///< a pointer to the lower layer - L2LogicalChannel *mL2Upstream; ///< The logical channel containing the SAPMux containing us. - + friend class L2SAPMux; + L2SAPMux *mL2Downstream; ///< a pointer to the lower layer public: - L2DL() - :mL2Downstream(NULL), mL2Upstream(NULL) - { } + L2DL() :mL2Downstream(NULL) { } virtual ~L2DL() {} - void l2Downstream(SAPMux *wDownstream) { mL2Downstream = wDownstream; } - void l2Upstream(L2LogicalChannel *wUpstream) { mL2Upstream = wUpstream; } + void l2Downstream(L2SAPMux *wDownstream) { mL2Downstream = wDownstream; } - virtual void l2open(std::string wDescriptiveString) = 0; + virtual void l2dlOpen(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; /** N200 value for this channel, GSM 04.06 5.8.2. */ + // (pat) The values for N200 herein are for state timerRecovery; otherwise N200 is supposed to be 5. virtual unsigned N200() const = 0; /** T200 timeout for this channel, GSM 04.06 5.8.1. */ @@ -115,14 +113,14 @@ class L2DL { 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 l2WriteHighSide(const GSM::L3Frame&) = 0; + virtual void l2dlWriteHighSide(const GSM::L3Frame&) = 0; /** The L1->L2 interface */ - virtual void writeLowSide(const GSM::L2Frame&) = 0; + virtual void l2dlWriteLowSide(const GSM::L2Frame&) = 0; /** The L2->L3 interface. */ - virtual L3Frame* l2ReadHighSide(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; } @@ -130,34 +128,6 @@ class L2DL { - -/** - A "thin" L2 for CCCH. - This is a downlink-only channel. - It supports only the Bbis L2 frame format (GSM 04.06 2.1). - The "uplink" component of the CCCH is the RACH. - See GSM 04.03 4.1.2. -*/ -class CCCHL2 : public L2DL { - - public: - - unsigned N201(GSM::L2Control::ControlFormat format) const - { assert(format==L2Control::UFormat); return 22; } - - unsigned N200() const { return 0; } - - void l2open(std::string) {} - - void writeLowSide(const GSM::L2Frame&) { assert(0); } - - L3Frame* l2ReadHighSide(unsigned timeout=3600000) { if (timeout) {} assert(0); return NULL; } // The 'if' shuts up gcc. - - void l2WriteHighSide(const GSM::L3Frame&); - -}; - - /** A "thin" L2 for CBCH. This is a downlink-only channel and does not use LAPDm. @@ -171,13 +141,13 @@ class CBCHL2 : public L2DL { unsigned N200() const { return 0; } - void l2open(std::string) {} + void l2dlOpen(std::string) {} - void writeLowSide(const GSM::L2Frame&) { assert(0); } + void l2dlWriteLowSide(const GSM::L2Frame&) { assert(0); } - L3Frame* l2ReadHighSide(unsigned timeout=3600000) { if (timeout) {} assert(0); return NULL; } // The 'if' shuts up gcc. + //L3Frame* l2ReadHighSide(unsigned timeout=3600000) { if (timeout) {} assert(0); return NULL; } // The 'if' shuts up gcc. - void l2WriteHighSide(const GSM::L3Frame&); + void l2dlWriteHighSide(const GSM::L3Frame&); }; @@ -187,6 +157,7 @@ class CBCHL2 : public L2DL { /** + LAPDm == Link Access Procedure on the Dm channel. LAPDm transceiver, GSM 04.06, borrows from ITU-T Q.921 (LAPD) and ISO-13239 (HDLC). Dedicated control channels need full-blown LAPDm. @@ -220,7 +191,7 @@ class L2LAPDm : public L2DL { 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 + //L3FrameFIFO mL3Out; ///< we connect L2->L3 through a FIFO private: L2FrameFIFO mL1In; ///< we connect L1->L2 through a FIFO @@ -243,7 +214,7 @@ class L2LAPDm : public L2DL { LAPDState mState; ///< current protocol state Signal mAckSignal; ///< signal used to wake a thread waiting for an ack //@} - bool mEstablishmentInProgress; ///< flag described in GSM 04.06 5.4.1.4 + Bool_z mEstablishmentInProgress; ///< flag described in GSM 04.06 5.4.1.4 /**@name Segmentation and retransmission. */ //@{ BitVector2 mRecvBuffer; ///< buffer to concatenate received I-frames, same role as sk_rcvbuf in vISDN @@ -257,6 +228,7 @@ class L2LAPDm : public L2DL { //@} /** A handy idle frame. */ + // (pat) This is sent from TCHFACCHL1Encoder::dispatch() L2Frame mIdleFrame; /** A lock to control multi-threaded access to L1->L2. */ @@ -283,14 +255,14 @@ class L2LAPDm : public L2DL { /** Process an uplink L2 frame. */ - void writeLowSide(const GSM::L2Frame&); + void l2dlWriteLowSide(const GSM::L2Frame&); /** Read the L3 output, with a timeout. Caller is responsible for deleting returned object. */ - L3Frame* l2ReadHighSide(unsigned timeout=3600000) - { return mL3Out.read(timeout); } + //L3Frame* l2ReadHighSide(unsigned timeout=3600000) + // { LOG(DEBUG); return mL3Out.read(timeout); } /** Process a downlink L3 frame. @@ -299,11 +271,11 @@ class L2LAPDm : public L2DL { enqueued for transmission. That can take up to 1/2 second. */ - void l2WriteHighSide(const GSM::L3Frame&); + void l2dlWriteHighSide(const GSM::L3Frame&); /** Prepare the channel for a new transaction. */ - virtual void l2open(std::string wDescriptiveString); + virtual void l2dlOpen(std::string wDescriptiveString); /** Set the "master" SAP, SAP0; should be called no more than once. */ void master(L2LAPDm* wMaster) @@ -321,7 +293,7 @@ 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. */ + /** Send an L3Frame upstream on the L2->L3 interface. */ virtual void writeL3(L3Frame *f); // Over-ridden only by SACCHL2 void writeL1Ack(const L2Frame&); ///< send an ack-able frame on L2->L1 @@ -331,20 +303,20 @@ class L2LAPDm : public L2DL { void linkError(); /** Clear the state variables to released condition. */ - void clearState(Primitive releaseType=RELEASE); + void releaseLink(bool notifyL3,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); + void releaseLink(Primitive releaseType); /** We go here when something goes really wrong. */ - void abnormalRelease(); + void abnormalRelease(bool sendDM); + void normalRelease(); /** Abort link on unexpected message. */ - void unexpectedMessage(); + void unexpectedMessage(int whence); /** Process an ack. Also forces state to LinkEstablished. */ void processAck(unsigned NR); @@ -353,7 +325,7 @@ class L2LAPDm : public L2DL { void retransmissionProcedure(); /** Clear any outgoing L3 frame. */ - void discardIQueue() { mDiscardIQueue=true; } + //void discardIQueue() { mDiscardIQueue=true; } /** Accept and concatenate an I-frame data payload. @@ -397,7 +369,7 @@ class L2LAPDm : public L2DL { lapd_send_uframe with arguments that specify the DISC frame. In OpenBTS, you just call sendUFrameDISC. */ - void sendMultiframeData(const L3Frame&); ///< send an L3 frame in one or more I-frames + bool sendMultiframeData(const L3Frame&); ///< send an L3 frame in one or more I-frames 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 @@ -438,9 +410,9 @@ class L2LAPDm : public L2DL { bool stuckChannel(const L2Frame&); /** - The upstream service loop handles incoming L2 frames and T200 timeouts. + The upstream service loop handles incoming L2 frames from L1 and T200 timeouts. */ - void serviceLoop(); + void lapServiceLoop(); friend void *LAPDmServiceLoopAdapter(L2LAPDm*); @@ -517,7 +489,7 @@ class SACCHL2 : public L2LAPDm { /** SACCH does not use idle frames. */ void sendIdle() {}; - void writeL3(L3Frame *f); // Over-ridden only by SACCHL2 + // same as base class: void writeL3(L3Frame *f); // Over-ridden only by SACCHL2 public: diff --git a/GSM/GSML3CCElements.cpp b/GSM/GSML3CCElements.cpp index fb0fd01..a9d4b7f 100644 --- a/GSM/GSML3CCElements.cpp +++ b/GSM/GSML3CCElements.cpp @@ -3,8 +3,9 @@ */ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,7 +16,7 @@ */ - +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging #include "GSML3CCElements.h" @@ -357,7 +358,7 @@ void L3CallingPartyBCDNumber::text(ostream& os) const } -void L3Cause::parseV(const L3Frame& src, size_t &rp , size_t expectedLength) +void L3CauseElement::parseV(const L3Frame& src, size_t &rp , size_t expectedLength) { size_t pos = rp; rp += 8*expectedLength; @@ -375,7 +376,7 @@ void L3Cause::parseV(const L3Frame& src, size_t &rp , size_t expectedLength) } -void L3Cause::writeV(L3Frame& dest, size_t &wp) const +void L3CauseElement::writeV(L3Frame& dest, size_t &wp) const { // Write Octet3. dest.writeField(wp,0x0e,4); @@ -387,10 +388,10 @@ void L3Cause::writeV(L3Frame& dest, size_t &wp) const } -void L3Cause::text(ostream& os) const +void L3CauseElement::text(ostream& os) const { os << "location=" << mLocation; - os << " cause=0x" << hex << mCause << dec; + os << " cause=0x" << hex << mCause << dec <<" "< -#include +#include #include namespace GSM { @@ -188,7 +189,8 @@ public: mHaveOctet3a(false) { mType = (wDigits[0] == '+') ? GSM::InternationalNumber : GSM::NationalNumber; - LOG(DEBUG) << "L3CallingPartyBCDNumber ctor type=" << mType << " Digits " << wDigits; + //LOG(DEBUG) << "L3CallingPartyBCDNumber ctor type=" << mType << " Digits " << wDigits; (pat) what was "ctor"? + LOG(DEBUG) << "L3CallingPartyBCDNumber create type=" << mType << " Digits " << wDigits; } L3CallingPartyBCDNumber(const L3CallingPartyBCDNumber &other) @@ -274,72 +276,10 @@ public: and that format doesn't carry the "recommendation" field. */ -class L3Cause : public L3ProtocolElement { - +class L3CauseElement : public L3ProtocolElement { public: - - enum Location { - User=0, - PrivateServingLocal=1, - PublicServingLocal=2, - Transit=3, - PublicServingRemote=4, - PrivateServingRemote=5, - International=7, - 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, - }; + typedef L3Cause::Location Location; + typedef L3Cause::CCCause Cause; private: @@ -351,13 +291,13 @@ private: public: - L3Cause(Cause wCause=NormalCallClearing, Location wLocation=PrivateServingLocal) + L3CauseElement(Cause wCause=L3Cause::Normal_Call_Clearing, Location wLocation=L3Cause::Private_Serving_Local) :L3ProtocolElement(), mLocation(wLocation),mCause(wCause) { } Location location() const { return mLocation; } - unsigned cause() const { return mCause; } + Cause cause() const { return mCause; } // We don't support diagnostics, so length=2. size_t lengthV() const { return 2; } @@ -367,7 +307,6 @@ 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. */ @@ -409,6 +348,8 @@ class L3ProgressIndicator : public L3ProtocolElement { BeyondInternetworking=10 }; + // pat 2-2014: GSM 4.08 5.5.1: The ProgressIndicator is used to start in-band tones and announcements + // if the value is 1-3 or 6-20. enum Progress { Unspecified=0, NotISDN=1, @@ -466,6 +407,33 @@ class L3KeypadFacility : public L3ProtocolElement { void text(std::ostream&) const; }; + +/** GSM 04.08 10.5.4.23 */ +// (pat) 2-2014: Added to try to work around bug that ZTE phone does not play ring-back tone during Alerting. +class L3Signal : public L3ProtocolElement { + char mSignalValue; + public: + enum SignalValues { + SignalDialToneOn = 0, + SignalRingBackToneOn = 1, + SignalInterceptToneOn = 2, + SignalNetworkCongestionToneOn = 3, + SignalBusyToneOn = 4, + SignalConfirmToneOn = 5, + SignalAnswerToneOn = 6, + SignalCallWaitingToneOn = 7, + SignalTonesOff = 0x3f, + SignalAlertingOff = 0x4f + }; + L3Signal(SignalValues tone = SignalRingBackToneOn) : mSignalValue(tone) {} + + size_t lengthV() const { return 1; } + void writeV(L3Frame&, size_t&) const; + void parseV(const L3Frame&, size_t&, size_t) { assert(0); } + void parseV(const L3Frame& src, size_t& rp); + void text(std::ostream&) const; +}; + } // GSM #endif diff --git a/GSM/GSML3CCMessages.cpp b/GSM/GSML3CCMessages.cpp index ddf71aa..2bb95c4 100644 --- a/GSM/GSML3CCMessages.cpp +++ b/GSM/GSML3CCMessages.cpp @@ -6,7 +6,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -17,6 +17,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + @@ -346,6 +348,7 @@ void L3Setup::writeBody( L3Frame &dest, size_t &wp ) const if (mHaveCallingPartyBCDNumber) mCallingPartyBCDNumber.writeTLV(0x5C,dest, wp); if (mHaveCalledPartyBCDNumber) mCalledPartyBCDNumber.writeTLV(0x5E,dest, wp); if (mSupportedCodecs.mPresent) mSupportedCodecs.writeTLV(0x40, dest, wp); + if (mHaveSignal) mSignal.writeTV(0x34,dest,wp); ccCommonWrite(dest,wp); } @@ -386,7 +389,9 @@ void L3Setup::parseBody( const L3Frame &src, size_t &rp ) skipLV(src,rp); continue; case 0x34: // Signal (MTC only) - rp++; // It is TV, two bytes total. + mHaveSignal = true; + mSignal.parseV(src,rp); + // old: rp++; // It is TV, two bytes total. continue; case 0x5c: // Calling part BCD number. // You may encounter this error if the code is accidentally converting between L3Frame and L3Message. @@ -449,6 +454,7 @@ size_t L3Setup::l2BodyLength() const if(mBearerCapability.mPresent) len += mBearerCapability.lengthTLV(); if(mHaveCalledPartyBCDNumber) len += mCalledPartyBCDNumber.lengthTLV(); if(mHaveCallingPartyBCDNumber) len += mCallingPartyBCDNumber.lengthTLV(); + if (mHaveSignal) len += mSignal.lengthTV(); len += ccCommonLength(); return len; } @@ -465,6 +471,7 @@ void L3Setup::text(ostream& os) const os <<" BearerCapability=("<type()) { + case GSM::IMSIType: return 0 == strcmp(mobileId->mImsi.c_str(),this->digits()); + case GSM::IMEIType: return 0 == strcmp(mobileId->mImei.c_str(),this->digits()); + case GSM::TMSIType: return mobileId->mTmsi.valid() && mobileId->mTmsi.value() == this->TMSI(); + default: return false; // something wrong, but certainly no match + } +} + bool L3MobileIdentity::operator<(const L3MobileIdentity& other) const { if (other.mType != mType) return mType > other.mType; diff --git a/GSM/GSML3CommonElements.h b/GSM/GSML3CommonElements.h index fa82249..8353da0 100644 --- a/GSM/GSML3CommonElements.h +++ b/GSM/GSML3CommonElements.h @@ -4,10 +4,11 @@ /* * Copyright 2008-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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -25,7 +26,8 @@ #define GSMCOMMONELEMENTS_H #include "GSML3Message.h" -#include +#include +#include namespace GSM { @@ -139,6 +141,8 @@ class L3MobileIdentity : public L3ProtocolElement { /** Comparison. */ bool operator==(const L3MobileIdentity&) const; + bool fmidMatch(const Control::FullMobileId *mobileId) const; + /** Comparison. */ bool operator!=(const L3MobileIdentity& other) const { return !operator==(other); } diff --git a/GSM/GSML3GPRSElements.cpp b/GSM/GSML3GPRSElements.cpp index 14e2b8e..e3195ff 100644 --- a/GSM/GSML3GPRSElements.cpp +++ b/GSM/GSML3GPRSElements.cpp @@ -2,8 +2,9 @@ /* * Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2011 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -13,6 +14,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include #include diff --git a/GSM/GSML3GPRSElements.h b/GSM/GSML3GPRSElements.h index 6e1458e..339c225 100644 --- a/GSM/GSML3GPRSElements.h +++ b/GSM/GSML3GPRSElements.h @@ -2,8 +2,9 @@ /* * Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2011 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSM/GSML3MMElements.cpp b/GSM/GSML3MMElements.cpp index 6f0d4dd..cf2d3b6 100644 --- a/GSM/GSML3MMElements.cpp +++ b/GSM/GSML3MMElements.cpp @@ -4,10 +4,11 @@ /* * Copyright 2008 Free Software Foundation, Inc. * Copyright 2010, 2014 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -18,12 +19,15 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include #include "GSML3MMElements.h" #include +#include using namespace std; @@ -48,9 +52,6 @@ ostream& GSM::operator<<(ostream& os, CMServiceTypeCode code) 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; @@ -65,18 +66,18 @@ void L3CMServiceType::text(ostream& os) const os << mType; } -void L3RejectCause::writeV( L3Frame& dest, size_t &wp ) const +void L3RejectCauseIE::writeV( L3Frame& dest, size_t &wp ) const { dest.writeField(wp, mRejectCause, 8); } -void L3RejectCause::parseV(const L3Frame& src, size_t &rp) +void L3RejectCauseIE::parseV(const L3Frame& src, size_t &rp) { - mRejectCause = (RejectCause) src.readField(rp,8); + mRejectCause = (MMRejectCause) src.readField(rp,8); } -void L3RejectCause::text(ostream& os) const +void L3RejectCauseIE::text(ostream& os) const { os <<"0x"<< hex << mRejectCause << dec; } @@ -220,10 +221,12 @@ void L3TimeZoneAndTime::parseV(const L3Frame& src, size_t& rp) void L3TimeZoneAndTime::text(ostream& os) const { - char timeStr[26]; + //char timeStr[26]; const time_t seconds = mTime.sec(); - ctime_r(&seconds,timeStr); - timeStr[24]='\0'; + std::string timeStr(""); + Timeval::isoTime(seconds, timeStr, true); + //ctime_r(&seconds,timeStr); + //timeStr[24]='\0'; os << timeStr << " (local)"; } diff --git a/GSM/GSML3MMElements.h b/GSM/GSML3MMElements.h index 1cda43b..95bde7b 100644 --- a/GSM/GSML3MMElements.h +++ b/GSM/GSML3MMElements.h @@ -7,7 +7,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -23,8 +23,9 @@ #ifndef GSML3MMELEMENTS_H #define GSML3MMELEMENTS_H +#include "L3Enums.h" #include "GSML3Message.h" -#include +#include namespace GSM { @@ -46,11 +47,8 @@ class L3CMServiceType : public L3ProtocolElement { MobileTerminatedCall=100, ///< non-standard code MobileTerminatedShortMessage=101, ///< non-standard code - TestCall=102, ///< non-standard code HandoverCall=103, ///< non-standard code - FuzzCallTch=104, ///< non-standard code - FuzzCallSdcch=105, ///< non-standard code - LocationUpdateRequest=106, ///< non-standard code + LocationUpdateRequest=105, ///< non-standard code }; private: @@ -98,46 +96,13 @@ std::ostream& operator<<(std::ostream& os, CMServiceTypeCode code); // Better: 24.008 10.5.3.6 // This is the Mobility Management reject cause. // For RR causes see L3RRCause, and for CC Causes see L3Cause. -class L3RejectCause : public L3ProtocolElement { - public: - enum RejectCause { - Zero = 0, // This is NOT a GSM RejectCause, it is our unspecified value. - IMSIUnknownInHLR = 2, - IllegalMS = 3, - IMSIUnknownInVLR = 4, - IMEINotAccepted = 5, - IllegalME = 6, - PLMNNotAllowed = 0xb, - LocationAreaNotAllowed = 0xc, - RoamingNotAllowedInLA = 0xd, // Roaming not allowed in this Location Area - NoSuitableCellsInLA = 0xf, - NetworkFailure = 0x11, - MACFailure = 0x14, - SynchFailure = 0x15, - Congestion = 0x16, - GSMAuthenticationUnacceptable = 0x17, - NotAuthorizedInCSG = 0x19, - ServiceOptionNotSupported = 0x20, - RequestedServiceOptionNotSubscribed = 0x21, - ServiceOptionTemporarilyOutOfOrder = 0x22, - CallCannotBeIdentified = 0x26, - // 0x30 - 0x3f : retry upon entry into a new cell ??? - SemanticallyIncorrectMessage = 0x5f, - InvalidMandatoryInformation = 0x60, - MessageTypeInvalid = 0x61, // Message type non-existent or not implemented - MessageTypeNotCompatibleWithProtocolState = 0x62, - IEInvalid = 0x63, // IE non-existent or not implemented - ConditionalIEError = 0x64, - MessageNotCompatibleWithProtocolState = 0x65, - ProtocolErrorUnspecified = 0x6f - }; - +class L3RejectCauseIE : public L3ProtocolElement { private: - RejectCause mRejectCause; + MMRejectCause mRejectCause; public: - L3RejectCause( const RejectCause wRejectCause=Zero ) + L3RejectCauseIE( const MMRejectCause wRejectCause=L3RejectCause::Zero ) :L3ProtocolElement(),mRejectCause(wRejectCause) {} @@ -148,8 +113,6 @@ public: void text(std::ostream&) const; }; -typedef L3RejectCause::RejectCause MMRejectCause; - diff --git a/GSM/GSML3MMMessages.cpp b/GSM/GSML3MMMessages.cpp index df00c8e..b5c75c9 100644 --- a/GSM/GSML3MMMessages.cpp +++ b/GSM/GSML3MMMessages.cpp @@ -5,10 +5,11 @@ /* * Copyright 2008-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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -19,6 +20,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + @@ -136,6 +139,13 @@ void L3LocationUpdatingRequest::text(ostream& os) const { L3MMMessage::text(os); os << " UpdateType="< +#include #include "GSMCommon.h" #include "GSMTransfer.h" @@ -92,9 +93,8 @@ 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; + L3Frame* frame(GSM::Primitive prim=L3_DATA) const; /** Return the L3 protocol discriptor. */ virtual GSM::L3PD PD() const =0; diff --git a/GSM/GSML3RRElements.cpp b/GSM/GSML3RRElements.cpp index 294b519..aa51a8b 100644 --- a/GSM/GSML3RRElements.cpp +++ b/GSM/GSML3RRElements.cpp @@ -3,8 +3,9 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2010, 2013 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,6 +16,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include // for L3APDUData::text #include "GSML3RRElements.h" @@ -26,9 +29,10 @@ using namespace std; -using namespace GSM; +namespace GSM { +L3ControlChannelDescription *gControlChannelDescription = NULL; void L3CellOptionsBCCH::writeV(L3Frame& dest, size_t &wp) const @@ -91,25 +95,62 @@ void L3CellSelectionParameters::text(ostream& os) const } - - - - -unsigned L3ControlChannelDescription::getBS_PA_MFRMS() +L3ControlChannelDescription::L3ControlChannelDescription() + : L3ProtocolElement() { - unsigned bs_pa_mfrms = mBS_PA_MFRMS + 2; - if (bs_pa_mfrms != RN_BOUND(bs_pa_mfrms,2,sMax_BS_PA_MFRMS)) { - static bool printed_msg = false; - if (!printed_msg) { - LOG(ERR) << "Invalid BS_PA_MFRMS value, must be 2.."< +#include "L3Enums.h" #include "GSML3Message.h" #include "GSML3GPRSElements.h" -#include +#include namespace GSM { @@ -37,6 +38,10 @@ class L3CellOptionsBCCH : public L3ProtocolElement { unsigned mPWRC; ///< 1 -> downlink power control may be used unsigned mDTX; ///< discontinuous transmission state + // (pat) From GSM 5.08 5.2: RADIO_LINK_TIMEOUT is a counter used by the MS to decide when the radio link + // quality is poor enough to cancel the call. In short, it is a count of how many SACCH messages are lost. + // From GSM 5.08 section 9 table 1 we learn the value is in SACCH blocks (which are 480ms each) in the range 4-64, + // ie 15 steps of 4 SACCH blocks each, which translated is about 2 second granularity. unsigned mRADIO_LINK_TIMEOUT; ///< timeout to declare dead phy link public: @@ -69,7 +74,7 @@ class L3CellOptionsSACCH : public L3ProtocolElement { unsigned mPWRC; ///< 1 -> downlink power control may be used unsigned mDTX; ///< discontinuous transmission state - unsigned mRADIO_LINK_TIMEOUT; ///< timeout to declare dead phy link + unsigned mRADIO_LINK_TIMEOUT; ///< timeout to declare dead phy link See comments at L3CellOptionsBCCH public: @@ -136,36 +141,25 @@ class L3CellSelectionParameters : public L3ProtocolElement { /** Control Channel Description, GSM 04.08 10.5.2.11 */ class L3ControlChannelDescription : public L3ProtocolElement { - private: - - // (pat) 5-27-2012: I put in 'real' paging channels and used them for GPRS, - // but someone still needs to modify the GSM stack to use them and test them there. - // Then we could change the parameters below to provide more paging channels. - // See class CCCHCombinedChannel. - - unsigned mATT; ///< 1 -> IMSI attach/detach - unsigned mBS_AG_BLKS_RES; ///< access grant channel reservation - unsigned mCCCH_CONF; ///< channel combination for CCCH - unsigned mBS_PA_MFRMS; ///< paging channel configuration - // Note: This var is 0..7 representing BS_PA_MFRMS values 2..9. - unsigned mT3212; ///< periodic updating timeout - public: - /** Sets reasonable defaults for a single-ARFCN system. */ - L3ControlChannelDescription():L3ProtocolElement() - { - // Values dictated by the current implementation are hard-coded. - mBS_AG_BLKS_RES=2; // reserve 2 CCCHs for access grant - mBS_PA_MFRMS=0; // minimum PCH spacing - // Configurable values. - mATT=(unsigned)gConfig.getBool("Control.LUR.AttachDetach"); - mCCCH_CONF=gConfig.getNum("GSM.CCCH.CCCH-CONF"); - mT3212=gConfig.getNum("GSM.Timer.T3212")/6; - } + unsigned mATT; ///< 1 -> IMSI attach/detach + // BS_AG_BLKS_RES is the number of CCCH NOT used for paging, so the MS does not have to bother listening to them for paging. + unsigned mBS_AG_BLKS_RES; ///< access grant channel reservation. + // Values for CCCH_CONF defined in 4.08 10.5.2.11. 1 -> C-V beacon, 2->C-IV beacon. + unsigned mCCCH_CONF; ///< channel combination for CCCH. + // BS_PA_MFRMS represents the number of consecutive 51-multiframes used to spread pages. + // In 5.02 6.5.2 it is assumed to be in the range 2..9, + // but in 4.08 10.5.2.11 the same exact variable name (BS_PA_MFRMS) has the range 0..7. + // We are going to use the values 2..9 here, and translate it to 0..7 when writing it to the Control Channel Description IE. + unsigned mBS_PA_MFRMS; ///< BS_PA_MFRMS in the range 2..9. + unsigned mT3212; ///< periodic updating timeout - // BS_PA_MFRMS is the number of 51-multiframes used for paging in the range 2..9. - unsigned getBS_PA_MFRMS(); + /** Sets reasonable defaults for a single-ARFCN system. */ + L3ControlChannelDescription(); + + bool isCCCHCombined() { return mCCCH_CONF == 1; } + void validate(); size_t lengthV() const { return 3; } void writeV(L3Frame& dest, size_t &wp) const; @@ -175,6 +169,8 @@ class L3ControlChannelDescription : public L3ProtocolElement { }; +extern L3ControlChannelDescription *gControlChannelDescription; + /** @@ -313,13 +309,21 @@ class L3NCCPermitted : public L3ProtocolElement { // T3126 is how long the MS listens to CCCH after a RACH, and is dependent on parameters broadcast in L3RACHControlParameters. // 4.08/24.008 says value is T+2S but no longer than 5secs, where T and S are defined in 44.018 3.3.1.2: // T is the "Tx Integer" broadcast on BCCH (we default to 14), S is defined by table 3.3.1.1.2.1: -// TX-integer non-combined-CCCH combined-CCH/SDCCH -// 3,8,14,50 55 41 -// 4,9,16 76 52 -// 5,10,20 109 58 -// 6,11,25 163 86 -// 7,12,32 217 115 -// See further documentation at the config value GSM.CCCH.AGCH.QMax. +// See RACHSpreadSlots and RACHWaitSParam + +// (pat) Location Updating is triggered by MM layer, which then requests an RR connection, ie, RACH procedure. +// The RR layer retries M (max retrans) times, which we set to 1, then waits T3126 (max 5 seconds) and if no reply, +// either Immediate Assignment or Immediate Assignment Reject, signals a failure to the MM layer. +// The Immediate Assignment Reject can specify 4 MS and a delay up to 255 seconds. +// Random Access failure behavior is in GSM 4.08 4.4.4.9 paragraph c. +// MS starts T3213 (4sec) for this cell, and retries Loc Update. +// But first, if there are any other suitable cells tries cell reselection first with a 5 second prohibition +// timer to retry this cell. +// MS tries random access procedure twice, then if the update status is UPDATED and stored LAI matches: +// if attempt counter < 4: keep status updated and retry after T3211 (15sec.) +// otherwise: set update status to NOT UPDATED and enter MM IDLE sub-state ATTEMPTING TO UPDATE, +// and retry after T3211 (15Sec) if attempt counter < 4, else T3212 (broadcast on BCCH, in minutes, +// and as of 2-2014 is set to 0! /** RACH Control Parameters GSM 04.08 10.5.2.29 */ @@ -366,6 +370,10 @@ class L3RACHControlParameters : public L3ProtocolElement { /** PageMode, GSM 04.08 10.5.2.26 */ +// (pat) The page mode is included in Immediate Assignment and Paging messages. +// If the message is sent on a paging channel, it applies to ALL MS using that paging channel, not just the addressed MS. +// GSM 4.08 3.3.2.1.1: The page mode type "Paging Reorganization" causes the MS to listen to all CCCH and BCCH messages +// until it sees a new Page Mode. class L3PageMode : public L3ProtocolElement { @@ -565,37 +573,14 @@ 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 +class L3RRCauseElement : public L3ProtocolElement { - 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(RRCause wValue=NormalEvent) + L3RRCauseElement(RRCause wValue=L3RRCause::Normal_Event) :L3ProtocolElement() { mCauseValue=wValue; } @@ -608,7 +593,6 @@ class L3RRCause : public L3ProtocolElement void text(std::ostream&) const; }; -typedef L3RRCause::RRCause RRCause; @@ -720,12 +704,10 @@ std::ostream& operator<<(std::ostream&, L3ChannelMode::Mode); /** GSM 04.08 10.5.2.43 */ class L3WaitIndication : public L3ProtocolElement { - private: + public: unsigned mValue; ///< T3122 or T3142 value in seconds - public: - L3WaitIndication(unsigned seconds) :L3ProtocolElement(), mValue(seconds) @@ -832,13 +814,21 @@ class L3APDUData : public L3ProtocolElement { /** GSM 04.08 10.5.2.20 */ +// (pat) Also see 5.08 class L3MeasurementResults : public L3ProtocolElement { private: bool mBA_USED; bool mDTX_USED; - bool mMEAS_VALID; ///< 0 for valid, 1 for non-valid + bool mMEAS_VALID; ///< 0 for valid, 1 for non-valid NOTE!! + // (pat) 5.08 8.1.4 defines RXLEV values. MS measures RMS signal level in the range -110dB to -48dB. + // RXLEV 0 = < -110 dBm + SCALE + // RXLEV 1 = < -109 dBm + SCALE + // ... + // RXLEV 63 = > -48 dBm + SCALE + // The SCALE is used for Enhanced Measurement Report and is inconsequential for normal report. + // The MS always reports the 6 best cells from among the candidates in the BA list sent by the BTS. unsigned mRXLEV_FULL_SERVING_CELL; unsigned mRXLEV_SUB_SERVING_CELL; unsigned mRXQUAL_FULL_SERVING_CELL; @@ -853,7 +843,7 @@ class L3MeasurementResults : public L3ProtocolElement { L3MeasurementResults() :L3ProtocolElement(), - mMEAS_VALID(false), + mMEAS_VALID(true), // true means invalid. mRXLEV_FULL_SERVING_CELL(0), mRXLEV_SUB_SERVING_CELL(0), mRXQUAL_FULL_SERVING_CELL(0), @@ -873,14 +863,17 @@ class L3MeasurementResults : public L3ProtocolElement { bool BA_USED() const { return mBA_USED; } bool DTX_USED() const { return mDTX_USED; } - bool MEAS_VALID() const { return mMEAS_VALID; } - unsigned RXLEV_FULL_SERVING_CELL() const { return mRXLEV_FULL_SERVING_CELL; } - unsigned RXLEV_SUB_SERVING_CELL() const { return mRXLEV_SUB_SERVING_CELL; } + // The value MEAS_VALID == 0 means the serving cell measurements were valid; it is intuitively backwards. + //bool MEAS_VALID() const { return mMEAS_VALID; } // Get rid of this intuitively backwards method. + bool isServingCellValid() const { return mMEAS_VALID == 0; } + // (pat) The _RAW versions return the raw data from the IE, which must be scaled to convert to dB. + unsigned RXLEV_FULL_SERVING_CELL_RAW() const { return mRXLEV_FULL_SERVING_CELL; } + unsigned RXLEV_SUB_SERVING_CELL_RAW() const { return mRXLEV_SUB_SERVING_CELL; } unsigned RXQUAL_FULL_SERVING_CELL() const { return mRXQUAL_FULL_SERVING_CELL; } unsigned RXQUAL_SUB_SERVING_CELL() const { return mRXQUAL_SUB_SERVING_CELL; } unsigned NO_NCELL() const { return mNO_NCELL; } - unsigned RXLEV_NCELL(unsigned i) const { assert(i #include "GSML3RRMessages.h" +#include "GSMConfig.h" // For gBTS #include @@ -247,7 +250,7 @@ void L3PagingRequestType1::text(ostream& os) const L3RRMessage::text(os); os << " mobileIDs=("; for (unsigned i=0; i|--->| sapWriteFromL3 ------>X----------->|--->| l2dlWriteHighSide | +// | \ | | | | | | +// | --------------- writeToL3 |<--------------------------------X<-----------| writeL3 | +// | | | | ^ | | | +// | | | | | | | | +// | ------------------------->|--->| sapWriteFromL1 -->X--------------->|--->| l2dlWriteLowSide | +// | / | | | | | | | -> Queue mL1In | +// | | --writeToL1 |<------------------X<-------------------------| writeL1 | +// | | / | | ^ | | | | +----------------------------+ +// | | | | | | | | | | +// | | | | | | | | | | +----------------------------+ +// | | | | | | | | | | | L2LAPDm/SAP3 | +// | | | | | | | \ | | | | +// | | | | | | | ----------->|--->| l2dlWriteHighSide | +// | | | | | | | \ | | | +// | | | | | | | ------------| writeL3 | +// | | | | | | | | | | +// | | | | | | \ | | | +// | | | | | | --------------->|--->| l2dlWriteLowSide | +// | | | | | \ | | -> Queue mL1In | +// | | | | | --------------------------| writeL1 | +// | | | | | | +----------------------------+ +// | writeLowSide v | | | +// +---------------------------------+ +------------------------------------+ +// ^ | +// | | +// | v +// +---------------------------------+ +// | handleGoodFrame writeHighSide | +// | L1FEC | +// +---------------------------------+ #include "GSML3RRElements.h" @@ -28,41 +82,489 @@ #include "GSMConfig.h" #include +#include #include "GPRSExport.h" +#include #include + using namespace std; -using namespace GSM; +namespace GSM { +// Tell C++ to put the class vtables here. +void L2LogicalChannel::_define_vtable() {} +void L2LogicalChannelBase::_define_vtable() {} +void SACCHLogicalChannel::_define_vtable() {} -void L2LogicalChannel::open() +// Comments from David Burgess from wki ticket 1141: +// We are not generating correct idle sequences in L1. Most handsets are not sensitive to this, but some are. +// Unfortunately, one of the most sensitive, the Nokia 1600, is common in some of our target markets. +// As far as I can tell, the correct idle behaviors are: +// for an unused channel on C0 - dummy burst +// for an unused channel on other Cn - dead air, non-transmitting +// for an open-but-idle SDCCH - LAPDm L2 idle frames (an empty unit data frame, see GSM 04.06) +// for an open-but-idle TCH+FACCH prior to activating vocoder - LAPDm L2 idle frames +// for an open-but-idle TCH+FACCH after activating vocoder - silent vocoder frames + +// Semaphore works now and reduces cpu% on idle SACCH from 0.3 to negligible, but the thread start/stop logic is pretty weird +// and I am not sure all the cases are handled, so dont enable this. +#define USE_SEMAPHORE 0 + +void L2LogicalChannelBase::startl1() { - LOG(INFO); - LOG(DEBUG); - if (mSACCH) mSACCH->open(); - LOG(DEBUG); - if (mL1) mL1->open(); // (pat) L1FEC::open() - LOG(DEBUG); + LOG(DEBUG) <l1start(); +} + +void L2SAPMux::flushL3In() +{ + while (L3Frame *l3f = mL3In.readNoBlock()) { + LOG(ERR)<< "channel closure caused message to be discarded:"<l1close(); + // Clear the LAPDm input queue. + flushL3In(); + // Put the message in the queue and let the service loop serialize the request. + mL3In.write(new L3Frame(SAPI0,L3_HARDRELEASE_REQUEST)); // Reset LAPDm. + mL3In.write(new L3Frame(SAPI3,L3_HARDRELEASE_REQUEST)); +} + +void L2SAPMux::sapStart() +{ + LOG(DEBUG) <l2open(descriptiveString()); - LOG(DEBUG) << "SAPI=" << s << " open complete"; + // (pat) This starts sending LAPDm idle frames. + if (mL2[s]) mL2[s]->l2dlOpen(descriptiveString()); + } +} + +void L2LogicalChannelBase::connect(L1FEC *wL1) +{ + mL1 = wL1; + if (wL1) wL1->upstream(this); +} + +void L2SAPMux::sapInit(L2DL *sap0, L2DL *sap3) +{ + LOG(DEBUG); + mL2[0] = sap0; mL2[3] = sap3; + for (int s=0; s<4; s++) { + if (mL2[s]) { mL2[s]->l2Downstream(this); } + } + if (sap0 && sap3) { + dynamic_cast(sap3)->master(dynamic_cast(sap0)); + } +} + +void L2SAPMux::sapWriteFromL1(const L2Frame& frame) +{ + OBJLOG(DEBUG) << frame; + unsigned sap; + + // (pat) Add switch to validate upstream primitives. The upstream only generates a few primitives; + // the rest are created in L2LAPDm. + switch (frame.primitive()) { + case L2_DATA: + sap = frame.SAPI(); + assert(sap == SAPI0 || sap == SAPI3); + if (mL2[sap]) { + mL2[sap]->l2dlWriteLowSide(frame); + } else { + LOG(WARNING) << "received DATA for unsupported"<l2dlWriteLowSide(frame); // Note: the frame may have the wrong SAP in it, but LAPDm doesnt care. + } + return; + default: + // If you get this assertion, make SURE you know what will happen upstream to that primitive. + devassert(0); + return; // make g++ happy. + } +} + +bool L2SAPMux::multiframeMode(SAPI_t sap) const { + unsigned sapi = SAP2SAPI(sap); + assert(mL2[sapi]); + return mL2[SAP2SAPI(sapi)]->multiframeMode(); +} + +bool L2LogicalChannel::multiframeMode(SAPI_t sap) const { + if (SAPIsSacch(sap)) { + return getSACCH()->L2SAPMux::multiframeMode(sap); + } else { + return L2SAPMux::multiframeMode(sap); + } +} + +LAPDState L2SAPMux::getLapdmState(SAPI_t sap) const { return mL2[SAP2SAPI(sap)] ? mL2[SAP2SAPI(sap)]->getLapdmState() : LAPDStateUnused; } + +// For DCCH channels (FACCH, SACCH, SDCCH): +// This function calls virtual L2DL::l2dlWriteHighSide(L3Frame) which maps +// to L2LAPDm::l2dlWriteHighSide() 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), +// which does nothing but call mL1->writeHighSide(L2Frame), which is a pass-through +// except that the SapMux uses mDownStream which is copied from mL1, so there is a +// chance to redirect it. But wouldn't that be an error? +// Anyway, L1Encoder::writeHighSide is usually overridden. +// For TCH, it goes to XCCHL1Encoder::writeHighSide() which processes +// the L2Frame primitive, then sends traffic data to TCHFACCHL1Encoder::sendFrame(), +// which just enqueues the frame - it does not block. +// A thread runs GSM::TCHFACCHL1EncoderRoutine() which +// calls TCHFACCHL1Encoder::dispatch() which is synchronized with the gBTS clock, +// unsynchronized with the queue, because it must send data no matter what. +// Eventually it encodes the data and +// calls (ARFCNManager*)mDownStream->writeHighSideTx(), which writes to the socket. +// +// From here and below only SAPI0 and SAPI3 are used. +void L2SAPMux::sapWriteFromL3(const L3Frame& frame) +{ + LOG(DEBUG) <l2dlWriteHighSide(frame); +} + +// This is the start of a normal or error-caused release procedure, both of which are identical. +// deactivate SACCH means stop transmitting LAPDm frames, so that the handset will time-out based on RADIO_LINK_TIMEOUT +// and release the channel at the RR level. +// On C0 that means to start transmitting dummy frames instead of LAPDm idle frames. +// If you want to stop SACCH immediately, call l2stop() directly so you dont start the mT3109 timer. +void L2LogicalChannel::startNormalRelease() +{ + LOG(DEBUG) <l2stop(); // Go to LAPDm 'null' state immediately. + // We stopped SACCH and are dropping the channel, so no more messages should be sent. + // It is the responsibility of layer3 to make sure it does not happen from that direction, + // and if it happens from the handset nothing we can do about it. + flushL3In(); + mL3In.write(new L3Frame(SAPI0,L3_RELEASE_REQUEST)); + mL3In.write(new L3Frame(SAPI3,L3_RELEASE_REQUEST)); +} + +void L2LogicalChannel::l2sendf(const L3Frame& frame) +{ + SAPI_t sap = frame.getSAPI(); + WATCHINFO("l2sendf "<l2sendf(frame); return; } + switch (frame.primitive()) { + case L3_ESTABLISH_REQUEST: + if (sap != SAPI3) { LOG(NOTICE) << "unexpected"<getSAPI()) <primitive()) { + case L3_ESTABLISH_INDICATION: + case L3_ESTABLISH_CONFIRM: + case HANDOVER_ACCESS: + case L3_DATA: + case L3_UNIT_DATA: + break; + case MDL_ERROR_INDICATION: + // Normal release procedure initiated by handset, or due to error at LAPDm level. + // An error is handled identically to a normal release, because the handset may still be listening to us + // even though we lost contact with it, and we want to tell it to release as gracefully as possible + // even though the channel condition may suck. + if (frame->getSAPI() == SAPI0) { + // Release on host chan sap 0 is a total release. We will start the release now. + // FIXME: Are we supposed to wait for any pending SMS requests on SACCH to clear first? + startNormalRelease(); + } else { + // We dont kill the whole link for SAP3 release. + // Pass the message on to layer3 to abort whatever transaction is running on SAP3 + } + break; + case L3_DATA_CONFIRM: // Sent from LAPDm when data delivered, but we dont care. + WATCHINFO(this <getSAPI()) <getSAPI() == SAPI0) { + mT3109.reset(); + mT3111.set(); + } + break; + default: + assert(0); + } + mL3Out.write(frame); +} + + +void SACCHLogicalChannel::writeToL3(L3Frame*frame) +{ + switch (frame->primitive()) { + case L3_DATA: + case L3_UNIT_DATA: + if (processMeasurementReport(frame)) { return; } + // Fall Through... + case L3_ESTABLISH_CONFIRM: + case L3_ESTABLISH_INDICATION: + // The uplink message queue resides in L2LogicalChannel + mHost->writeToL3(frame); // TODO: frame should include channel indicator, but l3 doesnt care. Would be nice for debugging. + return; + case L3_RELEASE_INDICATION: + case MDL_ERROR_INDICATION: + frame->mSapi = (SAPI_t) (frame->mSapi | SAPChannelFlag); + mHost->writeToL3(frame); // FIXME: frame should include channel indicator. + return; + case L3_DATA_CONFIRM: // Sent from LAPDm when data delivered, but we dont care. + case L3_RELEASE_CONFIRM: // Sent from LAPDm when link release is confirmed, but we dont care. + delete frame; // We dont do anything with this; we are releasing regardless of whether we get a confirm or not. + return; + default: + assert(0); } } -// (pat) This is connecting layer2, not layer1. -void L2LogicalChannel::connect() +// FIXME: It blocks until L2LAPDm::sendIdle returns. That does not need to block. +// Other channels call it too, which is somewhat nonsensical; the l2open for other channels is empty. +// There is no close in L2 - the L1 encoder/decoder are closed individually when L2 sends a RELEASE/HARDRELEASE primitive. +void L2LogicalChannel::lcstart() { - 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]->l2Downstream(&mMux); - mL2[s]->l2Upstream(this); + LOG(DEBUG) <sapStart(); + sapStart(); + mL3Out.clear(); + mT3101.set(T3101ms); + mT3109.reset(gConfig.GSM.Timer.T3109); // redundant with init in lcinit but cant be too careful. + mT3111.reset(T3111ms); // redundant with init in lcinit but cant be too careful. +} + + +// (pat) For data channels this is called by getTCH or getSDCCH. +// TODO: Go through all the getTCH/getSDCCH users and make sure they lcstart. +void L2LogicalChannel::lcinit() +{ + LOG(DEBUG) <l1init(); // (pat) L1FEC::l1init() + devassert(mSACCH); + if (mSACCH) mSACCH->sacchInit(); + // We set T3101 now so the channel will become recyclable if the caller does nothing with it; + // the caller has this long to call lcstart before the channel goes back to the recyclable pool. + mT3101.set(T3101ms); // It will be started again in l2start. + mT3109.reset(gConfig.GSM.Timer.T3109); + mT3111.reset(T3111ms); + //mTRecycle.reset(500); // (pat) Must set a dummy value. + if (!mlcMessageLoopRunning) { + mlcMessageLoopRunning=true; + mlcMessageServiceThread.start2((void*(*)(void*))MessageServiceLoop,this,8000*sizeof(void*)); + } + if (!mlcControlLoopRunning) { + mlcControlLoopRunning=true; + mlcControlServiceThread.start2((void*(*)(void*))ControlServiceLoop,this,8000*sizeof(void*)); + } +} + +void L2LogicalChannel::lcopen() +{ + LOG(INFO) <<"open channel "<encoder()->l1IsIdle() && getSACCH()->mL1->encoder()->l1IsIdle(); +} + +// We know this channel is now unused. Finish deactivating it and mark it for reuse. +// The SACCH was already deactivated. Just close the main channel and become recyclable. +void L2LogicalChannel::immediateRelease() +{ + LOG(DEBUG) <l1active()) { + // We dont need the delay if this is a normal release, meaning the sacch was deactivated a long time ago. + mTRecycle.set(500); // (pat) Channel will be recyclable when this expires. SACCH is 480ms. Could actually be up to 1 second. + } else { + mTRecycle.expire(); // Recycle now. + } +#endif + getSACCH()->l2stop(); // Done already in the T3109 or T3111 expiry cases. + this->l2stop(); +} + +// Service the main SDCCH or TCH/FACCH L2LogicalChannel. +// We are just looking for released channels, which is timer based and so has to be checked from a service thread. +// Called from the service loop for the SACCH, because that is where the thread lives. +// WARNING: Runs in a different thread than everything else which runs in the LAPDm service handler thread. +void L2LogicalChannel::serviceHost() +{ + LOG(DEBUG); + // We do not test T3101 on SACCH because sometimes the first measurement report does not + // arrive in time to keep T3101 from expiring there. + if (mT3101.expired()) { + // Failure of MS to seize channel. + // Layer3 has not been started, so all we do is close down LAPDm immediately. + LOG(INFO) <l1active()) { + hostchan->serviceHost(); + } + + // The SACCH is deactivated before the host channel, so we check l1active to see if SACCH is still running. + SACCHLogicalChannel *sacch = hostchan->getSACCH(); + if (sacch->l1active() && sacch->sacchRadioFailure()) { + // GSM 4.08 3.4.13.2: layer2 is supposed to inform layer3 directly, bypassing LAPDm. + // The layer3 response is identical to a normal RELEASE from layer3: deactivate the SACCH, + // start T3109, recycle the channel when it expires. + // Just because we cannot hear the MS on SACCH does not mean that it cannot hear it, or that + // we have completely lost contact, so LAPDm can go ahead with the normal release procedure, + // ie, send a DISC on the main link and wait for a response - if we get it we can use T3111. + hostchan->startNormalRelease(); } } + //hostchan->mlcControlLoopRunning=false; + return NULL; +} + +// This drives messages from layer3 down through LAPDm, layer1, and all the way to the radio. +void *L2LogicalChannel::MessageServiceLoop(L2LogicalChannel* hostchan) +{ + WATCHINFO("Starting MessageServiceLoop for "<mL3In.read(52*4); // Add a delay so will exit at BTS shutdown. + // (pat) The frames may be L3_DATA which will block until delivered, which could take minutes, + // which is why we need a separate thread to drive this. + if (l3fp) { + hostchan->sapWriteFromL3(*l3fp); + delete l3fp; + } + } + //hostchan->mlcMessageLoopRunning = false; + return NULL; +} + +// Return true if the phy link is either off or failed. Those are the conditions under which Layer3 should punt. +// GSM 5.08 5.3: Radio link failure in the BSS is based on the error rate in the uplink SACCH or +// RXLEV/RXQUAL measurements of the MS. We use only the former. +bool L2LogicalChannel::radioFailure() const { + return !l1active() || getSACCH()->sacchRadioFailure(); +} + +// (pat) This is the primary way of detecting loss of contact with a handset. +bool SACCHLogicalChannel::sacchRadioFailure() const +{ + return mSACCHL1->decoder()->mBadFrameTracker > gConfig.GSM.BTS.RADIO_LINK_TIMEOUT; +} + + +void L2LogicalChannelBase::downstream(ARFCNManager* radio) +{ + assert(mL1); // This is L1FEC + mL1->downstream(radio); } @@ -70,193 +572,89 @@ void L2LogicalChannel::connect() // The L1FEC->downstream hooks the radio to this logical channel, permanently. void L2LogicalChannel::downstream(ARFCNManager* radio) { - assert(mL1); // This is L1FEC mL1->downstream(radio); - if (mSACCH) mSACCH->downstream(radio); + if (mSACCH) mSACCH->mL1->downstream(radio); } - // Serialize and send an L3Message with a given primitive. // The msg is not deleted; its value is used before return. -void L2LogicalChannel::l2sendm(const L3Message& msg, - const GSM::Primitive& prim, - SAPI_t SAPI) +void L2LogicalChannelBase::l2sendm(const L3Message& msg, GSM::Primitive prim, SAPI_t SAPI) { - OBJLOG(INFO) << "L3" <writeHighSide(frame); + break; + default: + OBJLOG(ERR) << "unhandled primitive " << frame.primitive() << " in L2->L1"; + devassert(0); + } +} + +void SACCHLogicalChannel::writeToL1(const L2Frame& frame) +{ + // The SAP may or may not be present, depending on the channel type. + OBJLOG(DEBUG) << frame; + switch (frame.primitive()) { + case L2_DATA: + mL1->writeHighSide(frame); + break; + default: + OBJLOG(ERR) << "unhandled primitive " << frame.primitive() << " in L2->L1"; + devassert(0); } } -void *GSM::CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel* chan) -{ - chan->serviceLoop(); - return NULL; -} - -#if ENABLE_PAGING_CHANNELS -// (pat) This routine is going to be entirely replaced with one that works better for gprs. -// In the meantime, just return a number that is large enough to cover -// the worst case, which assumes that the messages in mQ also -// must go out on the paging timeslot. -Time GSM::CCCHLogicalChannel::getNextPchSendTime(unsigned multiframe_index) -{ - L3ControlChannelDescription mCC; - // Paging is distributed over this many multi-frames. - unsigned bs_pa_mfrms = mCC.getBS_PA_MFRMS(); - - GSM::Time next = getNextWriteTime(); - unsigned next_multiframe_index = (next.FN() / 51) % bs_pa_mfrms; - assert(bs_pa_mfrms > 1); - assert(multiframe_index < bs_pa_mfrms); - assert(next_multiframe_index < bs_pa_mfrms); - int achload = mQ.size(); - if (mWaitingToSend) { achload++; } - - // Total wait time is time needed to empty queue, plus the time until the first - // paging opportunity, plus 2 times the number of guys waiting in the paging queue, - // but it is all nonsense because if a new agch comes in, - // it will displace the paging message because the q is sent first. - // This just needs to be totally redone, and the best way is not to figure out - // when the message will be sent at all, but rather use a call-back to gprs - // just before the message is finally sent. - int multiframesToWait = 0; - if (achload) { - multiframesToWait = bs_pa_mfrms - 1; // Assume worst case. - } else { - // If there is nothing else waiting, we can estimate better: - while (next_multiframe_index != multiframe_index) { - multiframe_index = (multiframe_index+1) % bs_pa_mfrms; - multiframesToWait++; - } - } - int total = achload + multiframesToWait + bs_pa_mfrms * mPagingQ[multiframe_index].size(); - - int fnresult = (next.FN() + total * 51) % gHyperframe; - GSM::Time result(fnresult); - LOG(DEBUG) << "CCCHLogicalChannel::getNextSend="<< next.FN() - <<" load="<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) <l1init(); // (pat) L1FEC::l1init() neighborClearMeasurements(); - mAverageRXLEV_SUB_SERVICING_CELL = 0; + //mAverageRXLEV_SUB_SERVICING_CELL = 0; // Just make sure any stray messages are flushed when we reactivate the channel. while (L3Message *straymsg = mTxQueue.readNoBlock()) { delete straymsg; } + mMeasurementResults = L3MeasurementResults(); // clear it #if USE_SEMAPHORE - cout << descriptiveString() << " POST" <primitive(); - if ((prim!=DATA) && (prim!=UNIT_DATA)) { + if ((prim!=L3_DATA) && (prim!=L3_UNIT_DATA)) { LOG(INFO) << "non-data primitive " << prim; return NULL; } - // FIXME -- Why, again, do we need to do this? (pat) Apparently, we dont. -// L3Frame realFrame = l3frame->segment(24, l3frame->size()-24); L3Message* message = parseL3(*l3frame); if (!message) { - LOG(WARNING) << "SACCH recevied unparsable L3 frame " << *l3frame; + LOG(WARNING) << "SACCH received unparsable L3 frame " << *l3frame; WATCHF("SACCH received unparsable L3 frame PD=%d MTI=%d",l3frame->PD(),l3frame->MTI()); } return message; } +bool SACCHLogicalChannel::processMeasurementReport(L3Frame *rrFrame) +{ + if (! (rrFrame->isData() && rrFrame->PD() == L3RadioResourcePD && rrFrame->MTI() == L3RRMessage::MeasurementReport)) { return false; } + + // Neither of these 'ifs' should fail, but be safe. + if (const L3Message* rrMessage = parseSACCHMessage(rrFrame)) { + if (const L3MeasurementReport* measurement = dynamic_cast(rrMessage)) { + OBJLOG(INFO) << "SACCH measurement report " <results(); + //if (mMeasurementResults.MEAS_VALID() == 0) { + // addSelfRxLev(mMeasurementResults.RXLEV_SUB_SERVING_CELL_dBm()); + //} + // Add the measurement results to the sql table (pat - no longer used) + // 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; + } + delete rrFrame; + return true; +} + +// This sends Layer3 messages into the high side of LAPDm. +// This blocks until a message is sent. +// Routine relies on l2sendf passing directly through LAPDm and going all the way to L1Encoder::transmit, which calls waitToSend. +void SACCHLogicalChannel::serviceSACCH(unsigned &count) +{ + LOG(DEBUG); + // Send any outbound messages. If the tx queue is empty send alternating SI5/6. + if (L3Frame *l3fp = mL3In.readNoBlock()) { + // We are writing on SAPI0 (instead of SAPI0Sacch), which is ok. At the SAP layer it is just SAPI0 or SAPI3 + sapWriteFromL3(*l3fp); + delete l3fp; + } else { + // Send alternating SI5/SI6. + // These L3Frames were created with the UNIT_DATA primivitive. + // (pat) blocks using waitToSend until L1Encoder::mPrevWriteTime + OBJLOG(DEBUG) << "sending SI5/6 on SACCH"; + if (count%2) sapWriteFromL3(gBTS.SI5Frame()); + else sapWriteFromL3(gBTS.SI6Frame()); + count++; + } + + // RSSIBumpDown moved to SACCHL1Decoder::countBadFrame(); +} // (pat) This is started when SACCH is opened, and runs forever. // The SACCHLogicalChannel are created by the SDCCHLogicalChannel and TCHFACCHLogicalChannel constructors. -void SACCHLogicalChannel::serviceLoop() +void *SACCHLogicalChannel::SACCHServiceLoop(SACCHLogicalChannel* sacch) { - - // run the loop + WATCHINFO("Starting SACCHServiceLoop for "<l1active()) { // 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 + sleepFrames(51); // In case the semaphore does not work. // (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++; - } - - // Receive inbound messages. - // This read loop flushes stray reports quickly. - while (true) { - - OBJLOG(DEBUG) << "polling SACCH for inbound messages"; - bool nothing = true; - - // Process SAP0 -- RR Measurement reports - 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); - if (measurement) { - mMeasurementResults = measurement->results(); - OBJLOG(DEBUG) << "SACCH measurement report " << mMeasurementResults; - // 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,this); - delete rrMessage; - } else { - if (Control::l3rewrite()) { - OBJLOG(DEBUG) << "chanEnqueuel3msg:"<chanEnqueuel3msg(rrMessage); - } else { - OBJLOG(NOTICE) << "SACCH SAP0 sent unaticipated message " << rrMessage; - delete rrMessage; - } - } - } -#endif - - // Process SAP3 -- SMS - L3Frame *smsFrame = L2LogicalChannel::l2recv(0,3); - if (smsFrame) { - nothing=false; - - OBJLOG(DEBUG) <<"received SMS frame:"<chanEnqueueFrame(smsFrame); - } - - // Did we get anything from the phone? - // If not, we may have lost contact. Bump the RSSI to induce more power - if (nothing) RSSIBumpDown(gConfig.getNum("Control.SACCHTimeout.BumpDown")); - - // Nothing happened? - if (nothing) break; - } + // (pat 4-2014) Added to detect RR failure. This is needed because a Layer3 MMContext is not allocated + // until the first L3 message arrives. If the channel fails without sending an L3 message we would never + // notice unless we are watching these RR level timers. + // Close the host channel, which will also close this SACCH. + sacch->serviceSACCH(count); } -} - - -void *GSM::SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel* chan) -{ - chan->serviceLoop(); + //sacch->mSacchRunning = false; dont think we would restart this one; would alloc a new one. return NULL; } // These have to go into the .cpp file to prevent an illegal forward reference. -void L2LogicalChannel::setPhy(float wRSSI, float wTimingError, double wTimestamp) - { assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError,wTimestamp); } +void L2LogicalChannel::l1InitPhy(float wRSSI, float wTimingError, double wTimestamp) + { assert(mSACCH); mSACCH->l1InitPhy(wRSSI,wTimingError,wTimestamp); } void L2LogicalChannel::setPhy(const L2LogicalChannel& other) - { assert(mSACCH); mSACCH->setPhy(*other.SACCH()); } + { assert(mSACCH); mSACCH->setPhy(*other.mSACCH); } MSPhysReportInfo * L2LogicalChannel::getPhysInfo() const { assert(mSACCH); return mSACCH->getPhysInfo(); } @@ -587,10 +891,11 @@ TCHFACCHLogicalChannel::TCHFACCHLogicalChannel( mL1 = mTCHL1; // SAP0 is RR/MM/CC, SAP3 is SMS // SAP1 and SAP2 are not used. - mL2[0] = new FACCHL2(1,SAPI0); - mL2[3] = new FACCHL2(1,SAPI3); + L2LAPDm *sap0 = new FACCHL2(1,SAPI0); + L2LAPDm *sap3 = new FACCHL2(1,SAPI3); + sapInit(sap0,sap3); mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this); - connect(); + connect(mL1); } @@ -599,71 +904,35 @@ TCHFACCHLogicalChannel::TCHFACCHLogicalChannel( CBCHLogicalChannel::CBCHLogicalChannel(const CompleteMapping& wMapping) { mL1 = new CBCHL1FEC(wMapping.LCH()); - mL2[0] = new CBCHL2; + L2DL *sap0 = new CBCHL2; + sapInit(sap0,NULL); mSACCH = new SACCHLogicalChannel(0,0,wMapping.SACCH(),this); - connect(); + connect(mL1); } void CBCHLogicalChannel::l2sendm(const L3SMSCBMessage& msg) { - L3Frame frame(UNIT_DATA,88*8); + L3Frame frame(L3_UNIT_DATA,88*8); msg.write(frame); - L2LogicalChannel::l2sendf(frame); + l2sendf(frame); } - - - -#if UNUSED -bool L2LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms) -{ - bool waiting = true; - while (waiting) { - L3Frame *req = recv(timeout_ms); - if (req==NULL) { - OBJLOG(NOTICE) << "timeout at uptime " << gBTS.uptime() << " frame " << gBTS.time(); - return false; - } - waiting = (req->primitive()!=primitive); - delete req; - } - return true; -} - - -void L2LogicalChannel::waitForPrimitive(Primitive primitive) -{ - bool waiting = true; - while (waiting) { - L3Frame *req = recv(); - if (req==NULL) continue; - waiting = (req->primitive()!=primitive); - delete req; - } -} -#endif - -// We only return state for SAPI0, although the state could be different in SAPI0 and SAPI3. -LAPDState L2LogicalChannel::getLapdmState() const -{ - // 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 L2LogicalChannel& chan) +ostream& operator<<(ostream& os, const L2LogicalChannelBase& chan) { os << chan.descriptiveString(); return os; } -std::ostream& GSM::operator<<(std::ostream&os, const L2LogicalChannel*ch) + +std::ostream& operator<<(std::ostream&os, const L2LogicalChannelBase*ch) { if (ch) { os <<*ch; } else { os << "(null L2Logicalchannel)"; } return os; } +ostream& operator<<(ostream& os, const L2LogicalChannel& chan) { return operator<<(os,(L2LogicalChannelBase&)chan); } +ostream& operator<<(ostream& os, const L2LogicalChannel* chan) { return operator<<(os,(L2LogicalChannelBase*)chan); } +}; // vim: ts=4 sw=4 diff --git a/GSM/GSMLogicalChannel.h b/GSM/GSMLogicalChannel.h index bbd4157..32d85de 100644 --- a/GSM/GSMLogicalChannel.h +++ b/GSM/GSMLogicalChannel.h @@ -7,7 +7,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -32,10 +32,11 @@ #include #include "GSML1FEC.h" -#include "GSMSAPMux.h" +//#include "GSMSAPMux.h" #include "GSML2LAPDm.h" #include "GSML3RRElements.h" #include "GSMTDMA.h" +#include "GSMChannelHistory.h" #include #include @@ -51,6 +52,7 @@ class L3Message; class L3RRMessage; class L3SMSCBMessage; class L2LogicalChannel; +class L2DL; /** @@ -69,244 +71,102 @@ class L2LogicalChannel; // 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 { +class L2LogicalChannelBase +{ + virtual void _define_vtable(); protected: - - /**@name Contained layer processors. */ - //@{ 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. - // (pat) The reverse pointer is SACCHLogicalChannel::mHost. -public: /** Blank initializer just nulls the pointers. Specific sub-class initializers allocate new components as needed. */ - L2LogicalChannel() - :mL1(NULL),mSACCH(NULL) - { - for (int i=0; i<4; i++) mL2[i]=NULL; - } + L2LogicalChannelBase() :mL1(NULL) { } + void startl1(); +public: - /** The destructor doesn't do anything since logical channels should not be destroyed. */ - virtual ~L2LogicalChannel() {}; - + virtual ~L2LogicalChannelBase() {}; /**@name Accessors. */ //@{ - SACCHLogicalChannel* SACCH() { return mSACCH; } - const SACCHLogicalChannel* SACCH() const { return mSACCH; } L3ChannelDescription channelDescription() const; //@} - /**@name Pass-throughs. */ - //@{ - // Pat 5-27-2012: Let the LogicalChannel know the next scheduled write time. GSM::Time getNextWriteTime() { return mL1->encoder()->getNextWriteTime(); } - /** Set L1 physical parameters from a RACH or pre-exsting channel. */ - virtual void setPhy(float wRSSI, float wTimingError, double wTimestamp); - - /* Set L1 physical parameters from an existing logical channel. */ - 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. - The default 15 second timeout works for most L3 operations. - @param timeout_ms A read timeout in milliseconds. - @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 * 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 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), - // which does nothing but call mL1->writeHighSide(L2Frame), which is a pass-through - // except that the SapMux uses mDownStream which is copied from mL1, so there is a - // chance to redirect it. But wouldn't that be an error? - // Anyway, L1Encoder::writeHighSide is usually overridden. - // For TCH, it goes to XCCHL1Encoder::writeHighSide() which processes - // the L2Frame primitive, then sends traffic data to TCHFACCHL1Encoder::sendFrame(), - // which just enqueues the frame - it does not block. - // A thread runs GSM::TCHFACCHL1EncoderRoutine() which - // calls TCHFACCHL1Encoder::dispatch() which is synchronized with the gBTS clock, - // unsynchronized with the queue, because it must send data no matter what. - // Eventually it encodes the data and - // calls (ARFCNManager*)mDownStream->writeHighSideTx(), which writes to the socket. - // - // For CCCH channels: - // CCCHLogicalChannel::send(L3RRMessage) wraps the message in an L3Frame - // and enqueues the message on CCCHLogicalChannel::mQ. - // CCCHLogicalChannel::serviceLoop() pulls it out and sends it to - // LogicalChannel::send(L3Frame) [this function], which is virtual, but I dont think it - // is over-ridden, so message goes to L2DL::writeHighSide(L3Frame) which - // is over-ridden to CCCHL2::writeHighSide(L3Frame) which creates an L2Frame - // and calls (SAPMux)mDownstream->writeHighSide(L2Frame), which just - // calls (L1FEC)mDownStream->writeHighSide(L2Frame), which - // (because CCCHL1FEC is nearly empty) just - // calls (L1Encoder)mEncoder->writeHighSide(L2Frame), which maps - // to CCCHL1Encoder which maps to XCCHL1Encoder::writeHighSide(L2Frame), - // which processes the L2Frame primitive, and sends traffic data to - // XCCHL1Encoder::sendFrame(L2Frame), which encodes the frame and then calls - // XCCHL1Encoder::transmit(implicit mI arg with encoded burst) that - // finally blocks until L1Encoder::mPrevWriteTime occurs, then sets the - // burst time to L1Encoder::mNextWriteTime and - // calls (ARFCNManager*)mDownStream->writeHighSideTx() which writes to the socket. - assert(mL2[SAPI]); - LOG(INFO) <l2WriteHighSide(frame); - } + // Note: l2recv is defined in L2SAPMux. + virtual void l2sendf(const L3Frame& frame) = 0; /** Send "naked" primitive down the channel. @param prim The primitive to send. @pram SAPI The service access point on which to send. */ - // (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)); } + void l2sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0) + { l2sendf(L3Frame(SAPI,prim)); } /** Serialize and send an L3Message with a given primitive. @param msg The L3 message. @param prim The primitive to use. */ - // (pat) This is never over-ridden except for testing. - virtual void l2sendm(const L3Message& msg, - const GSM::Primitive& prim=DATA, - 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. - */ - // unused - //void waitForPrimitive(GSM::Primitive primitive); - - /** - Block on a channel until a given primitive arrives. - Any payload is discarded. Block indefinitely, no timeout. - @param primitive The primitive to wait for. - @param timeout_ms The timeout in milliseconds. - @return True on success, false on timeout. - */ - // unused - //bool waitForPrimitive(GSM::Primitive primitive, unsigned timeout_ms); - - - + void l2sendm(const L3Message& msg, GSM::Primitive prim=L3_DATA, SAPI_t SAPI=SAPI0); //@} // L3 /**@name L1 interfaces */ //@{ - /** Write a received radio burst into the "low" side of the channel. */ - // (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); } + public: - /** Return true if the channel is safely abandoned (closed or orphaned). */ - virtual bool recyclable() const { assert(mL1); return mL1->recyclable(); } + // Over-ridden in L2LogicalChannel and SACCHLogicalChannel to handle uplink messages, setting the timers on the main channel. + // The other channel types do not have an uplink so they over-ride this with an devassert(0). + // Yes it might be better to have separate classes for these variations. + virtual void writeLowSide(const L2Frame& frame) = 0; - /** 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 downstream connection. + //virtual void writeHighSideL1(const L2Frame& frame) = 0; + virtual void writeToL1(const L2Frame& frame) = 0; + + /** Connect an ARFCN manager to link L1FEC to the radio. */ + virtual void downstream(ARFCNManager* radio); // Used by CCCHLogicalChannel /** 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(); } + const TDMAMapping& txMapping() const { devassert(mL1); return mL1->txMapping(); } /** The TDMAParameters for the receive side. */ // (pat) This lovely function is unused. Use L1Decoder::mapping() - const TDMAMapping& rcvMapping() const { assert(mL1); return mL1->rcvMapping(); } + const TDMAMapping& rcvMapping() const { devassert(mL1); return mL1->rcvMapping(); } /** GSM 04.08 10.5.2.5 type and offset code. */ - TypeAndOffset typeAndOffset() const { assert(mL1); return mL1->typeAndOffset(); } + TypeAndOffset typeAndOffset() const { devassert(mL1); return mL1->typeAndOffset(); } /** 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(); } + unsigned ARFCN() const { devassert(mL1); return mL1->ARFCN(); } + bool l1active() const { devassert(mL1); return mL1->l1active(); } /**@name Channel stats from the physical layer */ //@{ /** Carrier index. */ - unsigned CN() const { assert(mL1); return mL1->CN(); } + unsigned CN() const { devassert(mL1); return mL1->CN(); } /** Slot number. */ - unsigned TN() const { assert(mL1); return mL1->TN(); } + unsigned TN() const { devassert(mL1); return mL1->TN(); } /** Receive FER. */ - float FER() const { assert(mL1); return mL1->FER(); } + float FER() const { devassert(mL1); return mL1->FER(); } DecoderStats getDecoderStats() const { return mL1->decoder()->getDecoderStats(); } - // Obtains SACCH reporting info. - virtual MSPhysReportInfo *getPhysInfo() const; /** Control whether to accept a handover. */ - HandoverRecord& handoverPending(bool flag, unsigned handoverRef) { assert(mL1); return mL1->handoverPending(flag, handoverRef); } + HandoverRecord& handoverPending(bool flag, unsigned handoverRef) { devassert(mL1); return mL1->handoverPending(flag, handoverRef); } //@} //@} // L1 - /**@name L2 passthroughs */ - //@{ - unsigned N200() const { assert(mL2[0]); return mL2[0]->N200(); } - unsigned T200() const { assert(mL2[0]); return mL2[0]->T200(); } - bool multiframeMode(unsigned SAPI) const - { assert(mL2[SAPI]); return mL2[SAPI]->multiframeMode(); } - //@} - - //@} // passthrough - - - /** Connect an ARFCN manager to link L1FEC to the radio. */ - void downstream(ARFCNManager* radio); /** Return the channel type. */ virtual ChannelType chtype() const =0; @@ -314,25 +174,48 @@ public: /** 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. + (pat) LogicalChannel::lcopen() 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. + LogicalChannel::lcopen() 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, + In uplink: the MS always establishes SABM mode. After the lcopen(), when the first good frame arrives, an ESTABLISH primitive is sent upstream toward L3, which will notify the DCCHDispatcher to start looking for messages. + + (pat) 4-28-2014: Did a major overhaul of channel initialization. Formerly lcopen was called directly from getTCH (except for GPRS channels), + then some callers initialized SACCH, which may already be transmitting; old code also had a nasty side effect that getTCH blocked. + The phy parameters in the SACCH must be inited before it starts transmitting or some handsets reject the channel. + This code is also used during channel initialization when no phy parameters are known, eg in GSMConfig. + So the former one step lcopen is replaced with three steps: + 1. lcinit does base initialization of the channel; + 2. optionally call setPhy or initPhy on the underlying SACCH channels + 3. lcstart starts the channel actually transmitting. Note that lcstart may block. + Note getTCH is modified to call only lcinit, so callers that need the channel running must call lcstart. + The lcopen() method still exists for those callers who really dont care about phy parameters, which should only be GSMConfig for + the very initial channel creation when OpenBTS fires up, which should only be on ARFCN C0, which must be constantly transmitting + on all timeslots. + + (pat) 5-2014: Communication on SACCH is constant in both directions. + The loss of layer1 (RR) connection is detected by lack of communication on the SACCH, see GSM 5.08 5.3. + GSM 4.08 3.4.13.2: RR connection loss is communicated directly from layer1 to layer3, which is why there is no "PHY-ERROR" + primitive in LAPDm specs. Layer3 is supposed to release the channel, which means stop transmitting on SACCH and then + wait for T3109 to elapse before marking the channel available for reuse. OpenBTS totally botched this previously. + Note that it is common for the handset to be able to hear BTS even though the BTS cannot hear the handset. + So note that when we detect loss of SACCH measurement reports we must first wait by SACCH loss for a network-determined + period of time, but probably 30 secs like T3109, then layer3 is supposed to send a channel release, deactivate SACCH + and wait another T3109 period before reusing the channel. */ - virtual void open(); /**@ Debuging functions: will give access to all intermediate layers. */ //@{ - L2DL * debugGetL2(unsigned sapi){ return mL2[sapi]; } + // unused: L2DL * debugGetL2(unsigned sapi){ return mL2[sapi]; } L1FEC * debugGetL1(){ return mL1; } + L1FEC * lcGetL1() { return mL1; } //@} - const char* descriptiveString() const { return mL1->descriptiveString(); } + const char* descriptiveString() const { devassert(mL1); return mL1->descriptiveString(); } protected: @@ -341,16 +224,152 @@ public: Should be called from inside the constructor after the channel components are created. */ - virtual void connect(); + void connect(L1FEC *wL1); public: bool inUseByGPRS() const { return mL1->inUseByGPRS(); } bool decryptUplink_maybe(string wIMSI, int wA5Alg) { return mL1->decoder()->decrypt_maybe(wIMSI, wA5Alg); } }; +// (pat) This is used for channels that use LAPDm, and for historical reasons, CCCH which doesnt. +class L2SAPMux : public L2LogicalChannelBase +{ + protected: + // Input interface from Layer3. The queue is common to both L2LogicalChannel and SACCHLogicalChannel, so it was + // convenient to put it here, but all the methods accessing it are in the descendent classes. + InterthreadPriorityQueue mL3In; ///< we connect L3->L2 through a FIFO -std::ostream& operator<<(std::ostream&, const L2LogicalChannel&); + /** + Send an L3Frame on downlink. + Only call this from the service loop so that messages to LAPDm are serialized. + This method will block until the message is transferred to the transceiver. + @param frame The L3Frame to be sent. + @param SAPI The service access point indicator. + */ + void sapWriteFromL3(const L3Frame& frame); + void flushL3In(); + + mutable Mutex mSapLock; + L2DL * mL2[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. + public: + L2SAPMux(){ + mL2[0] = NULL; + mL2[1] = NULL; + mL2[2] = NULL; + mL2[3] = NULL; + } + void sapInit(L2DL *sap0, L2DL *sap3); + + virtual ~L2SAPMux() {} + + void sapWriteFromL1(const L2Frame& frame); + + virtual void writeToL3(L3Frame*frame) = 0; + + void sapStart(); + // (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(SAPI_t SAPI=SAPI0) const; + bool multiframeMode(SAPI_t SAPI) const; + void l2stop(); +}; + +// This is a main LogicalChannel, meaning SDCCH or TCH/FACCH. +// It is always associated with a SACCHLogicalChannel. +class L2LogicalChannel: public L2SAPMux, public Control::L3LogicalChannel +{ + virtual void _define_vtable(); + + // The messages from L2->L3 for both L2LogicalChannel and SACCHLogicalChannel go in this same queue. + InterthreadPriorityQueue mL3Out; ///< we connect L2->L3 through a FIFO + + Z100TimerThreadSafe mT3101; + Z100TimerThreadSafe mT3109; + Z100TimerThreadSafe mT3111; + // When the channel becomes recyclable there may be a downlink transmission queued up. + // SACCH transmissions are at 480ms intervals. + // I want to make sure those clear, so we will wait an additional time before recycling the channel. + // It also allows time for an ack as per spec. + //Z100TimerThreadSafe mTRecycle; // Additional delay before recycling channel. + + static void *MessageServiceLoop(L2LogicalChannel*); + static void *ControlServiceLoop(L2LogicalChannel*); + Thread mlcMessageServiceThread; ///< a thread for the service L3 queue loop + Thread mlcControlServiceThread; ///< a thread for the service loop + Bool_z mlcMessageLoopRunning; ///< true if the service loops are started + Bool_z mlcControlLoopRunning; ///< true if the service loops are started + + protected: + SACCHLogicalChannel *mSACCH; ///< The associated SACCH, if any. + // (pat) The reverse pointer is SACCHLogicalChannel::mHost. + void startNormalRelease(); + + public: + L2LogicalChannel() : mSACCH(NULL) {} + SACCHLogicalChannel* getSACCH() { return mSACCH; } + const SACCHLogicalChannel* getSACCH() const { return mSACCH; } + void lcinit(); + void lcstart(); + void lcopen(); + + L3Frame * l2recv(unsigned timeout_ms = 15000); + void l2sendf(const L3Frame& frame); + void l2sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0) { L2LogicalChannelBase::l2sendp(prim,SAPI); } + void l2sendm(const L3Message& msg, GSM::Primitive prim=L3_DATA, SAPI_t SAPI=SAPI0) { L2LogicalChannelBase::l2sendm(msg,prim,SAPI); } + bool multiframeMode(SAPI_t SAPI) const; + + void writeLowSide(const L2Frame& frame); + + // Returns true if link is lost due to RR failure or normal channel closure. + bool radioFailure() const; + + /** Connect an ARFCN manager to link L1FEC to the radio. */ + void downstream(ARFCNManager* radio); + + /** Return true if the channel is safely abandoned (closed or orphaned). */ + bool recyclable(); + + + // Frame comes down from L2LAPDm writeL1. + void writeToL1(const L2Frame& frame); + + // Frame comes from LapDm toward L3. + void writeToL3(L3Frame*frame); + + const char* descriptiveString() const { devassert(mL1); return mL1->descriptiveString(); } + std::string displayTimers() const; + + void serviceHost(); + void immediateRelease(); + + /** Set L1 physical parameters from a RACH or pre-exsting channel. */ + // This just passes it off to the SACCH channel, which then just passes it off to SACCH L1. + virtual void l1InitPhy(float wRSSI, float wTimingError, double wTimestamp); + + /* Set L1 physical parameters from an existing logical channel. */ + virtual void setPhy(const L2LogicalChannel&); + virtual ChannelType chtype() const =0; // Yet another redundant decl for wonderful C++. + + + /**@name L2 passthroughs */ + //@{ + virtual MSPhysReportInfo *getPhysInfo() const; + virtual const L3MeasurementResults& measurementResults() const; + ChannelHistory *getChannelHistory(); + //@} + // FIXME: reset T3101 when the handover starts, and get rid of debug3101remaining + void addT3101(int msecs) { mT3101.addTime(msecs); } + long debug3101remaining() { return mT3101.remaining(); } + +}; + + +// (pat) The operator<< for L2LogicalChannel should not be necessary but g++ prints warnings in other directories if missing. Compiler bug? +std::ostream& operator<<(std::ostream&os, const L2LogicalChannel&ch); std::ostream& operator<<(std::ostream&os, const L2LogicalChannel*ch); +std::ostream& operator<<(std::ostream&os, const L2LogicalChannelBase&ch); +std::ostream& operator<<(std::ostream&os, const L2LogicalChannelBase*ch); /** @@ -379,54 +398,49 @@ class SDCCHLogicalChannel : public L2LogicalChannel { /** Logical channel for NDCCHs that use Bbis format and a pseudolength. - This is a virtual base class this is extended for CCCH & BCCH. + This is a virtual base class this is extended for CCCH only. (formerly used for BCCH) See GSM 04.06 4.1.1, 4.1.3. + There is no SAPMux or LAPDm. */ -class NDCCHLogicalChannel : public L2LogicalChannel { +class NDCCHLogicalChannel : public L2LogicalChannelBase { + void writeToL1(const L2Frame& frame) { mL1->writeHighSide(frame); } public: + // For CCCH channels: + // This skips LAPDm entirely. + // calls (L1FEC)mL1->writeHighSide(L2Frame), which + // (because CCCHL1FEC is nearly empty) just + // calls (L1Encoder)mEncoder->writeHighSide(L2Frame), which maps + // to CCCHL1Encoder which maps to XCCHL1Encoder::writeHighSide(L2Frame), + // which processes the L2Frame primitive, and sends traffic data to + // XCCHL1Encoder::sendFrame(L2Frame), which encodes the frame and then calls + // XCCHL1Encoder::transmit(implicit mI arg with encoded burst) that + // finally blocks until L1Encoder::mPrevWriteTime occurs, then sets the + // burst time to L1Encoder::mNextWriteTime and + // calls (ARFCNManager*)mDownStream->writeHighSideTx() which writes to the socket. + void l2sendf(const L3Frame& l3frame) { + //OBJLOG(DEBUG) <<"NDCCH::writeHighSide " << l3frame; + devassert(l3frame.primitive()==L3_UNIT_DATA); + L2Header header(L2Length(l3frame.L2Length())); + writeToL1(L2Frame(header,l3frame,true)); + } /** This channel only sends RR protocol messages. */ - virtual void l2sendm(const L3RRMessage& msg) - { L2LogicalChannel::l2sendm((const L3Message&)msg,UNIT_DATA); } + void l2sendm(const L3RRMessage& msg) + { L2LogicalChannelBase::l2sendm((const L3Message&)msg,L3_UNIT_DATA); } /** This channel only sends RR protocol messages. */ - //void send(const L3Message&) { assert(0); } // old method name. - void l2sendm(const L3Message&) { assert(0); } + void l2sendm(const L3Message&) { devassert(0); } + // No upstream messages ever. + L3Frame * l2recv(unsigned /*timeout_ms = 15000*/, unsigned /*SAPI=0*/) { devassert(0); return NULL; } + void writeLowSide(const L2Frame& /*frame*/) { devassert(0); } + void writeToL3(L3Frame*) { devassert(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(); -}; - - - /** Slow associated control channel. @@ -443,21 +457,32 @@ class NeighborCache { 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 L2LogicalChannel, public NeighborCache { +class SACCHLogicalChannel : public L2SAPMux, public ChannelHistory +{ + friend class L2LogicalChannel; + virtual void _define_vtable(); + //SAPMux mSapMux; ///< service access point multiplex - protected: InterthreadQueue mTxQueue; // FIXME: not currently used. Queue of outbound messages from Layer3 for this SACCH. SAPI is determined from message PD. +#if USE_SEMAPHORE sem_t mOpenSignal; // (pat 7-25-2013) +#endif SACCHL1FEC *mSACCHL1; - Thread mServiceThread; ///< a thread for the service loop - bool mRunning; ///< true is the service loop is started + Thread mSacchServiceThread; ///< a thread for the service loop + Bool_z mSacchRunning; ///< true if the service loop is started + + protected: /** MeasurementResults from the MS. They are caught in serviceLoop, accessed for recording along with GPS and other data in MobilityManagement.cpp */ + // (pat) This is the most recent measurement; it is replaced every 480ms. L3MeasurementResults mMeasurementResults; + // Return true if the frame was processed and discarded. + bool processMeasurementReport(L3Frame *frame); + // (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 @@ -470,18 +495,17 @@ class SACCHLogicalChannel : public L2LogicalChannel, public NeighborCache { // 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! + //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; - } + //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. + L2LogicalChannel *mHost; public: @@ -493,18 +517,34 @@ class SACCHLogicalChannel : public L2LogicalChannel, public NeighborCache { ChannelType chtype() const { return SACCHType; } - void open(); + void sacchInit(); - friend void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*); + // L2LAPDm low-side connections: via SAPMux interface: + void writeLowSide(const L2Frame& frame) { sapWriteFromL1(frame); } + // Frame comes down from L2LAPDm writeL1. + void writeToL1(const L2Frame& frame); + + // L2LAPDm high-side connections + // Frame comes from LapDm toward L3. + void writeToL3(L3Frame*frame); + + + // Layer3 interface: + void l2sendf(const L3Frame& frame); + + /** A C interface for the SACCHLogicalChannel embedded loop. */ + static void *SACCHServiceLoop(SACCHLogicalChannel*); /**@name Pass-through accoessors to L1. */ //@{ // Obtains SACCH reporting info. MSPhysReportInfo *getPhysInfo() const { return mSACCHL1->getPhysInfo(); } - void setPhy(float RSSI, float timingError, double wTimestamp) - { mSACCHL1->setPhy(RSSI,timingError,wTimestamp); } + ChannelHistory *getChannelHistory() { return static_cast(this); } + DecoderStats getDecoderStats() const { return mSACCHL1->decoder()->getDecoderStats(); } + void l1InitPhy(float RSSI, float timingError, double wTimestamp) + { mSACCHL1->l1InitPhy(RSSI,timingError,wTimestamp); } void setPhy(const SACCHLogicalChannel& other) { mSACCHL1->setPhy(*other.mSACCHL1); } - void RSSIBumpDown(int dB) { assert(mL1); mSACCHL1->RSSIBumpDown(dB); } + void RSSIBumpDown(int dB) { devassert(mL1); mSACCHL1->RSSIBumpDown(dB); } //@} @@ -513,112 +553,17 @@ class SACCHLogicalChannel : public L2LogicalChannel, public NeighborCache { const L3MeasurementResults& measurementResults() const { return mMeasurementResults; } //@} - /** Get active state from the host DCCH. */ - bool active() const { assert(mHost); return mHost->active(); } - /** Get recyclable state from the host DCCH. */ - bool recyclable() const { assert(mHost); return mHost->recyclable(); } + //bool recyclable() { devassert(mHost); return mHost->recyclable(); } + bool sacchRadioFailure() const; L2LogicalChannel *hostChan() const { return mHost; } - protected: - - /** Read and process a measurement report, called from the service loop. */ - void getReport(); - + private: /** This is a loop in its own thread that sends SI5 and SI6. */ - void serviceLoop(); + void serviceSACCH(unsigned &count); }; -/** A C interface for the SACCHLogicalChannel embedded loop. */ -void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*); - - -/** - Common control channel. - The "uplink" component of the CCCH is the RACH. - See GSM 04.03 4.1.2: "A common control channel is a point-to-multipoint - bi-directional control channel. Common control channels are physically - sub-divided into the common control channel (CCCH), the packet common control - channel (PCCCH), and the Compact packet common control channel (CPCCCH)." - (pat) To implement DRX and paging I added the CCCHCombinedChannel to which CCCH messages - should now be sent, and this class is now just a private attachment point whose primary - purpose is to house the serviceloop for a single CCCH. -*/ -class CCCHLogicalChannel : public NDCCHLogicalChannel { - - protected: - friend class GSMConfig; - - /* - Because the CCCH is written by multiple threads, - we funnel all of the outgoing messages into a FIFO - and empty that FIFO with a service loop. - */ - - Thread mServiceThread; ///< a thread for the service loop - L3FrameFIFO mQ; ///< because the CCCH is written by multiple threads -#if ENABLE_PAGING_CHANNELS - L3FrameFIFO mPagingQ[sMax_BS_PA_MFRMS]; ///< A queue for each paging channel on this timeslot. -#endif - bool mRunning; ///< a flag to indication that the service loop is running - bool mWaitingToSend; // If this is set, there is another CCCH message - // waiting in the encoder serviceloop. - // This variable is not mutex locked and could - // be incorrect, but it is not critical. - - public: - - CCCHLogicalChannel(const TDMAMapping& wMapping); - - void open(); - - void l2sendm(const L3RRMessage& msg) - { - // DEBUG: - //LOG(WARNING) << "CCCHLogicalChannel2::write q"; - mQ.write(new L3Frame((const L3Message&)msg,UNIT_DATA)); - } - - void l2sendm(const L3Message&) { assert(0); } - - /** This is a loop in its own thread that empties mQ. */ - void serviceLoop(); - - /** Return the number of messages waiting for transmission. */ - unsigned load() const { return mQ.size(); } - - // (pat) GPRS needs to know exactly when the CCCH message will be sent downstream, - // because it needs to allocate an upstream radio block after that time, - // and preferably as quickly as possible after that time. - // For now, I'm going to punt on this and return the worst case. - // TODO: This is the wrong way to do this. - // First, this calculation should not be here; it will be hard for anyone maintaining - // the code and making changes that would affect this calculation to find it here. - // Second, it depends on what kind of C0T0 beacon we have. - // We should wait until it is time to send the message, then create it. - // To do this, either the CCCHLogicalChannel::serviceLoop should be rewritten, - // or we should hook XCCHL1Encoder::sendFrame(L2Frame) to modify the message - // if it is a packet message. Or more drastically, make the CCCHLogicalChannel::mQ - // queue hold internal messages not L3Frames, for example, for RACH a struct - // with the arrival time, RACH message, signal strength and timing advance, - // and delay generating the RRMessage until it is ready to send. - // - // But for now, just punt and send a frame time far enough in the future that it - // is guaranteed to work: - // Note: Time wraps at gHyperFrame. - Time getNextMsgSendTime(); - - ChannelType chtype() const { return CCCHType; } - - friend void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*); - -}; - -/** A C interface for the CCCHLogicalChannel embedded loop. */ -void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*); - - class TCHFACCHLogicalChannel : public L2LogicalChannel { @@ -628,8 +573,8 @@ class TCHFACCHLogicalChannel : public L2LogicalChannel { /**@name Sockets for RTP traffic, must be non-blocking. */ //@{ - UDPSocket * mRTPSocket; ///< RTP traffic - UDPSocket * mRTCPSocket; ///< RTP control + //UDPSocket * mRTPSocket; ///< RTP traffic + //UDPSocket * mRTCPSocket; ///< RTP control //@} public: @@ -645,27 +590,27 @@ class TCHFACCHLogicalChannel : public L2LogicalChannel { ChannelType chtype() const { return FACCHType; } - void sendTCH(AudioFrame* frame) - { assert(mTCHL1); mTCHL1->sendTCH(frame); } + void sendTCH(SIP::AudioFrame* frame) + { devassert(mTCHL1); mTCHL1->sendTCH(frame); } - AudioFrame* recvTCH() - { assert(mTCHL1); return mTCHL1->recvTCH(); } + SIP::AudioFrame* recvTCH() + { devassert(mTCHL1); return mTCHL1->recvTCH(); } unsigned queueSize() const - { assert(mTCHL1); return mTCHL1->queueSize(); } + { devassert(mTCHL1); return mTCHL1->queueSize(); } // (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(); } + // { devassert(mTCHL1); return mTCHL1->radioFailure(); } }; - /** Cell broadcast control channel (CBCH). See GSM 04.12 3.3.1. */ -class CBCHLogicalChannel : public NDCCHLogicalChannel { +// (pat) This uses one SDCCH slot, so it has a SACCH that doesnt do anything. +class CBCHLogicalChannel : public L2LogicalChannel { protected: @@ -673,14 +618,13 @@ class CBCHLogicalChannel : public NDCCHLogicalChannel { The CBCH should be written be a single thread. The input interface is *not* multi-thread safe. */ - public: CBCHLogicalChannel(const CompleteMapping& wMapping); void l2sendm(const L3SMSCBMessage& msg); - void l2sendm(const L3Message&) { assert(0); } + void l2sendm(const L3Message&) { devassert(0); } ChannelType chtype() const { return CBCHType; } @@ -711,21 +655,12 @@ class L3LoopbackLogicalChannel : public Control::L3LogicalChannel { ChannelType chtype() const { return SDCCHType; } /** L3 Loopback */ - // (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) - //{ l2sendp(prim,SAPI); } - - void l2sendp(const GSM::Primitive prim, SAPI_t SAPI=SAPI0) - { mL3Q[SAPI].write(new L3Frame(SAPI,prim)); } + void l2sendf(const L3Frame& frame) + { + mL3Q[frame.getSAPI()].write(new L3Frame(frame)); + } /** L3 Loopback */ L3Frame* l2recv(unsigned timeout_ms = 15000, unsigned SAPI=0) @@ -735,6 +670,7 @@ class L3LoopbackLogicalChannel : public Control::L3LogicalChannel { +#if WHATISTHIS class SDCCHLogicalChannel_LB : public SDCCHLogicalChannel { public : @@ -755,6 +691,7 @@ public: const CompleteMapping& wMapping); }; +#endif //@} diff --git a/GSM/GSMRadioResource.cpp b/GSM/GSMRadioResource.cpp new file mode 100644 index 0000000..8dedd55 --- /dev/null +++ b/GSM/GSMRadioResource.cpp @@ -0,0 +1,173 @@ +/* +* Copyright 2014, 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. +*/ + +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + +#include "GSMRadioResource.h" +#include "GSMCommon.h" +#include "GSMConfig.h" +#include "GSMLogicalChannel.h" +#include "GSMCCCH.h" +#include +#include // For L3TranEntryId, NewTransactionTable. +#include + +namespace GSM { +using namespace Control; + +/** + Determine the channel type needed. GSM 04.08 9.1.8, Table 9.3 and 9.3a. + Assumes we do not support call reestablishment. + @param RA The request reference from the channel request message. + @return channel type code, undefined if not a supported service +*/ +ChannelType decodeChannelNeeded(unsigned RA) +{ + // 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. + // FIXME -- So we probably should not use "any channel" in the paging indications. + if (RA5 == 0x04) return TCHFType; // any channel or any TCH. + if (RA4 == 0x01) return SDCCHType; // SDCCH + if (RA4 == 0x02) return TCHFType; // TCH/F + if (RA4 == 0x03) return TCHFType; // TCH/F + if ((RA&0xf8) == 0x78 && RA != 0x7f) return PSingleBlock1PhaseType; + if ((RA&0xf8) == 0x70) return PSingleBlock2PhaseType; + + int NECI = gConfig.getNum("GSM.CellSelection.NECI"); + if (NECI==0) { + if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures + if (RA5 == 0x00) return SDCCHType; // location updating + } else { + if (gConfig.getBool("Control.VEA")) { + // Very Early Assignment + if (RA5 == 0x07) return TCHFType; // MOC for TCH/F + if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient + } else { + // Early Assignment + if (RA5 == 0x07) return SDCCHType; // MOC for TCH/F + if (RA4 == 0x04) return SDCCHType; // MOC, TCH/H sufficient + } + if (RA4 == 0x00) return SDCCHType; // location updating + if (RA4 == 0x01) return SDCCHType; // other procedures on SDCCH + } + + // Anything else falls through to here. + // We are still ignoring data calls, LMU. + return UndefinedCHType; +} + +/** Return true if RA indicates LUR. */ +bool requestingLUR(unsigned RA) +{ + int NECI = gConfig.getNum("GSM.CellSelection.NECI"); + if (NECI==0) return ((RA>>5) == 0x00); + else return ((RA>>4) == 0x00); +} + + + +// Return a RACH channel request message (what we call RA) for various types of channel requests. +// The RA is only 8 bits. +static unsigned createFakeRachRA(FakeRachType rachtype) +{ + switch (rachtype) { + default: devassert(0); + case FakeRachTCH: return 0xe0 | (0x1f & random()); // top 3 bits are 0x7. + case FakeRachSDCCH: return 0x10 | (0x0f & random()); // top 4 bits are 0001 + case FakeRachLUR: return 0x00 | (0x0f & random()); // top 4 bits are 0000 + case FakeRachGPRS: return 0x70 | (0x7 & random()); // top 5 bits are 01110. + case FakeRachAnswerToPaging: return 0x80 | (0x1f & random()); // top 3 bits are 100. + } +} + +FakeRachType fakeRachTypeTranslate(const char *name) +{ + if (strcasestr(name,"tch")) return FakeRachTCH; + if (strcasestr(name,"sdcch")) return FakeRachSDCCH; + if (strcasestr(name,"lur")) return FakeRachLUR; + if (strcasestr(name,"gprs")) return FakeRachGPRS; + if (strcasestr(name,"pag")) return FakeRachAnswerToPaging; + LOG(ERR) <<"Unrecognized fake rach type: " <MaxTA) { + RachInfo tmpRACH(RA,when,RadData(RSSI,timingError)); // Temporary just so we can print it more easily. + LOG(NOTICE) << "ignoring RACH burst TE > "<62) initialTA=62; + + RachInfo *rip = new RachInfo(RA,when,RadData(RSSI,initialTA),TN); + LOG(DEBUG) << "Incoming RACH:" <<*rip <mWhen.FN() % 42432); + enqueueRach(rip); +} + +}; diff --git a/GSM/GSMRadioResource.h b/GSM/GSMRadioResource.h new file mode 100644 index 0000000..1819aca --- /dev/null +++ b/GSM/GSMRadioResource.h @@ -0,0 +1,115 @@ +/* +* Copyright 2014, 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. +*/ +#ifndef GSMRadioResource_H +#define GSMRadioResource_H 1 + +#include "Defines.h" +#include "GSMCommon.h" +#include "MemoryLeak.h" + +namespace GSM { +class L2LogicalChannel; + + +struct RadData { + bool mValid; + float mRSSI; + float mTimingError; + RadData() { mValid = false; } + RadData(float wRSSI, float wTimingError) : mValid(true),mRSSI(wRSSI),mTimingError(wTimingError) {} +}; + +struct RachInfo { + unsigned mRA; + const GSM::Time mWhen; + RadData mRadData; + unsigned mTN; + + // We now pre-allocate channels for RACHes for TCH/SDCCH services, then wait until the mReadyTime + // has passed before servicing them by sending an ImmediateAssignment. + L2LogicalChannel *mChan; + GSM::Time mReadyTime; // When the SACCH is known to have started transmiting correctly. + + int initialTA() const { return mRadData.mTimingError; } + float RSSI() const { return mRadData.mRSSI; } + + // Gotta love this language. + RachInfo(unsigned wRA, const GSM::Time &wWhen, RadData wRD, unsigned wTN=0) + : mRA(wRA), mWhen(wWhen), mRadData(wRD), mTN(wTN), mChan(NULL) + { RN_MEMCHKNEW(RachInfo) } + ~RachInfo() { RN_MEMCHKDEL(RachInfo) } + +#if 0 + // (pat) This is used when a RachInfo is placed in a priority_queue. + // Return true if rach1 should appear before rach2 in the priority_queue, + // meaning that rach1 will be serviced before rach2. + bool operator>(const RachInfo *rach1, const RachInfo *rach2) { + if (rach1->mChan) { + assert(rach2->mChan); + return rach1->mReadyTime < rach2->mreadyTime; + } + return rach1->mWhen < rach2-mWhen; + } +#endif +}; +std::ostream& operator<<(std::ostream& os, const RachInfo &rach); +std::ostream& operator<<(std::ostream& os, const RachInfo *rach); + +#if unused +/** This record carries all of the parameters associated with a RACH burst. */ +class ChannelRequestRecord { + + private: + + unsigned mRA; ///< request reference + GSM::Time mFrame; ///< receive timestamp + float mRSSI; ///< dB wrt full scale + float mTimingError; ///< correlator timing error in symbol periods + + public: + + ChannelRequestRecord( + unsigned wRA, const GSM::Time& wFrame, + float wRSSI, float wTimingError) + :mRA(wRA), mFrame(wFrame), + mRSSI(wRSSI), mTimingError(wTimingError) + { } + + unsigned RA() const { return mRA; } + const GSM::Time& frame() const { return mFrame; } + float RSSI() const { return mRSSI; } + float timingError() const { return mTimingError; } + +}; +#endif + +// Enum used to create fake RACHes for testing purposes. +enum FakeRachType { + FakeRachTCH, + FakeRachSDCCH, + FakeRachLUR, + FakeRachGPRS, + FakeRachAnswerToPaging +}; +FakeRachType fakeRachTypeTranslate(const char *name); +extern void createFakeRach(FakeRachType rachtype); + +extern void AccessGrantResponder(unsigned RA, const GSM::Time& when, float RSSI, float timingError, int TN); +extern ChannelType decodeChannelNeeded(unsigned RA); +extern bool requestingLUR(unsigned RA); + +}; +#endif diff --git a/GSM/GSMSAPMux.cpp b/GSM/GSMSAPMux.cpp deleted file mode 100644 index 392ee06..0000000 --- a/GSM/GSMSAPMux.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* -* Copyright 2008, 2009 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 "GSMSAPMux.h" -#include "GSMTransfer.h" -#include "GSML1FEC.h" -#include "GSML2LAPDm.h" - -#include - - -using namespace GSM; - - -void SAPMux::writeHighSide(const L2Frame& frame) -{ - // The SAP may or may not be present, depending on the channel type. - OBJLOG(DEBUG) << frame; - ScopedLock lock(mLock); - mDownstream->writeHighSide(frame); -} - - - -void SAPMux::writeLowSide(const L2Frame& frame) -{ - 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 (mUpstream[SAPI]) { - mUpstream[SAPI]->writeLowSide(frame); - } else { - LOG(WARNING) << "received DATA for unsupported SAP " << SAPI; - } - return; -} - - - -void LoopbackSAPMux::writeHighSide(const L2Frame& frame) -{ - OBJLOG(DEBUG) << "Loopback " << frame; - // Substitute primitive - L2Frame newFrame(frame); - unsigned SAPI = frame.SAPI(); - switch (frame.primitive()) { - case ERROR: SAPI=0; break; - case RELEASE: return; - default: break; - } - // Because this is a test fixture, as assert here. - // If this were not a text fixture, we would print a warning - // and ignore the frame. - assert(mUpstream[SAPI]); - ScopedLock lock(mLock); - mUpstream[SAPI]->writeLowSide(newFrame); -} - - - -void LoopbackSAPMux::writeLowSide(const L2Frame& frame) -{ - assert(mDownstream); - L2Frame newFrame(frame); - mDownstream->writeHighSide(newFrame); -} - - - - - -// vim: ts=4 sw=4 diff --git a/GSM/GSMSAPMux.h b/GSM/GSMSAPMux.h deleted file mode 100644 index b2ac382..0000000 --- a/GSM/GSMSAPMux.h +++ /dev/null @@ -1,127 +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 SAPMUX_H -#define SAPMUX_H - -#include "GSMTransfer.h" -#include "GSML1FEC.h" - -#include - -namespace GSM { - -class L2DL; - - -/** - A SAPMux is a multipexer the connects a single L1 to multiple L2s. - A "service access point" in GSM/ISDN is analogous to port number in IP. - GSM allows up to 4 SAPs, although only two are presently used. - See GSM 04.05 5.2 for an introduction. - (pat) SAPs exist at every level in the OSI model. This should probably be called L2SAPMux. -*/ -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: - - SAPMux(){ - mUpstream[0] = NULL; - mUpstream[1] = NULL; - mUpstream[2] = NULL; - mUpstream[3] = NULL; - mDownstream = NULL; - } - - virtual ~SAPMux() {} - - virtual void writeHighSide(const L2Frame& frame); - virtual void writeLowSide(const L2Frame& frame); - - void upstream( L2DL * wUpstream, unsigned wSAPI=0 ) - { assert(mUpstream[wSAPI]==NULL); mUpstream[wSAPI]=wUpstream; } - void downstream( L1FEC * wDownstream ) - { assert(mDownstream==NULL); mDownstream=wDownstream; } - -}; - - - - -/** - The LoopbackSAPMux is a test fixture. - Writes to the high side are eachoed back to the high side. - Writes to the low side are echoed back to the low side. -*/ -class LoopbackSAPMux : public SAPMux { - - public: - - LoopbackSAPMux():SAPMux() {} - - void writeHighSide(const L2Frame& frame); - void writeLowSide(const L2Frame& frame); - -}; - - -/** - The L1TestPointSAPMux is a test fixture. - Writes to the high side pass through to the low side. - Writes to the low side are dumped to cout. -*/ -class L1TestPointSAPMux : public SAPMux { - - public: - - L1TestPointSAPMux():SAPMux() {} - ~L1TestPointSAPMux() {} - - // These are defined in the .h so that we - // don't have to link in all of L2 to - // use them. - - void writeHighSide(const L2Frame& frame) - { - assert(mDownstream); - mLock.lock(); - mDownstream->writeHighSide(frame); - mLock.unlock(); - } - - void writeLowSide(const L2Frame& frame) - { - LOG(DEBUG) << "SAPMux::writeLowSide frame=" << frame; - } - -}; - - - - -} // namespace GSM - - -#endif -// vim: ts=4 sw=4 diff --git a/GSM/GSMSMSCBL3Messages.cpp b/GSM/GSMSMSCBL3Messages.cpp index f7d85b3..7469295 100644 --- a/GSM/GSMSMSCBL3Messages.cpp +++ b/GSM/GSMSMSCBL3Messages.cpp @@ -1,9 +1,10 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,6 +15,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include "GSMSMSCBL3Messages.h" diff --git a/GSM/GSMSMSCBL3Messages.h b/GSM/GSMSMSCBL3Messages.h index 64b9c1a..de6331c 100644 --- a/GSM/GSMSMSCBL3Messages.h +++ b/GSM/GSMSMSCBL3Messages.h @@ -1,9 +1,10 @@ /* * 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSM/GSMTAPDump.cpp b/GSM/GSMTAPDump.cpp index 6190353..c9e1e37 100644 --- a/GSM/GSMTAPDump.cpp +++ b/GSM/GSMTAPDump.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * This program is distributed in the hope that it will be useful, @@ -9,14 +10,14 @@ * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. -* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. */ #include "GSMTAPDump.h" #include "GSMTransfer.h" #include -#include +#include UDPSocket GSMTAPSocket; diff --git a/GSM/GSMTAPDump.h b/GSM/GSMTAPDump.h index b3acdbc..b6117a6 100644 --- a/GSM/GSMTAPDump.h +++ b/GSM/GSMTAPDump.h @@ -1,6 +1,8 @@ /* * Copyright 2008, 2009 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. +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/GSM/GSMTDMA.cpp b/GSM/GSMTDMA.cpp index ee818ff..dcef521 100644 --- a/GSM/GSMTDMA.cpp +++ b/GSM/GSMTDMA.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,6 +13,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include "GSMTDMA.h" @@ -64,24 +67,15 @@ const unsigned BCCHFrames[] = {2,3,4,5}; MAKE_TDMA_MAPPING(BCCH,TDMA_BEACON_BCCH,true,false,0x55,true,51); // Note that we removed frames for the SDCCH components of the Combination-V C0T0. +// (pat) This comes from GSM 5.02 clause 7 table 5 of 9 page 46. const unsigned RACHC5Frames[] = {4,5,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,45,46}; -MAKE_TDMA_MAPPING(RACHC5,TDMA_BEACON,false,true,0x55,true,51); +// allowedSlots == 0x1 means only on timeslot 0. +MAKE_TDMA_MAPPING(RACHC5,TDMA_BEACON,false,true,0x1,true,51); -// CCCH 0-2 are used in C-IV and C-V. The others are used in C-IV only. +// Combination-V beacon has three CCCH. +const unsigned CCCH_C5Frames[] = { 6,7,8,9, 12,13,14,15, 16,17,18,19 }; +MAKE_TDMA_MAPPING(CCCH_C5,TDMA_BEACON_CCCH,true,false,0x01,true,51); -const unsigned CCCH_0Frames[] = {6,7,8,9}; -MAKE_TDMA_MAPPING(CCCH_0,TDMA_BEACON_CCCH,true,false,0x55,true,51); - -const unsigned CCCH_1Frames[] = {12,13,14,15}; -MAKE_TDMA_MAPPING(CCCH_1,TDMA_BEACON_CCCH,true,false,0x55,true,51); - -const unsigned CCCH_2Frames[] = {16,17,18,19}; -MAKE_TDMA_MAPPING(CCCH_2,TDMA_BEACON_CCCH,true,false,0x55,true,51); - -const unsigned CCCH_3Frames[] = {22,23,24,25}; -MAKE_TDMA_MAPPING(CCCH_3,TDMA_BEACON_CCCH,true,false,0x55,true,51); - -// TODO -- Other CCCH subchannels 4-8 for support of C-IV. const unsigned SDCCH_4_0DFrames[] = {22,23,24,25}; MAKE_TDMA_MAPPING(SDCCH_4_0D,SDCCH_4_0,true,false,0x01,true,51); @@ -232,7 +226,8 @@ MAKE_TDMA_MAPPING(SACCH_C8_7U,SDCCH_8_7,false,true,0xFF,true,102); -// (pat) The basic 26-multi-frame has SACCH in timeslot #12 and an idle frame in timeslot #35. +// GSM 5.02 clause 7 table 1 of 9. (after sec 6.5) +// (pat) The basic 26-multi-frame has SACCH in timeslot #12 and an idle frame in timeslot #25. // The entries below all follow this format, but a single SACCH message, // comprised of 4 frames, starts at a different spot for each TCH, // which is another way of saying that the TCH itself starts in a different place, diff --git a/GSM/GSMTDMA.h b/GSM/GSMTDMA.h index 7f2470e..b558e77 100644 --- a/GSM/GSMTDMA.h +++ b/GSM/GSMTDMA.h @@ -1,8 +1,9 @@ /**@file Common-use GSM declarations, most from the GSM 04.xx and 05.xx series. */ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -115,15 +116,7 @@ extern const TDMAMapping gSCHMapping; ///< GSM 05.02 Clause 7 Table 3 Line 2 B0 extern const TDMAMapping gBCCHMapping; ///< GSM 05.02 Clause 7 Table 3 Line 3 /// GSM 05.02 Clause 7 Table 3 Line 7 B0-B50, excluding C-V SDCCH parts (SDCCH/4 and SCCH/C4) extern const TDMAMapping gRACHC5Mapping; -extern const TDMAMapping gCCCH_0Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B0 -extern const TDMAMapping gCCCH_1Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B1 -extern const TDMAMapping gCCCH_2Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B2 -extern const TDMAMapping gCCCH_3Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B3 -extern const TDMAMapping gCCCH_4Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B4 -extern const TDMAMapping gCCCH_5Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B5 -extern const TDMAMapping gCCCH_6Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B6 -extern const TDMAMapping gCCCH_7Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B7 -extern const TDMAMapping gCCCH_8Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B8 +extern const TDMAMapping gCCCH_C5Mapping; //@} /**@name SDCCH */ //@{ diff --git a/GSM/GSMTransfer.cpp b/GSM/GSMTransfer.cpp index 5ed3597..ec58400 100644 --- a/GSM/GSMTransfer.cpp +++ b/GSM/GSMTransfer.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,6 +13,8 @@ */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + @@ -46,16 +49,21 @@ ostream& GSM::operator<<(ostream& os, const L2Frame* frame) } -ostream& GSM::operator<<(ostream& os, const L3Frame& frame) +void L3Frame::text(std::ostream &os) const { - os << "L3Frame(primitive=" << frame.primitive(); - if (frame.isData()) { - string mtiname = mti2string(frame.PD(),frame.MTI()); - os <MTI()) <<"("<hex(os); os << "))"; +} + +ostream& GSM::operator<<(ostream& os, const L3Frame& frame) +{ + frame.text(os); return os; } @@ -270,8 +278,8 @@ void L2Frame::randomizeFiller(const L2Header& header) } -L2Frame::L2Frame(const BitVector& bits, Primitive prim) - :BitVector(23*8),mPrimitive(prim) +L2Frame::L2Frame(const BitVector& bits) + :BitVector(23*8), mPrimitive(L2_DATA) { idleFill(); assert(bits.size()<=this->size()); @@ -280,7 +288,7 @@ L2Frame::L2Frame(const BitVector& bits, Primitive prim) L2Frame::L2Frame(const L2Header& header, const BitVector& l3, bool noran) - :BitVector(23*8),mPrimitive(DATA) + :BitVector(23*8),mPrimitive(L2_DATA) { idleFill(); //printf("header.bitsNeeded=%d l3.size=%d this.size=%d\n", @@ -294,7 +302,7 @@ L2Frame::L2Frame(const L2Header& header, const BitVector& l3, bool noran) L2Frame::L2Frame(const L2Header& header) - :BitVector(23*8),mPrimitive(DATA) + :BitVector(23*8),mPrimitive(L2_DATA) { idleFill(); header.write(*this); @@ -523,9 +531,25 @@ ostream& GSM::operator<<(ostream& os, L2Control::FrameType cmd) } +#define CASENAME_OS(x) case x: os << #x; break; ostream& GSM::operator<<(ostream& os, Primitive prim) { switch (prim) { + CASENAME_OS(L2_DATA) + CASENAME_OS(L3_DATA) + CASENAME_OS(L3_DATA_CONFIRM) + CASENAME_OS(L3_UNIT_DATA) + CASENAME_OS(L3_ESTABLISH_REQUEST) + CASENAME_OS(L3_ESTABLISH_INDICATION) + CASENAME_OS(L3_ESTABLISH_CONFIRM) + CASENAME_OS(L3_RELEASE_REQUEST) + CASENAME_OS(L3_RELEASE_CONFIRM) + CASENAME_OS(L3_HARDRELEASE_REQUEST) + CASENAME_OS(MDL_ERROR_INDICATION) + CASENAME_OS(L3_RELEASE_INDICATION) + CASENAME_OS(PH_CONNECT) + CASENAME_OS(HANDOVER_ACCESS) + /** case ESTABLISH: os << "ESTABLISH"; break; case RELEASE: os << "RELEASE"; break; case DATA: os << "DATA"; break; @@ -533,7 +557,9 @@ ostream& GSM::operator<<(ostream& os, Primitive prim) case ERROR: os << "ERROR"; break; case HARDRELEASE: os << "HARDRELEASE"; break; case HANDOVER_ACCESS: os << "HANDOVER_ACCESS"; break; - default: os << "?" << (int)prim << "?"; + **/ + //old:os << "?" << (int)prim << "?"; + default: os << "(unrecognized primitive:" << (int)prim << ")"; } return os; } @@ -543,6 +569,8 @@ const char *GSM::SAPI2Text(SAPI_t sapi) switch (sapi) { case SAPI0: return "SAPI0"; case SAPI3: return "SAPI3"; + case SAPI0Sacch: return "SAPI0-Sacch"; + case SAPI3Sacch: return "SAPI3-Sacch"; case SAPIUndefined: return "SAPIUndefined"; default: return "SAP-Invalid!"; } @@ -553,20 +581,25 @@ ostream& GSM::operator<<(ostream& os, SAPI_t sapi) } - +void L3Frame::f3init() +{ + mTimestamp = timef(); +} L3Frame::L3Frame(const L3Message& msg, Primitive wPrimitive, SAPI_t wSapi) :BitVector(msg.bitsNeeded()),mPrimitive(wPrimitive),mSapi(wSapi), mL2Length(msg.l2Length()) { + f3init(); msg.write(*this); } L3Frame::L3Frame(const char* hexString) - :mPrimitive(DATA),mSapi(SAPIUndefined) + :mPrimitive(L3_DATA),mSapi(SAPIUndefined) { + f3init(); size_t len = strlen(hexString); mL2Length = len/2; resize(len*4); @@ -581,8 +614,9 @@ L3Frame::L3Frame(const char* hexString) L3Frame::L3Frame(const char* binary, size_t len) - :mPrimitive(DATA),mSapi(SAPIUndefined) + :mPrimitive(L3_DATA),mSapi(SAPIUndefined) { + f3init(); mL2Length = len; resize(len*8); size_t wp=0; @@ -626,15 +660,14 @@ void L3Frame::writeL(size_t &wp) writeField(wp,fillBit,1); } - 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. + 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(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. diff --git a/GSM/GSMTransfer.h b/GSM/GSMTransfer.h index eda6943..4fc2b86 100644 --- a/GSM/GSMTransfer.h +++ b/GSM/GSMTransfer.h @@ -1,7 +1,8 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -21,8 +22,10 @@ #include "Interthread.h" #include "BitVector.h" #include "ByteVector.h" +#include "L3Enums.h" #include "GSMCommon.h" #include "GSM503Tables.h" +#include "SIPRtp.h" // For AudioFrame /* Data transfer objects for the GSM core. */ @@ -34,6 +37,7 @@ class TxBurst; class RxBurst; class L3Message; class L2LogicalChannel; // Used as transparent pointer in Control directory. +class SACCHLogicalChannel; /**@name Positions of stealing bits within a normal burst, GSM 05.03 3.1.4. */ //@{ @@ -56,33 +60,77 @@ static const unsigned gSlotLen = 148; ///< number of symbols per slot, not count our primitive set is simple. */ enum Primitive { - // (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 + // Skip value 0 so uninitialized does not mean something. + L2_DATA = 1, ///< data at L1<->L2 interface is just data. + L3_DATA, ///< L2<->L3 acknowledged mode (multiframe) data. + L3_DATA_CONFIRM, ///< sent Lapdm->L2 on successfull acknowledged mode delivery, but currently discarded en route. + L3_UNIT_DATA, ///< L2<->L3 unacknowledged mode datagram-type data. + L3_ESTABLISH_REQUEST, // Sent from L3 to L2 and forwarded to LAPDm, only on SAP3 for SMS, + // since handset always establishes SAPBM mode on host chan SAP0, and we dont use SACCH SAP0. + L3_ESTABLISH_INDICATION, // Sent from LAPDm to L3 when SABM established as a result of request from handset. + L3_ESTABLISH_CONFIRM, // Sent from LAPDm to L3 on completion of L3_ESTABLISH_REQUEST. + // Note that SAP3 may be established by handset or L3, so Layer3 must always check for + // both L3_ESTABLISH_CONFIRM and L3_ESTABLISH_INDICATION in every place it checks + // to handle the case where SABM was sent by BTS and handset simultaneously, so we dont bother. + L3_RELEASE_REQUEST, // Sent from L3 to L2 and forwarded to LAPDm for normal release; + // if on SAP0, deactivate SACCH and start a release of everything. + L3_RELEASE_CONFIRM, // Sent from LAPDm when link release is confirmed, but currently discarded en route because no one cares. + L3_HARDRELEASE_REQUEST, // Sent from L3 to L2 when in cases where we know with certainty that the channel is unused, + // which are: channel change, after handover, or if channel must be returned + // to the free channel pool before being used. + // Message forwarded to LAPDm to tell it to immediately to idle mode without sending anything. + // Called MDL-RELEASE in 3GPP docs, example 4.06 4.1.1.9, and also "local end release" in LAPDm. + // Note that on ARFCN C0 any release implies to start sending dummy bursts (not LAPDm idle frames.) + MDL_ERROR_INDICATION, // Sent from LAPDm to layer2/3 on loss of contact. This is somewhat redundant with detection + // of loss of radio loss in layer1; it might be also used if there are bugs in LAPDm or the phone. + L3_RELEASE_INDICATION, // Sent from LAPDm to layer2/3 on normal release. + // ?? sent from L2 to L3 to indicate channel was released, possibly by loss of contact. + PH_CONNECT, // sent from L1 to LAPDm via L2 when first good burst is detected on a channel. Formerly used ESTABLISH. + HANDOVER_ACCESS, // Sent from L1 to L3 when handover access burst is detected. + + // Note: Internal to layer2: + // Radio loss detection: handle like layer3 RELEASE. + +/* 6-2014: (pat) OLD PRIMITIVES, keep around until new code works, then trash. + * // (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 + * RELEASE_CONFIRM, ///< message from LAPDm to L2LogicalChannel indicating RELEASE confirmation, which allows using release timer T3111. + * // (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 above L1 + * L1LOST, ///< (pat added) communication with handset lost at L1 level. + * // (pat) In GSM 4.06 (LAPDm) the HARDRELEASE corresponds to LAPDm MDL-RELEASE (defined in 4.1.1.10 described in 5.4.4.4) + * // RELEASE corresponds to LAPDm DL-RELEASE (defined in 4.1.1.2 described in 5.4.4.2) + * HARDRELEASE, ///< forced release after an assignment + * HANDOVER_ACCESS ///< received inbound handover access burst + */ }; std::ostream& operator<<(std::ostream& os, Primitive prim); +// At layer3 there is a single L3LogicalChannel connection to the handset, and we no longer distinguish between +// SACCHLogicalChannel or L2LogicalChannel, we just view it as 3 different SAPs, which are: +#define SAPChannelFlag 4 // If set, means SACCH instead of host channel. enum SAPI_t { - SAPIUndefined = 4, // Any other value is fine. SAPI0 = 0, - SAPI3 = 3 + SAPI3 = 3, + SAPI0Sacch = (SAPChannelFlag|0), + SAPI3Sacch = (SAPChannelFlag|3), + SAPIUndefined = 16 // We cant use 0, and cant be negative, but any other value is fine. }; +#define SAPIsSacch(sap) (!!((sap)&SAPChannelFlag)) +#define SAP2SAPI(sap) ((sap)&(SAPChannelFlag-1)) const char *SAPI2Text(SAPI_t sapi); std::ostream& operator<<(std::ostream& os, SAPI_t sapi); @@ -157,8 +205,6 @@ class TxBurstQueue : public InterthreadPriorityQueue { - - /** Class to represent one timeslot of channel bits with soft encoding. // (pat) A "normal burst" looks like this: @@ -494,11 +540,16 @@ unsigned N201(ChannelType, L2Header::FrameFormat); The bits of an L2Frame Bit ordering is MSB-first in each octet. */ +#define NEWL2MESSAGE 0 class L2Frame : public BitVector { private: +#if NEWL2MESSAGE +#else GSM::Primitive mPrimitive; + //RRCause mCause; // (pat) Added 5-2014. +#endif public: @@ -509,30 +560,28 @@ class L2Frame : public BitVector { void idleFill(); /** Build an empty frame with a given primitive. */ - explicit L2Frame(GSM::Primitive wPrimitive=UNIT_DATA) - :BitVector(23*8), - mPrimitive(wPrimitive) +#if NEWL2MESSAGE + explicit L2Frame() : BitVector(23*8) +#else + // (pat) The default value is never used explicitly, but this is the default constructor for an unspecified L2Frame constructor in descendent classes. + //explicit L2Frame(GSM::Primitive wPrimitive=UNIT_DATA) : BitVector(23*8), mPrimitive(wPrimitive) + explicit L2Frame(GSM::Primitive wPrimitive=L2_DATA) : BitVector(23*8), mPrimitive(wPrimitive) + //,mCause(L3RRCause::NormalEvent) +#endif { idleFill(); } - /** Make a new L2 frame by copying an existing one. */ - // (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. BitVector must fit in the L2Frame. */ - L2Frame(const BitVector&, GSM::Primitive); + explicit L2Frame(const BitVector&); /** Make an L2Frame from a payload using a given header. The L3Frame must fit in the L2Frame. The primitive is DATA. */ - L2Frame(const L2Header&, const BitVector&, bool noran=false); + explicit L2Frame(const L2Header&, const BitVector&, bool noran=false); /** Make an L2Frame from a header with no payload. @@ -600,13 +649,32 @@ class L2Frame : public BitVector { //@} +#if NEWL2MESSAGE +#else Primitive primitive() const { return mPrimitive; } /** This is used only for testing. */ void primitive(Primitive wPrimitive) { mPrimitive=wPrimitive; } +#endif }; +#if NEWL2MESSAGE +class L2Message { + GSM::Primitive mPrimitive; + RRCause mCause; // (pat) Added 5-2014. + L2Frame mL2Frame; + public: + explicit L2Message(GSM::Primitive wPrimitive=L2_DATA) : mPrimitive(wPrimitive), mCause(L3RRCause::NormalEvent) {} + + Primitive primitive() const { return mPrimitive; } + /** This is used only for testing. */ + void primitive(Primitive wPrimitive) { mPrimitive=wPrimitive; } +}; +#else +typedef L2Frame L2Message; +#endif + /** Return a reference to the standard LAPDm downlink idle frame. */ const L2Frame& L2IdleFrame(); @@ -632,69 +700,61 @@ class L3Frame : public BitVector { // (pat) This is in Layer3, common to UMTS a // 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. + friend class SACCHLogicalChannel; // So it can modify mSapi. 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.) + double mTimestamp; // When created. + void f3init(); public: - L3Frame(const L3Frame &other) : BitVector(other), mPrimitive(other.mPrimitive), mSapi(other.mSapi), mL2Length(other.mL2Length) { } + explicit L3Frame(const L3Frame &other) : BitVector(other), mPrimitive(other.mPrimitive), mSapi(other.mSapi), mL2Length(other.mL2Length) { f3init(); } -#if ORIGINAL - /** Empty frame with a primitive. */ - L3Frame(Primitive wPrimitive=DATA, size_t len=0) - :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(size_t bitsNeeded) :BitVector(bitsNeeded),mPrimitive(DATA),mSapi(SAPI0),mL2Length(bitsNeeded/8) { f3init(); } - 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) { } + explicit L3Frame(Primitive wPrimitive) :BitVector((size_t)0),mPrimitive(wPrimitive),mSapi(SAPI0),mL2Length(0) { f3init(); } + explicit L3Frame(SAPI_t wSapi, Primitive wPrimitive) :BitVector((size_t)0),mPrimitive(wPrimitive),mSapi(wSapi),mL2Length(0) { f3init(); } - L3Frame(Primitive wPrimitive, size_t len) - :BitVector(len),mPrimitive(wPrimitive),mSapi(SAPIUndefined),mL2Length(len) - { } + explicit L3Frame(Primitive wPrimitive, size_t len) + :BitVector(len),mPrimitive(wPrimitive),mSapi(SAPI0),mL2Length(len) + { f3init(); } /** Put raw bits into the frame. */ // (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) + explicit L3Frame(SAPI_t wSapi,const BitVector& source, Primitive wPrimitive=L3_DATA) :mPrimitive(wPrimitive),mSapi(wSapi),mL2Length(source.size()/8) - { clone(source); if (source.size()%8) mL2Length++; } + { f3init(); clone(source); if (source.size()%8) mL2Length++; } /** Concatenate 2 L3Frames */ // (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), + // :BitVector(f1,f2),mPrimitive(L3_DATA),mSapi(SAPI0), // 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), + explicit L3Frame(SAPI_t wSapi, const BitVector& f1, const BitVector& f2) // (pat) added to replace above. + :BitVector(f1,f2),mPrimitive(L3_DATA),mSapi(wSapi), mL2Length((f1.size() + f2.size())/8) - { } + { f3init(); } /** Build from an L2Frame. */ // (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()); } + :mPrimitive(L3_DATA), mSapi(wSapi),mL2Length(source.L()) + { f3init(); clone(source.L3Part()); } /** Serialize a message into the frame. */ // (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); + explicit L3Frame(const L3Message& msg, Primitive wPrimitive=L3_DATA, SAPI_t sapi=SAPI0); /** Get a frame from a hex string. */ explicit L3Frame(const char*); /** Get a frame from raw binary. */ - L3Frame(const char*, size_t len); + explicit L3Frame(const char*, size_t len); /** Protocol Discriminator, GSM 04.08 10.2. */ L3PD PD() const { return (L3PD)peekField(4,4); } @@ -709,7 +769,7 @@ class L3Frame : public BitVector { // (pat) This is in Layer3, common to UMTS a /** Return the associated primitive. */ GSM::Primitive primitive() const { return mPrimitive; } - bool isData() const { return mPrimitive == DATA || mPrimitive == UNIT_DATA; } + bool isData() const { return mPrimitive == L3_DATA || mPrimitive == L3_UNIT_DATA; } /** Return frame length in BYTES. */ size_t length() const { return size()/8; } @@ -723,6 +783,16 @@ class L3Frame : public BitVector { // (pat) This is in Layer3, common to UMTS a void writeH(size_t& wp); void writeL(size_t& wp); SAPI_t getSAPI() const { return mSapi; } + void text(std::ostream&os) const; + + // (pat) This is used by PointerCompare when an L3Frame is placed in an InterthreadPriorityQueue. + // The "greatest" element is placed at the top of the queue. + bool operator>(const L3Frame&other) const { + // SAP 0 messages have priority over SAP 3. + if ((int)this->mSapi < (int) other.mSapi) { return true; } // SAP 0 trumps SAP 3. + if ((int)this->mSapi > (int) other.mSapi) { return false; } // SAP 3 is not as good as SAP 0. + return this->mTimestamp > other.mTimestamp; // Otherwise just order by time of creation. + } }; @@ -741,8 +811,7 @@ typedef InterthreadQueue L3FrameFIFO; // 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; +typedef InterthreadQueue AudioFrameFIFO; /** (pat) This is the old comment for the GSM Vocoder frame, which has been replaced by AudioFrameRtp. @@ -755,7 +824,7 @@ typedef InterthreadQueue AudioFrameFIFO; ie, 0xd followed by 260 bits of payload. (260+4)/8 == 33 bytes. */ -class AudioFrameRtp : public AudioFrame { +class AudioFrameRtp : public SIP::AudioFrame { // For AMR mode: // The 3 fields are bit-aligned (closest packing) in "bandwidth-efficient" mode: @@ -770,20 +839,20 @@ class AudioFrameRtp : public AudioFrame { // Everything else is payload. public: - static const int RtpHeaderSize = 4; - static const int RtpPlusAmrHeaderSize = 4 + 6; + static int RtpHeaderSize() { return 4; } + static int RtpPlusAmrHeaderSize() { return 4 + 6; } AMRMode mMode; static int headerSizeBits(AMRMode wMode) { - return (wMode == TCH_FS) ? RtpHeaderSize : RtpPlusAmrHeaderSize; + return (wMode == TCH_FS) ? RtpHeaderSize() : RtpPlusAmrHeaderSize(); } // 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); - // 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) {} + // Load the generic data from a ByteVector (aka AudioFrame) into this object and set the AMRMode so the RTP data can be decoded. + AudioFrameRtp(AMRMode wMode, const SIP::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; diff --git a/GSM/Makefile.am b/GSM/Makefile.am index aac0a2c..992ad65 100644 --- a/GSM/Makefile.am +++ b/GSM/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -20,12 +21,15 @@ include $(top_srcdir)/Makefile.common -AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) #AM_CXXFLAGS = -O2 -g noinst_LTLIBRARIES = libGSM.la libGSM_la_SOURCES = \ + GSMChannelHistory.cpp \ + GSMCCCH.cpp \ + GSMRadioResource.cpp \ GSML3SSMessages.cpp \ GSM610Tables.cpp \ GSMCommon.cpp \ @@ -42,7 +46,6 @@ libGSM_la_SOURCES = \ GSML3RRElements.cpp \ GSML3RRMessages.cpp \ GSMLogicalChannel.cpp \ - GSMSAPMux.cpp \ GSMTDMA.cpp \ GSMTransfer.cpp \ GSMTAPDump.cpp \ @@ -51,6 +54,9 @@ libGSM_la_SOURCES = \ PhysicalStatus.cpp noinst_HEADERS = \ + GSMChannelHistory.h \ + GSMCCCH.h \ + GSMRadioResource.h \ GSML3SSMessages.h \ GSM610Tables.h \ GSMCommon.h \ @@ -67,7 +73,6 @@ noinst_HEADERS = \ GSML3RRElements.h \ GSML3RRMessages.h \ GSMLogicalChannel.h \ - GSMSAPMux.h \ GSMTDMA.h \ GSMTransfer.h \ PowerManager.h \ diff --git a/GSM/PhysicalStatus.cpp b/GSM/PhysicalStatus.cpp index c2f4679..0b1b527 100644 --- a/GSM/PhysicalStatus.cpp +++ b/GSM/PhysicalStatus.cpp @@ -6,7 +6,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -23,6 +23,8 @@ * */ +#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging + #include "PhysicalStatus.h" #include @@ -39,10 +41,17 @@ #include #include +#include "NodeManager.h" +extern NodeManager gNodeManager; + using namespace std; using namespace GSM; +#define RN_DISABLE_PHYSICAL_DB 1 // (pat 3-2014) Try disabling this for the major load test. + +#if RN_DISABLE_PHYSICAL_DB +#else static const char* createPhysicalStatus = { "CREATE TABLE IF NOT EXISTS PHYSTATUS (" "CN_TN_TYPE_AND_OFFSET STRING PRIMARY KEY, " // CnTn - @@ -61,9 +70,12 @@ static const char* createPhysicalStatus = { "NCELL_RSSI INTEGER DEFAULT NULL " // RSSI of strongest neighbor ")" }; +#endif int PhysicalStatus::open(const char* wPath) { +#if RN_DISABLE_PHYSICAL_DB +#else int rc = sqlite3_open(wPath, &mDB); if (rc) { LOG(EMERG) << "Cannot open PhysicalStatus database at " << wPath << ": " << sqlite3_errmsg(mDB); @@ -79,6 +91,7 @@ int PhysicalStatus::open(const char* wPath) if (!sqlite3_command(mDB,enableWAL)) { LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mDB); } +#endif return 0; } @@ -87,7 +100,7 @@ PhysicalStatus::~PhysicalStatus() if (mDB) sqlite3_close(mDB); } -bool PhysicalStatus::createEntry(const L2LogicalChannel* chan) +bool PhysicalStatus::createEntry(const SACCHLogicalChannel* chan) { assert(mDB); assert(chan); @@ -110,23 +123,19 @@ bool PhysicalStatus::createEntry(const L2LogicalChannel* chan) return false; } -bool PhysicalStatus::setPhysical(const L2LogicalChannel* chan, +bool PhysicalStatus::setPhysical(const SACCHLogicalChannel* chan, const L3MeasurementResults& measResults) { // TODO -- It would be better if the argument what just the channel // and the key was just the descriptiveString. - - assert(mDB); assert(chan); - // If MEAS_VALID is true, we don't have valid measurements. - // Really. See GSM 04.08 10.5.2.20. - if (measResults.MEAS_VALID()) return true; + if (!measResults.isServingCellValid()) { + return true; + } ScopedLock lock(mLock); - createEntry(chan); - int CN = -1; if (measResults.NO_NCELL()>0) CN = measResults.BCCH_FREQ_NCELL(0); int ARFCN = -1; @@ -141,9 +150,16 @@ bool PhysicalStatus::setPhysical(const L2LogicalChannel* chan, } } - char query[500]; - MSPhysReportInfo *phys = chan->getPhysInfo(); + +#if RN_DISABLE_PHYSICAL_DB + if (ARFCN) {} // shuts up gcc. + return true; +#else + assert(mDB); + createEntry(chan); + + char query[500]; if (ARFCN<0) { sprintf(query, "UPDATE PHYSTATUS SET " @@ -204,6 +220,7 @@ bool PhysicalStatus::setPhysical(const L2LogicalChannel* chan, LOG(DEBUG) << "Query: " << query; return sqlite3_command(mDB, query); +#endif } #if 0 diff --git a/GSM/PhysicalStatus.h b/GSM/PhysicalStatus.h index c41059f..83e04fe 100644 --- a/GSM/PhysicalStatus.h +++ b/GSM/PhysicalStatus.h @@ -1,11 +1,11 @@ /**@file Declarations for PhysicalStatus and related classes. */ /* * Copyright 2010 Kestrel Signal Processing, 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 -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -33,7 +33,7 @@ struct sqlite3; namespace GSM { class L3MeasurementResults; -class L2LogicalChannel; +class SACCHLogicalChannel; /** 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 L2LogicalChannel* chan, const L3MeasurementResults& measResults); + bool setPhysical(const SACCHLogicalChannel* 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 L2LogicalChannel* chan); + bool createEntry(const SACCHLogicalChannel* chan); }; diff --git a/GSM/PowerManager.cpp b/GSM/PowerManager.cpp index 05bcdb1..e645ddf 100644 --- a/GSM/PowerManager.cpp +++ b/GSM/PowerManager.cpp @@ -1,7 +1,8 @@ /* * Copyright 2009 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -12,112 +13,4 @@ */ -#include "PowerManager.h" -#include -#include -#include -#include -#include - -extern TransceiverManager gTRX; - -using namespace GSM; - - - -void PowerManager::increasePower() -{ - int maxAtten = gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB"); - int minAtten = gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB"); - if ((int)mAtten==minAtten) { - LOG(DEBUG) << "power already at maximum"; - return; - } - mAtten--; // raise power by reducing attenuation - if ((int)mAttenmaxAtten) mAtten=maxAtten; - LOG(INFO) << "power increased to -" << mAtten << " dB"; - mRadio->setPower(mAtten); -} - -void PowerManager::reducePower() -{ - int maxAtten = gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB"); - int minAtten = gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB"); - if ((int)mAtten==maxAtten) { - LOG(DEBUG) << "power already at minimum"; - return; - } - mAtten++; // reduce power be increasing attenuation - if ((int)mAttenmaxAtten) mAtten=maxAtten; - LOG(INFO) << "power decreased to -" << mAtten << " dB"; - mRadio->setPower(mAtten); -} - - -// internal method, does the control step -void PowerManager::internalControlStep() -{ - unsigned target = gConfig.getNum("GSM.Radio.PowerManager.TargetT3122"); - LOG(DEBUG) << "Avg T3122 " << mAveragedT3122 << ", target " << target; - // Adapt the power. - if (mAveragedT3122 > target) reducePower(); - else increasePower(); -} - - -void PowerManager::sampleT3122() -{ - // Tweak it down a little just in case there's no activity. - mSamples[mNextSampleIndex] = gBTS.shrinkT3122(); - unsigned numSamples = gConfig.getNum("GSM.Radio.PowerManager.NumSamples"); - mNextSampleIndex = (mNextSampleIndex + 1) % numSamples; - long sum = 0; - for (unsigned i=0; iserviceLoop(); - return NULL; -} - - -void PowerManager::start() -{ - mRadio = gTRX.ARFCN(0); - mRadio->setPower(mAtten); - mThread.start((void*(*)(void*))PowerManagerServiceLoopAdapter,this); -} - - // vim: ts=4 sw=4 diff --git a/GSM/PowerManager.h b/GSM/PowerManager.h index 2af1024..6d87782 100644 --- a/GSM/PowerManager.h +++ b/GSM/PowerManager.h @@ -1,7 +1,8 @@ /* * Copyright 2009 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -14,67 +15,6 @@ #ifndef __POWER_CONTROL__ #define __POWER_CONTROL__ - -#include - -#include -#include - -// forward declaration -//class Timeval; - -// Make it all inline for now - no change to automake - -class ARFCNManager; - - -namespace GSM { - - -class PowerManager { - -private: - - Thread mThread; - ARFCNManager* mRadio; - unsigned mAveragedT3122; ///< averaged over the last NumSamples taken SamplePeriod apart kept in mSamples - volatile unsigned mAtten; ///< current attenuation - either set by ourselves or by setPower - Timeval mLast; ///< when controller operated last time - unsigned mSamples[100]; - unsigned mNextSampleIndex; - - - - void increasePower(); - void reducePower(); - - // internal method, does the control step - void internalControlStep(); - - void sampleT3122(); - - void serviceLoop(); - -public: - - PowerManager(); - - void start(); - - int power() { return -mAtten; } - - friend void* PowerManagerServiceLoopAdapter(PowerManager *pm); - -}; - - -void *PowerManagerServiceLoopAdapter(PowerManager *pm); - - - -} // namespace GSM - - #endif // __POWER_CONTROL__ // vim: ts=4 sw=4 diff --git a/GSMShare/A51.cpp b/GSMShare/A51.cpp index f5217fd..538377c 100644 --- a/GSMShare/A51.cpp +++ b/GSMShare/A51.cpp @@ -12,7 +12,7 @@ * 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. + * the following conditions are adhered 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 diff --git a/GSMShare/A51Test.cpp b/GSMShare/A51Test.cpp index e34b3f4..5e25ba9 100644 --- a/GSMShare/A51Test.cpp +++ b/GSMShare/A51Test.cpp @@ -12,7 +12,7 @@ * 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. + * the following conditions are adhered 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 @@ -91,7 +91,9 @@ #include #include #include "./A51.h" - +// We must have a gConfig now to include BitVector. +#include "Configuration.h" +ConfigurationTable gConfig; /* Test the code by comparing it against * a known-good test vector. */ diff --git a/GSMShare/AMRTest.cpp b/GSMShare/AMRTest.cpp index 8105690..3766dba 100644 --- a/GSMShare/AMRTest.cpp +++ b/GSMShare/AMRTest.cpp @@ -1,7 +1,7 @@ /* -* 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. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -20,6 +20,9 @@ #include "GSM503Tables.cpp" using namespace std; +// We must have a gConfig now to include BitVector. +#include "Configuration.h" +ConfigurationTable gConfig; BitVector randomBitVector(int n) { diff --git a/GSMShare/AmrCoder.cpp b/GSMShare/AmrCoder.cpp index 20fbced..d02e69c 100644 --- a/GSMShare/AmrCoder.cpp +++ b/GSMShare/AmrCoder.cpp @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSMShare/AmrCoder.h b/GSMShare/AmrCoder.h index 29e93cf..c1df823 100644 --- a/GSMShare/AmrCoder.h +++ b/GSMShare/AmrCoder.h @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSMShare/GSM503Tables.cpp b/GSMShare/GSM503Tables.cpp index 267f01a..02150f2 100644 --- a/GSMShare/GSM503Tables.cpp +++ b/GSMShare/GSM503Tables.cpp @@ -1,7 +1,7 @@ /* -* 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. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSMShare/GSM503Tables.h b/GSMShare/GSM503Tables.h index 6c9d27e..6b6327c 100644 --- a/GSMShare/GSM503Tables.h +++ b/GSMShare/GSM503Tables.h @@ -1,7 +1,7 @@ /* -* 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. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSMShare/L3Enums.cpp b/GSMShare/L3Enums.cpp new file mode 100644 index 0000000..518d43f --- /dev/null +++ b/GSMShare/L3Enums.cpp @@ -0,0 +1,250 @@ +/* +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#include "L3Enums.h" +#include +#include +#define CASENAME(x) case x: return #x; + +namespace GSM { +using std::string; +using std::map; + +std::map gCauseNameMap; + +// The load order in the makefile is such that L3Enums.cpp does not get loaded, +// so force it to load by calling this dummy function from this directory. +void _force_L3Enums_to_load() {} + +const char *L3RRCause::RRCause2Str(L3RRCause::RRCause cause) +{ + switch (cause) { + CASENAME(Normal_Event) + CASENAME(Unspecified) + CASENAME(Channel_Unacceptable) + CASENAME(Timer_Expired) + CASENAME(No_Activity_On_The_Radio) + CASENAME(Preemptive_Release) + CASENAME(UTRAN_Configuration_Unknown) + CASENAME(Handover_Impossible) + CASENAME(Channel_Mode_Unacceptable) + CASENAME(Frequency_Not_Implemented) + CASENAME(Leaving_Group_Call_Area) + CASENAME(Lower_Layer_Failure) + CASENAME(Call_Already_Cleared) + CASENAME(Semantically_Incorrect_Message) + CASENAME(Invalid_Mandatory_Information) + CASENAME(Message_Type_Invalid) + CASENAME(Message_Type_Not_Compapatible_With_Protocol_State) + CASENAME(Conditional_IE_Error) + CASENAME(No_Cell_Available) + CASENAME(Protocol_Error_Unspecified) + // Do not add a default case - we want the compiler to whine if a case is undefined... + } // switch + + return ""; +} + +const char *L3RejectCause::rejectCause2Str(L3RejectCause::RejectCause cause) +{ + switch (cause) { + CASENAME(Zero) + CASENAME(IMSI_Unknown_In_HLR) + CASENAME(Illegal_MS) + CASENAME(IMSI_Unknown_In_VLR) + CASENAME(IMEI_Not_Accepted) + CASENAME(Illegal_ME) + CASENAME(PLMN_Not_Allowed) + CASENAME(Location_Area_Not_Allowed) + CASENAME(Roaming_Not_Allowed_In_LA) + CASENAME(No_Suitable_Cells_In_LA) + CASENAME(Network_Failure) + CASENAME(MAC_Failure) + CASENAME(Synch_Failure) + CASENAME(Congestion) + CASENAME(GSM_Authentication_Unacceptable) + CASENAME(Not_Authorized_In_CSG) + CASENAME(Service_Option_Not_Supported) + CASENAME(Requested_Service_Option_Not_Subscribed) + CASENAME(Service_Option_Temporarily_Out_Of_Order) + CASENAME(Call_Cannot_Be_Identified) + CASENAME(Semantically_Incorrect_Message) + CASENAME(Invalid_Mandatory_Information) + CASENAME(Message_Type_Invalid) + CASENAME(Message_Type_Not_Compatible_With_Protocol_State) + CASENAME(IE_Invalid) + CASENAME(Conditional_IE_Error) + CASENAME(Message_Not_Compatible_With_Protocol_State) + CASENAME(Protocol_Error_Unspecified) + // Do not add a default case - we want the compiler to whine if a case is undefined... + } // switch + + return ""; +} + +const char *L3Cause::CCCause2Str(CCCause cause) +{ + switch (cause) { + CASENAME(Unknown_L3_Cause) + CASENAME(Unassigned_Number) + CASENAME(No_Route_To_Destination) + CASENAME(Channel_Unacceptable) + CASENAME(Operator_Determined_Barring) + CASENAME(Normal_Call_Clearing) + CASENAME(User_Busy) + CASENAME(No_User_Responding) + CASENAME(User_Alerting_No_Answer) + CASENAME(Call_Rejected) + CASENAME(Number_Changed) + CASENAME(Preemption) + CASENAME(Non_Selected_User_Clearing) + CASENAME(Destination_Out_Of_Order) + CASENAME(Invalid_Number_Format) + CASENAME(Facility_Rejected) + CASENAME(Response_To_STATUS_ENQUIRY) + CASENAME(Normal_Unspecified) + CASENAME(No_Channel_Available) + CASENAME(Network_Out_Of_Order) + CASENAME(Temporary_Failure) + CASENAME(Switching_Equipment_Congestion) + CASENAME(Access_Information_Discarded) + CASENAME(Requested_Channel_Not_Available) + CASENAME(Resources_Unavailable) + CASENAME(Quality_Of_Service_Unavailable) + CASENAME(Requested_Facility_Not_Subscribed) + CASENAME(Incoming_Calls_Barred_Within_CUG) + CASENAME(Bearer_Capability_Not_Authorized) + CASENAME(Bearer_Capability_Not_Presently_Available) + CASENAME(Service_Or_Option_Not_Available) + CASENAME(Bearer_Service_Not_Implemented) + CASENAME(ACM_GE_Max) + CASENAME(Requested_Facility_Not_Implemented) + CASENAME(Only_Restricted_Digital_Information_Bearer_Capability_Is_Available) + CASENAME(Service_Or_Option_Not_Implemented) + CASENAME(Invalid_Transaction_Identifier_Value) + CASENAME(User_Not_Member_Of_CUG) + CASENAME(Incompatible_Destination) + CASENAME(Invalid_Transit_Network_Selection) + CASENAME(Semantically_Incorrect_Message) + CASENAME(Invalid_Mandatory_Information) + CASENAME(Message_Type_Not_Implemented) + CASENAME(Messagetype_Not_Compatible_With_Protocol_State) + CASENAME(IE_Not_Implemented) + CASENAME(Conditional_IE_Error) + CASENAME(Message_Not_Compatible_With_Protocol_State) + CASENAME(Recovery_On_Timer_Expiry) + CASENAME(Protocol_Error_Unspecified) + CASENAME(Interworking_Unspecified) + // Do not add a default case - we want the compiler to whine if a case is undefined... + } // switch + + return ""; +} + +const char *L3Cause::BSSCause2Str(L3Cause::BSSCause cause) +{ + switch (cause) { + CASENAME(Radio_Interface_Failure) + CASENAME(Uplink_Quality) + CASENAME(Uplink_Strength) + CASENAME(Downlink_Quality) + CASENAME(Downlink_Strength) + CASENAME(Distance) + CASENAME(Operator_Intervention) + CASENAME(Channel_Assignment_Failure) + CASENAME(Handover_Successful) + CASENAME(Better_Cell) + CASENAME(Traffic) + CASENAME(Reduce_Load_In_Serving_Cell) + CASENAME(Traffic_Load) + CASENAME(Traffic_Load_In_Target_Cell_Higher_Than_In_Source_Cell) + CASENAME(Relocation_Triggered) + CASENAME(Equipment_Failure) + CASENAME(No_Radio_Resource_Available) + CASENAME(CCCH_Overload) + CASENAME(Processor_Overload) + CASENAME(Emergency_Preemption) + CASENAME(DTM_Handover_SGSN_Failure) + CASENAME(DTM_Handover_PS_Allocation_Failure) + CASENAME(Transcoding_Mismatch) + CASENAME(Requested_Speech_Version_Unavailable) + CASENAME(Ciphering_Algorithm_Not_Supported) + // Do not add a default case - we want the compiler to whine if a case is undefined... + }; + return ""; +} + +const char *L3Cause::CustomCause2Str(L3Cause::CustomCause cause) +{ + switch (cause) { + CASENAME(No_Paging_Response) + CASENAME(IMSI_Detached) + CASENAME(Handover_Outbound) + CASENAME(Handover_Error) + CASENAME(Invalid_Handover_Message) + CASENAME(Missing_Called_Party_Number) + CASENAME(Layer2_Error) + CASENAME(Sip_Internal_Error) + CASENAME(L3_Internal_Error) + CASENAME(No_Transaction_Expected) + CASENAME(Already_Closed) + CASENAME(SMS_Timeout) + CASENAME(SMS_Error) + CASENAME(SMS_Success) + CASENAME(USSD_Error) + CASENAME(USSD_Success) + CASENAME(MM_Success) + // Do not add a default case - we want the compiler to whine if a case is undefined... + }; + return ""; +} + +const char *L3Cause::AnyCause2Str(AnyCause cause) +{ + switch (cause.getLocus()) { + case LocusCC: return CCCause2Str(cause.ccCause); + case LocusMM: return L3RejectCause::rejectCause2Str((L3RejectCause::RejectCause) cause.getNonLocus()); + case LocusBSS: return BSSCause2Str((BSSCause) cause.value); + case LocusCustom: return CustomCause2Str((CustomCause) cause.value); + } + return ""; +} + +void L3Cause::createCauseMap() +{ + if (gCauseNameMap.size()) { return; } // Created previously. + AnyCause acause; + for (acause.value = 0; acause.value < LocusCustom+0xff; acause.value++) { + const char *name = AnyCause2Str(acause); + if (name[0] != '<') { + gCauseNameMap[string(name)] = acause.value; + } + } +} + +// Accept cause name, identical as used in the enums, of any type of cause (except RR) and return the AnyCause value, +// which means the high byte is the Locus and the low byte is the cause. +// Return 0 if not found. +int CauseName2Cause(string causeName) +{ + if (gCauseNameMap.size() == 0) { L3Cause::createCauseMap(); } + map::const_iterator it = gCauseNameMap.find(causeName); + if (it != gCauseNameMap.end()) { + return it->second; + } else { + return 0; + } +} + +}; diff --git a/GSMShare/L3Enums.h b/GSMShare/L3Enums.h new file mode 100644 index 0000000..bdd238d --- /dev/null +++ b/GSMShare/L3Enums.h @@ -0,0 +1,273 @@ +/* +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ +#ifndef _L3ENUMS_H_ +#define _L3ENUMS_H_ 1 +#include +#include + +namespace GSM { + +// Maps string name of cause to an anycause. +extern std::map gCauseNameMap; + +// The values from the RR Cause Information Element. +/** GSM 04.08 10.5.2.31 */ +struct L3RRCause { + enum RRCause { + Normal_Event = 0, + Unspecified = 1, + Channel_Unacceptable = 2, + Timer_Expired = 3, + No_Activity_On_The_Radio = 4, + Preemptive_Release = 5, + UTRAN_Configuration_Unknown = 6, + Handover_Impossible = 8, // (Timing out of range) + Channel_Mode_Unacceptable = 9, + Frequency_Not_Implemented = 0xa, + Leaving_Group_Call_Area = 0xb, + Lower_Layer_Failure = 0xc, + Call_Already_Cleared = 0x41, + Semantically_Incorrect_Message = 0x5f, + Invalid_Mandatory_Information = 0x60, + Message_Type_Invalid = 0x61, // Not implemented or non-existent + Message_Type_Not_Compapatible_With_Protocol_State = 0x62, + Conditional_IE_Error = 0x64, + No_Cell_Available = 0x65, + Protocol_Error_Unspecified = 0x6f + }; + static const char *RRCause2Str(RRCause cause); +}; +typedef L3RRCause::RRCause RRCause; + +/** RejectCause, GSM 04.08 10.5.3.6 */ +// Better: 24.008 10.5.3.6 +// This is the Mobility Management reject cause. +// For RR causes see L3RRCause, and for CC Causes see L3Cause. +struct L3RejectCause { + public: + enum RejectCause { + Zero = 0, // This is NOT a GSM RejectCause, it is our unspecified value. + IMSI_Unknown_In_HLR = 2, + Illegal_MS = 3, + IMSI_Unknown_In_VLR = 4, + IMEI_Not_Accepted = 5, + Illegal_ME = 6, + PLMN_Not_Allowed = 0xb, + Location_Area_Not_Allowed = 0xc, + Roaming_Not_Allowed_In_LA = 0xd, // Roaming not allowed in this Location Area + No_Suitable_Cells_In_LA = 0xf, + Network_Failure = 0x11, + MAC_Failure = 0x14, + Synch_Failure = 0x15, + Congestion = 0x16, + GSM_Authentication_Unacceptable = 0x17, + Not_Authorized_In_CSG = 0x19, + Service_Option_Not_Supported = 0x20, + Requested_Service_Option_Not_Subscribed = 0x21, + Service_Option_Temporarily_Out_Of_Order = 0x22, + Call_Cannot_Be_Identified = 0x26, + // 0x30 - 0x3f : retry upon entry into a new cell ??? + Semantically_Incorrect_Message = 0x5f, + Invalid_Mandatory_Information = 0x60, + Message_Type_Invalid = 0x61, // Message type non-existent or not implemented + Message_Type_Not_Compatible_With_Protocol_State = 0x62, + IE_Invalid = 0x63, // IE non-existent or not implemented + Conditional_IE_Error = 0x64, + Message_Not_Compatible_With_Protocol_State = 0x65, + Protocol_Error_Unspecified = 0x6f + }; + static const char *rejectCause2Str(RejectCause cause); +}; + +typedef L3RejectCause::RejectCause MMRejectCause; + +// All the other causes here; the L3Cause class is used primarily to provide a namespace wrapper. +// This (together with L3RejectCause, which is referenced by Locus below) encompasses the complete list of reasons a transaction can be terminated. +// See the TermCause class for conversion of these causes into other cause types (CC-cause, SIP error code, or Q.850 code.) +struct L3Cause { + // GSM 4.08 10.5.4.11 Location as used in the Layer3 Cause Information Element. + enum Location { + User=0, + Private_Serving_Local=1, + Public_Serving_Local=2, + Transit=3, + Public_Serving_Remote=4, + Private_Serving_Remote=5, + International=7, + Beyond_Inter_Networking=10 + }; + + + // GSM 4.08 10.5.4.11 call-control cause. + // Note that these are almost the same as ITU Recommendation Q.850 codes, but not quite - the Preemption value is different. + enum CCCause { + Unknown_L3_Cause = 0, // Range addition. + Unassigned_Number = 1, // or unallocated number + No_Route_To_Destination = 3, + Channel_Unacceptable = 6, + Operator_Determined_Barring = 8, + Normal_Call_Clearing = 16, // One of the two handsets hang up. + User_Busy = 17, // "the called user has indicated the inablity to accept another call" + No_User_Responding = 18, // (pat) I dont understand the H.1.7 description: + // "This cause is used when a user does not respond to a call establishment message with either + // "an alerting or connect indication within the prescribed period of time allocated + // "(defined by the expiry of either timer T303 or T310)." + // In other words, the MTC handset is present, but when the BTS sends setup it + // does not respond with alerting. Why not? Busy with GPRS? Authentication failure? + User_Alerting_No_Answer = 19, // The phone rang, but the user did not pick up. + Call_Rejected = 21, // The user "does not wish to accept this call" although they are neither busy nor incompatible. + Number_Changed = 22, + Preemption = 25, // Including pre-emption by emergency call. + Non_Selected_User_Clearing = 26, + Destination_Out_Of_Order = 27, // user equipment off-line. No answer to page. + Invalid_Number_Format = 28, // invalid or incomplete number + Facility_Rejected = 29, + Response_To_STATUS_ENQUIRY = 30, + Normal_Unspecified = 31, + No_Channel_Available = 34, + Network_Out_Of_Order = 38, + Temporary_Failure = 41, + Switching_Equipment_Congestion = 42, + Access_Information_Discarded = 43, + Requested_Channel_Not_Available = 44, + Resources_Unavailable = 47, + Quality_Of_Service_Unavailable = 49, + Requested_Facility_Not_Subscribed = 50, + Incoming_Calls_Barred_Within_CUG = 55, + Bearer_Capability_Not_Authorized = 57, + Bearer_Capability_Not_Presently_Available = 58, + Service_Or_Option_Not_Available = 63, + Bearer_Service_Not_Implemented = 65, + ACM_GE_Max = 68, // ACM greater or equal to ACM max. Whatever that is. + Requested_Facility_Not_Implemented = 69, + Only_Restricted_Digital_Information_Bearer_Capability_Is_Available = 70, // If you ever use, go ahead and abbreviate it. + Service_Or_Option_Not_Implemented = 79, + Invalid_Transaction_Identifier_Value = 81, + User_Not_Member_Of_CUG = 87, + Incompatible_Destination = 88, + Invalid_Transit_Network_Selection = 91, + Semantically_Incorrect_Message = 95, + Invalid_Mandatory_Information = 96, + Message_Type_Not_Implemented = 97, + Messagetype_Not_Compatible_With_Protocol_State = 98, + IE_Not_Implemented = 99, // Information Element non-existent or not implemented. + Conditional_IE_Error = 100, + Message_Not_Compatible_With_Protocol_State = 101, + Recovery_On_Timer_Expiry = 102, + Protocol_Error_Unspecified = 111, + Interworking_Unspecified = 127, + }; + + // We use an integer value to identify any one of these cause types. + // The low byte of the cause value is the cause and the high byte is the Locus from this list. + // We dont put RR causes here because they are somewhat orthogonal to the other causes here and add no new information. + enum Locus { + LocusCC = 0, // Low byte is an L3Cause::CCCause + LocusMM = 0x100, // Low byte is an L3RejectCause::RejectCause + LocusBSS = 0x200, // Low byte is an L3Cause::BSSCause + LocusCustom = 0x300, // Low byte is an L3Cause::CustomCause. + }; + + // MSC-BSS Causes from GSM 48.008 3.2.2.5. 8.08 3.2.2.5 only defines a subset of these. + // This is not the entire list, just the ones we might possibly use. + enum BSSCause { + Radio_Interface_Failure=1 | LocusBSS, + Uplink_Quality=2 | LocusBSS, + Uplink_Strength=3 | LocusBSS, + Downlink_Quality=4 | LocusBSS, + Downlink_Strength=5 | LocusBSS, + Distance=6 | LocusBSS, + Operator_Intervention=7 | LocusBSS, // O&M Intervention BTS Operator Intervention. + Channel_Assignment_Failure=0xa | LocusBSS, // (called: Radio Interface Failure, reversion to old channel) + Handover_Successful=0xb | LocusBSS, // BTS + Better_Cell=0xc | LocusBSS, + Traffic = 0xf | LocusBSS, + + // Only in 48.008: + Reduce_Load_In_Serving_Cell = 0x10 | LocusBSS, + Traffic_Load_In_Target_Cell_Higher_Than_In_Source_Cell = 0x11 | LocusBSS, + Relocation_Triggered = 0x12 | LocusBSS, + + Equipment_Failure=0x20 | LocusBSS, + No_Radio_Resource_Available=0x21 | LocusBSS, + CCCH_Overload=0x23 | LocusBSS, + Processor_Overload = 0x24 | LocusBSS, + Traffic_Load=0x28 | LocusBSS, + Emergency_Preemption=0x29 | LocusBSS, // Emergency Preemption as opposed to Operator_Intervention (Defined as just Preemption in 48.008, but we must distinguish from CCCause::Preemption.) + DTM_Handover_SGSN_Failure = 0x2a | LocusBSS, + DTM_Handover_PS_Allocation_Failure = 0x2b | LocusBSS, + + Transcoding_Mismatch=0x30 | LocusBSS, + Requested_Speech_Version_Unavailable=0x33 | LocusBSS, + + Ciphering_Algorithm_Not_Supported=0x40 | LocusBSS, + }; + + // Custom causes defined within OpenBTS. + enum CustomCause { + // We use No_Paging_Response because there is no precise L3Cause for this (closest is DestinationOutOfOrder) + No_Paging_Response = 1 | LocusCustom, + IMSI_Detached, // This is a MAP error code. If we did not have this we would have to use DestinationOutOfOrder. + Handover_Outbound, + Handover_Error, // Logic error during handover. + Invalid_Handover_Message, + Missing_Called_Party_Number, + Layer2_Error, + Sip_Internal_Error, + L3_Internal_Error, + No_Transaction_Expected, // Used when we dont expect termination to have any transactions to close out. + Already_Closed, // Used when we know for sure the transaction has already been terminated. + SMS_Timeout, + SMS_Error, + SMS_Success, + USSD_Error, + USSD_Success, + MM_Success, + }; + + // Any of the causes described by the Locus above. + // C++ has no graceful way to do this; enums cannot be inherited or extended. + union AnyCause { + CCCause ccCause; + MMRejectCause mmCause; + BSSCause bssCause; + CustomCause customCause; + int value; + //AnyCause(CCCause cause) : ccCause(cause) { } + //AnyCause(MMRejectCause cause) : mmCause(cause) { } + //AnyCause(BSSCause cause) : bssCause(cause) { } + //AnyCause(CustomCause cause) : customCause(cause) { } + AnyCause(int wValue) { value = wValue; } + AnyCause() : value(0) { } // initialize to known and unused value. + bool isEmpty() { return value == 0; } + Locus getLocus() { return (Locus) (value & 0xff00); } + Locus getNonLocus() { return (Locus) (value & 0x00ff); } + }; + + static const char *CCCause2Str(CCCause cause); + static const char *BSSCause2Str(BSSCause cause); + static const char *CustomCause2Str(CustomCause cause); + static const char *AnyCause2Str(AnyCause cause); + static void createCauseMap(); // inits gCauseNameMap, only needs to be called once. +}; + +typedef L3Cause::AnyCause AnyCause; + +typedef L3Cause::CCCause CCCause; +extern void _force_L3Enums_to_load(); +extern int CauseName2Cause(std::string causeName); + + +}; // namespace +#endif diff --git a/GSMShare/Makefile.am b/GSMShare/Makefile.am index d91d7cb..55e789c 100644 --- a/GSMShare/Makefile.am +++ b/GSMShare/Makefile.am @@ -1,6 +1,6 @@ # # Copyright 2008, 2009 Free Software Foundation, Inc. -# Copyright 2011, 2012 Range Networks, Inc. +# Copyright 2011, 2012, 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -27,6 +27,7 @@ AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread noinst_LTLIBRARIES = libGSMShare.la libGSMShare_la_SOURCES = \ + L3Enums.cpp \ AmrCoder.cpp \ GSM503Tables.cpp \ ViterbiR204.cpp \ @@ -40,6 +41,7 @@ noinst_PROGRAMS = \ # ReportingTest noinst_HEADERS = \ + L3Enums.h \ Viterbi.h \ ViterbiR204.h \ AmrCoder.h \ @@ -47,13 +49,33 @@ noinst_HEADERS = \ 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 +# (pat 8-2013) I don't know how to properly include the CommonLibs directory using autofoo. +# You are welcome to correct this reference. +ViterbiTest_SOURCES = ViterbiTest.cpp +ViterbiTest_LDADD = \ + $(GLOBALS_LA) \ + $(noinst_LTLIBRARIES) \ + ../CommonLibs/libcommon.la \ + $(GSM_LA) \ + $(COMMON_LA) \ + $(SQLITE_LA) + AMRTest_SOURCES = AMRTest.cpp -AMRTest_LDADD = $(noinst_LTLIBRARIES) ../CommonLibs/libcommon.la +AMRTest_LDADD = \ + $(GLOBALS_LA) \ + $(noinst_LTLIBRARIES) \ + ../CommonLibs/libcommon.la \ + $(GSM_LA) \ + $(COMMON_LA) \ + $(SQLITE_LA) A51Test_SOURCES = A51Test.cpp -A51Test_LDADD = $(noinst_LTLIBRARIES) ../CommonLibs/libcommon.la +A51Test_LDADD = \ + $(GLOBALS_LA) \ + $(noinst_LTLIBRARIES) \ + ../CommonLibs/libcommon.la \ + $(GSM_LA) \ + $(COMMON_LA) \ + $(SQLITE_LA) diff --git a/GSMShare/Viterbi.h b/GSMShare/Viterbi.h index de1155c..8635bf5 100644 --- a/GSMShare/Viterbi.h +++ b/GSMShare/Viterbi.h @@ -3,7 +3,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/GSMShare/ViterbiR204.cpp b/GSMShare/ViterbiR204.cpp index 71fe13d..999b31b 100644 --- a/GSMShare/ViterbiR204.cpp +++ b/GSMShare/ViterbiR204.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * * This software is distributed under the terms of the GNU Affero Public License. diff --git a/GSMShare/ViterbiR204.h b/GSMShare/ViterbiR204.h index 88eeae3..4fd053f 100644 --- a/GSMShare/ViterbiR204.h +++ b/GSMShare/ViterbiR204.h @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 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. diff --git a/GSMShare/ViterbiTest.cpp b/GSMShare/ViterbiTest.cpp index 151b8d0..eadb10d 100644 --- a/GSMShare/ViterbiTest.cpp +++ b/GSMShare/ViterbiTest.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * * This software is distributed under the terms of the GNU Affero Public License. @@ -41,8 +42,8 @@ using namespace std; // We must have a gConfig now to include BitVector. -//#include "Configuration.h" -//ConfigurationTable gConfig; +#include "Configuration.h" +ConfigurationTable gConfig; void origTest() diff --git a/Globals/GlobalVars.cpp b/Globals/GlobalVars.cpp new file mode 100644 index 0000000..c513391 --- /dev/null +++ b/Globals/GlobalVars.cpp @@ -0,0 +1,86 @@ +/**@file Global system parameters. */ +/* +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#include "config.h" +#include + +#define PROD_CAT "P" + +#define FEATURES "+GPRS " + +// Note that __TIME__ will only get updated if this file gets recompiled, so +// it will usually be wrong. Correct solution is to put all the special defines +// in a header file that always gets generated and recompiled. +const char *gVersionString = "release " VERSION "+" REPO_REV FEATURES PROD_CAT " built " TIMESTAMP_ISO " "; + +const char* gOpenBTSWelcome = + //23456789123456789223456789323456789423456789523456789623456789723456789 + "OpenBTS\n" + "Copyright 2008, 2009, 2010 Free Software Foundation, Inc.\n" + "Copyright 2010 Kestrel Signal Processing, Inc.\n" + "Copyright 2011, 2012, 2013, 2014 Range Networks, Inc.\n" + "Release " VERSION "+" REPO_REV " " PROD_CAT " formal build date " TIMESTAMP_ISO "\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, Michael Iedema, Dave Gotwisner\n" + " Kestrel Signal Processing, Inc.:\n" + " David Burgess, Harvind Samra, Raffi Sevlian, Roshan Baliga\n" + " GNU Radio:\n" + " Johnathan Corgan\n" + " Others:\n" + " Anne Kwong, Jacob Appelbaum, Joshua Lackey, Alon Levy\n" + " Alexander Chemeris, Alberto Escudero-Pascual\n" + "Incorporated L/GPL libraries and components:\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" + " Google Core Dumper, BSD 3-Clause License, Copyright (c) 2005-2007, Google Inc.\n" + "Incorporated public domain libraries and components:\n" + " sqlite3, released to public domain 15 Sept 2001, www.sqlite.org\n" + "\n" + "\nThis program comes with ABSOLUTELY NO WARRANTY.\n" + "\nUse of this software may be subject to other legal restrictions,\n" + "including patent licensing and radio spectrum licensing.\n" + "All users of this software are expected to comply with applicable\n" + "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" + + +; diff --git a/Globals/Globals.cpp b/Globals/Globals.cpp index 39d4a62..3b4434a 100644 --- a/Globals/Globals.cpp +++ b/Globals/Globals.cpp @@ -6,7 +6,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -27,63 +27,9 @@ #define FEATURES "+GPRS " -const char *gVersionString = "release " VERSION FEATURES PROD_CAT " built " TIMESTAMP_ISO " " REPO_REV " "; - -const char* gOpenBTSWelcome = - //23456789123456789223456789323456789423456789523456789623456789723456789 - "OpenBTS\n" - "Copyright 2008, 2009, 2010 Free Software Foundation, Inc.\n" - "Copyright 2010 Kestrel Signal Processing, Inc.\n" - "Copyright 2011, 2012, 2013, 2014 Range Networks, Inc.\n" - "Release " VERSION " " PROD_CAT " formal build date " TIMESTAMP_ISO " " REPO_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, Michael Iedema, Dave Gotwisner\n" - " Kestrel Signal Processing, Inc.:\n" - " David Burgess, Harvind Samra, Raffi Sevlian, Roshan Baliga\n" - " GNU Radio:\n" - " Johnathan Corgan\n" - " Others:\n" - " Anne Kwong, Jacob Appelbaum, Joshua Lackey, Alon Levy\n" - " Alexander Chemeris, Alberto Escudero-Pascual\n" - "Incorporated L/GPL libraries and components:\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" - "\nThis program comes with ABSOLUTELY NO WARRANTY.\n" - "\nUse of this software may be subject to other legal restrictions,\n" - "including patent licensing and radio spectrum licensing.\n" - "All users of this software are expected to comply with applicable\n" - "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" - - -; - +const char *gVERSION(void) { return VERSION; } +const char *gFEATURES(void) { return FEATURES; } +const char *gPROD_CAT(void) { return PROD_CAT; } CommandLine::Parser gParser; diff --git a/Globals/Globals.h b/Globals/Globals.h index d2c0352..d2fcd41 100644 --- a/Globals/Globals.h +++ b/Globals/Globals.h @@ -5,7 +5,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -23,8 +23,9 @@ #ifndef GLOBALS_H #define GLOBALS_H +#if !defined(BUILD_CLI) #include -#include +#include #include #include //#include @@ -35,6 +36,10 @@ #define RN_DEVERLOPER_MODE 0 #endif +#ifndef RN_DISABLE_MEMORY_LEAK_TEST +#define RN_DISABLE_MEMORY_LEAK_TEST 1 +#endif + namespace GPRS { extern unsigned GPRSDebug; } /** Date-and-time string, defined in OpenBTS.cpp. */ @@ -44,7 +49,8 @@ extern const char* gDateTime; Just about everything goes into the configuration table. This should be defined in the main body of the top-level application. */ -extern ConfigurationTable gConfig; +extern OpenBTSConfig gConfig; +#endif // !defined(BUILD_CLI) /** The OpenBTS welcome message. */ extern const char* gOpenBTSWelcome; @@ -52,6 +58,7 @@ extern const char* gOpenBTSWelcome; /** The OpenBTS version string. */ extern const char *gVersionString; +#if !defined(BUILD_CLI) /** The central parser. */ extern CommandLine::Parser gParser; @@ -70,4 +77,9 @@ size_t gWatchdogRemaining(); bool gWatchdogExpired(); extern ReportingTable gReports; + +extern const char *gVERSION(void); +extern const char *gFEATURES(void); +extern const char *gPROD_CAT(void); +#endif // !defined(BUILD_CLI) #endif diff --git a/Globals/GrabRepoInfo.sh b/Globals/GrabRepoInfo.sh index c04a31d..b720282 100755 --- a/Globals/GrabRepoInfo.sh +++ b/Globals/GrabRepoInfo.sh @@ -1,4 +1,25 @@ #!/bin/sh + +# +# Copyright 2014 Range Networks, 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 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + cd $1 INFO="" diff --git a/Globals/Makefile.am b/Globals/Makefile.am index f987b33..088897f 100644 --- a/Globals/Makefile.am +++ b/Globals/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -25,7 +26,7 @@ AM_CXXFLAGS = -Wall noinst_LTLIBRARIES = libglobals.la -libglobals_la_SOURCES = Globals.cpp +libglobals_la_SOURCES = Globals.cpp GlobalVars.cpp noinst_PROGRAMS = diff --git a/LEGAL b/LEGAL index 561b969..f6faa38 100644 --- a/LEGAL +++ b/LEGAL @@ -2,7 +2,7 @@ OpenBTS Most parts copyright 2008-2011 Free Software Foundation. Some parts copyright 2010 Kestrel Signal Processing, Inc. -Some parts copyright 2011-2013 Range Networks, Inc. +Some parts copyright 2011-2014 Range Networks, Inc. Patent Laws diff --git a/Makefile.am b/Makefile.am index b126135..11d150d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -20,17 +21,17 @@ include $(top_srcdir)/Makefile.common -DESTDIR := +DESTDIR := AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES) AM_CXXFLAGS = -Wall -pthread -ldl + #AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread -ldl #AM_CFLAGS = -Wall -O2 -NDEBUG -pthread -ldl # Order must be preserved _SUBDIRS = \ config \ - sqlite3 \ CommonLibs \ Globals \ SIP \ @@ -68,6 +69,7 @@ EXTRA_DIST = \ INSTALLATION \ LEGAL \ COPYING \ + README.APIs \ README install: all @@ -82,7 +84,4 @@ else endif endif -dox: FORCE - doxygen doxconfig - FORCE: diff --git a/Makefile.common b/Makefile.common index 3fa2181..6b3ce52 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -18,11 +19,12 @@ # along with this program. If not, see . # +GPROF_OPTIONS ?= CXXFLAGS := -g -O2 -DTIMESTAMP_ISO=`date +'"%Y-%m-%dT%H:%M:%S"'` CFLAGS := -g -O2 -DTIMESTAMP_ISO=`date +'"%Y-%m-%dT%H:%M:%S"'` AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES) $(OPENBTS_CPPFLAGS) -AM_CXXFLAGS = -Wall -pthread -ldl $(OPENBTS_CXXFLAGS) +AM_CXXFLAGS = -Wall -pthread -rdynamic -ldl $(OPENBTS_CXXFLAGS) COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs CONTROL_INCLUDEDIR = $(top_srcdir)/Control @@ -35,16 +37,17 @@ SMS_INCLUDEDIR = $(top_srcdir)/SMS TRX_INCLUDEDIR = $(top_srcdir)/TRXManager GLOBALS_INCLUDEDIR = $(top_srcdir)/Globals CLI_INCLUDEDIR = $(top_srcdir)/CLI -SQLITE_INCLUDEDIR = $(top_srcdir)/sqlite3 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 +APPS_INCLUDEDIR = $(top_srcdir)/apps REPOREV = -D'REPO_REV="$(shell ./$(top_builddir)/Globals/GrabRepoInfo.sh $(top_builddir))"' -STD_DEFINES_AND_INCLUDES = \ +STD_DEFINES_AND_INCLUDES = $(GPROF_OPTIONS) \ $(REPOREV) \ + -I$(APPS_INCLUDEDIR) \ -I$(COMMON_INCLUDEDIR) \ -I$(CONTROL_INCLUDEDIR) \ -I$(GSM_INCLUDEDIR) \ @@ -59,8 +62,7 @@ STD_DEFINES_AND_INCLUDES = \ -I$(PEERING_INCLUDEDIR) \ -I$(NODEMANAGER_INCLUDEDIR) \ -I$(JSONBOX_INCLUDEDIR) \ - -I$(SCANNING_INCLUDEDIR) \ - -I$(SQLITE_INCLUDEDIR) + -I$(SCANNING_INCLUDEDIR) # These macros are referenced in apps/Makefile.am, which must be changed in sync with these. COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la @@ -77,6 +79,5 @@ CLI_LA = $(top_builddir)/CLI/libcli.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 index 1410565..836ae03 160000 --- a/NodeManager +++ b/NodeManager @@ -1 +1 @@ -Subproject commit 14105653ee19d48b313f6c5cbc0689775a6c5e81 +Subproject commit 836ae03d868bdc09601f66fc926b7610fb1cb782 diff --git a/Peering/Makefile.am b/Peering/Makefile.am index 63982e5..78d0a94 100644 --- a/Peering/Makefile.am +++ b/Peering/Makefile.am @@ -1,7 +1,7 @@ # # Copyright 2008 Free Software Foundation, Inc. # Copyright 2010 Kestrel Signal Processing, Inc. -# Copyright 2011 Range Networks, Inc. +# Copyright 2011, 2014 Range Networks, 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/Peering/NeighborTable.cpp b/Peering/NeighborTable.cpp index 4a8828c..127cbc1 100644 --- a/Peering/NeighborTable.cpp +++ b/Peering/NeighborTable.cpp @@ -1,6 +1,17 @@ /* - * Copright 2011 Range Networks, Inc. - * All rights reserved. +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ #include "NeighborTable.h" @@ -15,15 +26,14 @@ #include #include +#include using namespace Peering; using namespace std; +static void runSomeTests(); - - - - +#if NEIGHBOR_TABLE_ON_DISK static const char* createNeighborTable = { "CREATE TABLE IF NOT EXISTS NEIGHBOR_TABLE (" "IPADDRESS TEXT UNIQUE NOT NULL, " // IP address of peer BTS (pat) it includes the port. @@ -33,14 +43,53 @@ static const char* createNeighborTable = { "BSIC INTEGER DEFAULT NULL" // peer BTS BSIC ")" }; +#endif +// Return a copy of the pentry if *entry is non-NULL. +bool NeighborTable::ntFindByIP(string ip, NeighborEntry *pentry) +{ + ScopedLock lock(mLock); + NeighborTableMap::iterator mit = mNeighborMap.find(ip); + if (mit == mNeighborMap.end()) { return false; } + if (pentry) { *pentry = mit->second; } + return true; +} +// Return a copy of the pentry if *entry is non-NULL. +bool NeighborTable::ntFindByPeerAddr(const struct ::sockaddr_in* peer, NeighborEntry *pentry) +{ + string ipaddr = sockaddr2string(peer, false); + ScopedLock lock(mLock); + NeighborTableMap::iterator mit = mNeighborMap.find(ipaddr); + if (mit == mNeighborMap.end()) { return false; } + if (pentry) { *pentry = mit->second; } + return true; +} +// The C0 ARFCN is not sufficient because there could be multiple BTS on the same ARFCN, so we need to match the BSIC too. +bool NeighborTable::ntFindByArfcn(int arfcn, int bsic, NeighborEntry *pentry) +{ + if (arfcn < 0 || bsic < 0) { + // These were uninitialized values. It would not have hurt anything to just let this fall through and search for them. + return false; + } + // Requires a brute force search. + for (NeighborTableMap::iterator mit = mNeighborMap.begin(); mit != mNeighborMap.end(); mit++) { + NeighborEntry &entry = mit->second; + if (entry.mC0 == arfcn && (int)entry.mBSIC == bsic) { + if (pentry) { *pentry = entry; } + return true; + } + } + return false; +} void NeighborTable::NeighborTableInit(const char* wPath) { + if (0) runSomeTests(); +#if NEIGHBOR_TABLE_ON_DISK int rc = sqlite3_open(wPath,&mDB); if (rc) { LOG(ALERT) << "Cannot open NeighborTable database: " << sqlite3_errmsg(mDB); @@ -55,15 +104,16 @@ void NeighborTable::NeighborTableInit(const char* wPath) if (!sqlite3_command(mDB,enableWAL)) { LOG(ALERT) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mDB); } +#endif // Fill the database. - fill(); + ntFill(); } // Does the ipaddr include a port specification? // This will fail miserably with ipv6. -bool includesPort(const char *ipaddr) +static bool includesPort(const char *ipaddr) { const char *colon = strrchr(ipaddr,':'); const char *dot = strrchr(ipaddr,'.'); @@ -71,21 +121,18 @@ bool includesPort(const char *ipaddr) } -// (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() +// Create a set of IP addresses of resolved neighbor addresses. +static void makeNeighborSet(set &neighborSet) { - 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. vector neighbors = gConfig.getVectorOfStrings("GSM.Neighbors"); LOG(DEBUG) << "neighbor list length " << neighbors.size(); - unsigned short port = gConfig.getNum("Peering.Port"); + + unsigned short defaultPort = gConfig.getNum("Peering.Port"); for (unsigned int i = 0; i < neighbors.size(); i++) { - struct sockaddr_in address; + struct sockaddr_in peer; // (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. @@ -95,20 +142,62 @@ void NeighborTable::fill() bool validAddr; if (includesPort(host)) { LOG(DEBUG) << "resolving host name for " << host; - validAddr = resolveAddress(&address, host); + validAddr = resolveAddress(&peer, host); } else { - LOG(DEBUG) << "resolving host name for " << host <<" + port:" <first; + NeighborTableMap::iterator thisone = mit++; + if (neighborSet.find(key) == neighborSet.end()) { + LOG(DEBUG) <<"Deleting Neighbor IPaddress="<::iterator it = neighborSet.begin(); it != neighborSet.end(); it++) { + LOG(DEBUG) <<"Adding Neighbor IPaddress="<<*it; + mNeighborMap[*it].mIPAddress = *it; // Creates new entry if necessary. + } + + // Update ARFCN list because some neighbors might have been eliminated. + //mBCCSet = getBCCSet(); + mARFCNList = getARFCNs(); +#endif } +#if NEIGHBOR_TABLE_ON_DISK // (pat) This just adds the address; the BSIC will be filled in later when we hear from the peer. void NeighborTable::addNeighbor(const struct ::sockaddr_in* address) { @@ -154,7 +265,7 @@ void NeighborTable::addNeighbor(const struct ::sockaddr_in* address) LOG(DEBUG) << "adding " << addrString << ":" << ntohs(address->sin_port) << " to neighbor table"; char query[200]; - sprintf(query, + snprintf(query,sizeof(query), "INSERT OR IGNORE INTO NEIGHBOR_TABLE (IPADDRESS) " "VALUES ('%s:%d')", addrString,(int)ntohs(address->sin_port)); @@ -166,22 +277,26 @@ void NeighborTable::addNeighbor(const struct ::sockaddr_in* address) mConfigured.insert(p); // update mBCCSet (pat - why?) - mBCCSet = getBCCSet(); + //mBCCSet = getBCCSet(); // update mARFCNList and check for a change mARFCNList = getARFCNs(); } +#endif -bool NeighborTable::addInfo(const struct ::sockaddr_in* address, // neighbor IP - unsigned updated, // current time. (pat) This is the wrong type. - unsigned C0, unsigned BSIC) // Describes the neighbor radio. +//bool NeighborTable::addInfo(const struct ::sockaddr_in* address, // neighbor IP +// unsigned updated, // current time. (pat) This is the wrong type. +// unsigned C0, unsigned BSIC, // Describes the neighbor radio. +// int noise) // noise level (not supported by NEIGHBOR_TABLE_ON_DISK) +bool NeighborTable::ntAddInfo(NeighborEntry &newentry) { - if (mDB == NULL) { return false; } // we already threw an ALERT. ScopedLock lock(mLock); +#if NEIGHBOR_TABLE_ON_DISK + if (mDB == NULL) { return false; } // we already threw an ALERT. // Get a string for the sockaddr_in. char addrString[256]; const char *ret = inet_ntop(AF_INET,&(address->sin_addr),addrString,255); @@ -190,49 +305,74 @@ bool NeighborTable::addInfo(const struct ::sockaddr_in* address, // neighbor IP return false; } - char query[200]; + //char query[200]; 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", oldC0) || - !sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", query, "BSIC", oldBSIC)) { - LOG(NOTICE) << "Ignoring unsolicited 'RSP NEIGHBOR_PARAMS' from " << query; + //snprintf(query, sizeof(query), "%s:%d", addrString,(int)ntohs(address->sin_port)); + if (!sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", newentry.mIPAddress, "C0", oldC0) || + !sqlite3_single_lookup(mDB, "NEIGHBOR_TABLE", "IPADDRESS", newentry.mIPAddress, "BSIC", oldBSIC)) { + LOG(NOTICE) << "Ignoring unsolicited 'RSP NEIGHBOR_PARAMS' from " << newentry.mIPAddress; return false; } LOG(DEBUG) << "updating " << addrString << ":" << ntohs(address->sin_port) << " in neighbor table" <sin_port),updated,C0,BSIC); + "VALUES ('%s',%u,%u,%u,0) ", + newentry.mIPAddress,newentry.mUpdated,newentry.mC0,newentry.mBSIC); if (!sqlite3_command(mDB,query)) { LOG(ALERT) << "write to neighbor table failed: " << query; return false; } - +#else + NeighborTableMap::iterator mit = mNeighborMap.find(newentry.mIPAddress); + if (mit == mNeighborMap.end()) { + LOG(NOTICE) << "Ignoring unsolicited 'RSP NEIGHBOR_PARAMS' from " << newentry.mIPAddress; + } + NeighborEntry &oldentry = mit->second; + // (pat) Added test to see if C0 or BCC changed. That would not change the beacon but we need to recheck for conflicts + // with the current BTS. + bool change = oldentry.mC0 != newentry.mC0 || oldentry.mBSIC != newentry.mBSIC; + newentry.mUpdated = time(NULL); + newentry.mHoldoff = oldentry.mHoldoff; // Preserve holdoff if any. + oldentry = newentry; +#endif // update mBCCSet - mBCCSet = getBCCSet(); + //mBCCSet = getBCCSet(); // update mARFCNList and check for a change std::vector newARFCNs = getARFCNs(); - bool change = (newARFCNs!=mARFCNList); - if (change) mARFCNList = newARFCNs; + if (newARFCNs!=mARFCNList) { + mARFCNList = newARFCNs; + change = true; + } - // (pat) Added test to see if C0 or BCC changed. That would not change the beacon but we need to recheck for conflicts - // with the current BTS. - bool result = change || (oldC0 != C0) || oldBSIC != BSIC; - LOG(DEBUG) << LOGVAR(result); - return result; + LOG(DEBUG) << LOGVAR(change); + return change; } - -void NeighborTable::refresh() +void NeighborTable::pingPeer(string ipString) { - if (mDB == NULL) { return; } // we already threw an ALERT. - fill(); + struct sockaddr_in address; + if (!resolveAddress(&address, ipString.c_str())) { + LOG(ERR) << "cannot resolve neighbor address=" << ipString; + } else { + LOG(INFO) << "sending neighbor param request to:" << ipString; + gPeerInterface.sendNeighborParamsRequest(&address); + } +} + +void NeighborTable::ntRefresh() +{ + ntFill(); + ScopedLock lock(mLock); time_t now = time(NULL); time_t then = now - gConfig.getNum("Peering.Neighbor.RefreshAge"); - char query[400]; - sprintf(query,"SELECT IPADDRESS FROM NEIGHBOR_TABLE WHERE UPDATED < %u",(unsigned)then); +#if NEIGHBOR_TABLE_ON_DISK + if (mDB == NULL) { return; } // we already threw an ALERT. + char query[100]; + snprintf(query,sizeof(query),"SELECT IPADDRESS FROM NEIGHBOR_TABLE WHERE UPDATED < %u",(unsigned)then); sqlite3_stmt *stmt; if (sqlite3_prepare_statement(mDB,&stmt,query)) { LOG(ALERT) << "read of neighbor table failed: " << query; @@ -246,48 +386,67 @@ void NeighborTable::refresh() src = sqlite3_step(stmt); continue; } - struct sockaddr_in address; - if (!resolveAddress(&address, addrString)) { - LOG(ALERT) << "cannot resolve neighbor address " << addrString; - src = sqlite3_step(stmt); - continue; - } - LOG(INFO) << "sending neighbor param request to " << addrString; - gPeerInterface.sendNeighborParamsRequest(&address); + pingPeer(addrString); src = sqlite3_step(stmt); } sqlite3_finalize(stmt); +#else + for (NeighborTableMap::iterator mit = mNeighborMap.begin(); mit != mNeighborMap.end(); mit++) { + NeighborEntry &entry = mit->second; + if (entry.mUpdated < then) { + pingPeer(entry.mIPAddress); + } + } +#endif } -string NeighborTable::getAddress(unsigned BCCH_FREQ_NCELL, unsigned BSIC) +string NeighborTable::getAddress(unsigned arfcn, unsigned BSIC, string &whatswrong) { - if (mDB == NULL) { return string(""); } // we already threw an ALERT. + LOG(DEBUG) <second; + if ((int)arfcn == entry.mC0 && (int)BSIC == entry.mBSIC) { + return entry.mIPAddress; + } + } + whatswrong = format("arfcn %u not found in sqlite NeighborTable",arfcn); + return string(""); +#endif } /* Return the ARFCN given its position in the BCCH channel list. @@ -306,58 +465,116 @@ int NeighborTable::getARFCN(unsigned BCCH_FREQ_NCELL) return mARFCNList[BCCH_FREQ_NCELL]; } - - -void NeighborTable::holdOff(const char* address, unsigned seconds) +int NeighborTable::getFreqIndexForARFCN(unsigned arfcn) { + ScopedLock lock(mLock); + int freqIndex = 0; + for (std::vector::iterator it = mARFCNList.begin(); it != mARFCNList.end(); it++, freqIndex++) { + if (*it == arfcn) return freqIndex; + } + return -1; // Not found. +} + + + +void NeighborTable::setHoldOff(string ipaddress, unsigned seconds) +{ + ScopedLock lock(mLock); +#if NEIGHBOR_TABLE_ON_DISK if (mDB == NULL) { return; } // we already threw an ALERT. - assert(address); - LOG(DEBUG) << "address " << address << " seconds " << seconds; + assert(ipaddress.size()); + LOG(DEBUG) <second; + entry.mHoldoff = time(NULL) + seconds; +#endif } -void NeighborTable::holdOff(const struct sockaddr_in* peer, unsigned seconds) +void NeighborTable::setHoldOff(const struct sockaddr_in* peer, unsigned seconds) { - if (mDB == NULL) { return; } // we already threw an ALERT. if (!seconds) return; - char addrString[256]; - const char *ret = inet_ntop(AF_INET,&(peer->sin_addr),addrString,255); - if (!ret) { + string addrString = sockaddr2string(peer, false); + if (addrString.size() == 0) { LOG(ERR) << "cannot parse peer socket address"; return; } - return holdOff(addrString,seconds); + return setHoldOff(addrString,seconds); } -bool NeighborTable::holdingOff(const char* address) +bool NeighborTable::holdingOff(const char* ipaddress) { + ScopedLock lock(mLock); +#if NEIGHBOR_TABLE_ON_DISK if (mDB == NULL) { return true; } // we already threw an ALERT. unsigned holdoffTime; - if (!sqlite3_single_lookup(mDB,"NEIGHBOR_TABLE","IPADDRESS",address,"HOLDOFF",holdoffTime)) { + if (!sqlite3_single_lookup(mDB,"NEIGHBOR_TABLE","IPADDRESS",ipaddress,"HOLDOFF",holdoffTime)) { LOG(ALERT) << "cannot read neighbor table"; return false; } time_t now = time(NULL); - LOG(DEBUG) << "hold-off time for " << address << ": " << holdoffTime << ", now: " << now; + LOG(DEBUG) << "hold-off time for " << ipaddress << ": " << holdoffTime << ", now: " << now; return now < (time_t)holdoffTime; +#else + NeighborEntry entry; + if (! ntFindByIP(ipaddress,&entry)) { + LOG(NOTICE) << "Can not find unknown IP address:"< 0; +#endif +} + +// This should be a library utility routine. +template +static void uniquify(vector &vec) +{ + if (vec.size() < 2) return; + unsigned i = 0, j = 1; + while (j < vec.size()-1) { + if (vec[i] != vec[j]) { + i++; + if (i != j) { vec[i] = vec[j]; } + } + j++; + } + // i indexes the new final element. + vec.resize(i+1); +} + +template +static void rollLeft(vector &vec) +{ + T first = vec.front(); + for (unsigned i = 0; i < vec.size()-1; i++) { + vec[i] = vec[i+1]; + } + vec.back() = first; } std::vector NeighborTable::getARFCNs() const { - char query[500]; + ScopedLock lock(mLock); vector bcchChannelList; - sprintf(query,"SELECT C0 FROM NEIGHBOR_TABLE WHERE BSIC > -1 ORDER BY UPDATED DESC LIMIT %lu", gConfig.getNum("GSM.Neighbors.NumToSend")); +#if NEIGHBOR_TABLE_ON_DISK + char query[200]; + // (pat) ORDER BY UPDATED looks wrong to me. + // 3GPP 44.018 10.5.2.20 says the ARFCNs are in increasing order of ARFCN, except that ARFCN 0 must appear last. + snprintf(query,sizeof(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) { @@ -371,11 +588,41 @@ std::vector NeighborTable::getARFCNs() const src = sqlite3_step(stmt); } sqlite3_finalize(stmt); +#else + int limit = gConfig.getNum("GSM.Neighbors.NumToSend"); + for (NeighborTableMap::const_iterator mit = mNeighborMap.begin(); mit != mNeighborMap.end(); mit++) { + const NeighborEntry &entry = mit->second; + if (entry.mBSIC >= 0 && entry.mC0 >= 0) { // Ignore entries for which the peer has not responded. + bcchChannelList.push_back(entry.mC0); + } + if ((int)bcchChannelList.size() >= limit) break; + } + + if (bcchChannelList.size()) { + // Sort it. + sort(bcchChannelList.begin(),bcchChannelList.end()); + + // Eliminate duplicate ARFCNs. + uniquify(bcchChannelList); + + // If first element is ARFCN 0, move it to the back. + if (bcchChannelList.size() >= 2 && bcchChannelList[0] == 0) { + rollLeft(bcchChannelList); + } + } + if (IS_LOG_LEVEL(DEBUG)) { + ostringstream ss; + ss << "bcchChannelList size="<= 0); // This can not go negative because it is a time in the past. + return updated; +} + +long NeighborEntry::getHoldoff() const +{ + if (mHoldoff == 0) { return 0; } + time_t now = time(NULL); + if (now > mHoldoff) { return 0; } // holdoff time is passed. + return mHoldoff - now; // seconds remaining. +} + +void NeighborTable::getNeighborVector(std::vector &nvec) +{ + ScopedLock lock(mLock); + nvec.clear(); + for (NeighborTableMap::iterator mit = mNeighborMap.begin(); mit != mNeighborMap.end(); mit++) { + LOG(DEBUG) <<"Pushing IPaddr="<first <<" entry.mIPAddress="<second.mIPAddress; + nvec.push_back(mit->second); + } +} + +bool NeighborTable::neighborCongestion(unsigned arfcn, unsigned bsic) +{ + return false; +} + +static void runSomeTests() +{ + vector foo; + foo.push_back(7); + foo.push_back(19); + foo.push_back(7); + foo.push_back(19); + foo.push_back(5); + foo.push_back(4); + foo.push_back(0); + foo.push_back(3); + foo.push_back(3); + foo.push_back(2); + foo.push_back(0); + + for (vector::iterator it = foo.begin(); it != foo.end(); it++) printf("%d ",*it); + printf("\n"); + + sort(foo.begin(),foo.end()); + printf("after sort: "); for (vector::iterator it = foo.begin(); it != foo.end(); it++) printf("%d ",*it); + printf("\n"); + uniquify(foo); + printf("after uniquify: "); for (vector::iterator it = foo.begin(); it != foo.end(); it++) printf("%d ",*it); + printf("\n"); + rollLeft(foo); + printf("after rollLeft: "); for (vector::iterator it = foo.begin(); it != foo.end(); it++) printf("%d ",*it); + printf("\n"); +} diff --git a/Peering/NeighborTable.h b/Peering/NeighborTable.h index 71842f4..b14a21b 100644 --- a/Peering/NeighborTable.h +++ b/Peering/NeighborTable.h @@ -1,17 +1,32 @@ /**@ The global table of neighboring BTS units. */ /* - * Copright 2011 Range Networks, Inc. - * All rights reserved. +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ #ifndef NEIGHBORTABLE_H #define NEIGHBORTABLE_H +#define NEIGHBOR_TABLE_ON_DISK 0 + +#include #include #include #include #include +#include using namespace std; @@ -20,60 +35,94 @@ struct sqlite3; namespace Peering { +struct NeighborEntry { + string mIPAddress; // always contains a port ("x.x.x.x:p") + time_t mUpdated; // Updated is 0, or is in the past. + time_t mHoldoff; // Holdoff is 0, or is in the future. + long getUpdated() const, getHoldoff() const; + // All these values except noise are unsigned, but we use int so -1 means not sent by peer. + int mC0; // int because -1 is used as an error. + int mBSIC; + int mNumArfcns; + Int_z mNoise; + int mTchAvail; + int mTchTotal; + string neC0PlusBSIC() const; + NeighborEntry() : mUpdated(0), mHoldoff(0), mC0(-1), mBSIC(-1), mNumArfcns(-1), mTchAvail(-1), mTchTotal(-1) {} +}; + +typedef vector NeighborEntryVector; + class NeighborTable { private: +#if NEIGHBOR_TABLE_ON_DISK struct sqlite3* mDB; ///< database connection + set mConfigured; ///< which ipaddresses in the table are configured +#else + typedef std::map NeighborTableMap; + NeighborTableMap mNeighborMap; +#endif std::vector mARFCNList; ///< current ARFCN list - int mBCCSet; ///< set of current BCCs mutable Mutex mLock; - set mConfigured; ///< which ipaddresses in the table are configured + /** Get the neighbor cell ARFCNs as a vector. */ + std::vector getARFCNs() const; public: - /** Age parameter value for undefined records. */ - static const unsigned UNKNOWN = 0xFFFFFFFF; - // (pat) In order to prevent crashes caused by static initialization races, I added // an empty constructor and moved initialization to the function NeighborTableInit. +#if NEIGHBOR_TABLE_ON_DISK + // (pat) The BCC set is not used anywhere... + int mBCCSet; ///< set of current BCCs NeighborTable() : mDB(0), mBCCSet(0) {} + /** Create a new neighbor record if it does not already exist. */ + void addNeighbor(const struct sockaddr_in* address); +#else + NeighborTable() {} +#endif + bool ntFindByIP(string ip, NeighborEntry *entry); + bool ntFindByPeerAddr(const struct ::sockaddr_in* peer, NeighborEntry *entry); + bool ntFindByArfcn(int arfcn, int bsic, NeighborEntry *entry); + /** Constructor opens the database and creates/populates the new table as needed. */ void NeighborTableInit(const char* dbPath); /** Fill the neighbor table from GSM.Neighbors config. */ - void fill(); - - /** Create a new neighbor record if it does not already exist. */ - void addNeighbor(const struct sockaddr_in* address); + void ntFill(); /** Add new information to a neighbor record to the table. @return true if the neighbor ARFCN list changed */ - bool addInfo(const struct sockaddr_in* address, unsigned updated, unsigned C0, unsigned BSIC); + bool ntAddInfo(NeighborEntry &entry); /** Gets age of parameters in seconds or UNDEFINED if unknown. */ unsigned paramAge(const char* address); /** Returns a C-string that must be free'd by the caller. */ - string getAddress(unsigned BCCH_FREQ_NCELL, unsigned BSIC); + string getAddress(unsigned arfcn, unsigned BSIC, string &whatswrong); - /** Return the ARFCN given its position in the BCCH channel list (GSM 04.08 10.5.2.20). */ + /** Return the ARFCN given its position (frequency index) in the BCCH channel list (GSM 04.08 10.5.2.20). */ int getARFCN(unsigned BCCH_FREQ_NCELL); - /** Start the holdoff timer on this neighbor. */ - void holdOff(const char* address, unsigned seconds); + // (pat 3-2014) Return the Frequency Index given an ARFCN, or -1 on error. + int getFreqIndexForARFCN(unsigned arfcn); /** Start the holdoff timer on this neighbor. */ - void holdOff(const struct sockaddr_in* peer, unsigned seconds); + void setHoldOff(string ipaddress, unsigned seconds); + + /** Start the holdoff timer on this neighbor. */ + void setHoldOff(const struct sockaddr_in* peer, unsigned seconds); /** Return true if we are holding off requests to this neighbor. */ - bool holdingOff(const char* address); + bool holdingOff(const char* ipaddress); /** Send out new requests for old entries. */ - void refresh(); + void ntRefresh(); + void pingPeer(string ipaddr); /** Get the neighbor cell ARFCNs as a vector. @@ -83,15 +132,15 @@ class NeighborTable { std::vector ARFCNList() const { ScopedLock lock(mLock); return mARFCNList; } /** Get the neighbor cell BCC set as a bitmask. */ - int BCCSet() const { return mBCCSet; } - - private: - + // unused: int BCCSet() const { return mBCCSet; } /** Get the neighbor cell BCC set as a bitmask. */ - int getBCCSet() const; + //int getBCCSet() const; - /** Get the neighbor cell ARFCNs as a vector. */ - std::vector getARFCNs() const; + // We pass a copy of the neighbor table to the CLI to avoid locking issues. + void getNeighborVector(std::vector &nvec); + + // Is this neighbor congested? + bool neighborCongestion(unsigned arfcn, unsigned bsic); }; diff --git a/Peering/Peering.cpp b/Peering/Peering.cpp index 6c4f6b5..df78c23 100644 --- a/Peering/Peering.cpp +++ b/Peering/Peering.cpp @@ -1,9 +1,21 @@ /**@file Messages for peer-to-peer protocol */ /* - * Copright 2011, 2014 Range Networks, Inc. - * All rights reserved. +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ +#define LOG_GROUP LogGroup::Control #include "Peering.h" #include "NeighborTable.h" @@ -24,6 +36,12 @@ using namespace Control; // Original Diagram courtesy Doug: // MS BS1 BS2 switch +// NeighborTable Refresh every N seconds: +// X --- REQ NEIGHBOR_PARAMS -------------> X +// X <-- RSP NEIGHBOR_PARAMS C0 BSIC ------ X +// X --- REQ NEIGHBOR_PARAMS v=2 ---------> X +// X <-- RSP NEIGHBOR_PARAMS key=value... - X +// // // A ------------- REQ HANDOVER -----------> a // (BS1tranid, L3TI, IMSI, called/caller, SIP REFER message) @@ -31,7 +49,7 @@ using namespace Control; // (BS1tranid, cause, L3HandoverCommand) // <------------- L3HandoverCommand ---------- D // -------------------------------- handover access -----------------------------------> b -// <------------------------------- physical information ------------------------------- c +// <------------------------- physical information (with TA) --------------------------- c // -------------------------------- handover complete ---------------------------------> d // e ----- re-INVITE ------> // f <-------- OK ---------- @@ -58,6 +76,17 @@ using namespace Control; // E = processHandoverComplete in BS1 +string sockaddr2string(const struct sockaddr_in* peer, bool noempty) +{ + // Get a string for the sockaddr_in. + char addrString[256]; + const char *ret = inet_ntop(AF_INET,&(peer->sin_addr),addrString,255); + if (!ret) { + LOG(ERR) << "cannot parse peer socket address"; + return string(noempty ? "" : ""); + } + return format("%s:%d",addrString,(int)ntohs(peer->sin_port)); +} void PeerMessageFIFOMap::addFIFO(unsigned transactionID) { @@ -102,14 +131,14 @@ PeerInterface::PeerInterface() } -void* foo(void*) +static void* foo(void*) { gPeerInterface.serviceLoop1(NULL); return NULL; } -void* bar(void*) +static void* bar(void*) { gPeerInterface.serviceLoop2(NULL); return NULL; @@ -129,9 +158,14 @@ void* PeerInterface::serviceLoop1(void*) { // gTRX.C0() needs some time to get ready sleep(8); - while (1) { - gNeighborTable.refresh(); - sleep(1); // (pat) Every second? Give me a break. + while (!gBTS.btsShutdown()) { + gNeighborTable.ntRefresh(); + // (pat) This sleep is how often we look through the neighbor table, not how often we ping the peers; + // that is determined inside the neighbor table loop by searching for peers who have not been refreshed + // in the Peering.Neighbor.RefreshAge. + // We do not need 1 second granularity here, and querying that quickly may be interfering with the ability + // of gNeighborTable.getAddress() to access the NeighborTable. + sleep(10); } return NULL; } @@ -142,7 +176,7 @@ void* PeerInterface::serviceLoop2(void*) { // gTRX.C0() needs some time to get ready sleep(8); - while (1) { + while (!gBTS.btsShutdown()) { drive(); usleep(10000); } @@ -226,49 +260,105 @@ static void logAlert(string message) // 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 %u %u"; + try { + + // Create a unique id so we can tell if we send a message to ourself. + static uint32_t btsid = 0; + while (btsid == 0) { btsid = (uint32_t) random(); } + + // (pat) This is the original format, which I now call version 1. + static const char rspFormatV1[] = "RSP NEIGHBOR_PARAMS %u %u"; // C0 BSIC + // (pat) This is the 3-2014 format. Extra args are ignored by older versions of OpenBTS. + //static const char rspFormat[] = "RSP NEIGHBOR_PARAMS %u %u %u %d %u %u %u"; // C0 BSIC uniqueID noise numArfcns tchavail tchtotal LOG(DEBUG) << "got message " << message; if (0 == strncmp(message,"REQ ",4)) { // REQ? Send a RSP. - char rsp[100]; - sprintf(rsp, rspFormat, gTRX.C0(), gBTS.BSIC()); - sendMessage(peer,rsp); + char rsp[150]; + if (! strchr(message,'=')) { + // Send version 1. + snprintf(rsp, sizeof(rsp), rspFormatV1, gTRX.C0(), gBTS.BSIC()); + } else { + // Send version 2. + int myNoise = gTRX.ARFCN(0)->getNoiseLevel(); + unsigned tchTotal = gBTS.TCHTotal(); + unsigned tchAvail = tchTotal - gBTS.TCHActive(); + snprintf(rsp, sizeof(rsp), "RSP NEIGHBOR_PARAMS V=2 C0=%u BSIC=%u btsid=%u noise=%d arfcns=%d TchAvail=%u TchTotal=%u", + gTRX.C0(), gBTS.BSIC(), btsid, myNoise, (int)gConfig.getNum("GSM.Radio.ARFCNs"), tchAvail, tchTotal); + sendMessage(peer,rsp); + } return; } if (0 == strncmp(message,"RSP ",4)) { // RSP? Digest it. - // (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) { - logAlert(format("badly formatted peering message: %s",message)); + NeighborEntry newentry; + + if (! strchr(message,'=')) { + // Version 1 message. + int r = sscanf(message, rspFormatV1, &newentry.mC0, &newentry.mBSIC); + if (r != 2) { + logAlert(format("badly formatted peering message: %s",message)); + return; + } + } else { + SimpleKeyValue keys; + keys.addItems(message + sizeof("RSP NEIGHBOR_PARAMS")); // sizeof is +1 which is ok - we are skipping the initial space. + + { bool valid; + unsigned neighborID = keys.getNum("btsid",valid); + if (valid && neighborID == btsid) { + LOG(ERR) << "BTS is in its own GSM.Neighbors list."; + return; + } + } + + newentry.mC0 = keys.getNumOrBust("C0"); + newentry.mBSIC = keys.getNumOrBust("BSIC"); + newentry.mNoise = keys.getNumOrBust("noise"); + newentry.mNumArfcns = keys.getNumOrBust("arfcns"); + newentry.mTchAvail = keys.getNumOrBust("TchAvail"); + newentry.mTchTotal = keys.getNumOrBust("TchTotal"); + } + + newentry.mIPAddress = sockaddr2string(peer, false); + if (newentry.mIPAddress.size() == 0) { + LOG(ERR) << "cannot parse peer socket address for"<= (int) newentry.mC0; + if (overlap) { + LOG(WARNING) << format("neighbor IP=%s BSIC=%d ARFCNs=%u to %d overlaps with this BTS ARFCNs=%d to %d", + newentry.mIPAddress.c_str(), newentry.mBSIC, newentry.mC0, neighborCEnd, myC0, myCEnd); + } + // Is there an NCC conflict? - int neighborNCC = neighborBSIC >> 3; + // (pat) ARFCN-C0 (the one carrying BCCH) and BSIC consisting of NCC (Network Color Code) and BCC. (Base station Color Code) + int neighborNCC = newentry.mBSIC >> 3; int NCCMaskBit = 1 << neighborNCC; int ourNCCMask = gConfig.getNum("GSM.CellSelection.NCCsPermitted"); ourNCCMask |= 1 << gConfig.getNum("GSM.Identity.BSIC.NCC"); @@ -281,13 +371,16 @@ void PeerInterface::processNeighborParams(const struct sockaddr_in* peer, const return; } - LOG(ALERT) << "unrecognized message: " << message; -} + LOG(ALERT) << "unrecognized Peering message: " << message; + } catch (SimpleKeyValueException &e) { + LOG(ERR) << format("invalid message (%s) from peer %s: %s",e.what(),sockaddr2string(peer,true),message); + } +} void PeerInterface::sendNeighborParamsRequest(const struct sockaddr_in* peer) { - sendMessage(peer,"REQ NEIGHBOR_PARAMS"); + sendMessage(peer,"REQ NEIGHBOR_PARAMS V=2"); // (pat) The v=2 requests version 2 of the response. // Get a string for the sockaddr_in. char addrString[256]; const char *ret = inet_ntop(AF_INET,&(peer->sin_addr),addrString,255); @@ -298,6 +391,22 @@ void PeerInterface::sendNeighborParamsRequest(const struct sockaddr_in* peer) LOG(DEBUG) << "requested parameters from " << addrString << ":" << ntohs(peer->sin_port); } +static void addHandoverPenalty(GSM::L2LogicalChannel *chan,NeighborEntry &nentry, const char *cause) +{ + if (chan == NULL) { return; } // Huh? + if (cause == NULL) { + LOG(WARNING)<<"Empty cause in handover record, handover penalty ignored"; + return; + } + + int penaltyTime = 30; // 30 seconds. + + NeighborPenalty npenalty; + npenalty.mARFCN = nentry.mC0; + npenalty.mBSIC = nentry.mBSIC; + npenalty.mPenaltyTime.future(penaltyTime * 1000); + chan->chanSetHandoverPenalty(npenalty); +} // (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. @@ -321,6 +430,14 @@ void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const // find existing transaction record if this is a duplicate REQ HANDOVER Control::TranEntry* transaction = gNewTransactionTable.ttFindHandoverOther(mobileID, oldTransID); + // (pat 3-2014) This is a new test: look for the peer address. Previously we just looked for the transaction. + // I didnt really want to test this, I just needed the ARFCN. Could get it by looking up the peer in the transaction too. + NeighborEntry nentry; + if (! gNeighborTable.ntFindByPeerAddr(peer, &nentry)) { + LOG(WARNING)<<"Could not find handover neighbor from peer address:"<< sockaddr2string(peer, true); + return; // The transaction will die a death by expiry. + } + // and the channel that goes with it GSM::L2LogicalChannel* chan = NULL; unsigned horef; @@ -335,22 +452,24 @@ void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const // For now, we are assuming a full-rate channel. // And check gBTS.hold() time_t start = time(NULL); - if (!gBTS.hold()) { chan = gBTS.getTCH(); } // (pat) Starts T3101. Better finish before it expires. + if (!gBTS.btsHold()) { + chan = gBTS.getTCH(); // (pat) Starts T3101. Better finish before it expires. + if (!chan) { LOG(CRIT) << "congestion, incoming handover request denied"; } + } LOG(DEBUG) << "getTCH took " << (time(NULL) - start) << " seconds"; - // FIXME -- Somehow, getting from getTCH above to the test below can take several seconds. - // FIXME -- #797. + // (doug) FIXME -- Somehow, getting from getTCH above to the test below can take several seconds. + // (doug) FIXME -- #797. // If getTCH took so long that there's too little time left in T3101, ignore this REQ and get the next one. - if (chan && chan->SACCH()->debugGetL1()->decoder()->debug3101remaining() < 1000) { + if (chan && chan->debug3101remaining() < 1000) { LOG(NOTICE) << "handover TCH allocation took too long; risk of T3101 timeout; trying again"; - chan->l2sendp(HARDRELEASE); // (pat) added 9-6-2013 + chan->l2sendp(L3_HARDRELEASE_REQUEST); // (pat) added 9-6-2013 return; } // If there's no channel available, send failure. if (!chan) { - LOG(CRIT) << "congestion, incoming handover request denied"; char rsp[50]; // RR Cause 101 "cell allocation not available" // GSM 04.08 10.5.2.31 @@ -368,6 +487,9 @@ void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const mFIFOMap.addFIFO(transaction->tranID()); LOG(INFO) "creating new transaction " << *transaction; + // This prevents a reverse handover from BTS2->BTS1 after a successful handover from BTS1->BTS2. + addHandoverPenalty(chan,nentry,params.get("cause")); + // Set the channel state. // This starts T3103. chan->handoverPending(true,horef); @@ -377,6 +499,10 @@ void PeerInterface::processHandoverRequest(const struct sockaddr_in* peer, const LOG(DEBUG) << *transaction; } + // (pat 6-2014) FIXME We dont have TA yet, so we are just starting the channel with 0 TA. + // What is the correct procedure? Should we not start SACCH until we receive the HandoverReference? + chan->lcstart(); + // Send accept. // FIXME TODO_NOW: Get rid of this sleepFrames... sleepFrames(30); // Pat added delay to let SACCH get started. @@ -455,7 +581,7 @@ void PeerInterface::processHandoverFailure(const struct sockaddr_in* peer, const // Set holdoff on this BTS. // FIXME -- We need to decide what else to do here. See #817. - gNeighborTable.holdOff(peer,holdoff); + gNeighborTable.setHoldOff(peer,holdoff); char rsp[50]; sprintf(rsp,"ACK HANDOVER_FAILURE %u", transactionID); @@ -575,7 +701,6 @@ bool PeerInterface::sendUntilAck(const Control::HandoverEntry* hop, const char* // 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; @@ -602,9 +727,10 @@ void PeerInterface::sendHandoverFailure(const Control::HandoverEntry *hop, GSM:: gPeerInterface.sendUntilAck(hop,ind); } -bool PeerInterface::sendHandoverRequest(string peer, const RefCntPointer tran) +bool PeerInterface::sendHandoverRequest(string peer, const RefCntPointer tran, string cause) { string msg = string("REQ HANDOVER ") + tran->handoverString(peer); + if (cause.size()) msg += " cause=" + cause; struct sockaddr_in peerAddr; if (!resolveAddress(&peerAddr,peer.c_str())) { LOG(ALERT) << "cannot resolve peer address " << peer; diff --git a/Peering/Peering.h b/Peering/Peering.h index e6ea9f2..9317896 100644 --- a/Peering/Peering.h +++ b/Peering/Peering.h @@ -1,7 +1,18 @@ /**@file Messages for peer-to-peer protocol */ /* - * Copright 2011 Range Networks, Inc. - * All rights reserved. +* 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 distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ @@ -134,7 +145,7 @@ class PeerInterface { //@{ /** Process the NEIGHBOR_PARMS message/response. */ - void processNeighborParams(const struct ::sockaddr_in* peer, const char* message); + void processNeighborParams(const struct ::sockaddr_in* peer, const char* message); // Pre-3-2014 message format. /** Process REQ HANDOVER. */ void processHandoverRequest(const struct ::sockaddr_in* peer, const char* message); @@ -157,12 +168,13 @@ class PeerInterface { void sendHandoverFailure(const Control::HandoverEntry *hop,GSM::RRCause cause,unsigned holdoff); /** Send REQ HANDOVER */ - bool sendHandoverRequest(string peer, const RefCntPointer tran); + bool sendHandoverRequest(string peer, const RefCntPointer tran, string cause); //@} }; +extern string sockaddr2string(const struct sockaddr_in* peer, bool noempty); }; //namespace diff --git a/README b/README index 5158f4b..0d1cb5c 100644 --- a/README +++ b/README @@ -5,6 +5,12 @@ For free support, please subscribe to openbts-discuss@lists.sourceforge.net. See http://sourceforge.net/mailarchive/forum.php?forum_name=openbts-discuss and https://lists.sourceforge.net/lists/listinfo/openbts-discuss for details. +A5/3 support requires installation of liba53. This can be installed from: +git@github.com:RangeNetworks/liba53.git + +Starting with release 4, OpenBTS requires zeromq (zmq). This can be installed by running: +$ sudo ./NodeManager/install_libzmq.sh + For additional information, refer to http://openbts.org. @@ -41,7 +47,7 @@ These can be controlled in the CONFIG table in /etc/OpenBTS.db. Standrd paths: /OpenBTS -- Binary installation and authorization keys. /etc/OpenBTS -- Configuration databases. -/var/run -- Real-time reporting databases. +/var/run/ -- Real-time reporting databases. The script apps/setUpFiles.sh will create these directories and install the correct files in them. diff --git a/README.APIs b/README.APIs new file mode 100644 index 0000000..ea5c443 --- /dev/null +++ b/README.APIs @@ -0,0 +1,52 @@ +# APIs in OpenBTS + +There are going to be more and more APIs made available in OpenBTS. This is a live reference to their schemas and current versions. The Commands API is documented in the handbook in the NodeManager section. This document focuses on the the new events APIs. + +Individual Events API can be enabled or disabled via the key listed in each section. Also, a specific version of that API can be selected for application compatibility. Events are published on the port defined by the key: ```NodeManager.Events.Port```. An example client is in apps/JSONEventsClient.cpp. + + +## PhysicalStatus Events API + + - Current Version: 0.1 + - Available Versions: 0.1 + - Configured Via: ```NodeManager.API.PhysicalStatus``` + +The PhysicalStatus API provides raw physical readings from the SACCH (Slow Associated Control CHannel). The SACCH is active when interacting with a MS (Mobile Station) via a SDCCH (Standard Dedicated Control CHannel) or a TCH (Traffic CHannel). SDCCH interactions can be SMS exchanges, LUR (Location Update Requests) or voice call setups. TCH interactions are voice calls once media is flowing or GPRS sessions. + +Readings are available approximately every half-second on the SACCH. These readings contain information about the burst itself, the logical channel that is being used and neighbor reports from the handset. + +An example for version 0.1 of the event data emitted by this API follows: + +``` +{ + "name" : "PhysicalStatus", + "timestamp" : "18446744072283447705", + "version" : "0.1", + "data" : { + "burst" : { + "RSSI" : -49.4808, + "RSSP" : -27.4808, + "actualMSPower" : 11, + "actualMSTimingAdvance" : 0, + "timingError" : 1.59709 + }, + "channel" : { + "ARFCN" : 153, + "IMSI" : "001010000000001", + "carrierNumber" : 0, + "timeslotNumber" : 0, + "typeAndOffset" : "SDCCH/4-1", + "uplinkFrameErrorRate" : 0 + }, + "reports" : { + "neighboringCells" : [], + "servingCell" : { + "RXLEVEL_FULL_dBm" : -67, + "RXLEVEL_SUB_dBm" : -67, + "RXQUALITY_FULL_BER" : 0, + "RXQUALITY_SUB_BER" : 0 + } + } + } +} +``` diff --git a/SGSNGGSN/GPRSL3Messages.cpp b/SGSNGGSN/GPRSL3Messages.cpp index a3653d7..62f1ccd 100644 --- a/SGSNGGSN/GPRSL3Messages.cpp +++ b/SGSNGGSN/GPRSL3Messages.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/GPRSL3Messages.h b/SGSNGGSN/GPRSL3Messages.h index a72f761..9e147fe 100644 --- a/SGSNGGSN/GPRSL3Messages.h +++ b/SGSNGGSN/GPRSL3Messages.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -27,7 +26,6 @@ #include "ScalarTypes.h" #include "SgsnBase.h" //#include "GPRSL3New.h" -extern ConfigurationTable gConfig; using namespace std; #define DEHEXIFY(c) ((c)>=10 ? (c)-10+'a' : (c)+'0') diff --git a/SGSNGGSN/Ggsn.cpp b/SGSNGGSN/Ggsn.cpp index aa3d8dd..5de57b8 100644 --- a/SGSNGGSN/Ggsn.cpp +++ b/SGSNGGSN/Ggsn.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -22,6 +21,7 @@ #include "Sgsn.h" #include "Ggsn.h" #include "miniggsn.h" +#include //#include "MSInfo.h" // To dump MSInfo #include "GPRSL3Messages.h" #define CASENAME(x) case x: return #x; @@ -111,15 +111,15 @@ void *miniGgsnReadServiceLoop(void *arg) { Ggsn *ggsn = (Ggsn*)arg; sethighpri(); - while (ggsn->active()) { + while (ggsn->active() && !gBTS.btsShutdown()) { struct pollfd fds[1]; fds[0].fd = tun_fd; fds[0].events = POLLIN; fds[0].revents = 0; // being cautious // We time out occassionally to check if the user wants to shut the sgsn down. if (-1 == poll(fds,1,ggsn->mStopTimeout)) { - SGSNERROR("ggsn: poll failure"); - return 0; + SGSNERROR("ggsn: poll failure, errno="<active()) { + while (ggsn->active() && !gBTS.btsShutdown()) { // 8-6-2012 This interthreadqueue is clumping things up. Try taking out the timeout. //PdpPdu *npdu = ggsn->mTxQ.read(ggsn->mStopTimeout); PdpPdu *npdu = ggsn->mTxQ.read(); @@ -172,9 +172,11 @@ void *miniGgsnShellServiceLoop(void *arg) { Ggsn *ggsn = (Ggsn*)arg; std::string shname = gConfig.getStr(SQL_SHELLSCRIPT); - while (ggsn->active()) { + while (ggsn->active() && !gBTS.btsShutdown()) { ShellRequest *req = ggsn->mShellQ.read(ggsn->mStopTimeout); if (! req) continue; + // (pat added 3-2014) Dont try to exec a shell script that does not exist; prevents a warning. + if (0 != access(shname.c_str(),R_OK)) continue; runcmd("/bin/sh","sh",shname.c_str(), req->msrCommand.c_str(), req->msrArg1.c_str(), req->msrArg2.c_str(),req->msrArg3.c_str()); delete req; diff --git a/SGSNGGSN/Ggsn.h b/SGSNGGSN/Ggsn.h index e5c7f58..eb239d2 100644 --- a/SGSNGGSN/Ggsn.h +++ b/SGSNGGSN/Ggsn.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/LLC.cpp b/SGSNGGSN/LLC.cpp index bd2a035..b4a3ed1 100644 --- a/SGSNGGSN/LLC.cpp +++ b/SGSNGGSN/LLC.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/LLC.h b/SGSNGGSN/LLC.h index d7c4b0b..76b7462 100644 --- a/SGSNGGSN/LLC.h +++ b/SGSNGGSN/LLC.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/Makefile.am b/SGSNGGSN/Makefile.am index a4cd173..f24d160 100644 --- a/SGSNGGSN/Makefile.am +++ b/SGSNGGSN/Makefile.am @@ -1,6 +1,6 @@ - # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/SGSNGGSN/Sgsn.cpp b/SGSNGGSN/Sgsn.cpp index f44bd38..3301b74 100644 --- a/SGSNGGSN/Sgsn.cpp +++ b/SGSNGGSN/Sgsn.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -1342,6 +1341,18 @@ bool MSUEAdapter::sgsnGetGeranFeaturePackI(uint32_t mshandle) } #endif +// If the MS is registered return the IMSI, otherwise return an empty string. +string MSUEAdapter::sgsnFindImsiByHandle(uint32_t handle) +{ + ScopedLock lock(sSgsnListMutex); // Will be locked again recursively in findSgsnInfoByHandle. + if (SgsnInfo *si = findSgsnInfoByHandle(handle,false)) { + GmmInfo *gmm = si->getGmm(); + if (gmm) return gmm->mImsi.hexstr(); + } + return string(""); +} + + GmmState::state MSUEAdapter::sgsnGetRegistrationState(uint32_t mshandle) { SgsnInfo *si = sgsnGetSgsnInfoByHandle(mshandle,false); @@ -1584,7 +1595,7 @@ SgsnInfo * SgsnInfo::changeTlli(bool now) SgsnInfo *othersi = findSgsnInfoByHandle(newTlli,true); // We will use the new tlli for downlink l3 messages, eg, pdp context messages, // unless they use some other SI specifically, like AttachAccept - // must be sent on the SI tha the AttachRequest arrived on. + // must be sent on the SI that the AttachRequest arrived on. othersi->setGmm(gmm); if (now) { gmm->msi = othersi; } return othersi; diff --git a/SGSNGGSN/Sgsn.h b/SGSNGGSN/Sgsn.h index 74d0426..9be5a3e 100644 --- a/SGSNGGSN/Sgsn.h +++ b/SGSNGGSN/Sgsn.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/SgsnBase.h b/SGSNGGSN/SgsnBase.h index e59e726..6df0a3c 100644 --- a/SGSNGGSN/SgsnBase.h +++ b/SGSNGGSN/SgsnBase.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/SgsnCli.cpp b/SGSNGGSN/SgsnCli.cpp index ae8a908..3b6eefc 100644 --- a/SGSNGGSN/SgsnCli.cpp +++ b/SGSNGGSN/SgsnCli.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/SgsnExport.h b/SGSNGGSN/SgsnExport.h index 86c019d..b0cf9c8 100644 --- a/SGSNGGSN/SgsnExport.h +++ b/SGSNGGSN/SgsnExport.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -137,6 +136,7 @@ class MSUEAdapter { int sgsnGetMultislotClass(uint32_t mshandle); bool sgsnGetGeranFeaturePackI(uint32_t mshandle); #endif + string sgsnFindImsiByHandle(uint32_t handle); private: virtual uint32_t msGetHandle() = 0; // return URNTI or TLLI friend std::ostream& operator<<(std::ostream&os,const SgsnInfo*); diff --git a/SGSNGGSN/iputils.cpp b/SGSNGGSN/iputils.cpp index c45c798..d098112 100644 --- a/SGSNGGSN/iputils.cpp +++ b/SGSNGGSN/iputils.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SGSNGGSN/miniggsn.cpp b/SGSNGGSN/miniggsn.cpp index 312b336..3f2000e 100644 --- a/SGSNGGSN/miniggsn.cpp +++ b/SGSNGGSN/miniggsn.cpp @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -509,7 +508,9 @@ bool miniggsn_init() // This is the first message in the newly opened file. time(&gGgsnInitTime); - MGINFO("Initializing Mini GGSN %s",ctime(&gGgsnInitTime)); // ctime includes a newline. + std::string res; + Timeval::isoTime(gGgsnInitTime, res, true); + MGINFO("Initializing Mini GGSN %s\n",res.c_str()); if (mg_log_fp) { mg_debug_level = 1; diff --git a/SGSNGGSN/miniggsn.h b/SGSNGGSN/miniggsn.h index 5db10f7..d5329f6 100644 --- a/SGSNGGSN/miniggsn.h +++ b/SGSNGGSN/miniggsn.h @@ -1,10 +1,9 @@ /* -* Copyright 2011 Range Networks, Inc. -* All Rights Reserved. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SIP/Makefile.am b/SIP/Makefile.am index 1ebb1f0..8ce56e2 100644 --- a/SIP/Makefile.am +++ b/SIP/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, Inc. # # This software is distributed under the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -20,13 +21,14 @@ include $(top_srcdir)/Makefile.common -AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) \ - -I$(ORTP_INCLUDEDIR) $(ORTP_CPPFLAGS) +# Previously: -I$(ORTP_INCLUDEDIR) $(ORTP_CPPFLAGS) +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) AM_CXXFLAGS = -Wall -Wextra noinst_LTLIBRARIES = libSIP.la libSIP_la_SOURCES = \ + SIPRtp.cpp \ SIPParse.cpp \ SIPMessage.cpp \ SIPDialog.cpp \ @@ -36,6 +38,7 @@ libSIP_la_SOURCES = \ SIPTransaction.cpp noinst_HEADERS = \ + SIPRtp.h \ SIPParse.h \ SIPBase.h \ SIPDialog.h \ diff --git a/SIP/SIP2Interface.cpp b/SIP/SIP2Interface.cpp index 38dc056..bf4e9f8 100644 --- a/SIP/SIP2Interface.cpp +++ b/SIP/SIP2Interface.cpp @@ -4,7 +4,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -46,13 +46,15 @@ #include +#include #include #include -//#include +#include // For gReports. #include #include #include +#include #include "SIPUtility.h" #include "SIP2Interface.h" @@ -97,13 +99,14 @@ string SipDialogMap::makeTagKey(string callid, string 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) +SipDialogRef 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()) { + // We dont need a copy of the reference because it is still in the map and therefore cannot be destroyed. + SipDialogRef dialog = mDialogMap.readNoBlock(key); + if (! dialog.self() && 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,""); @@ -118,14 +121,16 @@ 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,"")); + SipDialogRef fnd; + string oldkey = makeTagKey(callid,""); string newkey = makeTagKey(callid, localtag); - devassert(fnd == dialog); - if (fnd) { - mDialogMap.write(newkey,dialog); + if (mDialogMap.getNoBlock(oldkey,fnd,true)) { // Removes from the map. + devassert(fnd.self() == dialog); + // I am adding dialog instead of fnd in case of some bug where the old did not exist, we ignore it and keep going. + mDialogMap.write(newkey,SipDialogRef(dialog)); } else { // If it is a duplicate ACK it will already be moved. - if (! mDialogMap.readNoBlock(newkey)) { + if (! mDialogMap.getNoBlock(newkey,fnd,false)) { // Do not remove from map LOG(ERR) << "Could not find dialog"<callId(), localtag = dialog->dsLocalTag(); - SipDialog *dialog1 = mDialogMap.getNoBlock(makeTagKey(callid,localtag)); // Removes the element. - if (dialog1) { + SipDialogRef dialog1; + bool extant1 = mDialogMap.getNoBlock(makeTagKey(callid,localtag),dialog1); // Removes the element. + if (extant1) { gSipInterface.mDeadDialogs.push_back(dialog1); } - SipDialog *dialog2 = mDialogMap.getNoBlock(makeTagKey(callid,"")); - if (dialog2) { + SipDialogRef dialog2; + bool extant2 = mDialogMap.getNoBlock(makeTagKey(callid,""),dialog2); // Removes the element. + if (extant2) { 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); + SipDialogRef 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. @@ -168,14 +175,16 @@ void SipDialogMap::dmAddCallDialog(SipDialog*dialog) LOG(ERR) << "Adding duplicate dialog."<second; + SipDialogRef dialog = it->second; it++; if (dialog->dialogPeriodicService()) { - gSipInterface.dmRemoveDialog(dialog); + gSipInterface.dmRemoveDialog(dialog.self()); } } } catch(exception &e) { @@ -264,7 +273,7 @@ bool SipTUMap::tuMapDispatch(SipMessage*msg) { // 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() +void MySipInterface::purgeDeadDialogs() { #if USE_SCOPED_ITERATORS DeadDialogListType::ScopedIterator sit(mDeadDialogs); @@ -273,18 +282,19 @@ void SipInterface::purgeDeadDialogs() DeadDialogListType::iterator sit; #endif for (sit = mDeadDialogs.begin(); sit != mDeadDialogs.end();) { - SipDialog *dialog = *sit; - LOG(DEBUG) << "purgeDeadDialogs"<dgIsDeletable()); + SipDialogRef dialog = *sit; + LOG(DEBUG) << "purgeDeadDialogs"<dgIsDeletable()) <<*dialog <mTranId)); if (dialog->dgIsDeletable()) { sit = mDeadDialogs.erase(sit); - delete dialog; + //delete dialog; + dialog.free(); } else { sit++; } } } -SipBase *SipDialogMap::dmFindDialogByRtp(RtpSession *session) +SipDialogRef SipDialogMap::dmFindDialogByRtp(RtpSession *session) { #if USE_SCOPED_ITERATORS DialogMap_t::ScopedIterator sit(mDialogMap); @@ -293,14 +303,15 @@ SipBase *SipDialogMap::dmFindDialogByRtp(RtpSession *session) DialogMap_t::iterator sit; #endif for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { - SipDialog *dialog = sit->second; + SipDialogRef dialog = sit->second; if (dialog->mSession == session) { - return (SipBase*)dialog; + return dialog; } } - return NULL; + return SipDialogRef(); // An empty one. } +#if UNUSED SipBase *SipDialogMap::dmFindDialogById(unsigned id) { #if USE_SCOPED_ITERATORS @@ -317,6 +328,7 @@ SipBase *SipDialogMap::dmFindDialogById(unsigned id) } return NULL; } +#endif void SipDialogMap::printDialogs(ostream&os) { @@ -327,7 +339,7 @@ void SipDialogMap::printDialogs(ostream&os) DialogMap_t::iterator sit; #endif for (sit = mDialogMap.begin(); sit != mDialogMap.end(); sit++) { - SipDialog *dialog = sit->second; + SipDialogRef dialog = sit->second; os << dialog->dialogText(false) << "\n"; } // ScopedLock lock(mDialogMapLock); @@ -348,9 +360,10 @@ void printDialogs(ostream &os) // This does NOT delete the msg. +// This writes all SIP messages void SipInterface::siWrite(const struct sockaddr_in* dest, SipMessage *msg) { - string msgstr = msg->smGenerate(); + string msgstr = msg->smGenerate(OpenBTSUserAgent()); string firstLine = msgstr.substr(0,msgstr.find('\n')); // For debug purposes dump the address assuming IPv4. @@ -360,8 +373,8 @@ void SipInterface::siWrite(const struct sockaddr_in* dest, SipMessage *msg) 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; + WATCHINFO("SIP write "<smGetPrecis()); + //LOG(INFO) << "write " << firstLine; LOG(DEBUG) << "write " <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 @@ -398,14 +411,14 @@ bool SipInterface::checkTU(SipMessage *msg) return tuMapDispatch(msg); } -void SipInterface::newDriveIncoming(char *content) +void MySipInterface::newDriveIncoming(char *content) { LOG(DEBUG) << "SIP recv:"<smGetPrecis()); + WATCHINFO("SIP recv "<smGetPrecis()); try { if (newCheckInvite(msg)) { delete msg; return; } @@ -416,8 +429,8 @@ void SipInterface::newDriveIncoming(char *content) if (checkTU(msg)) { delete msg; return; } // Send message to the appropriate SipDialog. - SipDialog *dialog = findDialogByMsg(msg); - if (dialog) { + SipDialogRef dialog = findDialogByMsg(msg); + if (dialog.self()) { 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 "<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:"<omsg()->call_id); @@ -521,7 +534,7 @@ void SipInterface::handleInvite(SipMessage *msg, bool isINVITE) // (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) { + if (existing.self()) { 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)) { @@ -587,7 +600,8 @@ void SipInterface::handleInvite(SipMessage *msg, bool isINVITE) // There is already a voice transaction running on this imsi. // We need to send supplementary services - see 04.80 and 04.83 //dialog->MTCEarlyError(486,"Busy Here"); - dialog->dialogCancel(CancelCauseBusy); + LOG(INFO) << "SIP term info dialogCancel called in handleInvite"; + dialog->dialogCancel(TermCause::Local(L3Cause::User_Busy)); return; } // Queue on MM for this IMSI. @@ -598,12 +612,10 @@ void SipInterface::handleInvite(SipMessage *msg, bool isINVITE) // Create an incipient TranEntry. It does not have a TI yet. FullMobileId msid; msid.mImsi = toIMSIDigits; + + // zero length is okay 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. - } + // Zero length message body is okay at this point string smsContentType = msg->smGetMessageContentType(); if (smsContentType == "") { LOG(NOTICE) << "MT-SMS incoming MESSAGE method with no content type (or memory error) for " << msid.mImsi; @@ -615,7 +627,7 @@ void SipInterface::handleInvite(SipMessage *msg, bool isINVITE) gMMLayer.mmAddMT(tran); } -bool SipInterface::newCheckInvite(SipMessage *msg) +bool MySipInterface::newCheckInvite(SipMessage *msg) { // Check for INVITE or MESSAGE methods. // Check channel availability now, too, @@ -633,7 +645,8 @@ bool SipInterface::newCheckInvite(SipMessage *msg) // 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)) { + SipDialogRef existing = findDialogByMsg(msg); + if (existing.self()) { // 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()) { @@ -646,9 +659,11 @@ bool SipInterface::newCheckInvite(SipMessage *msg) } } } + return false; } +// All inbound SIP messages go here for processing. SVGDBG void SipInterface::siDrive2() { // All inbound SIP messages go here for processing. @@ -672,17 +687,18 @@ void SipInterface::siDrive2() newDriveIncoming(mReadBuffer); } -static void driveLoop2( SipInterface * si) +// This is the thread that processes all in comming SIP messages +static void driveLoop2( MySipInterface * si) { - while (true) { + while (! gBTS.btsShutdown()) { si->siDrive2(); } } // (pat) Every now and then check every SipDialog engine for SIP timer expiration. -static void periodicServiceLoop(SipInterface *si) +static void periodicServiceLoop(MySipInterface *si) { - while (true) { + while (! gBTS.btsShutdown()) { si->tuMapPeriodicService(); si->dmPeriodicService(); si->purgeDeadDialogs(); @@ -695,11 +711,11 @@ static void periodicServiceLoop(SipInterface *si) } } -SipInterface gSipInterface; // Here it is. +MySipInterface 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) + static void ortpLogFunc(OrtpLogLevel /*lev unused*/, 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. @@ -718,7 +734,6 @@ extern "C" { } } - void SipInterface::siInit() { // We use random() alot in here for SIP tags, CSeq number starting points. @@ -728,6 +743,13 @@ void SipInterface::siInit() gettimeofday(&now,NULL); srandom(now.tv_usec); + mSIPSocket = new UDPSocket(gConfig.getNum("SIP.Local.Port")); +} + + +void MySipInterface::msiInit() +{ + siInit(); // Start the ortp stuff. ortp_init(); ortp_scheduler_init(); @@ -736,8 +758,6 @@ void SipInterface::siInit() ortp_set_log_handler(ortpLogFunc); - mSIPSocket = new UDPSocket(gConfig.getNum("SIP.Local.Port")); - mDriveThread.start((void *(*)(void*))driveLoop2, &gSipInterface ); mPeriodicServiceThread.start((void *(*)(void*))periodicServiceLoop, &gSipInterface ); } @@ -745,7 +765,7 @@ void SipInterface::siInit() void SIPInterfaceStart() { - gSipInterface.siInit(); + gSipInterface.msiInit(); } diff --git a/SIP/SIP2Interface.h b/SIP/SIP2Interface.h index be60c56..ae35728 100644 --- a/SIP/SIP2Interface.h +++ b/SIP/SIP2Interface.h @@ -1,9 +1,10 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -19,11 +20,11 @@ #ifndef SIP2INTERFACE_H #define SIP2INTERFACE_H -#include #include #include #include +#include #include "SIPMessage.h" #include "SIPDialog.h" #include "SIPTransaction.h" @@ -48,18 +49,18 @@ typedef std::map TUMap_t; // For reply matching we use SipTUMap, cf. class SipDialogMap { //Mutex mDialogMapLock; - typedef InterthreadMap DialogMap_t; + typedef InterthreadMap1 DialogMap_t; DialogMap_t mDialogMap; string makeTagKey(string callid, string localTag); public: - SipDialog *findDialogByMsg(SipMessage *msg); + SipDialogRef 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); + //SipBase *dmFindDialogById(unsigned id); + SipDialogRef dmFindDialogByRtp(RtpSession *session); }; // Match incoming replies to the TU that made the outgoing request. @@ -94,10 +95,8 @@ class SipTUMap { }; -class SipInterface : public SipDialogMap, public SipTUMap +class SipInterface { -private: - char mReadBuffer[MAX_UDP_LENGTH+500]; ///< buffer for UDP reads. The +500 is way overkill. UDPSocket *mSIPSocket; @@ -109,6 +108,30 @@ private: 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: + SipInterface() { + // 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 siWrite(const struct sockaddr_in*, SipMessage *); // new + /** Start the SIP drive loop. */ + void siDrive2(); + void siInit(); + virtual void newDriveIncoming(char *content) = 0; + + unsigned nextMessageCSeqNum() { return ++mMessageCSeqNum; } + unsigned nextInfoCSeqNum() { return ++mInfoCSeqNum; } + unsigned nextInviteCSeqNum() { return ++mInviteCSeqNum; } +}; + +class MySipInterface : public SipDialogMap, public SipTUMap, public SipInterface +{ + public: Thread mDriveThread; Thread mPeriodicServiceThread; @@ -129,31 +152,23 @@ public: // 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; + typedef ThreadSafeList DeadDialogListType; DeadDialogListType mDeadDialogs; /** Create the SIP interface to watch for incoming SIP messages. */ - SipInterface() + MySipInterface() // (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(); + void msiInit(); /** Receive, parse and dispatch a single SIP message. */ - void newDriveIncoming(char *readBuffer); + void newDriveIncoming(char *content); void purgeDeadDialogs(); /** @@ -169,19 +184,13 @@ public: 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; +extern MySipInterface gSipInterface; //@} }; // namespace SIP. diff --git a/SIP/SIPBase.cpp b/SIP/SIPBase.cpp index bb6e7f7..51c35b8 100644 --- a/SIP/SIPBase.cpp +++ b/SIP/SIPBase.cpp @@ -5,7 +5,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -27,89 +27,32 @@ #include #include -#include - -#include #include #include -#include -#include -#include -#include #include -#include -#include // For HandoverEntry +#include #include "SIPMessage.h" #include "SIPParse.h" // For SipParam #include "SIPBase.h" -#include "SIPDialog.h" #include "SIP2Interface.h" #include "SIPUtility.h" +#include "SIPRtp.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. - -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; - } -} +//void DialogMessage::_define_vtable() {} const char* SipStateString(SipState s) @@ -154,7 +97,7 @@ ostream& operator<<(ostream& os, SipState s) // Should we cancel a transaction based on the SIP state? bool SipBaseProtected::sipIsStuck() const { - switch (getDialogType()) { + switch (vgetDialogType()) { case SIPDTUndefined: LOG(ERR) << "undefined dialog type?"; return false; case SIPDTRegister: @@ -181,30 +124,6 @@ bool SipBaseProtected::sipIsStuck() const } } -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. @@ -227,9 +146,17 @@ string DialogStateVars::dsToString() const return ss.str(); } +void DialogStateVars::updateProxy(const char *sqlOption) +{ + string proxy = gConfig.getStr(sqlOption); + if (! proxy.empty() && ! mProxy.ipSet(proxy,sqlOption)) { + LOG(ALERT) << "cannot resolve IP address for"< 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. @@ -452,86 +328,6 @@ void writePrivateHeaders(SipMessage *msg, const L3LogicalChannel *l3chan) -// 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; - string authUri; - string authUsername; - string realm = gConfig.getStr("SIP.Realm"); - if (realm.length() > 0) { - authUri = string("sip:") + realm; - authUsername = string("IMSI") + msid.mImsi; - myUriString = makeUri(username,realm,0); - } else { - 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)) { - if (realm.length() > 0) { - string response = makeResponse(authUsername, realm, SRES, registerStr, authUri, RAND); - msg->msmAuthorizationValue = format("Digest realm=\"%s\", username=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=MD5 ", - realm.c_str(), authUsername.c_str(), RAND.c_str(), authUri.c_str(), response.c_str()); - } else { - 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. @@ -545,7 +341,9 @@ string SipBase::makeSDPAnswer() // Implicit parameters from SipBase: mCallId, mRemoteUsername, mRemoteDomain, mCSeq, etc. SipMessage *SipBase::makeRequest(string method,string requestUri, string whoami, SipPreposition*toHeader, SipPreposition*fromHeader, string branch) { + LOG(INFO) << "SIP term info makeRequest: " << method; SipMessage *invite = new SipMessage(); + invite->smInit(); invite->msmReqMethod = method; invite->msmCallId = this->mCallId; //string toContact = makeUri(mRemoteUsername,mRemoteDomain); // dialed_number@remote_ip @@ -553,6 +351,7 @@ SipMessage *SipBase::makeRequest(string method,string requestUri, string whoami, //invite->msmReqUri = makeUri(mRemoteUsername,mRemoteDomain); invite->msmReqUri = requestUri; invite->smAddViaBranch(this,branch); + // invite->copyTermCausetoMessage(this); // SVGDBG not needed for this message type ?? invite->msmCSeqMethod = invite->msmReqMethod; invite->msmCSeqNum = mLocalCSeq; // We dont need to advance for an initial request; caller advances if necessary. @@ -589,690 +388,6 @@ SipMessage *SipBase::makeInitialRequest(string method) 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." <callId() != other->callId()) { return false; } if (this->dsLocalTag() != other->dsLocalTag()) { return false; } @@ -1342,17 +457,6 @@ bool SipBase::sameInviteOrMessage(SipMessage * msg) return sameMsg(msg,getInvite()); } -// Only the SipMTInviteServerTransactionLayer and SipMOInviteClientTransactionLayer are allowed to call -// the underlying sipWrite method directly for the invite transactions. -void SipBase::sipWrite(SipMessage *sipmsg) -{ - if (!mProxy.mipValid) { - LOG(ERR) << "Attempt to write to invalid proxy ignored, address:"<smGenerate(); + string str = msg->smGenerate(OpenBTSUserAgent()); //LOG(DEBUG) <(this); +SipCallbacks::ttAddMessage_functype SipCallbacks::ttAddMessage_callback = (SipCallbacks::ttAddMessage_functype) 0; + +void SipCallbacks::ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg) { + if (ttAddMessage_callback) { ttAddMessage_callback(tranid,dmsg); } } -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); -} +SipCallbacks::writePrivateHeaders_functype SipCallbacks::writePrivateHeaders_callback = (SipCallbacks::writePrivateHeaders_functype) 0; -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 -#undef WARNING // The nimrods defined this to "warning" -#undef CR // This too - #include -#include -#include -#include +#include +#include #include +#include #include "SIPUtility.h" #include "SIPMessage.h" -extern int gCountRtpSessions; -extern int gCountRtpSockets; extern int gCountSipDialogs; +namespace Control { class L3LogicalChannel; } + namespace SIP { class SipDialog; +typedef RefCntPointer SipDialogRef; 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")); -} +extern string localIP(); // Replaces mSIPIP. +extern string localIPAndPort(); // Replaces mSIPIP and mSIPPort. // These are the overall dialog states. enum SipState { @@ -90,24 +80,28 @@ enum SipState { std::ostream& operator<<(std::ostream& os, SipState s); extern const char* SipStateString(SipState s); -enum DialogType { SIPDTUndefined, SIPDTRegister, SIPDTUnregister, SIPDTMOC, SIPDTMTC, SIPDTMOSMS, SIPDTMTSMS, SIPDTMOUssd }; +enum DialogType { + SIPDTUndefined, + SIPDTRegister, + SIPDTUnregister, + SIPDTMOC, // Mobile Originate Call + SIPDTMTC, // Mobile Terminate Call + 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 @@ -123,12 +117,14 @@ class SipBaseProtected // 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; + virtual DialogType vgetDialogType() const = 0; public: /** Return the current SIP call state. */ + // (pat) This is the dialog state, if we are a dialog. SipState getSipState() const { return mState; } - /** Set the state. Must be called by for all internal and external users. */ + // (pat) Set the dialog state. The purpose of this protected class is to enforce that the dialog type is set + // only with this method by 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. */ @@ -154,6 +150,7 @@ class SipBaseProtected case HandoverOutbound: return false; } + return false; /*NOTREACHED*/ } SipBaseProtected() : mState(SSNullState) { mStateAge.now(); } @@ -174,11 +171,15 @@ class DialogStateVars { 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. + // (pat 5-2104) The proxy address is set from config options depending on the type of message. + // We currently dont support multiple upside peers. We dont return the message using the via for example. + IPAddressSpec mProxy; // The remote URL. 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. + string dsPAssociatedUri; + string dsPAssertedIdentity; DialogStateVars(); string dsLocalTag() { return mLocalHeader.mTag; } @@ -238,93 +239,18 @@ class DialogStateVars { 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 +// SipBase is intended to have no dependencies on the rest of OpenBTS so that it can be used as the base class for +// the message parser classes, allowing them to be used stand-alone. +// SipDialog is the class with many dependencies elsewhere, including, for example, gSipInterface and SipRtp. +// SipBase includes much of the state for a SIP dialog, but SipBase can also be used to parse non-dialog SIP messages, +// in which case all the dialog state stuff is just ignored. DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipBase,MemCheckSipBase) class SipBase : public MemCheckSipBase, - public RefCntBase, public SipEngine, public DialogStateVars, public SipRtp, public SipBaseProtected + public RefCntBase, public SipEngine, public DialogStateVars, public SipBaseProtected { friend class SipInterface; protected: @@ -336,9 +262,7 @@ public: SipMessage *getInvite() const { return mInvite; } /**@name SIP UDP parameters */ - //@{ - SipState getSipState() const { return SipBaseProtected::getSipState(); } // stupid language. - + //SipState getSipState() const { return SipBaseProtected::getSipState(); } // stupid language. string localSipUri() { return format("sip:%s@%s", sipLocalUsername(), localIPAndPort()); @@ -355,7 +279,6 @@ public: 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; @@ -371,14 +294,22 @@ public: 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; } + int getLastResponseCode(string &reason) { // Return is the reason phrase from an error response. + if (mLastResponse) { reason = mLastResponse->smGetReason(); return mLastResponse->smGetCode(); } + else return 0; + } + string getLastResponseReasonHeader() { // Return is the reason phrase from an error response. + return mLastResponse ? mLastResponse->msmReasonHeader : string(""); + } + //string getLastResponseReason() { return mLastResponse ? mLastResponse->smGetReason() : string(""); } //@} // 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 *makeRequest(string method,string requri, string whoami, SipPreposition* toContact, SipPreposition* fromContact, string branch); SipMessage *makeInitialRequest(string method); // with default URIs public: Bool_z mIsHandover; // TRUE if this dialog was established by an inbound handover. @@ -404,37 +335,9 @@ public: 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; } + DialogType vgetDialogType() 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); @@ -444,138 +347,46 @@ public: /** Determine if this invite matches the saved one */ bool sameInviteOrMessage(SipMessage * msg); - bool sameDialog(SipDialog *other); + bool sameDialog(SipBase *other); bool matchMessage(SipMessage *msg); SipDialog *dgGetDialog(); public: - void sbText(std::ostringstream&os, bool verbose=false) const; + void sbText(std::ostringstream&os) const; string sbText() const; + virtual int vGetRtpPort() const { return 0; } + Control::CodecSet vGetCodecs() const { return CodecSet(); } - /** - 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); + // Add this to code where reason needs to be set + // Example: SipBase::addCallTerminationReasonDlg(CallTerminationCause(CallTerminationCause::eTermSIP/eQ850, 100, "This is an error")); + void addCallTerminationReasonDlg(CallTerminationCause::termGroup group, int cause, string desc) { + LOG(INFO) << "SIP term info addCallTerminationReasonDlg cause: " << cause; + SIPDlgCallTermList.add(group, cause, desc); } - 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 + SipTermList& getTermList() { return SIPDlgCallTermList; } +private: + SipTermList SIPDlgCallTermList; // List of call termination reasons }; +struct SipCallbacks { + // ttAddMessage + typedef void (*ttAddMessage_functype)(TranEntryId tranid,DialogMessage *dmsg); + static ttAddMessage_functype ttAddMessage_callback; + static void ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg); + static void setcallback_ttAddMessage(ttAddMessage_functype callback) { + ttAddMessage_callback = callback; + } -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 + // writePrivateHeaders + typedef void (*writePrivateHeaders_functype)(SipMessage *msg, const L3LogicalChannel *l3chan); + static writePrivateHeaders_functype writePrivateHeaders_callback; + static void writePrivateHeaders(SipMessage *msg, const L3LogicalChannel *l3chan); + static void setcallback_writePrivateHeaders(writePrivateHeaders_functype callback) { + writePrivateHeaders_callback = callback; + } }; diff --git a/SIP/SIPDialog.cpp b/SIP/SIPDialog.cpp index d351f0f..d7b4463 100644 --- a/SIP/SIPDialog.cpp +++ b/SIP/SIPDialog.cpp @@ -3,7 +3,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -22,16 +22,550 @@ #include "SIPDialog.h" #include "SIP2Interface.h" #include "SIPTransaction.h" -//#include +#include // For gReports. #include +#include #include #include // for L3CMServiceType +#include "config.h" // For VERSION +#include // for L3Cause #include namespace SIP { using namespace Control; SipDialog *gRegisterDialog = NULL; +// Only the SipMTInviteServerTransactionLayer and SipMOInviteClientTransactionLayer are allowed to call +// the underlying sipWrite method directly for the invite transactions. +void SipDialogBase::sipWrite(SipMessage *sipmsg) +{ + if (!mProxy.mipValid) { + LOG(ERR) << "Attempt to write to invalid proxy ignored, address:"<(this); +} + +SipState SipDialogBase::MODSendCANCEL(TermCause cause) +{ + LOG(INFO) << "SIP term info MODSendCANCEL cause: " << cause; + LOG(INFO) << sbText(); + setSipState(MODCanceling); // (pat) MOD sent a cancel. + SipMOCancelTU *cancelTU = new SipMOCancelTU(dynamic_cast(this),cause.tcGetSipReasonHeader()); + //cancelTU->mstOutRequest.addCallTerminationReasonSM(CallTerminationCause::eQ850, cause.tcGetCCCause(), ""); // MODSendCANCEL + cancelTU->sctStart(); + return getSipState(); +} + +void SipDialogBase::initRTP() +{ + SdpInfo sdp; + sdp.sdpParse(getSdpRemote().c_str()); + initRTP1(sdp.sdpHost.c_str(),sdp.sdpRtpPort,mDialogId); +} + + +string SipDialogBase::makeSDPOffer() +{ + SdpInfo sdp; + sdp.sdpInitOffer(this); + return sdp.sdpValue(); + //return makeSDP("0","0"); +} + +// mCodec is an implicit parameter, consisting of the chosen codec. +string SipDialogBase::makeSDPAnswer() +{ + SdpInfo answer; + answer.sdpInitOffer(this); + mSdpAnswer = answer.sdpValue(); + return mSdpAnswer; +} + +void SipDialogBase::MTCInitRTP() +{ + initRTP(); +} + +void SipDialogBase::MOCInitRTP() +{ + initRTP(); +} + + +void SipDialogBase::sdbText(std::ostringstream&os, bool verbose) const +{ + sbText(os); + + if (verbose || IS_LOG_LEVEL(DEBUG)) { + rtpText(os); + //os << "proxy=(" << mProxy.ipToText() << ")"; + } +} + +string SipDialogBase::sdbText() const +{ + std::ostringstream ss; + sdbText(ss); + return ss.str(); +} + +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); + + SipCallbacks::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="<dsPAssociatedUri.size()) { + invite->smAddHeader("P-Associated-URI",this->dsPAssociatedUri); + } + if (this->dsPAssertedIdentity.size()) { + invite->smAddHeader("P-Asserted-Identity",this->dsPAssertedIdentity); + } + + SipCallbacks::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. + } + + 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) << sdbText(); + //LOG(INFO) << "user " << mSipUsername << " state " << getSipState() <smAddBody(contentType,messageText); + moWriteLowSide(msg); + delete msg; + setSipState(MOSMSSubmit); +} + + +// 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 <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:"<, and Paul at null team incorrectly thought the <> were required, + // so we will include them as that appears to be common practice. + + string myUriString; + string authUri; + string authUsername; + string realm = gConfig.getStr("SIP.Realm"); + if (realm.length() > 0) { + authUri = string("sip:") + realm; + authUsername = string("IMSI") + msid.mImsi; + myUriString = makeUri(username,realm,0); + } else { + 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)) { + if (realm.length() > 0) { + string response = makeResponse(authUsername, realm, SRES, registerStr, authUri, RAND); + msg->msmAuthorizationValue = format("Digest realm=\"%s\", username=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=MD5, qop=\"auth\" ", + realm.c_str(), authUsername.c_str(), RAND.c_str(), authUri.c_str(), response.c_str()); + } else { + 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); + SipCallbacks::writePrivateHeaders(msg,chan); + return msg; +} + void SipDialog::dgReset() { mPrevDialogState = DialogState::dialogUndefined; sipStopTimers(); @@ -50,12 +644,14 @@ void SipDialog::dgReset() } -void SipDialog::MODSendBYE() +void SipDialog::MODSendBYE(TermCause cause) { - LOG(INFO) <mstOutRequest.addCallTerminationReasonSM(CallTerminationCause::eQ850, cause.tcGetCCCause(), ""); // MODSendBYE byeTU->sctStart(); } @@ -123,15 +719,9 @@ SipDialog *SipDialog::newSipDialogMOSMS( LOG(DEBUG) <dsSetLocalMO(fromMsId,gPeerIsBuggySmqueue ? true : false); + dialog->dsSetLocalMO(fromMsId, true); string calledDomain = dialog->localIP(); - if (gConfig.getStr("SIP.Realm").length() > 0) { - string tmpURI = makeUri(calledDigits,calledDomain); - tmpURI.erase(std::remove(tmpURI.begin(), tmpURI.end(), '+'), tmpURI.end()); - dialog->dsSetRemoteUri(tmpURI); - } else { - dialog->dsSetRemoteUri(makeUri(calledDigits,calledDomain)); - } + dialog->dsSetRemoteUri(makeUri(calledDigits,calledDomain)); dialog->smsBody = body; // Temporary until smqueue is fixed. dialog->smsContentType = contentType; // Temporary until smqueue is fixed. @@ -145,6 +735,32 @@ SipDialog *SipDialog::newSipDialogMOSMS( return dialog; } + +// SIP-URI = "sip:" [ userinfo ] hostport +// userinfo = ( user / telephone-subscriber ) [ ":" password ] "@" +// user = 1*( unreserved / escaped / user-unreserved ) +// unreserved = alphanum / mark +// mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" +// escaped = "%" HEXDIG HEXDIG +// user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" +// Any other character needs to be escaped. RFC 2396. +// +// In general, URI encoding (RFC2396) is kind of complicated - for example, we need a-priori knowledge if ";" in the string is +// marking a URI parameter, in which case it needs to be left alone, or if it is part of the URI component, in which case it must be escaped. +// RFC3267 talks about URBNF the URI string may contain: +// This handles the USSD special case. A USSD request can contain only digits, letters * and #, +// and of these the only special one is "#" turns into "%23". +string escapeUssdUri(string ss) +{ + size_t pos = 0; + while (1) { + pos = ss.find(pos,'#'); + if (pos == string::npos) return ss; + ss.replace(pos,1,"%23"); // it is ok not to advance pos because replacement "%23" does not include search "#". + } + return ss; +} + SipDialog *SipDialog::newSipDialogMOUssd( TranEntryId tranid, const FullMobileId &fromMsId, // caller imsi @@ -175,7 +791,19 @@ SipDialog *SipDialog::newSipDialogMOUssd( dialog->dialogQueueMessage(dmsg); return dialog; } - dialog->dsSetRemoteUri(makeUri(wUssd,dialog->localIP())); + + // Yes, you really do put the USSD request in the To: of the SIP as per 3GPP 24.090 Appendix A, but we need to + // escape the "#" character. We also add a "dialstring" tag. + // Like this: INVITE sip:*135%23;phone-context=home1.net;user=dialstring SIP/2.0 + // The obvious way to pass a USSD string is as a "tel:" domain, but evidently that was too easy, so we + // must add the user=dialstring tag as per RFC 4967, and then THAT requires a "context" as per RFC3966 because + // this is not a "global" tel number, and therefore by definition it must be a "local" tel number, which requires + // a context, but we learn from RFC 4967 that we can stick in anything we want for the context, since it is meaningless + // when used for a USSD string. This spec is not exactly an "A-team" effort. + + string ussdEscaped = escapeUssdUri(wUssd); + string ussdPerSpec = ussdEscaped + ";phone-context=irrelevant.net;user=dialstring"; + dialog->dsSetRemoteUri(makeUri(ussdPerSpec,dialog->localIP())); // TODO: What about codecs? The example in 24.390 annex A has them. gSipInterface.dmAddCallDialog(dialog); @@ -194,15 +822,9 @@ SipDialog *SipDialog::newSipDialogMOC( ) { -#ifdef C_CRELEASE - LOG(DEBUG) << "MOC SIP (INVITE)"<dsSetRemoteUri(makeUri(wCalledDigits,dialog->localIP())); } + string username = fromMsId.fmidUsername(); + if (username.length()) { + devassert(username.substr(0,4) == "IMSI"); + string pAssociatedUri, pAssertedIdentity; + gTMSITable.getSipIdentities(username.substr(4),pAssociatedUri,pAssertedIdentity); // They may be empty. + dialog->dsPAssociatedUri = pAssociatedUri; + dialog->dsPAssertedIdentity = pAssertedIdentity; + WATCH("MOC"<smAddHeader("P-Associated-URI",pAssociatedUri); } + //if (pAssertedIdentity.size()) { invite->smAddHeader("P-Asserted-Identity",pAssertedIdentity); } + } + dialog->mRTPPort = Control::allocateRTPPorts(); dialog->mCodec = wCodecs; @@ -298,16 +932,65 @@ TranEntry *SipDialog::findTranEntry() return gNewTransactionTable.ttFindById(this->mTranId); } +// If it is not an IMSI we think it may be a phone number. +static bool isPhoneNumber(string thing) +{ + if (thing.size() == 0) { return false; } // Not a phone number. + if (0 == strncasecmp(thing.c_str(),"IMSI",4)) { return false; } // It is an IMSI, not a phone number. + return true; // Well, maybe it is a phone number. +} + +static string removeUriFluff(string thing) +{ + if (unsigned first = thing.find('<') != string::npos) { // Remove the angle brackets. + thing = thing.substr(first,thing.find_last_of('>')); + } + thing = thing.substr(0,thing.find_last_of('@')); // Chop off the ip address, if any. + const char *str = thing.c_str(); + // Very clever, that a phone number may be prefixed with either sip: or tel: + if (0 == strncasecmp(str,"sip:",4) || 0 == strncasecmp(str,"tel:",4)) { + thing = thing.substr(4); + } else if (0 == strncasecmp(str,"sips:",5)) { // secure not supported, but always hopeful.... + thing = thing.substr(5); + } + return thing; // Hopefully, what is remaining is a phone number. +} + TranEntry *SipDialog::createMTTransaction(SipMessage *invite) { - // Create an incipient TranEntry. It does not have a TI yet. TranEntry *tran = NULL; string callerId; - if (gConfig.getStr("GSM.CallerID.Source").compare("username") == 0) { + string callerIdSource = gConfig.getStr("GSM.CallerID.Source"); + if (0 == strcasecmp(callerIdSource.c_str(),"auto")) { + // (pat 6-2014) Added automatic caller id identification. + // We can do this automatically because we know whether the displayname, etc, are imsi because they are preceded by "IMSI". + // btw, the P-Asserted-Identity should by an IMSI; the phone number is supposed to be in the P-Associated-URI. + callerId = sipRemoteDisplayname(); // This is out best guess. + LOG(DEBUG) << "CallerID=auto: sipRemoteDisplayname="<msmHeaders.paramFind("P-Associated-URI")); + LOG(DEBUG) << "CallerID=auto: P-Associated-URI="<msmHeaders.paramFind("P-Asserted-Identity")); + LOG(DEBUG) << "CallerID=auto: P-Asserted-Identity="<msmHeaders.paramFind("P-Asserted-Identity"); unsigned first = tmpcid.find("MTCSendTrying(); } else { - assert(0); + devassert(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) +// cause == This is the reason a Transaction (TranEntry) was cancelled +// Layer3 may call this multiple times just to be safe and make sure the dialog is truly cancelled; +// generally the first call includes the real causes, then the second call is made when the transaction +// is destroyed and the cause is some bogus cause. +void SipDialog::dialogCancel(TermCause cause) { - WATCH("dialogCancel"<getSipState(); + //bool bTerminationAdded = false; + + LOG(INFO) << "SIP term info dialogCancel begin cancel cause: " << cause; // SVGDBG + + WATCH("dialogCancel"<getSipState(); - LOG(INFO) << dialogText(); // "SIP state " << state; + LOG(DEBUG) << dialogText(); // "SIP state " << state; //SVGDBG switch to LOG(INFO) - switch (cause) { - case CancelCauseHandoverOutbound: - case CancelCauseSipInternalError: + if (cause.tcGetValue() == L3Cause::Handover_Outbound) { // 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); + LOG(INFO) << "SIP term info dialogCancel dmRemoveDialog return"; return; - default: - break; } - //why aren't we checking for failed here? -kurtis ; we are now. -david - if (this->sipIsFinished()) return; + // why aren't we checking for failed here? -kurtis ; we are now. -david + if (this->sipIsFinished()) { + // No bye or cancel message will be sent. + LOG(INFO) << "SIP term info dialogCancel sipIsFinished return SIP state: " << this->getSipState(); + return; + } switch (mDialogType) { case SIPDTRegister: case SIPDTUnregister: @@ -372,8 +1064,10 @@ void SipDialog::dialogCancel(CancelCause cause) LOG(ERR) "Unexpected SIP State:"<MODSendBYE(); + this->MODSendBYE(cause); + //bTerminationAdded = true; //then cleared sipStartTimers(); // formerly: MODWaitForBYEOK(); break; @@ -386,12 +1080,13 @@ void SipDialog::dialogCancel(CancelCause cause) 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 + this->MODSendCANCEL(cause); //Changes state to MODCanceling + //bTerminationAdded = true; } 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! + // Way back in version 3 this was used for MTC also. // RFC3261 (SIP) is internally inconsistent describing the error codes - the 4xxx and 5xx generic - // descriptions are contracted by specific error code descriptions. + // descriptions are contradicted 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) @@ -404,31 +1099,64 @@ void SipDialog::dialogCancel(CancelCause cause) // 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"; + if (cause.tcGetCCCause() == L3Cause::Normal_Call_Clearing) { + // (pat 7-2014) The handset MTC hung up normally but the SIP Dialog here is not in the Active state. + // This weird case occurs if the handset sends a disconnect before it sends a connect, + // which can happen on some if the user answers and hangs up immediately. + // We need to send a 200 OK and then BYE instead of sending an early error. + // We dont care about codecs because we are clearing immediately so just regurgitate the codecs from the INVITE. + LOG(DEBUG) << "SIP SPECIAL CASE: Disconnect before connect"; + this->MTCSendOK(this->vGetCodecs(), NULL); + goto caseActive; + } else { + this->MTCEarlyError(cause); + } +#if PREVIOUS_CODE +// (pat) Keeping this old code here for a while to show what SIP responses were returned by the version 4 code. +// Now SIP responses are formulated from the TermCause by tcGetSipCodeAndReason() + int sipcode = 486; const char *reason = "No answer"; switch (cause) { case CancelCauseHandoverOutbound: case CancelCauseSipInternalError: assert(0); // handled above + case CancelCauseNormalDisconnect: // 0 Loss of contact with MS or an error. 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. + + case CancelCauseUnknown: // 0 Loss of contact with MS or an error. + if (l3Cause == L3Cause::SwitchingEquipmentCongestion) { + sipcode = 503; reason = "Normal circuit congestion"; + } + else if (l3Cause == L3Cause::NoUserResponding) { + sipcode = 408; reason = "No user responding"; + } + else if (l3Cause == L3Cause::CallRejected) { + sipcode = 603; reason = "Call rejected"; + } + + break; + + case CancelCauseCongestion: // This reason is never used MS is here but no channel avail or other congestion. + sipcode = 503; reason = "Normal circuit congestion"; + break; + + case CancelCauseNoAnswerToPage: // Not used 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"; + sipcode = 408; reason = "No user responding"; break; case CancelCauseOperatorIntervention: sipcode = 487; reason = "Request Terminated Operator Intervention"; break; } this->MTCEarlyError(sipcode,reason); // The message must be 300-699. +#endif + //bTerminationAdded = true; } 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 MODCanceling: // (pat) MOD sent a cancel. + case MODError: // (pat) MOD sent an error response. case MTDClearing: // (pat) MTD received BYE. case MTDCanceling: // (pat) MTD received CANCEL case Canceled: // (pat) received OK to CANCEL. @@ -445,7 +1173,22 @@ void SipDialog::dialogCancel(CancelCause cause) assert(0); break; } -} + + LOG(INFO) << "SIP term info end dialogCancel, cancel cause: " << cause << " mDialogType: " << mDialogType <dialogText(); else os << "(null SipDialog)"; + return os; +} std::ostream& operator<<(std::ostream& os, const DialogState::msgState dstate) { @@ -930,5 +1687,12 @@ std::ostream& operator<<(std::ostream& os, const DialogMessage*dmsg) std::ostream& operator<<(std::ostream& os, const DialogMessage&dmsg) { os << &dmsg; return os; } // stupid language +string OpenBTSUserAgent() +{ + static const char* userAgent1 = "OpenBTS " VERSION " Build Date " TIMESTAMP_ISO; + string userAgent = string(userAgent1); + return userAgent; +} + }; // namespace diff --git a/SIP/SIPDialog.h b/SIP/SIPDialog.h index 33af288..de35a9d 100644 --- a/SIP/SIPDialog.h +++ b/SIP/SIPDialog.h @@ -3,7 +3,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -17,20 +17,160 @@ #ifndef SIPDIALOG_H #define SIPDIALOG_H +#include #include +#include #include "SIPExport.h" #include "SIPMessage.h" #include "SIPBase.h" +#include "SIPRtp.h" #include "SIPTransaction.h" namespace SIP { +class SipDialogBase: public virtual SipBase, public SipRtp +{ + public: + void sipWrite(SipMessage *sipmsg); + SipDialog *dgGetDialog(); + SipState MODSendCANCEL(Control::TermCause l3Cause); + void initRTP(); + void MOCInitRTP(); + void MTCInitRTP(); + string sdbText() const; + void sdbText(std::ostringstream&os, bool verbose=false) const; + string makeSDPOffer(); + string makeSDPAnswer(); + int vGetRtpPort() const { return this->SipRtp::mRTPPort; } + Control::CodecSet vGetCodecs() const { return this->SipRtp::mCodec; } +}; + + +// 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 SipDialogBase +{ + 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(Control::TermCause cause); // 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); + + bool moPeriodicService(); // Return TRUE to remove the dialog. + string motlText() const; // MO Transaction Layer Text +}; + +// MO, uses SIP Client transaction: +// us -> 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 -// 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, +DEFINE_MEMORY_LEAK_DETECTOR_CLASS(SipDialog,MemCheckSipDialog) +class SipDialog : public MemCheckSipDialog, public virtual SipDialogBase, public virtual SipMOInviteClientTransactionLayer, public virtual SipMTInviteServerTransactionLayer { @@ -48,6 +188,8 @@ class SipDialog : public MemCheckSipDialog, public virtual SipBase, void registerHandleSipCode(SipMessage *msg); public: + Bool_z mReceived180; // The 1xx response, with the caveat that 180 (ringing) is saved over others. + // 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. @@ -61,7 +203,7 @@ class SipDialog : public MemCheckSipDialog, public virtual SipBase, const char *dialogStateString() const { return DialogState::msgStateString(getDialogState()); } //void dialogOpen(const char *userid); // The userid is the IMSI. //void dialogClose(); - void MODSendBYE(); + void MODSendBYE(Control::TermCause l3Cause); void sendInfoDtmf(unsigned bcdkey); // Send an error code that terminates the dialog. @@ -103,7 +245,7 @@ class SipDialog : public MemCheckSipDialog, public virtual SipBase, } } - void dialogCancel(CancelCause cause = CancelCauseUnknown); + void dialogCancel(TermCause cause/*, GSM::CCCause l3Cause*/); //void dialogMOCSendInvite(const char *bcddigits,Control::CodecSet codecs); void dialogWriteDownlink(SipMessage *msg); void handleInviteResponse(int status, bool sendAck); @@ -112,12 +254,31 @@ class SipDialog : public MemCheckSipDialog, public virtual SipBase, Control::TranEntry *createMTTransaction(SipMessage *invite); Control::TranEntry *findTranEntry(); void handleUssdBye(SipMessage *msg); + /**@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)); }; + string vsdbText() const { return sdbText(); } + SipState vgetSipState() const { return getSipState(); } }; + std::ostream& operator<<(std::ostream& os, const DialogState); std::ostream& operator<<(std::ostream& os, const SipDialog&); +std::ostream& operator<<(std::ostream& os, const SipDialogRef&); std::ostream& operator<<(std::ostream& os, const SipDialog*); extern SipDialog *gRegisterDialog; +extern string OpenBTSUserAgent(); }; // namespace #endif diff --git a/SIP/SIPExport.h b/SIP/SIPExport.h index 081273b..525ee96 100644 --- a/SIP/SIPExport.h +++ b/SIP/SIPExport.h @@ -1,8 +1,8 @@ -/* Copyright 2013 Range Networks, Inc. +/* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -11,13 +11,16 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ + #ifndef _SIPEXPORTH_ #define _SIPEXPORTH_ 1 -#include +//#include +#include namespace SIP { -extern void startRegister(const Control::FullMobileId &msic, const string rand, const string sres, L3LogicalChannel *chan); +extern void startRegister(TranEntryId tid, const Control::FullMobileId &msic, const string rand, const string sres, L3LogicalChannel *chan); +extern void startUnregister(const FullMobileId &msid, L3LogicalChannel *chan); class SipDialog; extern SipDialog *getRegistrar(); @@ -46,13 +49,13 @@ struct DialogState { struct SipCode { int mCode; - const char *mReason; + string mReason; SipCode() : mCode(0), mReason("") {} - SipCode(int wCode, const char *wReason) : mCode(wCode), mReason(wReason) {} + SipCode(int wCode, string wReason) : mCode(wCode), mReason(wReason) {} }; class DialogMessage { - virtual void _define_vtable(); + //virtual void _define_vtable(); public: virtual ~DialogMessage() {} Control::TranEntryId mTranId; // The associated TransactionEntry or 0 for the old MobilityManagement SipBase which has none. diff --git a/SIP/SIPMessage.cpp b/SIP/SIPMessage.cpp index 46f0228..b62c846 100644 --- a/SIP/SIPMessage.cpp +++ b/SIP/SIPMessage.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -23,15 +24,13 @@ #include #include "SIPUtility.h" -#include "SIPBase.h" #include "SIPMessage.h" -#include "SIPDialog.h" -#include +#include "SIPBase.h" +#include #undef WARNING // The nimrods defined this to "warning" #undef CR // This too using namespace std; -using namespace Control; namespace SIP { @@ -186,7 +185,7 @@ static void appendHeader(string *result,const char *name,int val) } // Generate the string representing this sip message. -string SipMessage::smGenerate() +string SipMessage::smGenerate(string userAgent) { string result; result.reserve(1000); @@ -218,17 +217,29 @@ string SipMessage::smGenerate() 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. + if (! msmMaxForwards.empty()) { appendHeader(&result,"Max-Forwards",msmMaxForwards); } // 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); + // Take out the headers we are going to add below. + const char *name = it->mName.c_str(); + if (strcasecmp(name,"User-Agent") && /*strcasecmp(name,"Max-Forwards") &&*/ + strcasecmp(name,"Content-Type") && strcasecmp(name,"Content-Length")) { + 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")); + // Append termination cause text see examples below + // Reason: SIP ;cause=580 ;text="Precondition Failure" + // Reason: Q.850 ;cause=16 ;text="Terminated" + // SVG Added 4/21/14 for Lynx SIPCallTermination + + //LOG(INFO) << "SIP term info copy SIP call termination reasons to SIP message, count: " << SIPMsgCallTerminationList.size(); // SVGDBG + //string sTemp = SIPMsgCallTerminationList.getTextForAllMsgs();; + //LOG(INFO) << "SIP term info in smGenerate text: " << sTemp.c_str(); // SVGDBG + //result.append(sTemp.c_str()); + if (msmReasonHeader.size()) { appendHeader(&result,"Reason",msmReasonHeader); } // Create the body, if any. appendHeader(&result,"Content-Type",msmContentType); @@ -236,6 +247,8 @@ string SipMessage::smGenerate() result.append("\r\n"); result.append(msmBody); msmContent = result; + LOG(INFO) << "SIP term info generated SIP msg: \r\n" << result.c_str(); //SVGDBG + return msmContent; } @@ -246,17 +259,41 @@ void SipMessage::smCopyTopVia(SipMessage *other) } // Add a new Via with a new unique branch. -void SipMessage::smAddViaBranch(string transport, string branch) +void SipMessage::smAddViaBranch3(string transport, string proxy, string branch) { - string newvia = format("SIP/2.0/%s %s;branch=%s\r\n",transport,localIPAndPort(),branch); + string newvia = format("SIP/2.0/%s %s;branch=%s\r\n",transport,proxy,branch); commaListPushFront(&msmVias,newvia); } + +void SipMessage::smAddViaBranch(string transport, string branch) +{ + smAddViaBranch3(transport,localIPAndPort(),branch); +} + 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::smPopVia() // Pop and return the top via; modifies msmVias. +{ + return commaListPopFront(&msmVias); +} + +string SipMessage::smGetProxy() const +{ + string topvia = commaListFront(msmVias); + if (topvia.empty()) { return string(""); } // oops + SipVia via(topvia); + return via.mSentBy; +} + +void SipMessage::addCallTerminationReasonSM(CallTerminationCause::termGroup group, int cause, string desc) { + LOG(INFO) << "SIP term info addCallTerminationReasonSM cause: " << cause; + SIPMsgCallTerminationList.add(group, cause, desc); +} + string SipMessage::smGetBranch() { @@ -339,7 +376,7 @@ string SipMessage::smGetPrecis() const 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()); + return format("response code=%d %s %s to=%s",smGetCode(),smGetReason().c_str(),smCSeqMethod().c_str(),smGetToHeader().c_str()); } } @@ -412,11 +449,13 @@ string SipMessage::smUriUsername() //LOG(DEBUG) << LOGVAR(msmReqUri) <smInit(); this->msmCallId = other->msmCallId; this->msmReqMethod = method; this->msmCSeqMethod = method; @@ -445,6 +485,8 @@ SipMessageAckOrCancel::SipMessageAckOrCancel(string method, SipMessage *other) { this->smCopyTopVia(other); } +// (pat 5-2014) I dont think OpenBTS currently uses any of the below; it just sends all messages and replies to the single +// proxy specified by config option for each of Registration, Speech, and SMS type messages. // 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. @@ -487,6 +529,7 @@ SipMessageAckOrCancel::SipMessageAckOrCancel(string method, SipMessage *other) { // 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->smInit(); this->msmCallId = dialog->callId(); this->msmReqMethod = reqMethod; this->msmCSeqMethod = reqMethod; @@ -511,6 +554,7 @@ SipMessageRequestWithinDialog::SipMessageRequestWithinDialog(string reqMethod, S SipMessageHandoverRefer::SipMessageHandoverRefer(const SipBase *dialog, string peer) { // The request URI will be the BTS we are sending it to... + this->smInit(); this->msmReqMethod = string("REFER"); this->msmReqUri = makeUri("handover",peer); this->msmTo = *dialog->dsRequestToHeader(); @@ -561,7 +605,8 @@ SipMessageHandoverRefer::SipMessageHandoverRefer(const SipBase *dialog, string p // 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) { +SipMessageReply::SipMessageReply(SipMessage *request, int code, string reason, SipBase *dialog) { + LOG(INFO) << "SIP term info SipMessageReply SIP code: " << code; msmCode = code; msmReason = reason; if (dialog) { @@ -590,5 +635,21 @@ bool sameMsg(SipMessage *msg1, SipMessage *msg2) return msg1->msmCSeqNum == msg2->msmCSeqNum && msg1->msmCSeqMethod == msg2->msmCSeqMethod; } +// Return false if the Max-Forwards is 1 or less or non-integral. +bool SipMessage::smDecrementMaxFowards() +{ + if (msmMaxForwards.size()) { + assert(msmMaxForwards[0] != ' '); + int val = atoi(msmMaxForwards.c_str()); // If it is not numeric, it will return 0 silently. + val--; + if (val <= 0) { val = 0; } + msmMaxForwards = format("%d",val); + return val > 0; + } else { + msmMaxForwards = string("70"); + return true; + } +} + }; // namespace SIP diff --git a/SIP/SIPMessage.h b/SIP/SIPMessage.h index 24c6c65..a652c8d 100644 --- a/SIP/SIPMessage.h +++ b/SIP/SIPMessage.h @@ -1,8 +1,8 @@ /* * 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -20,9 +20,7 @@ #define _SIP2MESSAGE_H_ #include #include "SIPParse.h" -#include "config.h" // For VERSION - -#include // For CodecSet +#include namespace SIP { using namespace std; @@ -53,52 +51,62 @@ class SipMessage : public MemCheckSipMessage { 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 msmRoutes; // Route headers. + string msmRecordRoutes; // Record-route headers. string msmContactValue; //list msmBodies; string msmContentType; string msmBody; int msmCode; - string msmReason; + string msmReason; // This is the reason phrase from the first line, not the "Reason: Header". + string msmReasonHeader; // This is the "Reason:" header. 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. + string msmMaxForwards; // Must be a string so we can detect unset as a special case. + SipTermList SIPMsgCallTerminationList; // List of SIP termination reasons + // Modifiers: + void smInit() { msmMaxForwards = string("70"); } void smAddViaBranch(string transport, string branch); void smAddViaBranch(SipBase *dialog,string branch); - string smGetBranch(); - string smGetReturnIPAndPort(); + void smAddViaBranch3(string transport, string proxy, string branch); + string smPopVia(); // Pop and return the top via; modifies msmVias. 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; } + + SipMessage() : msmCode(0), msmCSeqNum(0), SIPMsgCallTerminationList() {} // mCSeq will be filled in later. + void smAddHeader(string name, string value) { SipParam param(name,value); msmHeaders.push_back(param); } + bool smDecrementMaxFowards(); + + string smGenerate(string userAgent); // Recreate the message from fields; return result and also leave it in msmContent. + + SipTermList& getTermList() { return SIPMsgCallTerminationList; } + void addCallTerminationReasonSM(CallTerminationCause::termGroup group, int cause, string desc); // Accessors: bool isRequest() { return msmCode == 0; } + bool isRequestNotAck() { return msmCode == 0 && strcasecmp(msmReqMethod.c_str(),"ACK"); } + int smCSeqNum() const { return msmCSeqNum; } + string smGetReturnIPAndPort(); 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 smUriUsername(); // The username in an originating request. 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; - } + string smGetBranch(); // The branch from the top via. + string smGetProxy() const; // The proxy from the top via. const char *smGetMethodName() const { return msmReqMethod.c_str(); } - const char *smGetReason() const { return msmReason.c_str(); } + const string smGetReason() const { return msmReason; } string smGetRand401(); int smGetCode() const { return msmCode; } int smGetCodeClass() const { return (msmCode / 100) * 100; } diff --git a/SIP/SIPParse.cpp b/SIP/SIPParse.cpp index 0c29d00..e08c0a0 100644 --- a/SIP/SIPParse.cpp +++ b/SIP/SIPParse.cpp @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -20,16 +20,15 @@ #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"; } @@ -39,6 +38,20 @@ struct SipParseError : public std::exception { }; +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); + } +} + + // 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. @@ -87,6 +100,21 @@ void commaListPushFront(string *cl, string val) } } +string commaListPopFront(string *cl) // Modify cl and return the result. +{ + string result; + if (cl->empty()) { return result; } // This is probably an error on the part of the caller. + size_t cn = cl->find_first_of(','); + if (cn == string::npos) { + result = *cl; + *cl = string(""); + } else { + result = cl->substr(0,cn); + *cl = trimboth(cl->substr(cn+1)," ,"); // The trimboth is paranoid overkill. + } + return trimboth(result," ,"); // Make extra sure there is no errant garbage around the result. +} + string commaListFront(string cl) { size_t cn = cl.find_first_of(','); @@ -136,6 +164,14 @@ char SipChar::charClassData[256]; // This can not distinguish between a missing param and one with an empty value. +SipParamList::iterator SipParamList::paramFindIt(const char *name) +{ + for (SipParamList::iterator it = this->begin(); it != this->end(); it++) { + if (strceql(it->mName.c_str(),name)) { return it; } + } + return this->end(); +} + string SipParamList::paramFind(const char*name) { for (SipParamList::iterator it = this->begin(); it != this->end(); it++) { @@ -596,12 +632,16 @@ class SipParseMessage : SipParseLine { commaListPushBack(&sipmsg->msmRecordRoutes,pp); } else if (strceql(name,"route")) { commaListPushBack(&sipmsg->msmRoutes,pp); + } else if (strceql(name,"max-forwards")) { + sipmsg->msmMaxForwards = trimboth(string(pp)); // We already trimmed left, but its ok to do it again. // 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 if (strceql(name,"reason")) { + sipmsg->msmReasonHeader = string(pp); } else { SipParam param(name,string(pp)); sipmsg->msmHeaders.push_back(param); @@ -638,7 +678,7 @@ SipMessage *sipParseBuffer(const char *buffer) return result; } -void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs) +void codecsToSdp(Control::CodecSet codecs, string *codeclist, string *attrs) { attrs->clear(); attrs->reserve(80); @@ -651,7 +691,7 @@ void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs) codeclist->append("0"); } ***/ - if (codecs.isSet(GSM_FR) || codecs.isSet(GSM_HR) || codecs.isEmpty()) { + if (codecs.isSet(Control::GSM_FR) || codecs.isSet(Control::GSM_HR) || codecs.isEmpty()) { attrs->append("a=rtpmap:3 GSM/8000\r\n"); if (!codeclist->empty()) { codeclist->append(" "); } codeclist->append("3"); @@ -659,7 +699,7 @@ void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs) // 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(" "); } @@ -706,9 +746,9 @@ void SdpInfo::sdpParse(const char *buffer) void SdpInfo::sdpInitOffer(const SipBase *dialog) { sdpUsername = dialog->sipLocalUsername(); - sdpRtpPort = dialog->mRTPPort; + sdpRtpPort = dialog->vGetRtpPort(); sdpHost = dialog->localIP(); - codecsToSdp(dialog->mCodec,&sdpCodecList,&sdpAttrs); + codecsToSdp(dialog->vGetCodecs(),&sdpCodecList,&sdpAttrs); static const string zero("0"); sdpSessionId = sdpVersionId = zero; } @@ -746,4 +786,74 @@ string SdpInfo::sdpValue() return result; } + +// See L3Cause in L3Enums.h +string CallTerminationCause::getQ850CallTermText(int l3Cause) { + switch ((GSM::L3Cause::CCCause) l3Cause) { + case GSM::L3Cause::Unknown_L3_Cause: return "Unknown Cause 0"; + case GSM::L3Cause::Unassigned_Number: return "Unallocated Number"; // 1 + case GSM::L3Cause::No_Route_To_Destination: return "No Route to Destination"; + case GSM::L3Cause::Channel_Unacceptable: return "Channel Unacceptable"; + case GSM::L3Cause::Operator_Determined_Barring: return "Preemption"; + case GSM::L3Cause::Normal_Call_Clearing: return "Normal Call Clearing"; // 16 + case GSM::L3Cause::User_Busy: return "User Busy"; // 17 + case GSM::L3Cause::No_User_Responding: return "No User Responding"; // 18 + case GSM::L3Cause::User_Alerting_No_Answer: return "No Answer from User (User alerted)"; + case GSM::L3Cause::Call_Rejected: return "Call Rejected"; // 21 + case GSM::L3Cause::Number_Changed: return "Number Changed"; + case GSM::L3Cause::Preemption: return "Exchange Routing Error"; + case GSM::L3Cause::Non_Selected_User_Clearing: return "Non-selected User Clearing"; + case GSM::L3Cause::Destination_Out_Of_Order: return "Destination Out of Order"; + case GSM::L3Cause::Invalid_Number_Format: return "Invalid Number Format (Address incomplete)"; + case GSM::L3Cause::Facility_Rejected: return "Facility Rejected"; + case GSM::L3Cause::Response_To_STATUS_ENQUIRY: return "Response to STATUS ENQUIRY"; + case GSM::L3Cause::Normal_Unspecified: return "Normal, Unspecified"; + case GSM::L3Cause::No_Channel_Available: return "No Circuit/Channel Available"; + case GSM::L3Cause::Network_Out_Of_Order: return "Network Out of Order"; + case GSM::L3Cause::Temporary_Failure: return "Temporary Failure"; + case GSM::L3Cause::Switching_Equipment_Congestion: return "Switching Equipment Congestion"; + case GSM::L3Cause::Access_Information_Discarded: return "Access Information Discarded"; + case GSM::L3Cause::Requested_Channel_Not_Available: return "Requested Circuit/Channel N/A"; + case GSM::L3Cause::Resources_Unavailable: return "Resource Unavailable, Unspecified"; + case GSM::L3Cause::Quality_Of_Service_Unavailable: return "Quality of Service Not Available"; + case GSM::L3Cause::Requested_Facility_Not_Subscribed: return "Requested Facility Not Subscribed"; + case GSM::L3Cause::Incoming_Calls_Barred_Within_CUG: return "Outgoing Calls Barred Within CUG"; + case GSM::L3Cause::Bearer_Capability_Not_Authorized: return "Incoming Calls Barred Within CUG"; + case GSM::L3Cause::Bearer_Capability_Not_Presently_Available: return "Bearer Capability Not Available"; + case GSM::L3Cause::Service_Or_Option_Not_Available: return "Service or Option N/A, unspecified"; + case GSM::L3Cause::Bearer_Service_Not_Implemented: return "Bearer Capability Not Implemented"; + case GSM::L3Cause::ACM_GE_Max: return "ACM greater or equal to ACM max"; + case GSM::L3Cause::Requested_Facility_Not_Implemented: return "Requested Facility Not Implemented"; + case GSM::L3Cause::Only_Restricted_Digital_Information_Bearer_Capability_Is_Available: return "Only Restricted Digital Bearer Cap supported"; + case GSM::L3Cause::Service_Or_Option_Not_Implemented: return "Service or Option Not Implemented, Unspecified"; + case GSM::L3Cause::Invalid_Transaction_Identifier_Value: return "Invalid Call Reference Value"; + case GSM::L3Cause::User_Not_Member_Of_CUG: return "User Not Member of CUG"; + case GSM::L3Cause::Incompatible_Destination: return "Incompatible Destination"; + case GSM::L3Cause::Invalid_Transit_Network_Selection: return "Invalid Transit Network Selection"; + case GSM::L3Cause::Semantically_Incorrect_Message: return "Invalid Message, Unspecified"; + case GSM::L3Cause::Invalid_Mandatory_Information: return "Mandatory Information Element is Missing"; + case GSM::L3Cause::Message_Type_Not_Implemented: return "Message Type Non-existent / Not Implemented"; + case GSM::L3Cause::Messagetype_Not_Compatible_With_Protocol_State: return "Message Incompatible With Call State or Message Type"; + case GSM::L3Cause::IE_Not_Implemented: return "IE/Parameter Non-existent or Not Implemented"; + case GSM::L3Cause::Conditional_IE_Error: return "Invalid Information Element Contents"; + case GSM::L3Cause::Message_Not_Compatible_With_Protocol_State: return "Message Not Compatible With Call State"; + case GSM::L3Cause::Recovery_On_Timer_Expiry: return "Recovery on Timer Expiry"; + case GSM::L3Cause::Protocol_Error_Unspecified: return "Message With Unrecognized Parameter, Discarded"; + case GSM::L3Cause::Interworking_Unspecified: return "Interworking, Unspecified"; + default: return ""; + } // switch + + return ""; +} // getQ850CallTermText + + +string CallTerminationCause::getSIPCallTermText(int iSIPError) { + // Fill in text as needed + if (iSIPError != 0) + return "SIP ERROR"; + else + return ""; +} + + }; // namespace SIP diff --git a/SIP/SIPParse.h b/SIP/SIPParse.h index e68ad0d..08a60a9 100644 --- a/SIP/SIPParse.h +++ b/SIP/SIPParse.h @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -18,7 +18,10 @@ #define _SIPPARSE_H_ 1 #include #include -//#include +#include +#include +//#include "SIPBase.h" + namespace SIP { using namespace std; @@ -34,6 +37,7 @@ 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); +extern string commaListPopFront(string *cl); // We use this for headers also. struct SipParam { @@ -42,12 +46,14 @@ struct SipParam { SipParam() {} }; struct SipParamList : public list { + SipParamList::iterator paramFindIt(const char *name); string paramFind(const char *name); string paramFind(string name) { return paramFind(name.c_str()); } }; string parseURI(const char *buffer, SipParamList ¶ms, SipParamList &headers); +// RFC2396 describes URI generic syntax. // 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. @@ -55,7 +61,7 @@ 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. + string uriUsername() const { // username without "sip:" or 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())<;tag=mTag string uriUsername() const { return mUri.uriUsername(); } string uriHostAndPort() const { return mUri.uriHostAndPort(); } @@ -140,6 +151,207 @@ struct SdpInfo { string sdpValue(); }; + +/* + * Single entry in the Call termination cause + * SIPCallTermination + */ +class CallTerminationCause { +public: + + enum termGroup { + eNone = 0, + eTermSIP, + eQ850 + }; + + string getQ850CallTermText(int l3Cause); + string getSIPCallTermText(int iSIPError); + + // (pat) The L3Cause::Preemption does not map into a Q850 cause, so this can not be copied directly. + CallTerminationCause(CallTerminationCause::termGroup group, int cause, string desc) { + //LOG(INFO) << "SIP term info CallTerminationCause begin ctor, group: " << group << " cause: "<< cause << " text: " << desc; + + mReasonGroup = group; + miCause = cause; + if (desc == "") { + if (mReasonGroup == eQ850) { + msCauseDesc = getQ850CallTermText(miCause); + } else if (mReasonGroup == eTermSIP) { + msCauseDesc = getSIPCallTermText(miCause); + } + } else { + msCauseDesc = desc; + } + //LOG(INFO) << "SIP term info CallTerminationCause end ctor, group: " << mReasonGroup << " cause: "<< miCause << " text: " << msCauseDesc; + } + + // Copy ctor + CallTerminationCause(const CallTerminationCause& Source) { + mReasonGroup = Source.mReasonGroup; + miCause = Source.miCause; + if (Source.msCauseDesc == "") { + if (mReasonGroup == eQ850) { + msCauseDesc = getQ850CallTermText(miCause); + } else if (mReasonGroup == eTermSIP) { + msCauseDesc = getSIPCallTermText(miCause); + } + } else { + msCauseDesc = Source.msCauseDesc; + } + + //LOG(INFO) << "SIP term info CallTerminationCause copy ctor, group: " << mReasonGroup << " cause: "<< miCause << " text: " << msCauseDesc; + //LOG(INFO) << "SIP term info text in copy ctor from getTextforEntry: " << this->getTextforEntry(); + } + + CallTerminationCause() { + //LOG(INFO) << "SIP term info CallTerminationCause no param ctor"; + mReasonGroup = eNone; + miCause = 0; + msCauseDesc = "Default"; + } + + string getTermGroupText() { + if (mReasonGroup == eTermSIP) + return "SIP"; + else if (mReasonGroup == eQ850) + return "Q.850"; + else { + string sTemp = format("Unknown reason group: %d", mReasonGroup); + return sTemp; + } + } + + CallTerminationCause::termGroup getTermGroup() { return mReasonGroup; } + + int getTermCause() { return miCause; } + + string getCauseDescription() { return msCauseDesc; } + + // Return text for one entry should look like the examples below + // Reason: SIP ;cause=580 ;text="Precondition Failure" + // Reason: Q.850 ;cause=16 ;text="Terminated" + string getTextforEntry() { + string sTemp; + //sTemp = "Reason: " + getTermGroupText() + "; cause=" + to_string(icause) + "; text=\"" + getcauseDescription() + "\"\r\n"; //SVGDBG could not get this to compile + sTemp = format("Reason:%s; cause=%d; text=\"%s\"\r\n", this->getTermGroupText().c_str(), miCause, msCauseDesc.c_str()); + return sTemp; + } + +private: + + /* + SIP: The cause parameter contains a SIP status code. See section 21 in RFC 3261 Q.850: The cause parameter contains an + ITU-T Q.850 cause value in decimal representation. */ + + termGroup mReasonGroup; // SIP or Q.850 reason + int miCause; // Numerical code + string msCauseDesc; +}; // CallTerminationCause + + +/* (pat 7-2014) This is no longer being used. +List of termination reasons. It is valid to 0 to N +SipTermList +Example: + SipBase::addCallTerminationReasonDlg(CallTerminationCause(CallTerminationCause::eTermSIP/eQ850, 100, "This is an error")); + SipMessage::addCallTerminationReasonSM(CallTerminationCause(CallTerminationCause::eTermSIP/eQ850, 100, "This is an error")); + CallTerminationCause sc(CallTerminationCause::eQ850, (int) GSM::L3Cause::NormalCallClearing, ""); + addCallTerminationReasonSM(sc); +*/ + + +class SipTermList { +public: + SipTermList() { + //LOG(INFO) << "SIP term info SipTermList ctor addr: " << (void*) this; + } + + // Copy ctor since SIP messages get copied + SipTermList(const SipTermList& Source) : lTermList(){ + LOG(INFO) << "SIP term info SipTermList copy ctor"; + for (vector::const_iterator it = Source.lTermList.begin(); it != Source.lTermList.end(); it++) { + lTermList.push_back(new CallTerminationCause( *(*it)) ); + } // for + } + + + void add(CallTerminationCause* pEntry) { + if (((int) pEntry->getTermGroup() != 0) && (pEntry->getTermCause() != 0)) { + //LOG(INFO) << "SIP term info adding entry to call termination list: " << pEntry->getTextforEntry(); // SVGDBG + //if (lTermList.size() > 1) { + // Added more that one entry log this so we can make sure this is correct + // LOG(INFO) << "SIP term info adding entry to call termination reason, table size: " << lTermList.size(); // SVGDBG + //} + lTermList.push_back(pEntry); + //LOG(INFO) << "SIP term info adding entry to call termination reason, table size: " << lTermList.size(); // SVGDBG + } else { + LOG(INFO) << "SIP term info tried to add termination reason with invalid data, term group: " \ + << pEntry->getTermGroup() << " term cause: " << pEntry->getTermCause() << " desc: " << pEntry->getCauseDescription(); + } + logList(); // SVGDBG + } + + + void add(CallTerminationCause::termGroup group, int cause, string desc) { + CallTerminationCause* pCause = new CallTerminationCause(group, cause, desc); + add(pCause); + } + + void logList() { + LOG(INFO) << "SIP term info List terminate table addr: " << (void*) this; + for (vector::iterator it = lTermList.begin(); it != lTermList.end(); it++) { + LOG(INFO) << "SIP term info list entry: " << (*it)->getTextforEntry(); // SVGDBG + } // for + } + + string getTextForAllMsgs() { + string sTemp; + for (vector::iterator it = lTermList.begin(); it != lTermList.end(); it++) { + sTemp = sTemp + (*it)->getTextforEntry(); + } // for + return sTemp; + } + + // Clear list delete all entries and empty list + void clearList() { + LOG(INFO) << "SIP term info clear list"; + for (vector::iterator it = lTermList.begin(); it != lTermList.end(); it++) { + //LOG(INFO) << "SIP term info clearList remove entry: " << (*it)->getTextforEntry(); // SVGDBG + delete (*it); + } + lTermList.clear(); + } + + // Used to copy from SIPDialog to SIPMessage + // Remove entries from source list so they don't get copied again + void copyEntireList(SipTermList &lDestTermList) { + LOG(INFO) << "SIP term info copyEntireList source size: " << lTermList.size(); + for (vector::iterator it = lTermList.begin(); it != lTermList.end(); it++) { + lDestTermList.add(new CallTerminationCause(*(*it))); + } // for + + clearList(); // Remove all entries from list + } + + ~SipTermList() { + //LOG(INFO) << "SIP term info SipTermList dtor address: " << (void*) this; // SVGDBG + logList(); + + for (vector::iterator it = lTermList.begin(); it != lTermList.end(); it++) { + //LOG(INFO) << "SIP term info SipTermList dtor remove entry: " << (*it)->getTextforEntry(); // SVGDBG + delete (*it); + } + } + + int size() { return lTermList.size(); } + + +//private: // SVGDBG + vector lTermList; +}; + + extern SipMessage *sipParseBuffer(const char *buffer); extern void parseAuthenticate(string stuff, SipParamList ¶ms); extern void parseToParams(string stuff, SipParamList ¶ms); diff --git a/SIP/SIPRtp.cpp b/SIP/SIPRtp.cpp new file mode 100644 index 0000000..ed8ed40 --- /dev/null +++ b/SIP/SIPRtp.cpp @@ -0,0 +1,438 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging +#include +#include +#include "SIPRtp.h" +#include "SIPBase.h" +#include "SIP2Interface.h" + +#include +#undef WARNING // The nimrods defined this to "warning" +#undef CR // This too + +// Type is RtpCallback, but args determined by rtp_signal_table_emit2 +extern "C" { + void ourRtpTimestampJumpCallback(RtpSession *session, unsigned long timestamp,unsigned long dialogid) + { + SIP::SipDialogRef dialog = SIP::gSipInterface.dmFindDialogByRtp(session); + if (dialog.self()) { + 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(AudioFrame* frame, unsigned numFlushed) +{ + if(vgetSipState()!=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." < +#include +#include +#include "SIPBase.h" + +#include +#undef WARNING // The nimrods defined this to "warning" +#undef CR // This too + +extern int gCountRtpSessions; +extern int gCountRtpSockets; + +namespace SIP { + +typedef ByteVector AudioFrame; + +class SipRtp { + Mutex mRtpLock; + public: + /**@name RTP state and parameters. */ + //@{ + unsigned mRTPPort; + Control::CodecSet 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 + uint64_t mRxRealTime; // In msecs. + uint64_t mTxRealTime; // In msecs. + //@} + + /**@name RFC-2833 DTMF state. */ + //@{ + // (pat) Dont change mDTMF to char. The unbelievably stupid <dmRand = rand; dmsg->dmRejectCause = atoi(gsmRejectCode.c_str()); - NewTransactionTable_ttAddMessage(mstTranId,dmsg); + devassert(mstTranId); + // The tran id is 0 for unregister messages. If one of those gets this far it is a bug. + if (mstTranId) { SipCallbacks::ttAddMessage(mstTranId,dmsg); } } void SipTransaction::sendAuthOKMessage(SipMessage *sipmsg) { + devassert(mstTranId); + // The tran id is 0 for unregister messages. If one of those gets this far it is a bug. + if (! mstTranId) { return; } + string imsi = sipmsg->msmTo.uriUsername(); // The To: and From: have the same value for a REGISTER message. // Validate the imsi: if (imsi.empty()) { @@ -155,13 +164,14 @@ void SipTransaction::sendAuthOKMessage(SipMessage *sipmsg) LOG(NOTICE) << "No Kc in SIP REGISTER response:"<msmContent; } - NewTransactionTable_ttAddMessage(mstTranId,dmsg); + //NewTransactionTable_ttAddMessage(mstTranId,dmsg); + SipCallbacks::ttAddMessage(mstTranId,dmsg); } // The cause is not currently used. void SipTransaction::stSetDialogState(SipState newState, int code, char timer) const { - if (SipDialog *dialog = mstDialog) { + if (SipDialog *dialog = mstDialog.self()) { dialog->dialogPushState(newState,code,timer); } } @@ -277,6 +287,8 @@ bool SipClientTrLayer::TLWriteHighSideV(SipMessage *sipmsg) } // This only gets called once. +// Looks like it never gets called SVG +// Outbound void SipClientTrLayer::TLWriteLowSideV(SipMessage *request) { LOG(DEBUG); @@ -289,9 +301,10 @@ void SipClientTrLayer::TLWriteLowSideV(SipMessage *request) mstState = stCallingOrTrying; } -void SipClientTrLayer::sctInitRegisterClientTransaction(SipDialog *wDialog, SipMessage *request, string branch) +void SipClientTrLayer::sctInitRegisterClientTransaction(SipDialog *wRegistrar, TranEntryId tid, SipMessage *request, string branch) { - stInitInDialogTransaction(wDialog, branch, request); // Do this first. + //stInitInDialogTransaction(wDialog, branch, request); // Do this first. + stInitNonDialogTransaction(tid, branch,request,wRegistrar->dsPeer()); mstOutRequest = *request; } @@ -518,94 +531,70 @@ bool SipClientTrLayer::TLPeriodicServiceV() // =========================================================================================== -//static void stashAuthInfo(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"; -// 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) { +// Note: The registrar may return messages, which we must ignore for the Unregister case since there is no transaction to receive them. +void SipRegisterTU::TUWriteHighSideV(SipMessage *sipmsg) +{ int code = sipmsg->msmCode; static const char *pRejectCauseHeader = "P-GSM-Reject-Cause"; + static const char *whatami = stKind == KindRegister ? "SIP Register " : "SIP UnRegister "; LOG(DEBUG) <smGetRand401(),sipmsg->msmHeaders.paramFind(pRejectCauseHeader)); + LOG(INFO) <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); + if (stKind == KindRegister) { + 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)); + if (stKind == KindRegister) { + sendAuthFailMessage(code,"",sipmsg->msmHeaders.paramFind(pRejectCauseHeader)); + } return; } } -SipRegisterTU::SipRegisterTU(SipDialog *registrar, SipMessage *request) +SipRegisterTU::SipRegisterTU(SipRegisterTU::Kind wKind, SipDialog *registrar, TranEntryId tid, 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()); + stKind = wKind; + sctInitRegisterClientTransaction(registrar, tid, request, request->smGetBranch()); } -void startRegister(const FullMobileId &msid, const string rand, const string sres, L3LogicalChannel *chan) // msid is imsi and/or tmsi +void startRegister(TranEntryId tid, 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); + SipRegisterTU *reg = new SipRegisterTU(SipRegisterTU::KindRegister,registrar,tid,request); + delete request; // sctInitRegisterTransaction made a copy. Kind of wasteful. + reg->sctStart(); +} + +void startUnregister(const FullMobileId &msid, L3LogicalChannel *chan) +{ + LOG(DEBUG) <makeRegisterMsg(SIPDTUnregister,chan,"",msid,NULL); + SipRegisterTU *reg = new SipRegisterTU(SipRegisterTU::KindUnRegister,registrar,(TranEntryId)0,request); delete request; // sctInitRegisterTransaction made a copy. Kind of wasteful. reg->sctStart(); } @@ -614,16 +603,17 @@ void startRegister(const FullMobileId &msid, const string rand, const string sre // =========================================================================================== void SipMOByeTU::TUWriteHighSideV(SipMessage *sipmsg) { - LOG(DEBUG); + LOG(INFO) << "SIP term info msmCode: " << sipmsg->msmCode; 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) +SipMOByeTU::SipMOByeTU(SipDialog *wDialog, string wReasonHeader) // : SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog) { - LOG(DEBUG); + LOG(INFO) << "SIP term info SipMOByeTU"; // SVGDBG string branch = make_branch(); SipMessage *bye = new SipMessageRequestWithinDialog(stGetMethodNameV(),wDialog,branch); + bye->msmReasonHeader = wReasonHeader; sctInitInDialogClientTransaction(wDialog, bye, branch); delete bye; } @@ -634,11 +624,14 @@ void SipMOCancelTU::TUWriteHighSideV(SipMessage *sipmsg) { //void SipMOCancelTU::TUTimeoutV() { stSetDialogState(Canceled,0); } -SipMOCancelTU::SipMOCancelTU(SipDialog *wDialog) // : SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog) -{ +SipMOCancelTU::SipMOCancelTU(SipDialog *wDialog,string wReasonHeader) { // : SipClientTrLayer(wDialog->dsPeer(), make_branch(),wDialog + LOG(INFO) << "SIP term info SipMOCancelTU"; // Mobile originate + string branch = make_branch(); SipMessage *cancelMsg = new SipMessageRequestWithinDialog(this->stGetMethodNameV(),wDialog,branch); - this->sctInitInDialogClientTransaction(wDialog, cancelMsg, branch); + cancelMsg->msmReasonHeader = wReasonHeader; + //wDialog->getTermList().copyEntireList(cancelMsg->getTermList()); // SVGDBG SipMOCancelTU + this->sctInitInDialogClientTransaction(wDialog, cancelMsg, branch); // Message gets copied in here delete cancelMsg; } diff --git a/SIP/SIPTransaction.h b/SIP/SIPTransaction.h index e3f9d06..41a1ea2 100644 --- a/SIP/SIPTransaction.h +++ b/SIP/SIPTransaction.h @@ -3,7 +3,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -19,7 +19,6 @@ #include "SIPUtility.h" // For SipTimer, IPAddressSpec #include "SIPBase.h" -#include "ControlTransfer.h" namespace SIP { using namespace std; @@ -62,7 +61,7 @@ class SipTransaction : public MemCheckSipTransaction, public SipTimers 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. + SipDialogRef 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: @@ -163,7 +162,6 @@ ostream& operator<<(ostream& os, const SipTransaction&st); // (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. @@ -177,6 +175,7 @@ class SipClientTrLayer : public SipTransaction void stDestroyV() { mstState = stTerminated; } public: + SipMessage mstOutRequest; // outbound request, eg INVITE, MESSAGE, REGISTER. // unused bool stIsTerminated() const { return mstState == stTerminated; } void setTransactionState(States st) { mstState = st; } bool stMatchesMessageV(SipMessage *msg); @@ -184,7 +183,7 @@ class SipClientTrLayer : public SipTransaction 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 sctInitRegisterClientTransaction(SipDialog *wRegistrar, TranEntryId tid, SipMessage *request, string branch); void sctInitInDialogClientTransaction(SipDialog *wDialog, SipMessage *request, string branch); void sctStart(); }; @@ -200,10 +199,11 @@ class SipInviteClientTrLayer : public SipClientTrLayer // and has only one reply, but we need to know when to destroy it. struct SipRegisterTU : public SipClientTrLayer { + enum Kind { KindRegister=1, KindUnRegister=2 } stKind; 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); + SipRegisterTU(Kind kind, SipDialog *registrar, TranEntryId tid, SipMessage *request); }; @@ -213,7 +213,7 @@ struct SipMOByeTU: public SipClientTrLayer void TUWriteHighSideV(SipMessage *sipmsg); // TUTimeoutV not needed; on timeout we set dialog state to SSFail. //void TUTimeoutV(); - SipMOByeTU(SipDialog *wDialog); + SipMOByeTU(SipDialog *wDialog, string wReasonHeader); }; struct SipMOCancelTU: public SipClientTrLayer @@ -222,7 +222,7 @@ struct SipMOCancelTU: public SipClientTrLayer void TUWriteHighSideV(SipMessage *sipmsg); // TUTimeoutV not needed; on timeout we set dialog state to SSFail. //void TUTimeoutV(); - SipMOCancelTU(SipDialog *wDialog); + SipMOCancelTU(SipDialog *wDialog, string wReasonHeader); }; diff --git a/SIP/SIPUtility.cpp b/SIP/SIPUtility.cpp index b228a48..0724d4b 100644 --- a/SIP/SIPUtility.cpp +++ b/SIP/SIPUtility.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -24,11 +25,10 @@ #include #include -//#include "SIPInterface.h" #include "SIPUtility.h" -#include -#include -#include +#include +#include +// #include // (pat 3-2014) Take this out temporarily to avoid referencing gBTS from the SIP directory. //#include "md5.h" @@ -37,6 +37,14 @@ namespace SIP { using namespace std; //using namespace MD5; +string localIP() { // Replaces mSIPIP. + return gConfig.getStr("SIP.Local.IP"); +} + +string localIPAndPort() { // Replaces mSIPIP and mSIPPort. + return format("%s:%u", localIP(), (unsigned) gConfig.getNum("SIP.Local.Port")); +} + // Unused, but here it is if you want it: // Pair is goofed up, so just make our own. It is trivial: // @@ -256,6 +264,15 @@ static uint32_t timeUniquifier() return ((now.tv_sec&0xffff)<<16) + (now.tv_usec/16); // 32 bit number that changes every 15-16 usecs. } +#if 1 +string globallyUniqueId(const char *start) +{ + // This is a a globally unique callid. + char buf[80]; + snprintf(buf,80,"%s%x%x",start,timeUniquifier(),(unsigned)(0xffffffff&random())); + return string(buf); +} +#else // (pat 3-2014) Take this out temporarily to avoid referencing gBTS from the SIP directory. static unsigned cellid() { // Cell id: @@ -280,6 +297,7 @@ string globallyUniqueId(const char *start) snprintf(buf,80,"%s%03u%02u%x%x-%x%x",start,lai.MCC(),lai.MNC(),lai.LAC(),cellid(), timeUniquifier(),(unsigned)(0xffffffff&random())); return string(buf); } +#endif static string make_tag1(const char *start) @@ -348,7 +366,7 @@ string makeMD5(string input) FILE *f = popen(os.str().c_str(), "r"); if (f == NULL) { LOG(CRIT) << "error: popen failed"; - return false; + return ""; } char digest[33]; char *buffer = fgets(digest, 33, f); @@ -375,6 +393,23 @@ string makeResponse(string username, string realm, string password, string metho return str3; } +// We will accept prefix or "sips:", which we dont support yet, but ever hopeful. +const char * sipSkipPrefix1(const char* in) +{ + if (0 == strncasecmp(in,"sip:",4)) { return in + 4; } + else if (0 == strncasecmp(in,"tel:",4)) { return in + 4; } + else if (0 == strncasecmp(in,"sips:",5)) { return in + 5; } + return in; +} + +string sipSkipPrefix(string in) +{ + if (0 == strncasecmp(in.c_str(),"sip:",4)) { return in.substr(4); } + else if (0 == strncasecmp(in.c_str(),"tel:",4)) { return in.substr(4); } + else if (0 == strncasecmp(in.c_str(),"sips:",5)) { return in.substr(5); } + return in; +} + }; // namespace SIP // vim: ts=4 sw=4 diff --git a/SIP/SIPUtility.h b/SIP/SIPUtility.h index 677590c..56a5de8 100644 --- a/SIP/SIPUtility.h +++ b/SIP/SIPUtility.h @@ -1,7 +1,8 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -82,7 +83,13 @@ class ThreadSafeList { namespace SIP { using namespace std; +// These could be global. +extern string localIP(); // Replaces mSIPIP. +extern string localIPAndPort(); // Replaces mSIPIP and mSIPPort. + struct IPAddressSpec { + enum ConnectionType { None, TCP, UDP }; + ConnectionType mipType; 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; @@ -90,9 +97,10 @@ struct IPAddressSpec { struct ::sockaddr_in mipSockAddr; ///< the ready-to-use UDP address bool ipSet(string addrSpec, const char *provenance); string ipToText() const; + string ipAddrStr() const { return format("%s:%d",mipIP,mipPort); } string ipTransportName() const { return "UDP"; } bool ipIsReliableTransport() const { return false; } // Right now we only support UDP - IPAddressSpec() : mipPort(0) {} + IPAddressSpec() : mipType(None), mipPort(0) {} }; // Ticket #1158 @@ -188,6 +196,8 @@ extern string dequote(const string); extern string makeMD5(string input); extern string makeResponse(string username, string realm, string password, string method, string uri, string nonce); +extern const char* sipSkipPrefix1(const char* in); +extern string sipSkipPrefix(string in); }; diff --git a/SMS/Makefile.am b/SMS/Makefile.am index 8d97290..7291f09 100644 --- a/SMS/Makefile.am +++ b/SMS/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/SMS/SMSMessages.cpp b/SMS/SMSMessages.cpp index 95f4afd..4bd17ee 100644 --- a/SMS/SMSMessages.cpp +++ b/SMS/SMSMessages.cpp @@ -1,22 +1,25 @@ /* * Copyright 2008, 2009, 2010, 2014 Free Software Foundation, Inc. +* Copyright 2014 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. */ +#define LOG_GROUP LogGroup::SMS #include #include #include #include "SMSMessages.h" +#include "Timeval.h" #include using namespace std; @@ -84,8 +87,10 @@ RPData *SMS::hex2rpdata(const char *hexstring) { RPData *rp_data = NULL; + //LOG(DEBUG) << "SMS RPDU string len: " << strlen(hexstring); BitVector2 RPDUbits(strlen(hexstring)*4); - if (!RPDUbits.unhex(hexstring)) { + if ((strlen(hexstring) == 0) || !RPDUbits.unhex(hexstring)) { + LOG(DEBUG) << "SMS RPDU string is empty"; return NULL; } LOG(DEBUG) << "SMS RPDU bits: " << RPDUbits; @@ -491,10 +496,12 @@ void TLValidityPeriod::write(TLFrame& dest, size_t& wp) const void TLValidityPeriod::text(ostream& os) const { - char str[27]; + //char str[27]; time_t seconds = mExpiration.sec(); - ctime_r(&seconds,str); - str[24]='\0'; + std::string str; + Timeval::isoTime(seconds, str, true); + //ctime_r(&seconds,str); + //str[24]='\0'; os << "expiration=(" << str << ")"; } diff --git a/SMS/SMSMessages.h b/SMS/SMSMessages.h index 12b088d..6571a56 100644 --- a/SMS/SMSMessages.h +++ b/SMS/SMSMessages.h @@ -1,7 +1,8 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/SMS/SMSTransfer.cpp b/SMS/SMSTransfer.cpp index 4da187a..ed2e80c 100644 --- a/SMS/SMSTransfer.cpp +++ b/SMS/SMSTransfer.cpp @@ -1,18 +1,20 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 2014 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. */ +#define LOG_GROUP LogGroup::SMS #include #include "SMSTransfer.h" diff --git a/SMS/SMSTransfer.h b/SMS/SMSTransfer.h index 2fc14fc..cd6c0c8 100644 --- a/SMS/SMSTransfer.h +++ b/SMS/SMSTransfer.h @@ -1,11 +1,12 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 2014 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -73,14 +74,14 @@ class RLFrame : public GSM::L3Frame #if ORIGINAL RLFrame(SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE, size_t len=0) - :L3Frame(GSM::DATA,len), mPrimitive(wPrimitive) + :L3Frame(GSM::L3_DATA,len), mPrimitive(wPrimitive) { } 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(size_t bitsNeeded=0) :L3Frame(GSM::L3_DATA,bitsNeeded) { /*RLFrameInit();*/ } RLFrame(const BitVector2& source) :L3Frame(GSM::SAPIUndefined,source) { /*RLFrameInit();*/ } void text(std::ostream& os) const; @@ -104,14 +105,14 @@ class TLFrame : public GSM::L3Frame #if ORIGINAL TLFrame(SMSPrimitive wPrimitive=SMS_UNDEFINED_PRIMITIVE, size_t len=0) - :L3Frame(GSM::DATA,len), mPrimitive(wPrimitive) + :L3Frame(GSM::L3_DATA,len), mPrimitive(wPrimitive) { } 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(size_t bitsNeeded=0) :L3Frame(GSM::L3_DATA,bitsNeeded) { /*TLFrameInit();*/ } TLFrame(const BitVector2& source) :L3Frame(GSM::SAPIUndefined,source) { /*TLFrameInit();*/ } #if UNUSED_PRIMITIVE diff --git a/Scanning/Makefile.am b/Scanning/Makefile.am index 70f7318..c2eda92 100644 --- a/Scanning/Makefile.am +++ b/Scanning/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/Scanning/Scanning.cpp b/Scanning/Scanning.cpp index 5c1ba00..1e8ef22 100644 --- a/Scanning/Scanning.cpp +++ b/Scanning/Scanning.cpp @@ -1,8 +1,18 @@ /* -* 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 +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ #include "Scanning.h" #include diff --git a/Scanning/Scanning.h b/Scanning/Scanning.h index 05db4df..f2181d5 100644 --- a/Scanning/Scanning.h +++ b/Scanning/Scanning.h @@ -1,9 +1,19 @@ /**@file Classes for managing BCCH channel scanning and related database tables. */ /* -* 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 +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ #ifndef SCANNING_H #define SCANNING_H diff --git a/TRXManager/Makefile.am b/TRXManager/Makefile.am index b7d741f..8503d6a 100644 --- a/TRXManager/Makefile.am +++ b/TRXManager/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/TRXManager/TRXManager.cpp b/TRXManager/TRXManager.cpp index 40e5010..f99a4e7 100644 --- a/TRXManager/TRXManager.cpp +++ b/TRXManager/TRXManager.cpp @@ -2,7 +2,7 @@ * Copyright 2008, 2010 Free Software Foundation, 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. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -77,7 +77,7 @@ void* ClockLoopAdapter(TransceiverManager *transceiver) // This loop has a period of about 3 seconds. gResetWatchdog(); - while (1) { + while (! gBTS.btsShutdown()) { transceiver->clockHandler(); LOG(DEBUG) << "watchdog timer expires in " << gWatchdogRemaining() << " seconds"; if (gWatchdogExpired()) { @@ -94,7 +94,13 @@ void* ClockLoopAdapter(TransceiverManager *transceiver) void TransceiverManager::clockHandler() { char buffer[MAX_UDP_LENGTH]; - int msgLen = mClockSocket.read(buffer,gConfig.getNum("TRX.Timeout.Clock")*1000); + int msgLen; + try { + msgLen = mClockSocket.read(buffer,gConfig.getNum("TRX.Timeout.Clock")*1000); + } catch (SocketError) { + LOG(ERR) <<"Transceiver Clock Interface read error:"< templates, these inline most operations for speed /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/DummyLoad.cpp b/TransceiverRAD1/DummyLoad.cpp index 51e5440..4cafb48 100644 --- a/TransceiverRAD1/DummyLoad.cpp +++ b/TransceiverRAD1/DummyLoad.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, 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/DummyLoad.h b/TransceiverRAD1/DummyLoad.h index 74a962d..99012d2 100644 --- a/TransceiverRAD1/DummyLoad.h +++ b/TransceiverRAD1/DummyLoad.h @@ -1,5 +1,6 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, 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/FactoryCalibration.cpp b/TransceiverRAD1/FactoryCalibration.cpp index bf1f268..3086b42 100644 --- a/TransceiverRAD1/FactoryCalibration.cpp +++ b/TransceiverRAD1/FactoryCalibration.cpp @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/FactoryCalibration.h b/TransceiverRAD1/FactoryCalibration.h index e600bc7..97ef041 100644 --- a/TransceiverRAD1/FactoryCalibration.h +++ b/TransceiverRAD1/FactoryCalibration.h @@ -1,9 +1,9 @@ /* -* Copyright 2013 Range Networks, Inc. +* 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. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/Makefile.am b/TransceiverRAD1/Makefile.am index 046e835..903edf1 100644 --- a/TransceiverRAD1/Makefile.am +++ b/TransceiverRAD1/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/PowerScanner.cpp b/TransceiverRAD1/PowerScanner.cpp index 7afc628..04ebb54 100644 --- a/TransceiverRAD1/PowerScanner.cpp +++ b/TransceiverRAD1/PowerScanner.cpp @@ -2,7 +2,7 @@ * Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2012, 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/RAD1Cmd.cpp b/TransceiverRAD1/RAD1Cmd.cpp index 827e41b..02d25a0 100644 --- a/TransceiverRAD1/RAD1Cmd.cpp +++ b/TransceiverRAD1/RAD1Cmd.cpp @@ -2,7 +2,8 @@ /* * USRP - Universal Software Radio Peripheral * - * Copyright (C) 2003,2004,2009 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2009 Free Software Foundation, Inc. + * Copyright 2014 Range Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/TransceiverRAD1/RAD1Device.cpp b/TransceiverRAD1/RAD1Device.cpp index 9733a21..83ba5fb 100644 --- a/TransceiverRAD1/RAD1Device.cpp +++ b/TransceiverRAD1/RAD1Device.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. @@ -387,6 +388,7 @@ double RAD1Device::setRxGain(double dB) { // NOTE: Assumes sequential reads +// (pat) The RSSI argument appears unused by anyone. int RAD1Device::readSamples(short *buf, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun, @@ -406,11 +408,13 @@ int RAD1Device::readSamples(short *buf, int len, bool *overrun, uint32_t readBuf[20000]; + // (pat) Step 1 is to read from USB into *data, which is a 2M*sizeof(short) buffer. + // The packet timestamp is used as an index into the data buffer so packets are arranged in order of increasing timestamp. while (1) { //guestimate USB read size int readLen=0; { - int numSamplesNeeded = timestamp + len - timeEnd; + int numSamplesNeeded = (int) (timestamp + (TIMESTAMP) len - timeEnd); if (numSamplesNeeded <=0) break; readLen = 512 * ((int) ceil((float) numSamplesNeeded/126.0)); if (readLen > 8000) readLen= (8000/512)*512; @@ -427,7 +431,8 @@ int RAD1Device::readSamples(short *buf, int len, bool *overrun, unsigned payloadSz = word0 & 0x1ff; LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp; - bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp); + // (pat) FIXME I dont think this incremeintHi32 logic works... SVGDBG look at this + bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffllu) > pktTimestamp); if (incrementHi32 && (timeStart!=0)) { LOG(DEBUG) << "high 32 increment!!!"; hi32Timestamp++; @@ -436,24 +441,29 @@ int RAD1Device::readSamples(short *buf, int len, bool *overrun, lastPktTimestamp = pktTimestamp; if (chan == 0x01f) { - // control reply, check to see if its ping reply - uint32_t word2 = usrp_to_host_u32(tmpBuf[2]); - if ((word2 >> 16) == ((0x01 << 8) | 0x02)) { - timestamp -= timestampOffset; - timestampOffset = pktTimestamp - pingTimestamp + PINGOFFSET; - LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset; - timestamp += timestampOffset; - isAligned = true; - } - continue; + // control reply, check to see if its ping reply + uint32_t word2 = usrp_to_host_u32(tmpBuf[2]); + if ((word2 >> 16) == ((0x01 << 8) | 0x02)) { + timestamp -= timestampOffset; + TIMESTAMP newTimestampOffset = pktTimestamp - pingTimestamp + PINGOFFSET; + if ((timestampOffset==0) || fabs((float) newTimestampOffset-(float) timestampOffset)/(float) timestampOffset < 0.1) + timestampOffset = newTimestampOffset; + else { // new offset is more than 10% from old one, then its bogus, so ignore it and keep going + LOG(ERR) << "Ignoring bad update of timestamp offset: " << newTimestampOffset << ", keeping offset at " << timestampOffset; + } + LOG(NOTICE) << "updating timestamp offset to: " << timestampOffset; + timestamp += timestampOffset; + isAligned = true; + } + continue; } if (chan != 0) { - LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz; - continue; + LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz; + continue; } if ((word0 >> 28) & 0x04) { - if (underrun) *underrun = true; - LOG(DEBUG) << "UNDERRUN in TRX->USRP interface"; + if (underrun) *underrun = true; + LOG(DEBUG) << "UNDERRUN in TRX->USRP interface"; } if (RSSI) *RSSI = (word0 >> 21) & 0x3f; @@ -461,18 +471,20 @@ int RAD1Device::readSamples(short *buf, int len, bool *overrun, unsigned cursorStart = pktTimestamp - timeStart + dataStart; while (cursorStart*2 > currDataSize) { - cursorStart -= currDataSize/2; + cursorStart -= currDataSize/2; } if (cursorStart*2 + payloadSz/2 > currDataSize) { - // need to circle around buffer - memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short)); - memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short)); + // need to circle around buffer + // (pat) This is trickey. For a cicular copy using memcpy(a,b,c); memcpy(d,e,f); it is required that e==b+c, + // but it does because tmpBuf is (uint32_t*) + memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short)); + memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short)); } else { - memcpy(data+cursorStart*2,tmpBuf+2,payloadSz); + memcpy(data+cursorStart*2,tmpBuf+2,payloadSz); } if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd) - timeEnd = pktTimestamp+payloadSz/2/sizeof(short); + timeEnd = pktTimestamp+payloadSz/2/sizeof(short); //LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp; diff --git a/TransceiverRAD1/RAD1Device.h b/TransceiverRAD1/RAD1Device.h index 52fe15c..9f1e902 100644 --- a/TransceiverRAD1/RAD1Device.h +++ b/TransceiverRAD1/RAD1Device.h @@ -1,5 +1,6 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. @@ -62,10 +63,10 @@ private: bool skipRx; ///< set if USRP is transmit-only. static const unsigned int currDataSize_log2 = 21; - static const unsigned long currDataSize = (1 << currDataSize_log2); + static const unsigned int currDataSize = (1 << currDataSize_log2); short *data; - unsigned long dataStart; - unsigned long dataEnd; + unsigned int dataStart; + unsigned int dataEnd; TIMESTAMP timeStart; TIMESTAMP timeEnd; bool isAligned; @@ -80,8 +81,8 @@ private: TIMESTAMP latestWriteTimestamp; ///< timestamp of most recent ping command TIMESTAMP pingTimestamp; ///< timestamp of most recent ping response static const TIMESTAMP PINGOFFSET = 272; ///< undetermined delay b/w ping response timestamp and true receive timestamp - unsigned long hi32Timestamp; - unsigned long lastPktTimestamp; + unsigned int hi32Timestamp; + unsigned int lastPktTimestamp; double rxGain; @@ -216,10 +217,10 @@ private: bool setRxFreq(double wFreq, double wAdjFreq = 0); /** Returns the starting write Timestamp*/ - TIMESTAMP initialWriteTimestamp(void) { return 40000;} + TIMESTAMP initialWriteTimestamp(void) { return 90000;} /** Returns the starting read Timestamp*/ - TIMESTAMP initialReadTimestamp(void) { return 40000;} + TIMESTAMP initialReadTimestamp(void) { return 90000;} /** returns the full-scale transmit amplitude **/ double fullScaleInputValue() {return 32000.0;} diff --git a/TransceiverRAD1/RAD1RxRawPower.cpp b/TransceiverRAD1/RAD1RxRawPower.cpp index 5fcdc4a..233a1bd 100644 --- a/TransceiverRAD1/RAD1RxRawPower.cpp +++ b/TransceiverRAD1/RAD1RxRawPower.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. @@ -73,6 +74,12 @@ int main(int argc, char *argv[]) { } printf("Moving average = %d\n",movingAverage); + int numOfSamples = -1; + if (argc > 5) { + numOfSamples = atoi(argv[5]); + } + int currSampleNum = 0; + bool underrun; usrp->updateAlignment(20000); @@ -103,10 +110,19 @@ int main(int argc, char *argv[]) { sum = 0; num = 0; } + + if (numOfSamples > 0) { + if ((currSampleNum + 1) < numOfSamples) { + currSampleNum++; + } else { + return 0; + } + } } } timestamp += rd; } } + return 0; } diff --git a/TransceiverRAD1/RAD1RxRawPowerSweep.cpp b/TransceiverRAD1/RAD1RxRawPowerSweep.cpp index e7ad89c..afad701 100644 --- a/TransceiverRAD1/RAD1RxRawPowerSweep.cpp +++ b/TransceiverRAD1/RAD1RxRawPowerSweep.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, 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/RAD1SN.cpp b/TransceiverRAD1/RAD1SN.cpp index f104540..42890e7 100644 --- a/TransceiverRAD1/RAD1SN.cpp +++ b/TransceiverRAD1/RAD1SN.cpp @@ -2,7 +2,8 @@ /* * USRP - Universal Software Radio Peripheral * - * Copyright (C) 2003,2004,2009 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2009 Free Software Foundation, Inc. + * Copyright 2014 Range Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/TransceiverRAD1/RAD1ping.cpp b/TransceiverRAD1/RAD1ping.cpp index 736ead2..12547a3 100644 --- a/TransceiverRAD1/RAD1ping.cpp +++ b/TransceiverRAD1/RAD1ping.cpp @@ -1,5 +1,6 @@ /* * Copyright 2008, 2009 Free Software Foundation, Inc. +* Copyright 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Public License. * See the COPYING file in the main directory for details. @@ -41,7 +42,9 @@ int main(int argc, char *argv[]) { else gLogInit("DEBUG"); int deviceID = 0; - if (argc>2) deviceID = atoi(argv[2]); + if (argc>2) { + deviceID = atoi(argv[2]); + } gLogInit("openbts",argv[1],LOG_LOCAL7); diff --git a/TransceiverRAD1/Transceiver.cpp b/TransceiverRAD1/Transceiver.cpp index 5da2e74..98d65b0 100644 --- a/TransceiverRAD1/Transceiver.cpp +++ b/TransceiverRAD1/Transceiver.cpp @@ -1,8 +1,8 @@ /* * Copyright 2008, 2009 Free Software Foundation, 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -24,6 +24,8 @@ #include #include #include +#include +using namespace GSM; extern ConfigurationTable gConfig; extern FactoryCalibration gFactoryCalibration; @@ -716,6 +718,7 @@ void Transceiver::driveControl(unsigned ARFCN) } +// (pat) Input is is coming from OpenBTS ARFCNManager::writeHighSideTx bool Transceiver::driveTransmitPriorityQueue(unsigned ARFCN) { @@ -764,7 +767,7 @@ bool Transceiver::driveTransmitPriorityQueue(unsigned ARFCN) LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot) <isUnderrun()) { // only do latency update every 10 frames, so we don't over update if (radioClock->get() > mLatencyUpdateTime + GSM::Time(100,0)) { - mTransmitLatency = mTransmitLatency + GSM::Time(1,0); + //mTransmitLatency = mTransmitLatency + GSM::Time(1,0); + unsigned bumpLatency = gConfig.getNum("TRX.LatencyBumpUp"); + mTransmitLatency = mTransmitLatency + GSM::Time(bumpLatency,0); LOG(INFO) << "new latency: " << mTransmitLatency; mLatencyUpdateTime = radioClock->get(); } @@ -866,6 +871,7 @@ void Transceiver::writeClockInterface() void *FIFOServiceLoopAdapter(Transceiver *transceiver) { + LOG(NOTICE) << "THREAD: FIFOLoopAdapter @ tid " << gettid(); while (1) { //transceiver->driveReceiveFIFO(); transceiver->driveTransmitFIFO(); @@ -876,6 +882,7 @@ void *FIFOServiceLoopAdapter(Transceiver *transceiver) void *RFIFOServiceLoopAdapter(Transceiver *transceiver) { + LOG(NOTICE) << "THREAD: RFIFOLoopAdapter @ tid " << gettid(); bool isMulti = transceiver->multiARFCN(); while (1) { transceiver->driveReceiveFIFO(); @@ -889,6 +896,7 @@ void *RFIFOServiceLoopAdapter(Transceiver *transceiver) void *ControlServiceLoopAdapter(ThreadStruct *ts) { + LOG(NOTICE) << "THREAD: ControlServiceLoopAdapter @ tid " << gettid(); Transceiver *transceiver = ts->trx; unsigned CN = ts->CN; while (1) { @@ -900,6 +908,7 @@ void *ControlServiceLoopAdapter(ThreadStruct *ts) void *DemodServiceLoopAdapter(Demodulator *demodulator) { + LOG(NOTICE) << "THREAD: DemodServiceLoopAdapter @ tid " << gettid(); while(1) { demodulator->driveDemod(false); pthread_testcancel(); @@ -911,6 +920,7 @@ void *TransmitPriorityQueueServiceLoopAdapter(ThreadStruct *ts) { Transceiver *transceiver = ts->trx; unsigned CN = ts->CN; + LOG(NOTICE) << "THREAD: TransmitServiceLoopAdapter @ tid " << gettid(); while (1) { bool stale = false; // Flush the UDP packets until a successful transfer. diff --git a/TransceiverRAD1/Transceiver.h b/TransceiverRAD1/Transceiver.h index 1e67e4e..1049517 100644 --- a/TransceiverRAD1/Transceiver.h +++ b/TransceiverRAD1/Transceiver.h @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/burn-rnrad1-eeprom.sh b/TransceiverRAD1/burn-rnrad1-eeprom.sh index 53b6bdd..546aa27 100755 --- a/TransceiverRAD1/burn-rnrad1-eeprom.sh +++ b/TransceiverRAD1/burn-rnrad1-eeprom.sh @@ -1,5 +1,19 @@ #!/bin/sh +# Copyright 2014 Range Networks, Inc. + +# This software is distributed under multiple licenses; see the COPYING +# file in the main directory for licensing information for this specific +# distribution. + +# This use of this software may be subject to additional restrictions. +# See the LEGAL file in the main directory for details. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + # comments tomr 12_14_11 # General procedure for a virgin eeprom (new SDR board or a corrupted one), note all LEDs will be off diff --git a/TransceiverRAD1/commands.h b/TransceiverRAD1/commands.h index 924f618..c0ed642 100644 --- a/TransceiverRAD1/commands.h +++ b/TransceiverRAD1/commands.h @@ -1,3 +1,18 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + + #ifndef _RAD1_COMMANDS_H_ #define _RAD1_COMMANDS_H_ diff --git a/TransceiverRAD1/interfaces.h b/TransceiverRAD1/interfaces.h index 4fd76f1..462390d 100644 --- a/TransceiverRAD1/interfaces.h +++ b/TransceiverRAD1/interfaces.h @@ -1,3 +1,18 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + + /* * We've now split the RAD1 into 3 separate interfaces. * diff --git a/TransceiverRAD1/radioDevice.h b/TransceiverRAD1/radioDevice.h index 8f9257f..08eada5 100644 --- a/TransceiverRAD1/radioDevice.h +++ b/TransceiverRAD1/radioDevice.h @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. diff --git a/TransceiverRAD1/radioInterface.cpp b/TransceiverRAD1/radioInterface.cpp index 79d5353..7fa985f 100644 --- a/TransceiverRAD1/radioInterface.cpp +++ b/TransceiverRAD1/radioInterface.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008, 2009, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -15,6 +16,8 @@ //#define NDEBUG #include "radioInterface.h" #include +#include +using namespace GSM; GSM::Time VectorQueue::nextTime() const @@ -79,6 +82,7 @@ RadioInterface::RadioInterface(RadioDevice *wRadio, mNumARFCNs = wNumARFCNs; loadTest = wLoadTest; + } RadioInterface::~RadioInterface(void) { @@ -161,6 +165,7 @@ void RadioInterface::pushBuffer(void) { if (sendCursor < 2*INCHUNK*samplesPerSymbol) return; // send resampleVector + // (pat) returned samplesWritten is always (INCHUNK*1)/2/sizeof(short); int samplesWritten = mRadio->writeSamples(sendBuffer, INCHUNK*samplesPerSymbol, &underrun, @@ -268,6 +273,7 @@ void RadioInterface::start() void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) { + LOG(NOTICE) << "THREAD: AlignRadioServiceLoopAdapter @ tid " << gettid(); while (1) { radioInterface->alignRadio(); pthread_testcancel(); @@ -312,6 +318,7 @@ void RadioInterface::driveReceiveRadio() { while (rcvSz > (symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol) { signalVector rxVector((symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol); unRadioifyVector(rcvBuffer+readSz*2,rxVector); + GSM::Time tmpTime = rcvClock; if (rcvClock.FN() >= 0) { //LOG(DEBUG) << "FN: " << rcvClock.FN(); @@ -334,6 +341,7 @@ void RadioInterface::driveReceiveRadio() { readSz += (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol; rcvSz -= (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol; + tN = rcvClock.TN(); } diff --git a/TransceiverRAD1/radioInterface.h b/TransceiverRAD1/radioInterface.h index 4e99046..1a4e025 100644 --- a/TransceiverRAD1/radioInterface.h +++ b/TransceiverRAD1/radioInterface.h @@ -1,7 +1,8 @@ /* * Copyright 2008, 2014 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -18,6 +19,7 @@ #include "GSMCommon.h" #include "LinkedLists.h" #include "radioDevice.h" +#include /** samples per GSM symbol */ #define SAMPSPERSYM 1 @@ -142,6 +144,11 @@ private: short *rcvBuffer; //[2*2*OUTCHUNK]; unsigned rcvCursor; + signalVector *txHistoryVector; + signalVector *rxHistoryVector; + signalVector *inverseCICFilter; + signalVector *rcvInverseCICFilter; + bool underrun; ///< indicates writes to USRP are too slow bool overrun; ///< indicates reads from USRP are too slow TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP diff --git a/TransceiverRAD1/rnrad1.h b/TransceiverRAD1/rnrad1.h index fe46485..b79d2b6 100644 --- a/TransceiverRAD1/rnrad1.h +++ b/TransceiverRAD1/rnrad1.h @@ -1,3 +1,17 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + #ifndef RNRAD1_H #define RNRAD1_H diff --git a/TransceiverRAD1/rnrad1Core.cpp b/TransceiverRAD1/rnrad1Core.cpp index 54d15eb..2856db9 100644 --- a/TransceiverRAD1/rnrad1Core.cpp +++ b/TransceiverRAD1/rnrad1Core.cpp @@ -1,3 +1,17 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + #include "rnrad1Core.h" #ifdef HAVE_CONFIG_H @@ -535,6 +549,7 @@ rnrad1Core::rnrad1Core (int which_board, mFpgaMasterClockFreq = 52000000; memset (mFpgaShadows, 0, sizeof (mFpgaShadows)); + LOG(INFO) << "rad1Core starting on board number "<1) numARFCN = atoi(argv[1]); + // pat 3-2014: If no args, get params from config file. Why do we even bother making this particular parameter a transceiver argument + // when the rest of the parameters come from the config file? + if (argc>1) { + numARFCN = atoi(argv[1]); + } else { + if (gConfig.defines("GSM.Radio.ARFCNs")) { numARFCN = gConfig.getNum("GSM.Radio.ARFCNs"); } + } int deviceID = 0; - if (argc>2) deviceID = atoi(argv[2]); + // (pat 3-2014) DO NOT CHANGE THIS methodology for using multiple radio boards. I use it for handover testing. + // This code should be duplicated in RAD1ping. + if (gConfig.defines("TRX.RadioNumber")) { + // (pat 3-2014) If you take out this code (again) without consulting pat.thompson, you will be shot. + // We must use a config file anyway - see comments ConfigurationTable gConfig. + // Ideally we wouldn't init gConfig until main(), but we cant yet. + deviceID = gConfig.getNum("TRX.RadioNumber"); // 1 based number. + } + + LOG(NOTICE) << format("transceiver: started with ARFCNs=%d on board number %d port %s with config %s\n",numARFCN,deviceID,gConfig.getStr("TRX.Port").c_str(),configFile); + if (deviceID > 0) { deviceID--; } // Config TRX.RadioNumber is 1 based, actual board number is 0 based. + gFactoryCalibration.readEEPROM(deviceID); @@ -138,6 +161,7 @@ int main(int argc, char *argv[]) //int i = 0; while(!gbShutdown) { sleep(1); } //i++; if (i==60) exit(1);} + LOG(NOTICE) << "Shutting down transceiver..."; cout << "Shutting down transceiver..." << endl; // trx->stop(); @@ -210,5 +234,17 @@ ConfigurationKeyMap getConfigurationKeys() map[tmp->getName()] = *tmp; delete tmp; + // (pat 4-2014) Added this to fix problems with "radio dropouts" and the solution was to change this to larger number (5). + { ConfigurationKey tmp("TRX.LatencyBumpUp","1", + "time measured in GSM timeslots", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:10", + false, + "Hardware-specific latency adjustment for transceiver clock. This affects the speed of upward latency adjustments. " + ); + map[tmp.getName()] = tmp; + } + return map; } diff --git a/TransceiverRAD1/sigProcLib.cpp b/TransceiverRAD1/sigProcLib.cpp index bfd460e..d2e923e 100644 --- a/TransceiverRAD1/sigProcLib.cpp +++ b/TransceiverRAD1/sigProcLib.cpp @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -20,6 +21,7 @@ #include "GSMCommon.h" #include +using namespace GSM; #define TABLESIZE 1024 @@ -1223,7 +1225,7 @@ signalVector *createLPF(float cutoffFreq, // Blackman -- less brickwall (sloping transition) but larger stopband attenuation float yw = 0.42 - 0.5*cos(((float)i)*M_2PI_F/(float)(filterLen)) + 0.08*cos(((float)i)*2*M_2PI_F/(float)(filterLen)); // Hamming -- more brickwall with smaller stopband attenuation - //float yw = 0.53836F - 0.46164F * cos(((float)i)*M_2PI_F/(float)(filterLen+1)); +// float yw = 0.53836F - 0.46164F * cos(((float)i)*M_2PI_F/(float)(filterLen+1)); *itr++ = (complex) ys*yg*yw; sum += ys*yg*yw; } diff --git a/TransceiverRAD1/sigProcLib.h b/TransceiverRAD1/sigProcLib.h index 9e9c387..cecb9ca 100644 --- a/TransceiverRAD1/sigProcLib.h +++ b/TransceiverRAD1/sigProcLib.h @@ -1,7 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. +* Copyright 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 software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -16,11 +17,9 @@ #include "Vector.h" #include "Complex.h" -#include "GSMTransfer.h" +#include -using namespace GSM; - /** Indicated signalVector symmetry */ typedef enum Symmetry { NONE = 0, diff --git a/TransceiverRAD1/sigProcLibTest.cpp b/TransceiverRAD1/sigProcLibTest.cpp index 2255dc9..0799aec 100644 --- a/TransceiverRAD1/sigProcLibTest.cpp +++ b/TransceiverRAD1/sigProcLibTest.cpp @@ -1,7 +1,8 @@ /* * Copyright (c) 2008, 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 information for this specific distribuion. +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribution. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +17,10 @@ Harvind S. Samra, hssamra@kestrelsp.com #include "sigProcLib.h" -//#include "radioInterface.h" #include #include +#include +using namespace GSM; using namespace std; diff --git a/apps/.gdbinit b/apps/.gdbinit new file mode 100644 index 0000000..480eec9 --- /dev/null +++ b/apps/.gdbinit @@ -0,0 +1,6 @@ +file /OpenBTS/OpenBTS +set logging on +set print frame-arguments all +set print pretty on +run +bt diff --git a/apps/GetConfigurationKeys.cpp b/apps/GetConfigurationKeys.cpp index 8823fc3..404a751 100644 --- a/apps/GetConfigurationKeys.cpp +++ b/apps/GetConfigurationKeys.cpp @@ -5,7 +5,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -21,8 +21,8 @@ #include #include -#include - +#include +#include "OpenBTSConfig.h" std::string getARFCNsString(unsigned band) { std::stringstream ss; @@ -84,6 +84,99 @@ std::string getARFCNsString(unsigned band) { return tmp.substr(0, tmp.size()-1); } +// The old handover config keys that have disappeared include: +// (pat) The LocalRSSIMin option was misnamed. It checks RXLEV reported by the MS, which is not RSSI. +// "GSM.Handover.ThresholdDelta","10", // Closest new option is GSM.Handover.Target +// "GSM.Handover.LocalRSSIMin","-80" // Closest new option is GSM.Handover.RXLEV_DL.Margin +// "GSM.Handover.Averaging","5", // Closest new option is GSM.Handover.RXLEV_DL.History +// "GSM.Ny1", // Renamed to: GSM.Handover.Ny1 +static void makeHandoverKeys(ConfigurationKeyMap &map) +{ +#define RXDIFF_HELP + + { ConfigurationKey tmp("GSM.Handover.FailureHoldoff","20", // (pat 3-2014) Increased from 5. + "seconds", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "1:9999", + false, + "The number of seconds to wait before attempting another handover with a given neighbor BTS." + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.Handover.Margin","15", + "db", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "1:100", + true, + "Unconditional handover if RXDIFF exceeds this margin. " + "The GSM.Handover.RXLEV_DL.PenaltyTime will prevent reverse handovers for that period. " + RXDIFF_HELP + ); + map[tmp.getName()] = tmp; + } + + // (pat) 5 is way too low; the MS does not have to respond. There is no penalty for making this too big, so I am increasing it a lot. + { ConfigurationKey tmp("GSM.Handover.Ny1","50", + "repeats", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "1:200", + true, + "Maximum number of repeats of the Physical Information Message during handover procedure, GSM 04.08 11.1.3." + ); + map[tmp.getName()] = tmp; + } + + // (pat) There used to be a Handover.History.Min, but it was replaced by individual history counts for each handover parameter. + { ConfigurationKey tmp("GSM.Handover.History.Max","32", + "reports", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "2:128", + false, + "Maximum neighbor history to consider for handover. " + "Units are number of measurement reports, which occur once each 480ms. " + ); + map[tmp.getName()] = tmp; + } + + + // (pat) All these handover keys are almost identical, so simplify a bit... + struct HandoverKey : public ConfigurationKey { + char namebuf[100]; + char defaultValueBuf[40]; + + HandoverKey(ConfigurationKeyMap &map,const char *name, int defaultValue, const char *units, const char *range, const char *help = "") + { + snprintf(namebuf,sizeof(namebuf),"GSM.Handover.%s",name); + snprintf(defaultValueBuf,sizeof(defaultValueBuf),"%d",defaultValue); + ConfigurationKey tmp(namebuf, + defaultValueBuf, + units, + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + range, + false, + help); + map[namebuf] = tmp; + } + }; + + const char *RXLEV_Help = "RXLEV_DL is reported by the MS for the serving cell and each neighbor cell. " + "Handover attempted if the serving cell RXLEV_DL < GSM.Handover.RXLEV_DL.Target and RXDIFF > GSM.Handover.RXLEV_DL.Margin"; + const char *History_Help = "The number of 480ms periods to consider for this handover criteria."; + const char *PenaltyTime_Help = "After a handover a reverse handover to the originating BTS is prevented for this period of time in seconds. "; + + HandoverKey(map, "RXLEV_DL.Target", 60, "dB", "0:100", RXLEV_Help); + HandoverKey(map, "RXLEV_DL.History", 6, "periods", "2:32", History_Help); + HandoverKey(map, "RXLEV_DL.Margin", 10, "dB", "0:100", RXLEV_Help); + HandoverKey(map, "RXLEV_DL.PenaltyTime", 20, "seconds", "0:99999", PenaltyTime_Help); + +} + ConfigurationKeyMap getConfigurationKeys() { @@ -127,20 +220,76 @@ ConfigurationKeyMap getConfigurationKeys() */ ConfigurationKeyMap map; - ConfigurationKey *tmp; - tmp = new ConfigurationKey("CLI.SocketPath","/var/run/command", + makeHandoverKeys(map); + + { ConfigurationKey tmp("Core.File","core.openbts", "", ConfigurationKey::FACTORY, - ConfigurationKey::FILEPATH, + ConfigurationKey::STRING, "", false, - "Path for Unix domain datagram socket used for the OpenBTS console interface." + "Constant part of core file name to use (excluding optional pid)" ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Call.QueryRRLP.Early","0", + { ConfigurationKey tmp("Core.Pid","0", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::BOOLEAN, + "", + false, + "1 to add a .pid number to the end of the filename" + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("Core.SaveFiles","1", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::BOOLEAN, + "", + false, + "1 to save system files in a tarball for post-mortem analysis" + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("Core.TarFile","/tmp/openbtsfiles.tgz", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::STRING, + "", + false, + "Name of filename to save /proc files for post-mortem analysis after a crash" + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("CLI.Port","49300", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::PORT, + "", + false, + "Port number (tcp/udp) for use in communicating between CLI and OpenBTS" + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("CLI.Interface","127.0.0.1", + "", + ConfigurationKey::FACTORY, + ConfigurationKey::IPADDRESS, + "", + false, + "Interface for use in communicating between CLI and OpenBTS, use \"any\" for all interfaces, otherwise, a comma separated list of interfaces" + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("Control.Call.QueryRRLP.Early","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -148,10 +297,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for its location via RRLP during the setup of a call." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Call.QueryRRLP.Late","0", + { ConfigurationKey tmp("Control.Call.QueryRRLP.Late","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -159,11 +308,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for its location via RRLP during the teardown of a call." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.GSMTAP.GPRS","0", + { ConfigurationKey tmp("Control.GSMTAP.GPRS","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -171,10 +320,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Capture GPRS signaling and traffic at L1/L2 interface via GSMTAP." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.GSMTAP.GSM","0", + { ConfigurationKey tmp("Control.GSMTAP.GSM","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -182,10 +331,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Capture GSM signaling at L1/L2 interface via GSMTAP." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.GSMTAP.TargetIP","127.0.0.1", + { ConfigurationKey tmp("Control.GSMTAP.TargetIP","127.0.0.1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::IPADDRESS, @@ -193,10 +342,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Target IP address for GSMTAP packets; the IP address of Wireshark, if you use it for real time traces." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.AttachDetach","1", + { ConfigurationKey tmp("Control.LUR.AttachDetach","1", "", ConfigurationKey::CUSTOMERWARN, // (pat) We have never tested with AttachDetach == 0; so customers should not use it! ConfigurationKey::BOOLEAN, @@ -207,10 +356,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.RegistrationMessageFrequency","FIRST", + { ConfigurationKey tmp("Control.LUR.RegistrationMessageFrequency","FIRST", "^PLMN|NORMAL|FIRST$", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::STRING, @@ -223,10 +372,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.FailedRegistration.Message","Your handset is not provisioned for this network. ", + { ConfigurationKey tmp("Control.LUR.FailedRegistration.Message","Your handset is not provisioned for this network. ", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING_OPT,// audited @@ -234,10 +383,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Send this text message, followed by the IMSI, to unprovisioned handsets that are denied registration." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.FailedRegistration.ShortCode","1000", + { ConfigurationKey tmp("Control.LUR.FailedRegistration.ShortCode","1000", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, @@ -246,10 +395,10 @@ ConfigurationKeyMap getConfigurationKeys() "The return address for the failed registration message. " "If unset, the message will not be sent. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.NormalRegistration.Message","", + { ConfigurationKey tmp("Control.LUR.NormalRegistration.Message","", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING_OPT,// audited @@ -260,10 +409,10 @@ ConfigurationKeyMap getConfigurationKeys() "To have a message sent, specify one. " "To stop sending messages again, execute \"unconfig Control.LUR.NormalRegistration.Message\". " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.NormalRegistration.ShortCode","0000", + { ConfigurationKey tmp("Control.LUR.NormalRegistration.ShortCode","0000", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, @@ -272,10 +421,10 @@ ConfigurationKeyMap getConfigurationKeys() "The return address for the normal registration message. " "If unset, the message will not be sent. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.OpenRegistration","", + { ConfigurationKey tmp("Control.LUR.OpenRegistration","", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::REGEX_OPT,// audited @@ -287,10 +436,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is ", + { ConfigurationKey tmp("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is ", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, @@ -298,10 +447,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Send this text message, followed by the IMSI, to unprovisioned handsets when they attach on Um due to open registration." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.OpenRegistration.Reject","", + { ConfigurationKey tmp("Control.LUR.OpenRegistration.Reject","", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::REGEX_OPT,// audited @@ -314,10 +463,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.OpenRegistration.ShortCode","101", + { ConfigurationKey tmp("Control.LUR.OpenRegistration.ShortCode","101", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, @@ -325,10 +474,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "The return address for the open registration message." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.QueryClassmark","0", + { ConfigurationKey tmp("Control.LUR.QueryClassmark","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -336,10 +485,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for classmark during LUR." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.QueryIMEI","0", + { ConfigurationKey tmp("Control.LUR.QueryIMEI","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -347,10 +496,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for IMEI during initial LUR." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.QueryRRLP","0", + { ConfigurationKey tmp("Control.LUR.QueryRRLP","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -358,11 +507,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for its location via RRLP during LUR." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.SendTMSIs","0", + { ConfigurationKey tmp("Control.LUR.SendTMSIs","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -370,10 +519,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Send new TMSI assignments to handsets that are allowed to attach." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.FailMode","ACCEPT", + { ConfigurationKey tmp("Control.LUR.FailMode","ACCEPT", "", // no units ConfigurationKey::CUSTOMER, ConfigurationKey::CHOICE, @@ -383,15 +532,15 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = 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 tmp("Control.LUR.UnprovisionedRejectCause","0x04", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -424,10 +573,10 @@ ConfigurationKeyMap getConfigurationKeys() "Reject causes come from GSM 04.08 10.5.3.6. " "Reject cause 0x02 or 0x04 is usually the right one." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.404RejectCause","0x04", + { ConfigurationKey tmp("Control.LUR.404RejectCause","0x04", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -460,10 +609,10 @@ ConfigurationKeyMap getConfigurationKeys() "Reject causes come from GSM 04.08 10.5.3.6. " "Reject cause 0x02 or 0x04 is usually the right one." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.LUR.TestMode","0", + { ConfigurationKey tmp("Control.LUR.TestMode","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -471,10 +620,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Used for testing the LUR procedure." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.NumSQLTries","3", + { ConfigurationKey tmp("Control.NumSQLTries","3", "attempts", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -482,10 +631,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Number of times to retry SQL queries before declaring a database access failure." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Reporting.PhysStatusTable","/var/run/ChannelTable.db", + { ConfigurationKey tmp("Control.Reporting.PhysStatusTable","/var/run/ChannelTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -493,10 +642,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for channel status reporting database." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Reporting.StatsTable","/var/log/OpenBTSStats.db", + { ConfigurationKey tmp("Control.Reporting.StatsTable","/var/log/OpenBTSStats.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -504,10 +653,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for statistics reporting database." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Reporting.TMSITable","/var/run/TMSITable.db", + { ConfigurationKey tmp("Control.Reporting.TMSITable","/var/run/TMSITable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -515,10 +664,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for TMSITable database." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.TMSITable.MaxAge","576", + { ConfigurationKey tmp("Control.TMSITable.MaxAge","576", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -528,10 +677,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Reporting.TransactionMaxCompletedRecords","100", + { ConfigurationKey tmp("Control.Reporting.TransactionMaxCompletedRecords","100", "record", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -539,10 +688,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Maximum completed records to be stored for gathering by an external stats tool." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.Reporting.TransactionTable","/var/run/TransactionTable.db", + { ConfigurationKey tmp("Control.Reporting.TransactionTable","/var/run/TransactionTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -550,10 +699,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for transaction table database." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.SACCHTimeout.BumpDown","1", + { ConfigurationKey tmp("Control.SACCHTimeout.BumpDown","1", "dB", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -561,10 +710,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.SMS.QueryRRLP","0", + { ConfigurationKey tmp("Control.SMS.QueryRRLP","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -572,10 +721,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Query every MS for its location via RRLP during an SMS." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.SMSCB.Table","", + { ConfigurationKey tmp("Control.SMSCB.Table","", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH_OPT,// audited @@ -586,11 +735,11 @@ ConfigurationKeyMap getConfigurationKeys() "To enable, specify a file path for the database e.g. /var/run/SMSCB.db. " "To disable again, execute \"unconfig Control.SMSCB.Table\"." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.VEA","0", + { ConfigurationKey tmp("Control.VEA","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -602,10 +751,10 @@ ConfigurationKeyMap getConfigurationKeys() "See GSM 04.08 Sections 9.1.8 and 10.5.2.4 for an explanation of the NECI bit. " "Note that some handset models exhibit bugs when VEA is used and these bugs may affect performance." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Control.WatchdogMinutes","0", + { ConfigurationKey tmp("Control.WatchdogMinutes","0", "minutes", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -613,10 +762,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Number of minutes before the radio watchdog expires and OpenBTS is restarted, set to 0 to disable." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.DNS","", + { ConfigurationKey tmp("GGSN.DNS","", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::MIPADDRESS_OPT,// audited @@ -627,10 +776,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.Firewall.Enable","1", + { ConfigurationKey tmp("GGSN.Firewall.Enable","1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -640,10 +789,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "0=no firewall; 1=block MS attempted access to OpenBTS or other MS; 2=block all private IP addresses." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.IP.MaxPacketSize","1520", + { ConfigurationKey tmp("GGSN.IP.MaxPacketSize","1520", "bytes", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -652,10 +801,10 @@ ConfigurationKeyMap getConfigurationKeys() "Maximum size of an IP packet. " "Should normally be 1520." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.IP.ReuseTimeout","180", + { ConfigurationKey tmp("GGSN.IP.ReuseTimeout","180", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -663,10 +812,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "How long IP addresses are reserved after a session ends." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.IP.TossDuplicatePackets","0", + { ConfigurationKey tmp("GGSN.IP.TossDuplicatePackets","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -674,10 +823,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Toss duplicate TCP/IP packets to prevent unnecessary traffic on the radio." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.Logfile.Name","", + { ConfigurationKey tmp("GGSN.Logfile.Name","", "", ConfigurationKey::FACTORY, ConfigurationKey::FILEPATH_OPT, @@ -685,10 +834,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "If specified, internet traffic is logged to this file. E.g. ggsn.log." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.MS.IP.Base","192.168.99.1", + { ConfigurationKey tmp("GGSN.MS.IP.Base","192.168.99.1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::IPADDRESS, @@ -696,10 +845,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Base IP address assigned to MS." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.MS.IP.MaxCount","254", + { ConfigurationKey tmp("GGSN.MS.IP.MaxCount","254", "addresses", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -707,10 +856,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Number of IP addresses to use for MS." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.MS.IP.Route","", + { ConfigurationKey tmp("GGSN.MS.IP.Route","", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CIDR_OPT,// audited @@ -722,10 +871,10 @@ ConfigurationKeyMap getConfigurationKeys() "The address must encompass all MS IP addresses. " "To use the auto-generated value again, execute \"unconfig GGSN.MS.IP.Route\"." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.ShellScript","", + { ConfigurationKey tmp("GGSN.ShellScript","", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::FILEPATH_OPT,// audited @@ -736,10 +885,10 @@ ConfigurationKeyMap getConfigurationKeys() "To enable, specify an absolute path to the script you wish to execute e.g. /usr/bin/ms-attach.sh. " "To disable again, execute \"unconfig GGSN.ShellScript\"." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GGSN.TunName","sgsntun", + { ConfigurationKey tmp("GGSN.TunName","sgsntun", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, @@ -747,10 +896,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Tunnel device name for GGSN." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.advanceblocks","10", + { ConfigurationKey tmp("GPRS.advanceblocks","10", "blocks", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -758,10 +907,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Number of advance blocks to use in the CCCH reservation." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.CellOptions.T3168Code","5", + { ConfigurationKey tmp("GPRS.CellOptions.T3168Code","5", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -779,10 +928,10 @@ ConfigurationKeyMap getConfigurationKeys() "See GSM 04.60 12.24. " "Range 0..7, representing values from 0.5sec to 4sec in 0.5sec steps." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.CellOptions.T3192Code","0", + { ConfigurationKey tmp("GPRS.CellOptions.T3192Code","0", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -800,10 +949,10 @@ ConfigurationKeyMap getConfigurationKeys() "The value must be one of the codes described in GSM 04.60 12.24. " "Value 0 implies 500msec; 2 implies 1500msec; 3 imples 0msec." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.ChannelCodingControl.RSSI","-40", + { ConfigurationKey tmp("GPRS.ChannelCodingControl.RSSI","-40", "dB", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -812,10 +961,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Channels.Congestion.Threshold","200", + { ConfigurationKey tmp("GPRS.Channels.Congestion.Threshold","200", "probability in %", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -823,10 +972,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "The GPRS channel is considered congested if the desired bandwidth exceeds available bandwidth by this amount, specified in percent." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Channels.Congestion.Timer","60", + { ConfigurationKey tmp("GPRS.Channels.Congestion.Timer","60", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -834,10 +983,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long in seconds GPRS congestion exceeds the Congestion.Threshold before we attempt to allocate another channel for GPRS." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Channels.Min.C0","2", + { ConfigurationKey tmp("GPRS.Channels.Min.C0","2", "channels", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -845,10 +994,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Minimum number of channels allocated for GPRS service on ARFCN C0." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Channels.Min.CN","0", + { ConfigurationKey tmp("GPRS.Channels.Min.CN","0", "channels", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -856,11 +1005,11 @@ ConfigurationKeyMap getConfigurationKeys() true, "Minimum number of channels allocated for GPRS service on ARFCNs other than C0." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } #if GPRS_CHANNELS_MAX_SUPPORTED - tmp = new ConfigurationKey("GPRS.Channels.Max","4", + { ConfigurationKey tmp("GPRS.Channels.Max","4", "channels", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -868,15 +1017,15 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of channels allocated for GPRS service." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } #endif // (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 tmp("GPRS.Codecs.Downlink","1,4", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING_OPT, @@ -886,10 +1035,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Codecs.Uplink","1,4", + { ConfigurationKey tmp("GPRS.Codecs.Uplink","1,4", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING_OPT, @@ -898,10 +1047,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.Assign","10", + { ConfigurationKey tmp("GPRS.Counters.Assign","10", "messages", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -909,10 +1058,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of assign messages sent." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.N3101","20", + { ConfigurationKey tmp("GPRS.Counters.N3101","20", "responses", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -922,10 +1071,10 @@ ConfigurationKeyMap getConfigurationKeys() "Should be > 8. " "See GSM04.60 Sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.N3103","8", + { ConfigurationKey tmp("GPRS.Counters.N3103","8", "attempts", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -934,10 +1083,10 @@ ConfigurationKeyMap getConfigurationKeys() "Counts ACK/NACK attempts to detect nonresponsive MS. " "See GSM04.60 sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.N3105","12", + { ConfigurationKey tmp("GPRS.Counters.N3105","12", "responses", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -946,10 +1095,10 @@ ConfigurationKeyMap getConfigurationKeys() "Counts unused RRBP responses to detect nonresponsive MS. " "See GSM04.60 Sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.Reassign","6", + { ConfigurationKey tmp("GPRS.Counters.Reassign","6", "messages", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -957,10 +1106,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of reassign messages sent." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Counters.TbfRelease","5", + { ConfigurationKey tmp("GPRS.Counters.TbfRelease","5", "messages", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -968,10 +1117,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of TBF release messages sent." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Debug","0", + { ConfigurationKey tmp("GPRS.Debug","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -979,10 +1128,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Toggle GPRS debugging." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Downlink.KeepAlive","300", + { ConfigurationKey tmp("GPRS.Downlink.KeepAlive","300", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -992,10 +1141,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Downlink.Persist","0", + { ConfigurationKey tmp("GPRS.Downlink.Persist","0", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1004,10 +1153,10 @@ ConfigurationKeyMap getConfigurationKeys() "After completion, downlink TBFs are held open for this time in milliseconds. " "If non-zero, must be greater than GPRS.Downlink.KeepAlive." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Enable","0", + { ConfigurationKey tmp("GPRS.Enable","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -1016,10 +1165,10 @@ ConfigurationKeyMap getConfigurationKeys() "If enabled, GPRS service is advertised in the C0T0 beacon, and GPRS service may be started on demand. " "See also GPRS.Channels.*." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.LocalTLLI.Enable","1", + { ConfigurationKey tmp("GPRS.LocalTLLI.Enable","1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::BOOLEAN, @@ -1027,10 +1176,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Enable recognition of local TLLI." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.MS.KeepExpiredCount","20", + { ConfigurationKey tmp("GPRS.MS.KeepExpiredCount","20", "structs", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1038,10 +1187,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How many expired MS structs to retain; they can be viewed with gprs list ms -x" ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.MS.Power.Alpha","10", + { ConfigurationKey tmp("GPRS.MS.Power.Alpha","10", "alpha", ConfigurationKey::DEVELOPER, ConfigurationKey::CHOICE, @@ -1062,10 +1211,10 @@ ConfigurationKeyMap getConfigurationKeys() "Valid range is 0...10 for alpha values of 0...1.0. " "See GSM 05.08 10.2.1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.MS.Power.Gamma","31", + { ConfigurationKey tmp("GPRS.MS.Power.Gamma","31", "2 dB steps", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1078,10 +1227,10 @@ ConfigurationKeyMap getConfigurationKeys() "Valid range is 0...31 for gamma values of 0...62 dB. " "See GSM 05.08 10.2.1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.MS.Power.T_AVG_T","15", + { ConfigurationKey tmp("GPRS.MS.Power.T_AVG_T","15", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1089,10 +1238,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "MS power control parameter; see GSM 05.08 10.2.1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.MS.Power.T_AVG_W","15", + { ConfigurationKey tmp("GPRS.MS.Power.T_AVG_W","15", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1100,10 +1249,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "MS power control parameter; see GSM 05.08 10.2.1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Multislot.Max.Downlink","3", + { ConfigurationKey tmp("GPRS.Multislot.Max.Downlink","3", "channels", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1111,10 +1260,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of channels used for a single MS in downlink." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Multislot.Max.Uplink","2", + { ConfigurationKey tmp("GPRS.Multislot.Max.Uplink","2", "channels", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1122,10 +1271,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of channels used for a single MS in uplink." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.NC.NetworkControlOrder","2", + { ConfigurationKey tmp("GPRS.NC.NetworkControlOrder","2", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1134,10 +1283,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.NMO","2", + { ConfigurationKey tmp("GPRS.NMO","2", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -1151,10 +1300,10 @@ ConfigurationKeyMap getConfigurationKeys() "Mode II (2) is recommended. " "Mode I implies combined routing updating procedures." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.PRIORITY-ACCESS-THR","6", + { ConfigurationKey tmp("GPRS.PRIORITY-ACCESS-THR","6", "", ConfigurationKey::DEVELOPER, ConfigurationKey::CHOICE, @@ -1167,32 +1316,32 @@ ConfigurationKeyMap getConfigurationKeys() "Code contols GPRS packet access priorities allowed. " "See GSM04.08 table 10.5.76." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.RAC","0", + { ConfigurationKey tmp("GPRS.RAC","0", "", - ConfigurationKey::DEVELOPER, + ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:255", true, "GPRS Routing Area Code, advertised in the C0T0 beacon." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.RA_COLOUR","0", + { ConfigurationKey tmp("GPRS.RA_COLOUR","0", "", - ConfigurationKey::DEVELOPER, + ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:7", false, "GPRS Routing Area Color as advertised in the C0T0 beacon." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.RRBP.Min","0", + { ConfigurationKey tmp("GPRS.RRBP.Min","0", "reservations", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1202,10 +1351,10 @@ ConfigurationKeyMap getConfigurationKeys() "Should normally be 0. " "A non-zero value gives the MS more time to respond to the RRBP request." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Reassign.Enable","1", + { ConfigurationKey tmp("GPRS.Reassign.Enable","1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::BOOLEAN, @@ -1213,10 +1362,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Enable TBF Reassignment." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.SendIdleFrames","0", + { ConfigurationKey tmp("GPRS.SendIdleFrames","0", "", ConfigurationKey::FACTORY, ConfigurationKey::BOOLEAN, @@ -1224,11 +1373,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Should be 0 for current transceiver or 1 for deprecated version of transceiver." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } #if 0 // (pat) obsolete - tmp = new ConfigurationKey("GPRS.SGSN.port","1920", + { ConfigurationKey tmp("GPRS.SGSN.port","1920", "", ConfigurationKey::DEVELOPER, ConfigurationKey::PORT, @@ -1236,11 +1385,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Port number of the SGSN required for GPRS service. This must match the port specified in the SGSN config file, currently osmo_sgsn.cfg." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } #endif - tmp = new ConfigurationKey("GPRS.TBF.Downlink.Poll1","10", + { ConfigurationKey tmp("GPRS.TBF.Downlink.Poll1","10", "blocks", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1248,10 +1397,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "When the first poll is sent for a downlink tbf, measured in blocks sent." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.TBF.EST","1", + { ConfigurationKey tmp("GPRS.TBF.EST","1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::BOOLEAN, @@ -1260,10 +1409,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.TBF.Expire","30000", + { ConfigurationKey tmp("GPRS.TBF.Expire","30000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1271,10 +1420,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long in milliseconds to try before giving up on a TBF." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.TBF.KeepExpiredCount","20", + { ConfigurationKey tmp("GPRS.TBF.KeepExpiredCount","20", "structs", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1282,10 +1431,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How many expired TBF structs to retain; they can be viewed with gprs list tbf -x." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.TBF.Retry","1", + { ConfigurationKey tmp("GPRS.TBF.Retry","1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -1297,10 +1446,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.Channels.Idle","6000", + { ConfigurationKey tmp("GPRS.Timers.Channels.Idle","6000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1310,10 +1459,10 @@ ConfigurationKeyMap getConfigurationKeys() "Also depends on Channels.Min. " "Currently the channel cannot be returned to the pool while there is any GPRS activity on any channel." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.MS.Idle","600", + { ConfigurationKey tmp("GPRS.Timers.MS.Idle","600", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1321,10 +1470,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long in seconds an MS is idle before the BTS forgets about it." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.MS.NonResponsive","6000", + { ConfigurationKey tmp("GPRS.Timers.MS.NonResponsive","6000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1332,10 +1481,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long in milliseconds a TBF is non-responsive before the BTS kills it." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.T3169","5000", + { ConfigurationKey tmp("GPRS.Timers.T3169","5000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1344,10 +1493,10 @@ ConfigurationKeyMap getConfigurationKeys() "Nonresponsive uplink TBF resource release timer, in milliseconds. " "See GSM04.60 Sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.T3191","5000", + { ConfigurationKey tmp("GPRS.Timers.T3191","5000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1356,10 +1505,10 @@ ConfigurationKeyMap getConfigurationKeys() "Nonresponsive downlink TBF resource release timer, in milliseconds. " "See GSM04.60 Sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.T3193","0", + { ConfigurationKey tmp("GPRS.Timers.T3193","0", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1369,10 +1518,10 @@ ConfigurationKeyMap getConfigurationKeys() "The T3193 value should be slightly longer than that specified by the T3192Code. " "If 0, the BTS will fill in a default value based on T3192Code." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Timers.T3195","5000", + { ConfigurationKey tmp("GPRS.Timers.T3195","5000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1381,10 +1530,10 @@ ConfigurationKeyMap getConfigurationKeys() "Nonresponsive downlink TBF resource release timer, in milliseconds. " "See GSM04.60 Sec 13." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Uplink.KeepAlive","300", + { ConfigurationKey tmp("GPRS.Uplink.KeepAlive","300", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1392,10 +1541,10 @@ 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." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GPRS.Uplink.Persist","4000", + { ConfigurationKey tmp("GPRS.Uplink.Persist","4000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1405,79 +1554,112 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CallerID.Source","username", - "", - ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::CHOICE, - "displayname,username,p-asserted-identity", - false, - "The source for numeric Caller ID has traditionally been the username field. After version 4.0 this behavior " - "will be changed to use the displayname field as it is a more accepted practice. This parameter will " - "allow those with existing integrations to easily return to the legacy behavior until their SIP " - "switches can be reconfigured. Additionally, using the P-Asserted-Identity header to source the " - "Caller ID number is supported." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - // (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", + // (pat 6-2014) Changed default to "auto", and we'll just fish for the phone number in all the possible places. See code in SIPDialog.cpp + { ConfigurationKey tmp("GSM.CallerID.Source","auto", //"username", + "", ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::VALRANGE, - "2:8",// educated guess + ConfigurationKey::CHOICE, + "auto,displayname,username,p-asserted-identity", false, - "Maximum number of access grants to be queued for transmission on AGCH before declaring congestion." + "The source for numeric Caller ID has traditionally been the username field. After version 4.0 this behavior " + "will be changed to use the displayname field as it is a more accepted practice. This parameter will " + "allow those with existing integrations to easily return to the legacy behavior until their SIP " + "switches can be reconfigured. Additionally, using the P-Asserted-Identity header to source the " + "Caller ID number is supported." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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", + // (pat 2-2014) Beacon overall configuration. We now support at least type 0 and 1. + // This value is broadcast on the beacon and may not be changed after startup. + { ConfigurationKey tmp("GSM.CCCH.CCCH-CONF","1", "", ConfigurationKey::DEVELOPER, ConfigurationKey::CHOICE, - "1|C-V Beacon," - "2|C-IV Beacon", + "0|single Combination-IV Beacon (with 9 CCCH) on timeslot 0," + "1|single Combination-V Beacon (with 3 CCCH + 4 SDCCH)," + "2|2 x Combination-IV Beacons on timeslots 0+2," + "4|3 x Combination-IV Beacons on timeslots 0+2+4," + "6|4 x Combination-IV Beacons on timeslots 0+2+4+6", 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." + "CCCH configuration type. Defined in GSM 5.02 3.3.2.3. " + "DO NOT CHANGE THIS. Value is fixed by the implementation. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellOptions.RADIO-LINK-TIMEOUT","15", - "seconds", + // (pat 2-2014) Magic paging control parameter. + { ConfigurationKey tmp("GSM.CCCH.BS_AG_BLKS_RES","auto", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::CHOICE, + "auto,0,1,2,3,4,5,6,7", + true, + "CCCH paging configuration: number of CCCH blocks reserved for AGCH. Defined in GSM 5.02 3.3.2.3. " + "Value auto means OpenBTS picks a reasonable value, " + "otherwise range is 0..2 for a Combination-V beacon or 0..7 for a Combination-IV beacon. " + "Only super-experts should set this to any value other than auto. " + ); + map[tmp.getName()] = tmp; + } + + // (pat 2-2014) Magic paging control parameter. + { ConfigurationKey tmp("GSM.CCCH.BS_PA_MFRMS","2", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::CHOICE, + "2,3,4,5,6,7,8,9", + true, + "CCCH paging configuration: number of Paging Multiframes. Defined in GSM 5.02 3.3.2.3. " + "Only super-experts should change this. " + ); + map[tmp.getName()] = tmp; + } + +#if unused // (pat 4-24-2014) no longer implemented + // (pat) This option is here so it can be disabled to help testing Immediate Assignment Reject behavior; + // if this is non-zero you cant test it in the lab because the phone is always close enough to not be rejected. + { ConfigurationKey tmp("GSM.CCCH.FavorTA","0", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "0:62", + true, + "When there is congestion, handsets closer to the cell tower than this many timing advance units are processed first. " + "Value 0 disables, ie, all handsets treated equally. " + ); + map[tmp.getName()] = tmp; + } +#endif + + { ConfigurationKey tmp("GSM.CellOptions.RADIO-LINK-TIMEOUT","15", // 15 is roughly 7.5 seconds. + "480ms periods", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "10:20",// educated guess + "10:120",// educated guess true, - "Seconds before declaring a physical link dead." + "Number of failed SACCH reports before an MS declares a physical link dead. GSM 5.08 5.2. SACCH reports are at 480ms intervals. " + "This value, converted to seconds, should be less than GSM.Timer.T3109. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellSelection.CELL-RESELECT-HYSTERESIS","3", + { ConfigurationKey tmp("GSM.BTS.RADIO_LINK_TIMEOUT","15", // 15 is roughly 7.5 seconds. + "480ms periods", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "10:120",// educated guess + true, + "Number of failed SACCH reports before the BTS declares a physical link dead. GSM 5.08 5.2 and 5.3. SACCH reports are at 480ms intervals. " + "Similar to GSM.CellOptions.RADIO-LINK-TIMEOUT which serves the same purpose in the MS. " + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.CellSelection.CELL-RESELECT-HYSTERESIS","3", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -1494,10 +1676,10 @@ ConfigurationKeyMap getConfigurationKeys() "See GSM 04.08 10.5.2.4, Table 10.5.23 for encoding. " "Encoding is $2N$ dB, values of $N$ are 0...7 for 0...14 dB." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellSelection.MS-TXPWR-MAX-CCH","0", + { ConfigurationKey tmp("GSM.CellSelection.MS-TXPWR-MAX-CCH","0", "dB", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1506,10 +1688,10 @@ ConfigurationKeyMap getConfigurationKeys() "Cell selection parameters. " "See GSM 04.08 10.5.2.4." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellSelection.NCCsPermitted","0", + { ConfigurationKey tmp("GSM.CellSelection.NCCsPermitted","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -1520,10 +1702,11 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellSelection.NECI","1", + // (pat) In GSM 4.08 10.5.2.4: NECI is labeled "half rate support"? + { ConfigurationKey tmp("GSM.CellSelection.NECI","1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -1535,10 +1718,10 @@ ConfigurationKeyMap getConfigurationKeys() "It can be set to 1 even if you do not use VEA, so you might as well leave it as 1. " "See GSM 04.08 10.5.2.4, Table 10.5.23 and 04.08 9.1.8, Table 9.9 and the Control.VEA parameter." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.CellSelection.RXLEV-ACCESS-MIN","0", + { ConfigurationKey tmp("GSM.CellSelection.RXLEV-ACCESS-MIN","0", "dB", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1547,10 +1730,10 @@ ConfigurationKeyMap getConfigurationKeys() "Cell selection parameters. " "See GSM 04.08 10.5.2.4." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Channels.C1sFirst","0", + { ConfigurationKey tmp("GSM.Channels.C1sFirst","0", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::BOOLEAN, @@ -1559,35 +1742,38 @@ ConfigurationKeyMap getConfigurationKeys() "Allocate C-I slots first, starting at C0T1. " "Otherwise, allocate C-VII slots first." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Channels.NumC1s","7", + // (pat) 3-2014 added 'auto' and made it the default. + { ConfigurationKey tmp("GSM.Channels.NumC1s","auto", "timeslots", ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::VALRANGE, - "0:100",// this is crap, calc from arfcns + ConfigurationKey::REGEX, + "^auto$|^[0-9]+$", // It is calculated or validated on startup. true, "Number of Combination-I timeslots to configure. " - "The C-I slot carries a single full-rate TCH, used for speech calling." + "The Combination-1 timeslot carries a single full-rate TCH, used for speech calling. " + "If value is auto, OpenBTS picks the value by using all otherwise unused timeslots. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Channels.NumC7s","0", + // (pat) 3-2014 added 'auto' and made it the default. + { ConfigurationKey tmp("GSM.Channels.NumC7s","auto", "timeslots", ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::VALRANGE, - "0:100",// this is crap, calc from arfcns + ConfigurationKey::REGEX, + "^auto$|^[0-9]+$", // It is calculated or validated on startup. 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, you must also have at least one C-VII." + "The Combination-7 timeslot carries 8 SDCCHs, useful to handle high registration loads or SMS. " + "If value is auto, OpenBTS picks the value based on beacon type and number of ARFCNs. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Channels.SDCCHReserve","0", + { ConfigurationKey tmp("GSM.Channels.SDCCHReserve","0", "SDCCHs", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1596,10 +1782,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Cipher.CCHBER","0", + { ConfigurationKey tmp("GSM.Cipher.CCHBER","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -1607,10 +1793,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Probability of a bit getting toggled in a control channel burst for cracking protection." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Cipher.Encrypt","0", + { ConfigurationKey tmp("GSM.Cipher.Encrypt","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -1618,10 +1804,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Encrypt traffic between MS and OpenBTS." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Cipher.RandomNeighbor","0", + { ConfigurationKey tmp("GSM.Cipher.RandomNeighbor","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -1629,10 +1815,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Probability of a random neighbor being added to SI5 for cracking protection." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Cipher.ScrambleFiller","0", + { ConfigurationKey tmp("GSM.Cipher.ScrambleFiller","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -1640,10 +1826,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Scramble filler in layer 2 for cracking protection." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Control.GPRSMaxIgnore","5", + { ConfigurationKey tmp("GSM.Control.GPRSMaxIgnore","5", "messages", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -1652,21 +1838,11 @@ ConfigurationKeyMap getConfigurationKeys() "Ignore GPRS messages on GSM control channels. " "Value is number of consecutive messages to ignore." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Handover.FailureHoldoff","5", - "seconds", - ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::VALRANGE, - "2:7",// educated guess - false, - "The number of seconds to wait before attempting another handover with a given neighbor BTS." - ); - map[tmp->getName()] = *tmp; - delete tmp; - tmp = new ConfigurationKey("GSM.Timer.Handover.Holdoff","10", + { ConfigurationKey tmp("GSM.Timer.Handover.Holdoff","10", "seconds", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1674,32 +1850,12 @@ ConfigurationKeyMap getConfigurationKeys() true, "Handover will not be permitted until this time has elapsed after an initial channel seizure or handover." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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 RXLEV (reported by the MS) is above this level (in dBm), regardless of power difference." - ); - map[tmp->getName()] = *tmp; - delete tmp; - tmp = new ConfigurationKey("GSM.Handover.ThresholdDelta","10", - "dB", - ConfigurationKey::CUSTOMERTUNE, - ConfigurationKey::VALRANGE, - "5:20",// educated guess - false, - "A neighbor downlink signal must be this much stronger (in dB) than this downlink signal for handover to occur." - ); - map[tmp->getName()] = *tmp; - delete tmp; - tmp = new ConfigurationKey("GSM.Identity.BSIC.BCC","2", + { ConfigurationKey tmp("GSM.Identity.BSIC.BCC","2", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::VALRANGE, @@ -1710,10 +1866,10 @@ ConfigurationKeyMap getConfigurationKeys() "This value will also select the training sequence used for all slots on this unit.", ConfigurationKey::NEIGHBORSUNIQUE ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.BSIC.NCC","0", + { ConfigurationKey tmp("GSM.Identity.BSIC.NCC","0", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::VALRANGE, @@ -1724,10 +1880,10 @@ ConfigurationKeyMap getConfigurationKeys() "Must be distinct from NCCs of other GSM operators in your area.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.CI","10", + { ConfigurationKey tmp("GSM.Identity.CI","10", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::VALRANGE, @@ -1740,10 +1896,10 @@ ConfigurationKeyMap getConfigurationKeys() "Should be unique.", ConfigurationKey::GLOBALLYUNIQUE ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.LAC","1000", + { ConfigurationKey tmp("GSM.Identity.LAC","1000", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::VALRANGE, @@ -1754,10 +1910,10 @@ ConfigurationKeyMap getConfigurationKeys() "(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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.MCC","001", + { ConfigurationKey tmp("GSM.Identity.MCC","001", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::STRING, @@ -1767,10 +1923,10 @@ ConfigurationKeyMap getConfigurationKeys() "Defined in ITU-T E.212. Value of 001 for test networks.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.MNC","01", + { ConfigurationKey tmp("GSM.Identity.MNC","01", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::STRING, @@ -1781,10 +1937,10 @@ ConfigurationKeyMap getConfigurationKeys() "01 for test networks.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Identity.ShortName","Range", + { ConfigurationKey tmp("GSM.Identity.ShortName","Range", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::STRING, @@ -1794,24 +1950,24 @@ ConfigurationKeyMap getConfigurationKeys() "Optional but must be defined if you also want the network to send time-of-day.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MS.Power.Damping","75", + { ConfigurationKey tmp("GSM.MS.Power.Damping","75", "damping value in percent", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "0:100", false, - "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." + "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 and SNR entirely; " + "a value of 0 causes the MS power to change instantaneously based on RSSI or SNR, 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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MS.Power.Max","33", + { ConfigurationKey tmp("GSM.MS.Power.Max","33", "dBm", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1819,10 +1975,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum commanded MS power level in dBm." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MS.Power.Min","5", + { ConfigurationKey tmp("GSM.MS.Power.Min","5", "dBm", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1830,21 +1986,23 @@ ConfigurationKeyMap getConfigurationKeys() false, "Minimum commanded MS power level in dBm." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MS.TA.Damping","50", - "?damping value", + { ConfigurationKey tmp("GSM.MS.TA.Damping","50", + "damping value in percent", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, - "25:75",// educated guess + "5:90",// educated guess false, - "Damping value for timing advance control loop." + "Damping value for TA [timing advance] control loop, which specifies the TA to be applied by the MS. " + "This damping factor is meant to prevent a single bad incoming TA estimate from moving the TA value out of range. " + // (pat) But does it work? ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MS.TA.Max","62", + { ConfigurationKey tmp("GSM.MS.TA.Max","62", "symbol periods", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1856,10 +2014,10 @@ ConfigurationKeyMap getConfigurationKeys() "Can be used to limit service range. " "Valid range is 1..62." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.MaxSpeechLatency","2", + { ConfigurationKey tmp("GSM.MaxSpeechLatency","2", "20 millisecond frames", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1868,8 +2026,8 @@ ConfigurationKeyMap getConfigurationKeys() "Maximum allowed speech buffering latency, in 20 millisecond frames. " "If the jitter is larger than this delay, frames will be lost." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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. @@ -1878,7 +2036,7 @@ ConfigurationKeyMap getConfigurationKeys() // 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", + { ConfigurationKey tmp("GSM.SpeechBuffer","1", "milliseconds", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1892,10 +2050,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Neighbors","", + { ConfigurationKey tmp("GSM.Neighbors","", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::MIPADDRESS_OPT,// audited @@ -1908,25 +2066,17 @@ ConfigurationKeyMap getConfigurationKeys() "To disable again, execute \"unconfig GSM.Neighbors\".", ConfigurationKey::NODESPECIFIC ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - 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. + // Why would you put a bad IP addresses in the GSM.Neighbors list? I dont know. + // If we really wanted to implement >32 neighbors, we would continually rotate all (>31) possible neighbors through the beacon to test + // whether the phones in the cell can find them. + { ConfigurationKey tmp("GSM.Neighbors.NumToSend","31", // (pat) Increased from 8 to 31. Dont know why it was limited. "neighbors", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -1935,22 +2085,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum number of neighbors to send to handset in the neighbor list broadcast in the beacon." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - // (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: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 tmp("GSM.RACH.AC","0x0400", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -1963,10 +2101,10 @@ ConfigurationKeyMap getConfigurationKeys() "Set to 0 to allow full access. " "Set to 0x0400 to indicate no support for emergency calls." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RACH.MaxRetrans","1", + { ConfigurationKey tmp("GSM.RACH.MaxRetrans","1", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::CHOICE, @@ -1979,38 +2117,41 @@ ConfigurationKeyMap getConfigurationKeys() "This is the raw parameter sent on the BCCH. " "See GSM 04.08 10.5.2.29 for encoding." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RACH.TxInteger","14", + { ConfigurationKey tmp("GSM.RACH.TxInteger","14", "", - ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, - "0|3 slots," - "1|4 slots," - "2|5 slots," - "3|6 slots," - "4|7 slots," - "5|8 slots," - "6|9 slots," - "7|10 slots," - "8|11 slots," - "9|12 slots," - "10|14 slots," - "11|16 slots," - "12|20 slots," - "13|25 slots," - "14|32 slots," - "15|50 slots", + "0|T=3 S=55 or 41 slots," + "1|T=4 S=76 or 52 slots," + "2|T=5 S=109 or 58 slots," + "3|T=6 S=163 or 86 slots," + "4|T=7 S=217 or 115 slots," + "5|T=8 S=55 or 41 slots," + "6|T=9 S=76 or 52 slots," + "7|T=10 S=109 or 58 slots," + "8|T=11 S=163 or 86 slots," + "9|T=12 S=217 or 115 slots," + "10|T=14 S=55 or 41 slots," + "11|T=16 S=76 or 52 slots," + "12|T=20 S=109 or 58 slots," + "13|T=25 S=163 or 86 slots," + "14|T=32 S=217 or 115 slots," + "15|T=50 S=55 or 41 slots", false, "Parameter to spread RACH busts over time. " - "This is the raw parameter sent on the BCCH. " - "See GSM 04.08 10.5.2.29 for encoding." + "Warning: changing this parameter may cause intermittent channel acquisition failures. " + "This is the raw parameter sent on the BCCH used to determine the S (delay) and T (spread) parameters. " + "In the description for S: the first value is for beacon CCCH-CONF type 0,2,4 or 6 (non-combined CCCH) and the " + "second value is for beacon CCCH-CONF type 1 (combined CCCH+SDCCH.) " + "See GSM 04.08 10.5.2.29 and 3.3.1.1.2 for encoding; 05.02 clause 7 table 3 and 5 for definition of a RACH slot." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.ACCURACY","40", + { ConfigurationKey tmp("GSM.RRLP.ACCURACY","40", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2020,10 +2161,10 @@ ConfigurationKeyMap getConfigurationKeys() "K in r=10(1.1**K-1), where r is the accuracy in meters. " "See 3GPP 03.32 Sec 6.2." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.ASSIST.PRESENT","0", + { ConfigurationKey tmp("GSM.RRLP.ALMANAC.ASSIST.PRESENT","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -2031,10 +2172,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Send almanac info to mobile." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.REFRESH.TIME","24.0", + { ConfigurationKey tmp("GSM.RRLP.ALMANAC.REFRESH.TIME","24.0", "hours", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2042,10 +2183,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How often the almanac is refreshed, in hours." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.ALMANAC.URL","http://www.navcen.uscg.gov/?pageName=currentAlmanac&format=yuma", + { ConfigurationKey tmp("GSM.RRLP.ALMANAC.URL","http://www.navcen.uscg.gov/?pageName=currentAlmanac&format=yuma", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, @@ -2053,10 +2194,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "URL of the almanac source." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.ASSIST.COUNT","9", + { ConfigurationKey tmp("GSM.RRLP.EPHEMERIS.ASSIST.COUNT","9", "satellites", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2064,10 +2205,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Number of satellites to include in navigation model." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.REFRESH.TIME","1.0", + { ConfigurationKey tmp("GSM.RRLP.EPHEMERIS.REFRESH.TIME","1.0", "hours", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2075,10 +2216,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How often the ephemeris is refreshed, in hours." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.EPHEMERIS.URL","ftp://ftp.trimble.com/pub/eph/CurRnxN.nav", + { ConfigurationKey tmp("GSM.RRLP.EPHEMERIS.URL","ftp://ftp.trimble.com/pub/eph/CurRnxN.nav", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING, @@ -2086,10 +2227,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "URL of ephemeris source." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.RESPONSETIME","4", + { ConfigurationKey tmp("GSM.RRLP.RESPONSETIME","4", "", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2099,10 +2240,10 @@ ConfigurationKeyMap getConfigurationKeys() "(OpenBTS timeout is 130 sec = max response time + 2.) N in 2**N. " "See 3GPP 04.31 Sec A.2.2.1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.SEED.ALTITUDE","0", + { ConfigurationKey tmp("GSM.RRLP.SEED.ALTITUDE","0", "meters", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2110,10 +2251,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Seed altitude in meters wrt geoidal surface." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.SEED.LATITUDE","37.777423", + { ConfigurationKey tmp("GSM.RRLP.SEED.LATITUDE","37.777423", "degrees", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2122,10 +2263,10 @@ ConfigurationKeyMap getConfigurationKeys() "Seed latitude in degrees: " "-90 (south pole) .. +90 (north pole)." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.SEED.LONGITUDE","-122.39807", + { ConfigurationKey tmp("GSM.RRLP.SEED.LONGITUDE","-122.39807", "degrees", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2134,10 +2275,10 @@ ConfigurationKeyMap getConfigurationKeys() "Seed longitude in degrees: " "-180 (west of greenwich) .. +180 (east)." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.RRLP.SERVER.URL","", + { ConfigurationKey tmp("GSM.RRLP.SERVER.URL","", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING_OPT,// audited @@ -2148,22 +2289,22 @@ ConfigurationKeyMap getConfigurationKeys() "To enable, specify a server URL eg: http://localhost/cgi/rrlpserver.cgi. " "To disable again, execute \"unconfig GSM.RRLP.SERVER.URL\"." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.ARFCNs","1", + { ConfigurationKey tmp("GSM.Radio.ARFCNs","1", "ARFCNs", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, - "1:3", + "1:10",// educated guess true, "The number of ARFCNs to use. " "The ARFCN set will be C0, C0+2, C0+4, etc." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.Band","900", + { ConfigurationKey tmp("GSM.Radio.Band","900", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -2177,10 +2318,10 @@ ConfigurationKeyMap getConfigurationKeys() "For non-multiband units, this value is dictated by the hardware and should not be changed.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.C0","51", + { ConfigurationKey tmp("GSM.Radio.C0","51", "", ConfigurationKey::CUSTOMERSITE, ConfigurationKey::CHOICE, @@ -2190,10 +2331,10 @@ ConfigurationKeyMap getConfigurationKeys() "Also the base ARFCN for a multi-ARFCN configuration.", ConfigurationKey::NEIGHBORSUNIQUE ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.MaxExpectedDelaySpread","4", + { ConfigurationKey tmp("GSM.Radio.MaxExpectedDelaySpread","4", "symbol periods", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2204,10 +2345,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.NeedBSIC","0", + { ConfigurationKey tmp("GSM.Radio.NeedBSIC","0", "", ConfigurationKey::FACTORY, ConfigurationKey::BOOLEAN, @@ -2215,10 +2356,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Whether the Radio type requires the full BSIC." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.PowerManager.MaxAttenDB","10", + { ConfigurationKey tmp("GSM.Radio.PowerManager.MaxAttenDB","10", "dB", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2227,10 +2368,10 @@ ConfigurationKeyMap getConfigurationKeys() "Maximum transmitter attenuation level, in dB wrt full scale on the D/A output. " "This sets the minimum power output level in the output power control loop." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.PowerManager.MinAttenDB","0", + { ConfigurationKey tmp("GSM.Radio.PowerManager.MinAttenDB","0", "dB", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2239,69 +2380,76 @@ ConfigurationKeyMap getConfigurationKeys() "Minimum transmitter attenuation level, in dB wrt full scale on the D/A output. " "This sets the maximum power output level in the output power control loop." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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", + // (pat) 3-2014: Power Manager algorithm changed so OpenBTS starts at min power (MaxAttenDB) and slowly ramps up to max power (MinAttenDB). + // This variable controls the ramp-up time. + { ConfigurationKey tmp("GSM.Radio.PowerManager.RampTime","60", + "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, - "5:20",// educated guess + "0:1000", false, - "Number of samples averaged by the output power control loop." + "On start-up, OpenBTS ramps up power from GSM.Radio.PowerManager.MaxAttenDB to GSM.Radio.PowerManager.MinAttenDB. " + "This value is the duration of the ramp period in seconds. " ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.PowerManager.Period","6000", - "milliseconds", - ConfigurationKey::DEVELOPER, - ConfigurationKey::VALRANGE, - "4500:7500(100)",// educated guess - false, - "Power manager control loop master period, in milliseconds." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("GSM.Radio.PowerManager.SamplePeriod","2000", - "milliseconds", - ConfigurationKey::DEVELOPER, - ConfigurationKey::VALRANGE, - "1500:2500(100)",// educated guess - false, - "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, - ConfigurationKey::VALRANGE, - "3750:6250(100)",// educated guess - false, - "Target value for T3122, the random access hold-off timer, for the power control loop." - ); - map[tmp->getName()] = *tmp; - delete tmp; - - tmp = new ConfigurationKey("GSM.Radio.RSSITarget","-50", + { ConfigurationKey tmp("GSM.Radio.RSSITarget","-50", "dB", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, "-75:-25",// educated guess false, - "Target uplink RSSI for MS power control loop, in dB wrt to A/D full scale. " + "Target uplink RSSI (Received Signal Strength Indication) for MS power control loop, in dB wrt to A/D full scale. " + "The MS power control loop adjusts MS TXPWR (transmit power) to try to keep RSSI at this level, " + "or to satisfy GSM.Radio.SNRTarget, whichever requires more power. " "Should be 6-10 dB above the noise floor." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Radio.RxGain","47", + { ConfigurationKey tmp("GSM.Radio.RSSIAveragePeriod","8", + "samples", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:1000", + false, + "Number of RSSI samples averaged over when computing RSSI to compare to RSSITarget in the MS power control loop. " + "If this number is too low the ordered MS TXPWR may bounce around unnecessarily. " + "If this number is too high the MS TXPWR may not change quickly enough to respond to changing conditions. " + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.Radio.SNRTarget","10", + "ratio", + ConfigurationKey::CUSTOMERTUNE, + ConfigurationKey::VALRANGE, + "6:20",// educated guess + false, + "The MS power control loop adjusts MS TXPWR (transmit power) to try to keep SNR (Signal to Noise Ratio) above this level." + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.Radio.SNRAveragePeriod","8", + "samples", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:1000", + false, + "Number of SNR samples averaged over when computing SNR to compare to SNRTarget in the MS power control loop. " + "If this number is too low the ordered MS TXPWR may bounce around unnecessarily. " + "If this number is too high the MS TXPWR may not change quickly enough to respond to changing conditions. " + "This is not the control variable used for handover decisions. " + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.Radio.RxGain","47", "dB", ConfigurationKey::FACTORY, ConfigurationKey::VALRANGE, @@ -2312,10 +2460,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.ShowCountry","0", + { ConfigurationKey tmp("GSM.ShowCountry","0", "", ConfigurationKey::CUSTOMER, ConfigurationKey::BOOLEAN, @@ -2323,37 +2471,50 @@ ConfigurationKeyMap getConfigurationKeys() false, "Tell the MS to show the country name based on the MCC." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } // (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", + { ConfigurationKey tmp("GSM.Timer.T3103","12000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "2000:30000(100)", - true, + false, // not static "Handover timeout in milliseconds, GSM 04.08 11.1.2. " "This is the timeout for a handset to seize a channel during handover." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Timer.T3105","50", + { ConfigurationKey tmp("GSM.Timer.T3105","50", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, "25:75(5)",// educated guess - true, + false, // not static "Milliseconds for handset to respond to physical information. " "GSM 04.08 11.1.2." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("GSM.Timer.T3113","10000", + { ConfigurationKey tmp("GSM.Timer.T3109","30000", + "milliseconds", + ConfigurationKey::CUSTOMERWARN, + ConfigurationKey::VALRANGE, + "5000:60000", + false, // not static, updated each time channel is opened. + "When a channel is released or contact with a handset is lost, the channel is deactivated for this period of time before being reused. " + "This time must be longer than the time indicated by GSM.CellOptions.RADIO-LINK-TIMEOUT. " + "GSM 04.08 11.1.2. " + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("GSM.Timer.T3113","10000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2363,38 +2524,38 @@ ConfigurationKeyMap getConfigurationKeys() "This is the timeout for a handset to respond to a paging request. " "This should usually be the same as SIP.Timer.B in your VoIP network." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } // (pat) It is unfortunate that this is specified in msecs. - tmp = new ConfigurationKey("GSM.Timer.T3122Max","255000", - "milliseconds", - ConfigurationKey::DEVELOPER, - ConfigurationKey::VALRANGE, - "10000:255000(1000)", - false, - "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; + //{ ConfigurationKey tmp("GSM.Timer.T3122Max","255000", + // "milliseconds", + // ConfigurationKey::DEVELOPER, + // ConfigurationKey::VALRANGE, + // "10000:255000(1000)", + // false, + // "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; + //} - // (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:255000(1000)", - false, - "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; + //// (pat) It is unfortunate that this is specified in msecs. + //{ ConfigurationKey tmp("GSM.Timer.T3122Min","10000", // 2-2014: Pat upped from 2 to 10 seconds. + // "milliseconds", + // ConfigurationKey::DEVELOPER, + // ConfigurationKey::VALRANGE, + // "1000:255000(1000)", + // false, + // "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; + //} - tmp = new ConfigurationKey("GSM.Timer.T3212","0", + { ConfigurationKey tmp("GSM.Timer.T3212","0", "minutes", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2405,10 +2566,45 @@ ConfigurationKeyMap getConfigurationKeys() "Set to 0 to disable periodic registration. " "Should be smaller than SIP registration period." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Peering.Neighbor.RefreshAge","60", + { ConfigurationKey tmp("NodeManager.API.PhysicalStatus","disabled", + "version", + ConfigurationKey::DEVELOPER, + ConfigurationKey::CHOICE, + "disabled," + "0.1", + false, + "Which version of the PhysicalStatus event stream should be enabled." + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("NodeManager.Commands.Port","45060", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::PORT, + "", + true, + "Port used by the NodeManager to receive and respond to JSON formatted commands. " + "Some examples of the available commands and their formats are available in NodeManager/JSON_Interface.txt." + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("NodeManager.Events.Port","45160", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::PORT, + "", + true, + "Port used by the NodeManager to publish API events." + ); + map[tmp.getName()] = tmp; + } + + { ConfigurationKey tmp("Peering.Neighbor.RefreshAge","60", "seconds", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2416,10 +2612,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Seconds before refreshing parameters from a neighbor." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Peering.NeighborTable.Path","/var/run/NeighborTable.db", + { ConfigurationKey tmp("Peering.NeighborTable.Path","/var/run/NeighborTable.db", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::FILEPATH, @@ -2427,10 +2623,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "File path for neighbor information database." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Peering.Port","16001", + { ConfigurationKey tmp("Peering.Port","16001", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::PORT, @@ -2438,13 +2634,13 @@ ConfigurationKeyMap getConfigurationKeys() true, "The UDP port used by the peer interface for handover." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } // (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", + { ConfigurationKey tmp("Peering.ResendCount","20", "attempts", ConfigurationKey::DEVELOPER, // (pat) This Peering params should not be customer tunable. ConfigurationKey::VALRANGE, @@ -2452,10 +2648,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Number of tries to send message over the peer interface before giving up." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Peering.ResendTimeout","100", + { ConfigurationKey tmp("Peering.ResendTimeout","100", "milliseconds", ConfigurationKey::DEVELOPER, // (pat) This Peering params should not be customer tunable. ConfigurationKey::VALRANGE, @@ -2463,10 +2659,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Milliseconds before resending a message on the peer interface." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("RTP.Range","98", + { ConfigurationKey tmp("RTP.Range","98", "ports", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::VALRANGE, @@ -2475,10 +2671,10 @@ ConfigurationKeyMap getConfigurationKeys() "Range of RTP port pool. " "Pool is RTP.Start to RTP.Range - 1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("RTP.Start","16484", + { ConfigurationKey tmp("RTP.Start","16484", "", ConfigurationKey::CUSTOMERTUNE, ConfigurationKey::PORT, @@ -2487,10 +2683,10 @@ ConfigurationKeyMap getConfigurationKeys() "Base of RTP port pool. " "Pool is RTP.Start to RTP.Range - 1." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SGSN.Debug","0", + { ConfigurationKey tmp("SGSN.Debug","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -2498,10 +2694,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Add layer 3 messages to the GGSN.Logfile, if any." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SGSN.Timer.ImplicitDetach","3480", + { ConfigurationKey tmp("SGSN.Timer.ImplicitDetach","3480", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2510,10 +2706,10 @@ ConfigurationKeyMap getConfigurationKeys() "3GPP 24.008 11.2.2. " "GPRS attached MS is implicitly detached in seconds. " "Should be at least 240 seconds greater than SGSN.Timer.RAUpdate."); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SGSN.Timer.MS.Idle","600", + { ConfigurationKey tmp("SGSN.Timer.MS.Idle","600", "?seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2521,10 +2717,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long an MS is idle before the SGSN forgets TLLI specific information." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SGSN.Timer.RAUpdate","0", + { ConfigurationKey tmp("SGSN.Timer.RAUpdate","0", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2535,10 +2731,10 @@ ConfigurationKeyMap getConfigurationKeys() "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." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SGSN.Timer.Ready","44", + { ConfigurationKey tmp("SGSN.Timer.Ready","44", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2547,10 +2743,10 @@ ConfigurationKeyMap getConfigurationKeys() "Also known as T3314, 3GPP 24.008 4.7.2.1. " "Inactivity period required before MS may perform another routing area or cell update, in seconds." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.DTMF.RFC2833","1", + { ConfigurationKey tmp("SIP.DTMF.RFC2833","1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -2558,10 +2754,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Use RFC-2833 (RTP event signalling) for in-call DTMF." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.DTMF.RFC2833.PayloadType","101", + { ConfigurationKey tmp("SIP.DTMF.RFC2833.PayloadType","101", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::VALRANGE, @@ -2569,13 +2765,13 @@ ConfigurationKeyMap getConfigurationKeys() false, "Payload type to use for RFC-2833 telephone event packets." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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 tmp("SIP.DTMF.RFC2967","0", "", ConfigurationKey::DEVELOPER, ConfigurationKey::BOOLEAN, @@ -2583,11 +2779,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Obsolete; incorrect RFC number. Use SIP.DTMF.RFC2976." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = 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 tmp("SIP.DTMF.RFC2976","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -2595,10 +2791,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Use RFC-2976 (SIP INFO method) for in-call DTMF." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Local.IP","127.0.0.1", + { ConfigurationKey tmp("SIP.Local.IP","127.0.0.1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::IPADDRESS, @@ -2608,10 +2804,10 @@ ConfigurationKeyMap getConfigurationKeys() "If these are all local, this can be localhost.", ConfigurationKey::NODESPECIFIC ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Local.Port","5062", + { ConfigurationKey tmp("SIP.Local.Port","5062", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::PORT, @@ -2619,10 +2815,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "IP port that OpenBTS uses for its SIP interface." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.MaxForwards","70", + { ConfigurationKey tmp("SIP.MaxForwards","70", "referrals", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2630,12 +2826,12 @@ ConfigurationKeyMap getConfigurationKeys() false, "Maximum allowed number of referrals." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } // (pat) 5-1-2013 - tmp = new ConfigurationKey("SIP.Proxy.Mode","", // name and default value + { ConfigurationKey tmp("SIP.Proxy.Mode","", // name and default value "", // units ConfigurationKey::DEVELOPER, // visiblity ConfigurationKey::CHOICE_OPT, // type @@ -2643,10 +2839,10 @@ ConfigurationKeyMap getConfigurationKeys() 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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Proxy.Registration","127.0.0.1:5064", + { ConfigurationKey tmp("SIP.Proxy.Registration","127.0.0.1:5064", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::HOSTANDPORT, @@ -2656,10 +2852,10 @@ ConfigurationKeyMap getConfigurationKeys() "This should normally be the subscriber registry SIP interface, not Asterisk.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Proxy.SMS","127.0.0.1:5063", + { ConfigurationKey tmp("SIP.Proxy.SMS","127.0.0.1:5063", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::HOSTANDPORT, @@ -2669,10 +2865,10 @@ ConfigurationKeyMap getConfigurationKeys() "This is smqueue, for example.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Proxy.Speech","127.0.0.1:5060", + { ConfigurationKey tmp("SIP.Proxy.Speech","127.0.0.1:5060", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::HOSTANDPORT, @@ -2682,24 +2878,24 @@ ConfigurationKeyMap getConfigurationKeys() "This is Asterisk, for example.", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Proxy.USSD","", + { ConfigurationKey tmp("SIP.Proxy.USSD","", "", ConfigurationKey::CUSTOMERWARN, - ConfigurationKey::HOSTANDPORT_OPT, - "", + ConfigurationKey::REGEX_OPT, + "^[:0-9a-zA-Z_.~-]+:[0-9]+$|^testmode$", // The ":" is for IPv6. 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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Realm","", + { ConfigurationKey tmp("SIP.Realm","", "", ConfigurationKey::DEVELOPER, ConfigurationKey::STRING_OPT, @@ -2707,10 +2903,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "SIP Realm for interop with certain switches. Filling in a host here will also activate an new REGISTER auth method." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.RegistrationPeriod","90", + { ConfigurationKey tmp("SIP.RegistrationPeriod","90", "minutes", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2719,10 +2915,10 @@ ConfigurationKeyMap getConfigurationKeys() "Registration period in minutes for MS SIP users. " "Should be longer than GSM T3212." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.RFC3428.NoTrying","0", + { ConfigurationKey tmp("SIP.RFC3428.NoTrying","0", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::BOOLEAN, @@ -2730,10 +2926,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Send \"100 Trying\" response to SIP MESSAGE, even though that violates RFC-3428." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.SMSC","smsc", + { ConfigurationKey tmp("SIP.SMSC","smsc", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE_OPT,// audited @@ -2745,10 +2941,10 @@ ConfigurationKeyMap getConfigurationKeys() "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; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Timer.A","2000", + { ConfigurationKey tmp("SIP.Timer.A","2000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2756,10 +2952,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "SIP timer A, the INVITE retry period, RFC-3261 Section 17.1.1.2, in milliseconds." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Timer.B","10000", + { ConfigurationKey tmp("SIP.Timer.B","10000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2768,10 +2964,10 @@ ConfigurationKeyMap getConfigurationKeys() "INVITE transaction timeout in milliseconds. " "This value should usually match GSM.Timer.T3113." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Timer.E","500", + { ConfigurationKey tmp("SIP.Timer.E","500", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2779,10 +2975,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Non-INVITE initial request retransmit period in milliseconds." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Timer.F","5000", + { ConfigurationKey tmp("SIP.Timer.F","5000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2790,10 +2986,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Non-INVITE initial request timeout in milliseconds." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SIP.Timer.H","5000", + { ConfigurationKey tmp("SIP.Timer.H","5000", "milliseconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2801,10 +2997,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "ACK timeout period in milliseconds." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SMS.FakeSrcSMSC","0000", + { ConfigurationKey tmp("SMS.FakeSrcSMSC","0000", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::STRING, @@ -2812,10 +3008,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "Use this to fill in L4 SMSC address in SMS delivery." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("SMS.MIMEType","application/vnd.3gpp.sms", + { ConfigurationKey tmp("SMS.MIMEType","application/vnd.3gpp.sms", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::CHOICE, @@ -2826,10 +3022,10 @@ ConfigurationKeyMap getConfigurationKeys() "Valid values are \"application/vnd.3gpp.sms\" and \"text/plain\".", ConfigurationKey::GLOBALLYSAME ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.IP","127.0.0.1", + { ConfigurationKey tmp("TRX.IP","127.0.0.1", "", ConfigurationKey::CUSTOMERWARN, ConfigurationKey::IPADDRESS, @@ -2837,10 +3033,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "IP address of the transceiver application." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.MinimumRxRSSI","-63", + { ConfigurationKey tmp("TRX.MinimumRxRSSI","-63", "dB", ConfigurationKey::FACTORY, ConfigurationKey::VALRANGE, @@ -2852,10 +3048,10 @@ ConfigurationKeyMap getConfigurationKeys() "Do not adjust without proper calibration.", ConfigurationKey::NODESPECIFIC ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.Port","5700", + { ConfigurationKey tmp("TRX.Port","5700", "", ConfigurationKey::FACTORY, ConfigurationKey::PORT, @@ -2863,10 +3059,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "IP port of the transceiver application." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.RadioFrequencyOffset","128", + { ConfigurationKey tmp("TRX.RadioFrequencyOffset","128", "~170Hz steps", ConfigurationKey::FACTORY, ConfigurationKey::VALRANGE, @@ -2878,10 +3074,10 @@ ConfigurationKeyMap getConfigurationKeys() "Do not adjust without proper calibration.", ConfigurationKey::NODESPECIFIC ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.Timeout.Clock","10", + { ConfigurationKey tmp("TRX.Timeout.Clock","10", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2889,11 +3085,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long to wait during a read operation from the Transceiver before giving up." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } // unused? - tmp = new ConfigurationKey("TRX.Timeout.Start","2", + { ConfigurationKey tmp("TRX.Timeout.Start","2", "seconds", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2901,10 +3097,10 @@ ConfigurationKeyMap getConfigurationKeys() false, "How long to wait during system startup before checking to see if the Transceiver can be reached." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("TRX.TxAttenOffset","0", + { ConfigurationKey tmp("TRX.TxAttenOffset","0", "dB of attenuation", ConfigurationKey::FACTORY, ConfigurationKey::VALRANGE, @@ -2915,11 +3111,15 @@ ConfigurationKeyMap getConfigurationKeys() "Do not adjust without proper calibration.", ConfigurationKey::NODESPECIFIC ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } +#if 0 //kurtis - tmp = new ConfigurationKey("TRX.Args","", + // (pat 3-2014) Removed. This is a great idea that cannot work yet in the transceiver because of the order of initialization + // dictated by the gConfig implementation. Furthermore the code in OpenBTS.cpp s botched - it neither parses this for multiple + // arguments nor passes it properly, and rather than fix it I am just removing pending a demonstrated need. + { ConfigurationKey tmp("TRX.Args","", "", ConfigurationKey::CUSTOMER, ConfigurationKey::STRING, @@ -2927,10 +3127,11 @@ ConfigurationKeyMap getConfigurationKeys() false, "Extra arguments for the Transceiver." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } +#endif - tmp = new ConfigurationKey("Test.GSM.SimulatedFER.Downlink","0", + { ConfigurationKey tmp("Test.GSM.SimulatedFER.Downlink","0", "probability in %", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2938,10 +3139,10 @@ ConfigurationKeyMap getConfigurationKeys() false, // this option takes effect immediately "Probability (0-100) of dropping any downlink frame to test robustness." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Test.GSM.SimulatedFER.Uplink","0", + { ConfigurationKey tmp("Test.GSM.SimulatedFER.Uplink","0", "probability in %", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2949,10 +3150,10 @@ ConfigurationKeyMap getConfigurationKeys() false, // this option takes effect immediately "Probability (0-100) of dropping any uplink frame to test robustness." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Test.GSM.UplinkFuzzingRate","0", + { ConfigurationKey tmp("Test.GSM.UplinkFuzzingRate","0", "probability in %", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2960,10 +3161,10 @@ ConfigurationKeyMap getConfigurationKeys() true, "Probability (0-100) of flipping a bit in any uplink frame to test robustness." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } - tmp = new ConfigurationKey("Test.SIP.SimulatedPacketLoss","0", + { ConfigurationKey tmp("Test.SIP.SimulatedPacketLoss","0", "probability in %", ConfigurationKey::DEVELOPER, ConfigurationKey::VALRANGE, @@ -2971,9 +3172,41 @@ ConfigurationKeyMap getConfigurationKeys() true, "Probability (0-100) of dropping any inbound or outbound SIP packet to test robustness." ); - map[tmp->getName()] = *tmp; - delete tmp; + map[tmp.getName()] = tmp; + } return map; } + +void OpenBTSConfig::configUpdateKeys() +{ + // (pat) Doesnt hurt to use float (unless overflow) for integer keys, but not vice versa. +#define SAVE_NUMERIC_KEY(keyname) { keyname = getFloat(#keyname); } // if (defines(#keyname)) { keyname = getNum(#keyname); } + SAVE_NUMERIC_KEY(GSM.Handover.FailureHoldoff); + SAVE_NUMERIC_KEY(GSM.Handover.Margin); + SAVE_NUMERIC_KEY(GSM.Handover.Ny1); + + SAVE_NUMERIC_KEY(GSM.Handover.History.Max); + //not implemented: SAVE_NUMERIC_KEY(GSM.Handover.Penalty.Damping); + SAVE_NUMERIC_KEY(GSM.Handover.Noise.Factor); + + SAVE_NUMERIC_KEY(GSM.Handover.RXLEV_DL.Target); + SAVE_NUMERIC_KEY(GSM.Handover.RXLEV_DL.History); + SAVE_NUMERIC_KEY(GSM.Handover.RXLEV_DL.Margin); + SAVE_NUMERIC_KEY(GSM.Handover.RXLEV_DL.PenaltyTime); + + SAVE_NUMERIC_KEY(GSM.MS.Power.Min); + SAVE_NUMERIC_KEY(GSM.MS.Power.Max); + SAVE_NUMERIC_KEY(GSM.MS.Power.Damping); + + SAVE_NUMERIC_KEY(GSM.MS.TA.Damping); + SAVE_NUMERIC_KEY(GSM.MS.TA.Max); + + SAVE_NUMERIC_KEY(GSM.Timer.T3103); + SAVE_NUMERIC_KEY(GSM.Timer.T3105); + SAVE_NUMERIC_KEY(GSM.Timer.T3109); + SAVE_NUMERIC_KEY(GSM.Timer.T3113); + SAVE_NUMERIC_KEY(GSM.Timer.T3212); + SAVE_NUMERIC_KEY(GSM.BTS.RADIO_LINK_TIMEOUT); +} diff --git a/apps/JSONEventsClient.cpp b/apps/JSONEventsClient.cpp new file mode 100644 index 0000000..694f2bb --- /dev/null +++ b/apps/JSONEventsClient.cpp @@ -0,0 +1,40 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + zmq::context_t context(4); + zmq::socket_t targetPublisher(context, ZMQ_SUB); + std::string localopenbts = "tcp://127.0.0.1:45160"; + + targetPublisher.setsockopt(ZMQ_SUBSCRIBE, "", 0); + targetPublisher.connect(localopenbts.c_str()); + while (1) { + try { + zmq::message_t event; + targetPublisher.recv(&event); + std::cout << std::string(static_cast(event.data()), event.size()) << std::endl; + + } catch(const zmq::error_t& e) { + std::cout << "!! exception !!" << std::endl; + } + } +} diff --git a/apps/Makefile.am b/apps/Makefile.am index 1f58358..8ee8167 100644 --- a/apps/Makefile.am +++ b/apps/Makefile.am @@ -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 the terms of the GNU Public License. # See the COPYING file in the main directory for details. @@ -28,8 +28,8 @@ DESTDIR := # AM_CXXFLAGS = -Wall -ldl -pthread noinst_PROGRAMS = \ + JSONEventsClient \ OpenBTS \ - OpenBTSDo \ OpenBTSCLI OpenBTS_SOURCES = OpenBTS.cpp GetConfigurationKeys.cpp @@ -46,17 +46,26 @@ ourlibs = \ $(GLOBALS_LA) \ $(CONTROL_LA) \ $(COMMON_LA) \ - $(SQLITE_LA) \ $(SMS_LA) \ $(PEERING_LA) \ $(NODEMANAGER_LA) \ $(ORTP_LIBS) -OpenBTS_LDADD = $(ourlibs) -ldl -lortp -la53 +clilibs= \ + $(GLOBALS_LA) + +OpenBTS_LDADD = $(ourlibs) -lcrypto -lssl -ldl -lortp -la53 -lcoredumper +OpenBTS_LDFLAGS = $(GPROF_OPTIONS) -rdynamic +noinst_HEADERS = \ + OpenBTSConfig.h OpenBTSCLI_SOURCES = OpenBTSCLI.cpp -OpenBTSCLI_LDADD = -lreadline -OpenBTSDo_SOURCES = OpenBTSDo.cpp +OpenBTSCLI_LDADD = $(clilibs) -lreadline +OpenBTSCLI_CXXFLAGS = -DBUILD_CLI + +JSONEventsClient_SOURCES = JSONEventsClient.cpp +JSONEventsClient_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) +JSONEventsClient_LDADD = $(NODEMANAGER_LA) EXTRA_DIST = \ OpenBTS.example.sql \ @@ -64,15 +73,13 @@ EXTRA_DIST = \ openbts.conf \ generateConfigTable.sh \ exportConfigTable.sh \ - importConfigTable.sh \ - generateKeys.sh \ - verifyKey.sh + importConfigTable.sh -install: OpenBTS OpenBTSCLI OpenBTSDo +install: OpenBTS OpenBTSCLI mkdir -p "$(DESTDIR)/OpenBTS/" install OpenBTS "$(DESTDIR)/OpenBTS/" install OpenBTSCLI "$(DESTDIR)/OpenBTS/" - install OpenBTSDo "$(DESTDIR)/OpenBTS/" + install .gdbinit "$(DESTDIR)/OpenBTS/" mkdir -p "$(DESTDIR)/etc/init/" install openbts.conf "$(DESTDIR)/etc/init/" mkdir -p "$(DESTDIR)/etc/OpenBTS/" @@ -83,5 +90,4 @@ install: OpenBTS OpenBTSCLI OpenBTSDo 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 678c741..9c866bb 100644 --- a/apps/OpenBTS.cpp +++ b/apps/OpenBTS.cpp @@ -5,7 +5,7 @@ * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing -* information for this specific distribuion. +* information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. @@ -20,15 +20,20 @@ #include #include #include +#include // For VERSION +#include #include +#include -#include +#include "OpenBTSConfig.h" std::vector configurationCrossCheck(const std::string& key); std::string getARFCNsString(unsigned band); // Load configuration from a file. static const char *cOpenBTSConfigEnv = "OpenBTSConfigFile"; static const char *cOpenBTSConfigFile = getenv(cOpenBTSConfigEnv)?getenv(cOpenBTSConfigEnv):"/etc/OpenBTS/OpenBTS.db"; -ConfigurationTable gConfig(cOpenBTSConfigFile,"OpenBTS", getConfigurationKeys()); +OpenBTSConfig gConfig(cOpenBTSConfigFile,"OpenBTS", getConfigurationKeys()); + + #include Log dummy("openbts",gConfig.getStr("Log.Level").c_str(),LOG_LOCAL7); @@ -43,8 +48,8 @@ ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); //#include #include -#include #include +#include #include @@ -55,6 +60,7 @@ ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); #include #include "NeighborTable.h" #include +#include #include #include @@ -64,6 +70,8 @@ ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); #include #include +#include "SelfDetect.h" + // (pat) mcheck.h is for mtrace, which permits memory leak detection. // Set env MALLOC_TRACE=logfilename // Call mtrace() in the program. @@ -73,7 +81,7 @@ ReportingTable gReports(gConfig.getStr("Control.Reporting.StatsTable").c_str()); using namespace std; using namespace GSM; -const char* gDateTime = __DATE__ " " __TIME__; +const char* gDateTime = TIMESTAMP_ISO; // All of the other globals that rely on the global configuration file need to @@ -124,6 +132,9 @@ void purgeConfig(void*,int,char const*, char const*, sqlite3_int64) // (pat) NO NO NO. Do not call LOG from here - it may result in infinite recursion. // LOG(INFO) << "purging configuration cache"; gConfig.purge(); + gConfig.configUpdateKeys(); + // (pat) FIXME: We cannot regenerate the beacon too often because the changemark is only 2 bits; + // we need to be more careful to update the beacon only when it really changes. gBTS.regenerateBeacon(); gResetWatchdog(); gLogGroup.setAll(); @@ -138,9 +149,9 @@ pid_t gTransceiverPid = 0; void startTransceiver() { //if local kill the process currently listening on this port - char killCmd[32]; if (gConfig.getStr("TRX.IP") == "127.0.0.1"){ - sprintf(killCmd,"fuser -k -n udp %d",(int)gConfig.getNum("TRX.Port")); + char killCmd[32]; + snprintf(killCmd,31,"fuser -k -n udp %d",(int)gConfig.getNum("TRX.Port")); if (system(killCmd)) {} } @@ -148,13 +159,24 @@ void startTransceiver() // If the path is not defined, the transceiver must be started by some other process. char TRXnumARFCN[4]; sprintf(TRXnumARFCN,"%1d",(int)gConfig.getNum("GSM.Radio.ARFCNs")); - std::string extra_args = gConfig.getStr("TRX.Args"); - LOG(NOTICE) << "starting transceiver " << transceiverPath << " w/ " << TRXnumARFCN << " ARFCNs and Args:" << extra_args; + //std::string extra_args = gConfig.getStr("TRX.Args"); // (pat 3-2014) remvoed pending demonstrated need. + string usernotice = format("starting transceiver %s with %s ARFCNs", transceiverPath, TRXnumARFCN); + if (getenv(cOpenBTSConfigEnv)) { + usernotice += " using config file: "; + usernotice += cOpenBTSConfigFile; + } + + static char *argv[10]; int argc = 0; + argv[argc++] = const_cast(transceiverPath); + argv[argc++] = TRXnumARFCN; + argv[argc] = NULL; + + LOG(ALERT) << usernotice; gTransceiverPid = vfork(); LOG_ASSERT(gTransceiverPid>=0); if (gTransceiverPid==0) { // Pid==0 means this is the process that starts the transceiver. - execlp(transceiverPath,transceiverPath,TRXnumARFCN,extra_args.c_str(),(void*)NULL); + execvp(transceiverPath,argv); LOG(EMERG) << "cannot find " << transceiverPath; _exit(1); } else { @@ -305,6 +327,10 @@ void createStats() gReports.create("GPRS.TBF"); // number of MSInfo records generated gReports.create("GPRS.MSInfo"); + + // (pat) 1-2014 Added RTP thread performance reporting. + gReports.create("OpenBTS.RTP.AverageSlack"); // Average head room. + gReports.create("OpenBTS.RTP.MinSlack"); // Minimum slack. Negative is a whoops. } @@ -326,15 +352,17 @@ void createStats() // 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 +// 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 +// NodeManager.Commands.Port 45060 45062 +// CLI.Port 49300 49302 +// 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 @@ -344,11 +372,75 @@ void createStats() 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[]) +/** Application specific NodeManager logic for handling requests. */ +JsonBox::Object nmHandler(JsonBox::Object& request) +{ + JsonBox::Object response; + std::string command = request["command"].getString(); + + if (command.compare("monitor") == 0) { + response["code"] = JsonBox::Value(200); + response["data"]["noiseRSSI"] = JsonBox::Value(0 - gTRX.ARFCN(0)->getNoiseLevel()); + response["data"]["msTargetRSSI"] = JsonBox::Value((signed)gConfig.getNum("GSM.Radio.RSSITarget")); + // FIXME -- This needs to take GPRS channels into account. See #762. (note from CLI::load section) + response["data"]["gsmSDCCHActive"] = JsonBox::Value((int)gBTS.SDCCHActive()); + response["data"]["gsmSDCCHTotal"] = JsonBox::Value((int)gBTS.SDCCHTotal()); + response["data"]["gsmTCHActive"] = JsonBox::Value((int)gBTS.TCHActive()); + response["data"]["gsmTCHTotal"] = JsonBox::Value((int)gBTS.TCHTotal()); + response["data"]["gsmAGCHQueue"] = JsonBox::Value((int)gBTS.AGCHLoad()); + response["data"]["gsmPCHQueue"] = JsonBox::Value((int)gBTS.PCHLoad()); + } else if (command.compare("tmsis") == 0) { + int verbosity = 2; + bool rawFlag = true; + unsigned maxRows = 10000; + vector< vector > view = gTMSITable.tmsiTabView(verbosity, rawFlag, maxRows); + + int count = 0; + JsonBox::Array a; + for (vector< vector >::iterator it = view.begin(); it != view.end(); ++it) { + // skip the header line + // TODO : use the header line to grab appropriate fields and indexes + if (count == 0) { + count++; + continue; + } + vector &row = *it; + JsonBox::Object o; + o["IMSI"] = row.at(0); + o["TMSI"] = row.at(1); + o["IMEI"] = row.at(2); + o["AUTH"] = row.at(3); + o["CREATED"] = row.at(4); + o["ACCESSED"] = row.at(5); + o["TMSI_ASSIGNED"] = row.at(6); + o["PTMSI_ASSIGNED"] = row.at(7); + o["AUTH_EXPIRY"] = row.at(8); + o["REJECT_CODE"] = row.at(9); + o["ASSOCIATED_URI"] = row.at(10); + o["ASSERTED_IDENTITY"] = row.at(11); + o["WELCOME_SENT"] = row.at(12); + o["A5_SUPPORT"] = row.at(13); + o["POWER_CLASS"] = row.at(14); + o["RRLP_STATUS"] = row.at(15); + o["OLD_TMSI"] = row.at(16); + o["OLD_MCC"] = row.at(17); + o["OLD_MNC"] = row.at(18); + o["OLD_LAC"] = row.at(19); + a.push_back(JsonBox::Value(o)); + } + response["code"] = JsonBox::Value(200); + response["data"] = JsonBox::Value(a); + } else { + response["code"] = JsonBox::Value(501); + } + + return response; +} + +static bool bAllowMultipleInstances = false; + +void processArgs(int argc, char *argv[]) { - //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; @@ -356,7 +448,7 @@ int main(int argc, char *argv[]) if (!strcmp(argv[argi], "--version") || !strcmp(argv[argi], "-v")) { // Print the version number and exit immediately. cout << gVersionString << endl; - return 0; + exit(0); } if (!strcmp(argv[argi], "--test")) { testflag = true; @@ -364,14 +456,20 @@ int main(int argc, char *argv[]) } if (!strcmp(argv[argi], "--gensql")) { cout << gConfig.getDefaultSQL(string(argv[0]), gVersionString) << endl; - return 0; + exit(0); } if (!strcmp(argv[argi], "--gentex")) { cout << gConfig.getTeX(string(argv[0]), gVersionString) << endl; - return 0; + exit(0); } - // (pat) Adding support for specified sql file. + // Allow multiple occurrences of the program to run. + if (!strcmp(argv[argi], "-m")) { + bAllowMultipleInstances = true; + continue; + } + + // (pat) Adding support for specified sql config file. // Unfortunately, the Config table was inited quite some time ago, // so stick this arg in the environment, whence the ConfigurationTable can find it, and then reboot. if (!strcmp(argv[argi],"--config")) { @@ -393,8 +491,96 @@ int main(int argc, char *argv[]) printf("OpenBTS: unrecognized argument: %s\nexiting...\n",argv[argi]); } - if (testflag) { GSM::TestTCHL1FEC(); return 0; } + if (testflag) { GSM::TestTCHL1FEC(); exit(0); } } +} + +struct TimeSlot { + int mCN, mTN; + TimeSlot(int wCN,int wTN) : mCN(wCN), mTN(wTN) {} +}; + +std::deque timeSlotList; + +static int initTimeSlots() // Return how many slots used by beacon. +{ + // The first timeslot is special for the beacon: + unsigned beaconSlots = 1; + + int numARFCNs = gConfig.getNum("GSM.Radio.ARFCNs"); + int scount = 0; + for (int cn = 0; cn < numARFCNs; cn++) { + for (int tn = 0; tn < 8; tn++) { + if (cn == 0 && (beaconSlots & (1<powerOn(); - //sleep(gConfig.getNum("TRX.Timeout.Start")); - //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. + // (pat 3-16-2014) If there are multiple instances of OpenBTS running, dont go talking to some random transceiver. + // (pat) We dont - we talk to the transceiver on the specified port. + bool haveTRX = false; + //if (! bAllowMultipleInstances) { + // is the radio running? + // Start the transceiver interface. + LOG(INFO) << "checking transceiver"; + //gTRX.ARFCN(0)->powerOn(); + //sleep(gConfig.getNum("TRX.Timeout.Start")); + //bool haveTRX = gTRX.ARFCN(0)->powerOn(false); This prints an inapplicable warning message. + haveTRX = gTRX.ARFCN(0)->trxRunning(); // This does not print an inapplicable warning message. + //} Thread transceiverThread; if (!haveTRX) { - LOG(ALERT) << "starting the transceiver"; + //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 @@ -524,120 +708,71 @@ int main(int argc, char *argv[]) // Turn on and power up. C0radio->powerOn(true); - C0radio->setPower(gConfig.getNum("GSM.Radio.PowerManager.MinAttenDB")); + // (pat 3-2014) This previously started OpenBTS at maximum power (which is MinAtten) + // We want to bring the radio up at lowest power and ramp up. + C0radio->setPower(gConfig.getNum("GSM.Radio.PowerManager.MaxAttenDB")); // Previously: "GSM.Radio.PowerManager.MinAttenDB" - // - // Create a C-V channel set on C0T0. - // - // C-V on C0T0 - C0radio->setSlot(0,5); - // SCH - SCHL1FEC SCH; - SCH.downstream(C0radio); - SCH.open(); - // FCCH - FCCHL1FEC FCCH; - FCCH.downstream(C0radio); - FCCH.open(); - // BCCH - BCCHL1FEC BCCH; - BCCH.downstream(C0radio); - BCCH.open(); - // RACH - RACHL1FEC RACH(gRACHC5Mapping); - RACH.downstream(C0radio); - RACH.open(); - // CCCHs - CCCHLogicalChannel CCCH0(gCCCH_0Mapping); - CCCH0.downstream(C0radio); - CCCH0.open(); - CCCHLogicalChannel CCCH1(gCCCH_1Mapping); - CCCH1.downstream(C0radio); - CCCH1.open(); - CCCHLogicalChannel CCCH2(gCCCH_2Mapping); - CCCH2.downstream(C0radio); - CCCH2.open(); - // use CCCHs as AGCHs - gBTS.addAGCH(&CCCH0); - gBTS.addAGCH(&CCCH1); - gBTS.addAGCH(&CCCH2); + // (pat) GSM 5.02 6.4 describes the permitted channel combinations. + // I am leaving out the SACCH in these descriptions; all TCH or SDCCH include the same number of SACCH. + // Combination-V is BCCH beacon + 3 AGCH + 4 SDCCH. See GSM 5.02 figure 7 (page 59) + // Note: A complete C-V mapping requires two consecutive 51-multiframes because of the way SACCH are interleaved. + // Combination-IV is BCCH beacon + 9 AGCH. + // BS_CC_CHANS specifies how many timeslots support CCCH, from 1 to 4. + // BS_AG_BLKS_RES specifies the number of beacon AGCH NOT used by paging. + // Combination-VII is 8 SDCCH. + // Combination-I is a TCH/F+FACCH+SACCH (ie, traffic channel.) - // 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), - SDCCHLogicalChannel(0,0,gSDCCH_4_2), - SDCCHLogicalChannel(0,0,gSDCCH_4_3), - }; - Thread C0T0SDCCHControlThread[4]; - // Subchannel 2 used for CBCH if SMSCB enabled. - bool SMSCB = (gConfig.getStr("Control.SMSCB.Table").length() != 0); - CBCHLogicalChannel CBCH(gSDCCH_4_2); - Thread CBCHControlThread; - for (int i=0; i<4; i++) { - if (SMSCB && (i==2)) continue; - C0T0SDCCH[i].downstream(C0radio); - C0T0SDCCHControlThread[i].start((void*(*)(void*))Control::DCCHDispatcher,&C0T0SDCCH[i]); - C0T0SDCCH[i].open(); - gBTS.addSDCCH(&C0T0SDCCH[i]); - } - // Install CBCH if used. - if (SMSCB) { - LOG(INFO) << "creating CBCH for SMSCB"; - CBCH.downstream(C0radio); - CBCH.open(); - gBTS.addCBCH(&CBCH); - CBCHControlThread.start((void*(*)(void*))Control::SMSCBSender,NULL); - } + gBTS.createBeacon(C0radio); // // Configure the other slots. // - // Count configured slots. - unsigned sCount = 1; - - - if (!gConfig.defines("GSM.Channels.NumC1s")) { - int numChan = numARFCNs*7; - LOG(CRIT) << "GSM.Channels.NumC1s not defined. Defaulting to " << numChan << "."; - gConfig.set("GSM.Channels.NumC1s",numChan); - } - if (!gConfig.defines("GSM.Channels.NumC7s")) { - int numChan = numARFCNs-1; - LOG(CRIT) << "GSM.Channels.NumC7s not defined. Defaulting to " << numChan << "."; - 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 (TimeSlots::maxC1plusC7() < TimeSlots::getNumC1s() + TimeSlots::getNumC7s()) { + LOG(CRIT) << "scaling back GSM.Channels.NumC1s and GSM.Channels.NumC7s to fit inside number of available timeslots."; + // NOTE!!! Must set NumC7s before calling defaultC1s. + //gConfig.set("GSM.Channels.NumC7s",TimeSlots::defaultC7s()); + //gConfig.set("GSM.Channels.NumC1s",TimeSlots::defaultC1s()); + // Update: Just set them both to auto permanently. + gConfig.set("GSM.Channels.NumC7s","auto"); + gConfig.set("GSM.Channels.NumC1s","auto"); } + // Count configured slots. + int sCount = initTimeSlots(); // Returns number of timeslots used by beacon. + + gNumC7s = TimeSlots::getNumC7s(); + gNumC1s = TimeSlots::getNumC1s(); + LOG(NOTICE) << format("Creating %d Combination-1 (TCH/F) timeslots and %d Combination-7 (SDCCH) timeslots",gNumC1s,gNumC7s); + if (gConfig.getBool("GSM.Channels.C1sFirst")) { // Create C-I slots. - for (int i=0; i=(int)gBTS.SDCCHTotal()) { - unsigned val = gBTS.SDCCHTotal() - 1; + if (0 == gBTS.SDCCHTotal()) { + LOG(CRIT) << "No SDCCH channels are allocated! OpenBTS may not function properly."; + } else if (gConfig.getNum("GSM.Channels.SDCCHReserve")>=(int)gBTS.SDCCHTotal()) { + int val = gBTS.SDCCHTotal() - 1; + if (val < 0) { val = 0; } LOG(CRIT) << "GSM.Channels.SDCCHReserve too big, changing to " << val; gConfig.set("GSM.Channels.SDCCHReserve",val); } // OK, now it is safe to start the BTS. - gBTS.start(); + gBTS.gsmStart(); - struct sockaddr_un cmdSockName; - cmdSockName.sun_family = AF_UNIX; - const char* sockpath = gConfig.getStr("CLI.SocketPath").c_str(); - char rmcmd[strlen(sockpath)+5]; - sprintf(rmcmd,"rm -f %s",sockpath); - if (system(rmcmd)) {} // The 'if' shuts up gcc warnings. - strcpy(cmdSockName.sun_path,sockpath); - LOG(INFO) "binding CLI datagram socket at " << sockpath; - if (bind(sock, (struct sockaddr *) &cmdSockName, sizeof(struct sockaddr_un))) { - perror("binding name to cmd datagram socket"); - LOG(ALERT) << "cannot bind socket for CLI at " << sockpath; - gReports.incr("OpenBTS.Exit.CLI.Socket"); - exit(1); - } - COUT("\nsystem ready\n"); - if (chmod(sockpath, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0) - { - perror("sockpath"); - // don't exit, at this point, we must run CLI as root - COUT("\nuse the OpenBTSCLI utility to access CLI as root\n"); - } else - { - COUT("\nuse the OpenBTSCLI utility to access CLI\n"); - } LOG(INFO) << "system ready"; - gParser.startCommandLine(); - gNodeManager.start(45060); + gNodeManager.setAppLogicHandler(&nmHandler); + gNodeManager.start(gConfig.getNum("NodeManager.Commands.Port"), gConfig.getNum("NodeManager.Events.Port")); - while (1) { - char cmdbuf[1000]; - struct sockaddr_un source; - socklen_t sourceSize = sizeof(source); - int nread = recvfrom(sock,cmdbuf,sizeof(cmdbuf)-1,0,(struct sockaddr*)&source,&sourceSize); - gReports.incr("OpenBTS.CLI.Command"); - cmdbuf[nread]='\0'; - LOG(INFO) << "received command \"" << cmdbuf << "\" from " << source.sun_path; - std::ostringstream sout; - int res = gParser.process(cmdbuf,sout); - const std::string rspString= sout.str(); - const char* rsp = rspString.c_str(); - LOG(INFO) << "sending " << strlen(rsp) << "-char result to " << source.sun_path; - if (sendto(sock,rsp,strlen(rsp)+1,0,(struct sockaddr*)&source,sourceSize)<0) { - LOG(ERR) << "can't send CLI response to " << source.sun_path; - gReports.incr("OpenBTS.CLI.Command.ResponseFailure"); - } - // res<0 means to exit the application - if (res<0) break; - } + COUT("\nsystem ready\n"); + COUT("\nuse the OpenBTSCLI utility to access CLI\n"); + gParser.cliServer(); // (pat) This does not return unless the user directs us to kill OpenBTS. + + LOG(ALERT) << "exiting OpenBTS ..."; + // End CLI Interface code } // try @@ -735,11 +821,21 @@ int main(int argc, char *argv[]) LOG(EMERG) << "required configuration parameter " << e.key() << " not defined, aborting"; gReports.incr("OpenBTS.Exit.Error.ConfigurationParamterNotFound"); } - LOG(ALERT) << "exiting OpenBTS as directed by command line..."; + catch (exception e) { + // (pat) This is C++ standard exception. It will be thrown for string or STL [Standard Template Library] errors. + // They are also thrown from the zmq library used by the NodeManager, but the numnuts dont put any useful information in the e.what(0 field. + // man zmq_cpp for more info. + LOG(EMERG) << "C++ standard exception occurred: "< configurationCrossCheck(const string& key) { warning.str(std::string()); } - // GSM.Channels.NumC1s + GSM.Channels.NumC1s must fall within 8 * GSM.Radio.ARFCNs + // GSM.Channels.NumC1s + GSM.Channels.NumC1s must fall within allowed timeslots. } 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"); + int max = TimeSlots::maxC1plusC7(); + int current = TimeSlots::getNumC1s() + TimeSlots::getNumC7s(); 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."; diff --git a/apps/OpenBTS.example.sql b/apps/OpenBTS.example.sql index 8571de7..916ff14 100644 --- a/apps/OpenBTS.example.sql +++ b/apps/OpenBTS.example.sql @@ -1,6 +1,6 @@ -- --- This file was generated using: ./OpenBTS --gensql --- binary version: release 4.0TRUNK+GPRS P built Mar 25 2014 rev CommonLibs:rev +-- This file was generated using: ./apps/OpenBTS --gensql +-- binary version: release 5.0.0-prealpha+47f350b77d CommonLibs:3ad343b97b+GPRS P built 2014-07-16T23:39:34 -- -- Future changes should not be put in this file directly but -- rather in the program's ConfigurationKey schema. @@ -8,7 +8,8 @@ PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT ''); -INSERT OR IGNORE INTO "CONFIG" VALUES('CLI.SocketPath','/var/run/command',0,0,'Path for Unix domain datagram socket used for the OpenBTS console interface.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('CLI.Interface','127.0.0.1',0,0,'Interface for use in communicating between CLI and OpenBTS, use "any" for all interfaces, otherwise, a comma separated list of interfaces'); +INSERT OR IGNORE INTO "CONFIG" VALUES('CLI.Port','49300',0,0,'Port number (tcp/udp) for use in communicating between CLI and OpenBTS'); INSERT OR IGNORE INTO "CONFIG" VALUES('Control.Call.QueryRRLP.Early','0',0,0,'1=enabled, 0=disabled - Query every MS for its location via RRLP during the setup of a call.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Control.Call.QueryRRLP.Late','0',0,0,'1=enabled, 0=disabled - Query every MS for its location via RRLP during the teardown of a call.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Control.GSMTAP.GPRS','0',0,0,'1=enabled, 0=disabled - Capture GPRS signaling and traffic at L1/L2 interface via GSMTAP.'); @@ -44,6 +45,10 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('Control.SMSCB.Table','',1,0,'File path fo INSERT OR IGNORE INTO "CONFIG" VALUES('Control.TMSITable.MaxAge','576',0,0,'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. '); INSERT OR IGNORE INTO "CONFIG" VALUES('Control.VEA','0',0,0,'1=enabled, 0=disabled - Use very early assignment for speech call establishment. See GSM 04.08 Section 7.3.2 for a detailed explanation of assignment types. If VEA is selected, GSM.CellSelection.NECI should be set to 1. See GSM 04.08 Sections 9.1.8 and 10.5.2.4 for an explanation of the NECI bit. Note that some handset models exhibit bugs when VEA is used and these bugs may affect performance.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Control.WatchdogMinutes','0',0,0,'Number of minutes before the radio watchdog expires and OpenBTS is restarted, set to 0 to disable.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('Core.File','core.openbts',0,0,'Constant part of core file name to use (excluding optional pid)'); +INSERT OR IGNORE INTO "CONFIG" VALUES('Core.Pid','0',0,0,'1=enabled, 0=disabled - 1 to add a .pid number to the end of the filename'); +INSERT OR IGNORE INTO "CONFIG" VALUES('Core.SaveFiles','1',0,0,'1=enabled, 0=disabled - 1 to save system files in a tarball for post-mortem analysis'); +INSERT OR IGNORE INTO "CONFIG" VALUES('Core.TarFile','/tmp/openbtsfiles.tgz',0,0,'Name of filename to save /proc files for post-mortem analysis after a crash'); INSERT OR IGNORE INTO "CONFIG" VALUES('GGSN.DNS','',1,0,'The list of DNS servers to be used by downstream clients. 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". Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GGSN.Firewall.Enable','1',1,0,'0=no firewall; 1=block MS attempted access to OpenBTS or other MS; 2=block all private IP addresses. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GGSN.IP.MaxPacketSize','1520',1,0,'Maximum size of an IP packet. Should normally be 1520. Static.'); @@ -105,26 +110,34 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('GPRS.Timers.T3195','5000',0,0,'Nonrespons INSERT OR IGNORE INTO "CONFIG" VALUES('GPRS.Uplink.KeepAlive','300',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GPRS.Uplink.Persist','4000',1,0,'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. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GPRS.advanceblocks','10',0,0,'Number of advance blocks to use in the CCCH reservation.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CCCH.AGCH.QMax','3',0,0,'Maximum number of access grants to be queued for transmission on AGCH before declaring congestion.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CCCH.CCCH-CONF','1',1,0,'CCCH configuration type. DO NOT CHANGE THIS. Value is fixed by the implementation. 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. Static.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellOptions.RADIO-LINK-TIMEOUT','15',1,0,'Seconds before declaring a physical link dead. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.BTS.RADIO_LINK_TIMEOUT','15',1,0,'Number of failed SACCH reports before the BTS declares a physical link dead. GSM 5.08 5.2 and 5.3. SACCH reports are at 480ms intervals. Similar to GSM.CellOptions.RADIO-LINK-TIMEOUT which serves the same purpose in the MS. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CCCH.BS_AG_BLKS_RES','auto',1,0,'CCCH paging configuration: number of CCCH blocks reserved for AGCH. Defined in GSM 5.02 3.3.2.3. Value auto means OpenBTS picks a reasonable value, otherwise range is 0..2 for a Combination-V beacon or 0..7 for a Combination-IV beacon. Only super-experts should set this to any value other than auto. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CCCH.BS_PA_MFRMS','2',1,0,'CCCH paging configuration: number of Paging Multiframes. Defined in GSM 5.02 3.3.2.3. Only super-experts should change this. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CCCH.CCCH-CONF','1',1,0,'CCCH configuration type. Defined in GSM 5.02 3.3.2.3. DO NOT CHANGE THIS. Value is fixed by the implementation. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CallerID.Source','auto',0,0,'The source for numeric Caller ID has traditionally been the username field. After version 4.0 this behavior will be changed to use the displayname field as it is a more accepted practice. This parameter will allow those with existing integrations to easily return to the legacy behavior until their SIP switches can be reconfigured. Additionally, using the P-Asserted-Identity header to source the Caller ID number is supported.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellOptions.RADIO-LINK-TIMEOUT','15',1,0,'Number of failed SACCH reports before an MS declares a physical link dead. GSM 5.08 5.2. SACCH reports are at 480ms intervals. This value, converted to seconds, should be less than GSM.Timer.T3109. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellSelection.CELL-RESELECT-HYSTERESIS','3',0,0,'Cell Reselection Hysteresis. See GSM 04.08 10.5.2.4, Table 10.5.23 for encoding. Encoding is $2N$ dB, values of $N$ are 0...7 for 0...14 dB.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellSelection.MS-TXPWR-MAX-CCH','0',0,0,'Cell selection parameters. See GSM 04.08 10.5.2.4.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellSelection.NCCsPermitted','0',0,0,'NCCs Permitted. An 8-bit mask of allowed NCCs. The NCC of your own network is automatically included. Unless you are coordinating with another carrier, this should be left at zero.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellSelection.NECI','1',0,0,'NECI, New Establishment Causes. This must be set to 1 if you want to support very early assignment (VEA). It can be set to 1 even if you do not use VEA, so you might as well leave it as 1. See GSM 04.08 10.5.2.4, Table 10.5.23 and 04.08 9.1.8, Table 9.9 and the Control.VEA parameter.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.CellSelection.RXLEV-ACCESS-MIN','0',0,0,'Cell selection parameters. See GSM 04.08 10.5.2.4.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.C1sFirst','0',1,0,'1=enabled, 0=disabled - Allocate C-I slots first, starting at C0T1. Otherwise, allocate C-VII slots first. Static.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.NumC1s','7',1,0,'Number of Combination-I timeslots to configure. The C-I slot carries a single full-rate TCH, used for speech calling. Static.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.NumC7s','0',1,0,'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, you must also have at least one C-VII. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.NumC1s','auto',1,0,'Number of Combination-I timeslots to configure. The Combination-1 timeslot carries a single full-rate TCH, used for speech calling. If value is auto, OpenBTS picks the value by using all otherwise unused timeslots. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.NumC7s','auto',1,0,'Number of Combination-VII timeslots to configure. The Combination-7 timeslot carries 8 SDCCHs, useful to handle high registration loads or SMS. If value is auto, OpenBTS picks the value based on beacon type and number of ARFCNs. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Channels.SDCCHReserve','0',0,0,'Number of SDCCHs to reserve for non-LUR operations. This can be used to force LUR transactions into a lower priority.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Cipher.CCHBER','0',0,0,'Probability of a bit getting toggled in a control channel burst for cracking protection.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Cipher.Encrypt','0',0,0,'1=enabled, 0=disabled - Encrypt traffic between MS and OpenBTS.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Cipher.RandomNeighbor','0',0,0,'Probability of a random neighbor being added to SI5 for cracking protection.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Cipher.ScrambleFiller','0',0,0,'1=enabled, 0=disabled - Scramble filler in layer 2 for cracking protection.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Control.GPRSMaxIgnore','5',0,0,'Ignore GPRS messages on GSM control channels. Value is number of consecutive messages to ignore.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.FailureHoldoff','5',0,0,'The number of seconds to wait before attempting another handover with a given neighbor BTS.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.LocalRSSIMin','-80',0,0,'Do not handover if downlink RXLEV (reported by the MS) is above this level (in dBm), regardless of power difference.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.ThresholdDelta','10',0,0,'A neighbor downlink signal must be this much stronger (in dB) than this downlink signal for handover to occur.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.FailureHoldoff','20',0,0,'The number of seconds to wait before attempting another handover with a given neighbor BTS.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.History.Max','32',0,0,'Maximum neighbor history to consider for handover. Units are number of measurement reports, which occur once each 480ms. '); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.Margin','15',1,0,'Unconditional handover if RXDIFF exceeds this margin. The GSM.Handover.RXLEV_DL.PenaltyTime will prevent reverse handovers for that period. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.Ny1','50',1,0,'Maximum number of repeats of the Physical Information Message during handover procedure, GSM 04.08 11.1.3. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.RXLEV_DL.History','6',0,0,'The number of 480ms periods to consider for this handover criteria.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.RXLEV_DL.Margin','10',0,0,'RXLEV_DL is reported by the MS for the serving cell and each neighbor cell. Handover attempted if the serving cell RXLEV_DL < GSM.Handover.RXLEV_DL.Target and RXDIFF > GSM.Handover.RXLEV_DL.Margin'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.RXLEV_DL.PenaltyTime','20',0,0,'After a handover a reverse handover to the originating BTS is prevented for this period of time in seconds. '); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Handover.RXLEV_DL.Target','60',0,0,'RXLEV_DL is reported by the MS for the serving cell and each neighbor cell. Handover attempted if the serving cell RXLEV_DL < GSM.Handover.RXLEV_DL.Target and RXDIFF > GSM.Handover.RXLEV_DL.Margin'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.BSIC.BCC','2',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.BSIC.NCC','0',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.CI','10',0,0,'Cell ID, 16 bits. 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.'); @@ -132,19 +145,17 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.LAC','1000',0,0,'Location ar INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.MCC','001',0,0,'Mobile country code; must be three digits. Defined in ITU-T E.212. Value of 001 for test networks.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.MNC','01',0,0,'Mobile network code, two or three digits. Assigned by your national regulator. 01 for test networks.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Identity.ShortName','Range',0,0,'Network short name, displayed on some phones. Optional but must be defined if you also want the network to send time-of-day.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.Power.Damping','75',0,0,'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.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.Power.Damping','75',0,0,'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 and SNR entirely; a value of 0 causes the MS power to change instantaneously based on RSSI or SNR, 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. '); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.Power.Max','33',0,0,'Maximum commanded MS power level in dBm.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.Power.Min','5',0,0,'Minimum commanded MS power level in dBm.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.TA.Damping','50',0,0,'Damping value for timing advance control loop.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.TA.Damping','50',0,0,'Damping value for TA [timing advance] control loop, which specifies the TA to be applied by the MS. This damping factor is meant to prevent a single bad incoming TA estimate from moving the TA value out of range. '); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MS.TA.Max','62',0,0,'Maximum allowed timing advance in symbol periods. One symbol period of round-trip delay is about 0.55 km of distance. Ignore RACH bursts with delays greater than this. Can be used to limit service range. Valid range is 1..62.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.MaxSpeechLatency','2',0,0,'Maximum allowed speech buffering latency, in 20 millisecond frames. If the jitter is larger than this delay, frames will be lost.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Neighbors','',0,0,'A list of IP addresses of neighbor BTSs available for handover. 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".'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Neighbors.Averaging','4',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Neighbors.NumToSend','31',0,0,'Maximum number of neighbors to send to handset in the neighbor list broadcast in the beacon.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Ny1','50',1,0,'Maximum number of repeats of the Physical Information Message during handover procedure, GSM 04.08 11.1.3. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RACH.AC','0x0400',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RACH.MaxRetrans','1',0,0,'Maximum RACH retransmission attempts. This is the raw parameter sent on the BCCH. See GSM 04.08 10.5.2.29 for encoding.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RACH.TxInteger','14',0,0,'Parameter to spread RACH busts over time. This is the raw parameter sent on the BCCH. See GSM 04.08 10.5.2.29 for encoding.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RACH.TxInteger','14',0,0,'Parameter to spread RACH busts over time. Warning: changing this parameter may cause intermittent channel acquisition failures. This is the raw parameter sent on the BCCH used to determine the S (delay) and T (spread) parameters. In the description for S: the first value is for beacon CCCH-CONF type 0,2,4 or 6 (non-combined CCCH) and the second value is for beacon CCCH-CONF type 1 (combined CCCH+SDCCH.) See GSM 04.08 10.5.2.29 and 3.3.1.1.2 for encoding; 05.02 clause 7 table 3 and 5 for definition of a RACH slot.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RRLP.ACCURACY','40',0,0,'Requested accuracy of location request. K in r=10(1.1**K-1), where r is the accuracy in meters. See 3GPP 03.32 Sec 6.2.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RRLP.ALMANAC.ASSIST.PRESENT','0',0,0,'1=enabled, 0=disabled - Send almanac info to mobile.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.RRLP.ALMANAC.REFRESH.TIME','24.0',0,0,'How often the almanac is refreshed, in hours.'); @@ -164,24 +175,26 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.MaxExpectedDelaySpread','4',0,0 INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.NeedBSIC','0',0,0,'1=enabled, 0=disabled - Whether the Radio type requires the full BSIC.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.MaxAttenDB','10',0,0,'Maximum transmitter attenuation level, in dB wrt full scale on the D/A output. This sets the minimum power output level in the output power control loop.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.MinAttenDB','0',0,0,'Minimum transmitter attenuation level, in dB wrt full scale on the D/A output. This sets the maximum power output level in the output power control loop.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.NumSamples','10',0,0,'Number of samples averaged by the output power control loop.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.Period','6000',0,0,'Power manager control loop master period, in milliseconds.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.SamplePeriod','2000',0,0,'Sample period for the output power control loop in milliseconds.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.TargetT3122','5000',0,0,'Target value for T3122, the random access hold-off timer, for the power control loop.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.RSSITarget','-50',0,0,'Target uplink RSSI for MS power control loop, in dB wrt to A/D full scale. Should be 6-10 dB above the noise floor.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.PowerManager.RampTime','60',0,0,'On start-up, OpenBTS ramps up power from GSM.Radio.PowerManager.MaxAttenDB to GSM.Radio.PowerManager.MinAttenDB. This value is the duration of the ramp period in seconds. '); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.RSSIAveragePeriod','8',0,0,'Number of RSSI samples averaged over when computing RSSI to compare to RSSITarget in the MS power control loop. If this number is too low the ordered MS TXPWR may bounce around unnecessarily. If this number is too high the MS TXPWR may not change quickly enough to respond to changing conditions. '); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.RSSITarget','-50',0,0,'Target uplink RSSI (Received Signal Strength Indication) for MS power control loop, in dB wrt to A/D full scale. The MS power control loop adjusts MS TXPWR (transmit power) to try to keep RSSI at this level, or to satisfy GSM.Radio.SNRTarget, whichever requires more power. Should be 6-10 dB above the noise floor.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.RxGain','47',1,0,'Receiver gain setting in dB. 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. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.SNRAveragePeriod','8',0,0,'Number of SNR samples averaged over when computing SNR to compare to SNRTarget in the MS power control loop. If this number is too low the ordered MS TXPWR may bounce around unnecessarily. If this number is too high the MS TXPWR may not change quickly enough to respond to changing conditions. This is not the control variable used for handover decisions. '); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Radio.SNRTarget','10',0,0,'The MS power control loop adjusts MS TXPWR (transmit power) to try to keep SNR (Signal to Noise Ratio) above this level.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.ShowCountry','0',0,0,'1=enabled, 0=disabled - Tell the MS to show the country name based on the MCC.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.SpeechBuffer','1',0,0,'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. '); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.Handover.Holdoff','10',1,0,'Handover will not be permitted until this time has elapsed after an initial channel seizure or handover. Static.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3103','12000',1,0,'Handover timeout in milliseconds, GSM 04.08 11.1.2. This is the timeout for a handset to seize a channel during handover. Static.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3105','50',1,0,'Milliseconds for handset to respond to physical information. GSM 04.08 11.1.2. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3103','12000',0,0,'Handover timeout in milliseconds, GSM 04.08 11.1.2. This is the timeout for a handset to seize a channel during handover.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3105','50',0,0,'Milliseconds for handset to respond to physical information. GSM 04.08 11.1.2.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3109','30000',0,0,'When a channel is released or contact with a handset is lost, the channel is deactivated for this period of time before being reused. This time must be longer than the time indicated by GSM.CellOptions.RADIO-LINK-TIMEOUT. GSM 04.08 11.1.2. '); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3113','10000',0,0,'Paging timer T3113 in milliseconds. This is the timeout for a handset to respond to a paging request. This should usually be the same as SIP.Timer.B in your VoIP network.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3122Max','255000',0,0,'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.'); -INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3122Min','10000',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('GSM.Timer.T3212','0',0,0,'Registration timer T3212 period in minutes. Should be a factor of 6. Set to 0 to disable periodic registration. Should be smaller than SIP registration period.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Log.Alarms.Max','20',0,0,'Maximum number of alarms to remember inside the application.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Log.File','',0,0,'Path to use for textfile based logging. By default, this feature is disabled. To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log. To disable again, execute "unconfig Log.File".'); INSERT OR IGNORE INTO "CONFIG" VALUES('Log.Level','NOTICE',0,0,'Default logging level when no other level is defined for a file.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('NodeManager.API.PhysicalStatus','disabled',0,0,'Which version of the PhysicalStatus event stream should be enabled.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('NodeManager.Commands.Port','45060',1,0,'Port used by the NodeManager to receive and respond to JSON formatted commands. Some examples of the available commands and their formats are available in NodeManager/JSON_Interface.txt. Static.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('NodeManager.Events.Port','45160',1,0,'Port used by the NodeManager to publish API events. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Peering.Neighbor.RefreshAge','60',0,0,'Seconds before refreshing parameters from a neighbor.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Peering.NeighborTable.Path','/var/run/NeighborTable.db',1,0,'File path for neighbor information database. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('Peering.Port','16001',1,0,'The UDP port used by the peer interface for handover. Static.'); @@ -207,6 +220,7 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Proxy.SMS','127.0.0.1:5063',0,0,'The INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Proxy.Speech','127.0.0.1:5060',0,0,'The hostname or IP address and port of the proxy to be used for normal speech calls. This is Asterisk, for example.'); INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Proxy.USSD','',0,0,'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".'); INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.RFC3428.NoTrying','0',0,0,'1=enabled, 0=disabled - Send "100 Trying" response to SIP MESSAGE, even though that violates RFC-3428.'); +INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Realm','',0,0,'SIP Realm for interop with certain switches. Filling in a host here will also activate an new REGISTER auth method.'); INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.RegistrationPeriod','90',0,0,'Registration period in minutes for MS SIP users. Should be longer than GSM T3212.'); INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.SMSC','smsc',0,0,'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".'); INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Timer.A','2000',0,0,'SIP timer A, the INVITE retry period, RFC-3261 Section 17.1.1.2, in milliseconds.'); @@ -216,7 +230,6 @@ INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Timer.F','5000',0,0,'Non-INVITE initi INSERT OR IGNORE INTO "CONFIG" VALUES('SIP.Timer.H','5000',0,0,'ACK timeout period in milliseconds.'); INSERT OR IGNORE INTO "CONFIG" VALUES('SMS.FakeSrcSMSC','0000',0,0,'Use this to fill in L4 SMSC address in SMS delivery.'); INSERT OR IGNORE INTO "CONFIG" VALUES('SMS.MIMEType','application/vnd.3gpp.sms',0,0,'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".'); -INSERT OR IGNORE INTO "CONFIG" VALUES('TRX.Args','',0,0,'Extra arguments for the Transceiver.'); INSERT OR IGNORE INTO "CONFIG" VALUES('TRX.IP','127.0.0.1',1,0,'IP address of the transceiver application. Static.'); INSERT OR IGNORE INTO "CONFIG" VALUES('TRX.MinimumRxRSSI','-63',0,0,'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.'); INSERT OR IGNORE INTO "CONFIG" VALUES('TRX.Port','5700',1,0,'IP port of the transceiver application. Static.'); diff --git a/apps/OpenBTSCLI.cpp b/apps/OpenBTSCLI.cpp index 21c9574..c3f46ce 100644 --- a/apps/OpenBTSCLI.cpp +++ b/apps/OpenBTSCLI.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,11 @@ #include #include #include +#include +#include +#include +#include +#include #define HAVE_LIBREADLINE @@ -43,148 +49,362 @@ # include #endif +// Note that we ONLY use this for the name of the file to use. -#define DEFAULT_CMD_PATH "/var/run/command" -#define DEFAULT_RSP_PATH "./response" +// The assumption is that OpenBTS and OpenBTSCLI were built together. +#include "Globals.h" + +struct sockaddr_in sa; +static char *progname = (char*) ""; -int main(int argc, char *argv[]) +char target[64] = "127.0.0.1"; +int port = 49300; + +static void banner() { - - printf("OpenBTS Commnd Line Interface (CLI) utility\n"); - printf("Copyright 2012, 2013 Range Networks, Inc.\n"); + static int bannerPrinted = false; + if (bannerPrinted) return; + bannerPrinted = true; + printf("OpenBTS Command Line Interface (CLI) utility\n"); + printf("Copyright 2012, 2013, 2014 Range Networks, Inc.\n"); printf("Licensed under GPLv2.\n"); #ifdef HAVE_LIBREADLINE printf("Includes libreadline, GPLv2.\n"); #endif - const char* cmdPath = DEFAULT_CMD_PATH; - if (argc!=1) { - cmdPath = argv[1]; +} + +static void oops(const char *fmt, ...) +{ + banner(); + va_list ap; + va_start(ap,fmt); + vprintf(fmt,ap); + va_end(ap); + printf(" OpenBTSCLI options:\n" + " -t ip_address : specify IP address of target machine on which OpenBTS is running\n" + " -p port_number : specify OpenBTS port number\n" + " -c command .. : execute this OpenBTS command and exit; also suppresses extraneous banners\n" + " -d : read one OpenBTS command, execute it and exit\n" + "If -d or -c not specified, read OpenBTS commands and execute them.\n" + "To see OpenBTS help, type the 'help' command to OpenBTS, or for example: OpenBTSCLI -c help\n" + ); + exit(1); +} + +bool doCmd(int fd, char *cmd) // return false to abort/exit +{ + const int bufsz = 100000; + char resbuf[bufsz]; + int nread = 0; + + int len = strlen(cmd); + int svlen = len; + len = htonl(len); + if (send(fd, &len, sizeof(len), 0) < 0) { + perror("sending stream"); + return false; + } + len = svlen; + if (send(fd, cmd, strlen(cmd), 0) < 0) { + perror("sending stream"); + return false; + } + nread = recv(fd, &len, sizeof(len), 0); + if (nread < 0) { + perror("receiving stream"); + return false; + } + if (nread == 0) { + printf("Remote connection closed\n"); + exit(1); + } + if (nread != (int) sizeof(len)) { + printf("Partial read of length from server, expected %d, got %d\n", sizeof(len), len); + exit(1); + } + len = ntohl(len); + if (len >= bufsz-1) { + printf("Response of %d bytes is too long\n", len); + exit(1); + } + int off = 0; + svlen = len; + while(len != 0) { + nread = recv(fd,&resbuf[off],len,0); + if (nread < 0) { + perror("receiving stream"); + return false; + } + if (nread == 0) { + printf("Remote connection closed\n"); + exit(1); + } + off += nread; + len -= nread; + } + nread = svlen; + + if (nread<0) { + perror("receiving response"); + return false; + } + resbuf[nread] = '\0'; + if (strcmp("restart", cmd) == 0) + { + printf("OpenBTS has been shut down or restarted.\n"); + printf("You will need to restart OpenBTSCLI after it restarts.\n"); + return false; + } + if (strcmp("shutdown", cmd) == 0) + { + printf("OpenBTS has been shut down or restarted.\n"); + printf("You will need to restart OpenBTSCLI after it restarts.\n"); + return false; + } + printf("%s\n",resbuf); + if (nread==(bufsz-1)) { + printf("(response truncated at %d characters)\n",nread); + } + return true; +} + +int main(int argc, char *argv[]) +{ + bool isBTSDo = false; // If set, execute one command without prompting, then exit. + std::string sCommand(""); + progname = argv[0]; + argc--; argv++; // Skip program name. + while(argc > 0) { + if (argv[0][0] == '-') { + if (strlen(argv[0]) > 2) { + oops("Invalid option '%s'\n", argv[0]); + exit(1); + } + switch(argv[0][1]) { + case 'd': // OpenBTSDo interface + isBTSDo = true; + break; + case 'c': // Run command on command line then exit. + isBTSDo = true; + if (argc == 1) { + oops("Missing argument to -c\n"); + } + { + // Gather up the command line. + for (int j = 1; j < argc; j++) { + sCommand += argv[j]; + sCommand += " "; + } + } + argc = 1; // terminates while loop. + break; + case 'p': // TCP Port number + argc--, argv++; + port = atoi(argv[0]); + printf("TCP %d\n", port); + break; + case 't': // target + argc--, argv++; + snprintf(target, sizeof(target)-1, "%s", argv[0]); + break; + default: + oops("Invalid option '%s'\n", argv[0]); + exit(1); // NOTREACHED but makes the compiler happy. + } + argc--; + argv++; + } else { + oops("Invalid argument '%s'\n", argv[0]); + exit(1); // NOTREACHED but makes the compiler happy. + } } - char rspPath[200]; - sprintf(rspPath,"/tmp/OpenBTS.console.%d.%8lx",getpid(),time(NULL)); + // Note that this only works if we are communicating across localhost. + // TODO: Fix this so we can push the file across the socket if done + // remotely. + // Don't do this if running the single line configuration methods, we + // will assume running from an external script is mostly a testing + // thing, not a deployment thing for an actual base station. + if (sCommand.c_str()[0] == '\0') { + banner(); + printf("Connecting to %s:%d...\n", target, port); + } - printf("command socket path is %s\n", cmdPath); + int sock = -1; + char prompt[16] = "OpenBTS> "; - char prompt[strlen(cmdPath) + 20]; - sprintf(prompt,"OpenBTS> "); + // Define this stuff "globally" as it's needed in various places + memset(&sa, 0, sizeof(sa)); // the socket - int sock = socket(AF_UNIX,SOCK_DGRAM,0); + sock = socket(AF_INET,SOCK_STREAM,0); if (sock<0) { - perror("opening datagram socket"); + perror("opening stream socket"); exit(1); } // destination address - struct sockaddr_un cmdSockName; - cmdSockName.sun_family = AF_UNIX; - strcpy(cmdSockName.sun_path,cmdPath); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + if (inet_pton(AF_INET, target, &sa.sin_addr) <= 0) { + oops("unable to convert target to an IP address\n"); + } - // locally bound address - struct sockaddr_un rspSockName; - rspSockName.sun_family = AF_UNIX; - char rmcmd[strlen(rspPath)+10]; - sprintf(rmcmd,"rm -f %s",rspPath); - system(rmcmd); - strcpy(rspSockName.sun_path,rspPath); - if (bind(sock, (struct sockaddr *) &rspSockName, sizeof(struct sockaddr_un))) { - perror("binding name to datagram socket"); + if (0) { + // (pat) I used this code for testing. + // If you wanted to specify the port you were binding from, this is how you would do it... + // We dont use this code - we let the connect system call pick the port. + struct sockaddr_in sockAddrBuf; + memset(&sockAddrBuf,0,sizeof(sockAddrBuf)); // overkill. + sockAddrBuf.sin_family = AF_INET; + sockAddrBuf.sin_addr.s_addr = INADDR_ANY; + sockAddrBuf.sin_port = htons(13011); + if (bind(sock, (struct sockaddr *) &sockAddrBuf, sizeof(struct sockaddr_in))) { // Bind the socket to our assigned port. + printf("bind call failed: %s",strerror(errno)); + exit(2); + } + } + + if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + perror("connect stream socket"); + fprintf(stderr, "Is OpenBTS running?\n"); exit(1); } - printf("response socket bound to %s\n",rspSockName.sun_path); #ifdef HAVE_LIBREADLINE - // start console - using_history(); - - static const char * const history_file_name = "/.openbts_history"; char *history_name = 0; - char *home_dir = getenv("HOME"); + if (!isBTSDo) + { + // start console + using_history(); - if(home_dir) { - size_t home_dir_len = strlen(home_dir); - size_t history_file_len = strlen(history_file_name); - size_t history_len = home_dir_len + history_file_len + 1; - if(history_len > home_dir_len) { - if(!(history_name = (char *)malloc(history_len))) { - perror("malloc failed"); - exit(2); - } - memcpy(history_name, home_dir, home_dir_len); - memcpy(history_name + home_dir_len, history_file_name, - history_file_len + 1); - read_history(history_name); - } + static const char * const history_file_name = "/.openbts_history"; + char *home_dir = getenv("HOME"); + + if(home_dir) { + size_t home_dir_len = strlen(home_dir); + size_t history_file_len = strlen(history_file_name); + size_t history_len = home_dir_len + history_file_len + 1; + if(history_len > home_dir_len) { + if(!(history_name = (char *)malloc(history_len))) { + perror("malloc failed"); + exit(2); + } + memcpy(history_name, home_dir, home_dir_len); + memcpy(history_name + home_dir_len, history_file_name, + history_file_len + 1); + read_history(history_name); + } + } } #endif - printf("Remote Interface Ready.\nType:\n \"help\" to see commands,\n \"version\" for version information,\n \"notices\" for licensing information.\n \"quit\" to exit console interface\n"); + if (!isBTSDo) + printf("Remote Interface Ready.\nType:\n \"help\" to see commands,\n \"version\" for version information,\n \"notices\" for licensing information,\n \"quit\" to exit console interface.\n"); - while (1) { - + if (sCommand.c_str()[0] != '\0') { + doCmd(sock, (char *)sCommand.c_str()); + } else + while (1) + { #ifdef HAVE_LIBREADLINE - char *cmd = readline(prompt); - if (!cmd) break; - if (*cmd) add_history(cmd); + char *cmd = readline(isBTSDo ? NULL : prompt); + if (!cmd) continue; + if (cmd[0] == '\0') continue; + if (!isBTSDo) + if (*cmd) add_history(cmd); #else // HAVE_LIBREADLINE - printf("%s",prompt); - fflush(stdout); - char *inbuf = (char*)malloc(200); - char *cmd = fgets(inbuf,199,stdin); - if (!cmd) continue; - // strip trailing CR - cmd[strlen(cmd)-1] = '\0'; + if (!isBTSDo) + { + printf("%s",prompt); + fflush(stdout); + } + char *inbuf = (char*)malloc(BUFSIZ); + char *cmd = fgets(inbuf,BUFSIZ-1,stdin); + if (!cmd) + { + if (isBTSDo) + break; + continue; + } + if (cmd[0] == '\0') continue; + // strip trailing CR + cmd[strlen(cmd)-1] = '\0'; #endif - - // local quit? - if (strcmp(cmd,"quit")==0) { - printf("closing remote console\n"); - break; - } - // shell escape? - if (cmd[0]=='!') { - system(cmd+1); - continue; - } - // 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 (OpenBTS daemon) running?\n"); - continue; - } - free(cmd); - const int bufsz = 100000; - char resbuf[bufsz]; - int nread = recv(sock,resbuf,bufsz-1,0); - if (nread<0) { - perror("receiving response"); - continue; - } - resbuf[nread] = '\0'; - printf("%s\n",resbuf); - if (nread==(bufsz-1)) printf("(response truncated at %d characters)\n",nread); - } + if (!isBTSDo) + { + // local quit? + if (strcmp(cmd,"quit")==0) { + printf("closing remote console\n"); + break; + } + // shutdown via upstart + if (strcmp(cmd,"shutdown")==0) { + printf("terminating openbts\n"); + if (getuid() == 0) + system("stop openbts"); + else + { + printf("If prompted, enter the password you use for sudo\n"); + system("sudo stop openbts"); + } + break; + } + // shell escape? + if (cmd[0]=='!') { + int i = system(cmd+1); + if (i < 0) + { + perror("system"); + } + continue; + } + } + char *pCmd = cmd; + while(isspace(*pCmd)) pCmd++; // skip leading whitespace + if (*pCmd) + { + if (doCmd(sock, cmd) == false) + { + bool sd = false; + if (strcmp(cmd,"shutdown")==0) + sd = true; + else if (strcmp(cmd,"restart")==0) + sd = true; + free(cmd); + //{ + if (isBTSDo) + break; + if (sd) + break; + continue; + //} + } + } + free(cmd); + if (isBTSDo) + break; + } #ifdef HAVE_LIBREADLINE - if(history_name) { - int e = write_history(history_name); - if(e) { - fprintf(stderr, "error: history: %s\n", strerror(e)); - } - free(history_name); - history_name = 0; + if (!isBTSDo) + { + if(history_name) + { + int e = write_history(history_name); + if(e) { + fprintf(stderr, "error: history: %s\n", strerror(e)); + } + free(history_name); + history_name = 0; + } } #endif close(sock); - - // Delete the path to limit clutter in /tmp. - sprintf(rmcmd,"rm -f %s",rspPath); - system(rmcmd); } diff --git a/apps/OpenBTSConfig.h b/apps/OpenBTSConfig.h new file mode 100644 index 0000000..b8f09ab --- /dev/null +++ b/apps/OpenBTSConfig.h @@ -0,0 +1,60 @@ +/* +* Copyright 2014 Range Networks, Inc. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#ifndef _CONFIGKEYS_H_ +#define _CONFIGKEYS_H_ 1 + +#include + +// pat 3-2014: Added a better way to get these config values. +class OpenBTSConfig : public ConfigurationTable { + public: + OpenBTSConfig(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema) : + ConfigurationTable(filename, wCmdName, wSchema) + {} + OpenBTSConfig(void) {}; // used by CLI + + void configUpdateKeys(); + + // This structure mirrors the config variable names in GetConfigurationKeys.cpp. + // Any value added here must also be added to configUpdateKeys(), which function is + // called after any change to the config to update the values in this structure. + struct GSM { + struct Handover { + int FailureHoldoff; + int Margin; + int Ny1; + + struct History { int Max; } History; + struct Noise { int Factor; } Noise; + + struct RXLEV_DL { float Target; int History, Margin, PenaltyTime; } RXLEV_DL; + } Handover; + struct { + struct Power { int Min, Max, Damping; } Power; + struct TA { int Damping, Max; } TA; + } MS; + struct { + int T3103, T3105, T3109, T3113, T3212; + } Timer; + struct { + int RADIO_LINK_TIMEOUT; + } BTS; + } GSM; +}; + +extern OpenBTSConfig gConfig; +#endif diff --git a/apps/OpenBTSDo.cpp b/apps/OpenBTSDo.cpp deleted file mode 100644 index 870bab5..0000000 --- a/apps/OpenBTSDo.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* -* Copyright 2011, 2012, 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. -* -* 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 . - -*/ - - -// KEEP THIS FILE CLEAN FOR GPL PUBLIC RELEASE. - -#include -#include -#include -#include -#include -#include -#include - - -#define DEFAULT_CMD_PATH "/var/run/command" - -int main(int argc, char *argv[]) -{ - const char* cmdPath = DEFAULT_CMD_PATH; - if (argc!=1) { - cmdPath = argv[1]; - } - - char rspPath[200]; - sprintf(rspPath,"/tmp/OpenBTS.do.%d",getpid()); - - // the socket - int sock = socket(AF_UNIX,SOCK_DGRAM,0); - if (sock<0) { - perror("opening datagram socket"); - exit(1); - } - - // destination address - struct sockaddr_un cmdSockName; - cmdSockName.sun_family = AF_UNIX; - strcpy(cmdSockName.sun_path,cmdPath); - - // locally bound address - struct sockaddr_un rspSockName; - rspSockName.sun_family = AF_UNIX; - char rmcmd[strlen(rspPath)+10]; - sprintf(rmcmd,"rm -f %s",rspPath); - system(rmcmd); - strcpy(rspSockName.sun_path,rspPath); - if (bind(sock, (struct sockaddr *) &rspSockName, sizeof(struct sockaddr_un))) { - perror("binding name to datagram socket"); - exit(1); - } - - - char *inbuf = (char*)malloc(200); - char *cmd = fgets(inbuf,199,stdin); - if (!cmd) exit(0); - cmd[strlen(cmd)-1] = '\0'; - - if (sendto(sock,cmd,strlen(cmd)+1,0,(struct sockaddr*)&cmdSockName,sizeof(cmdSockName))<0) { - perror("sending datagram"); - exit(1); - } - - // buffer to be sized as necessary to accomodate config data length - const int bufsz = 8500; - char resbuf[bufsz]; - int nread = recv(sock,resbuf,bufsz-1,0); - if (nread<0) { - perror("receiving response"); - exit(1); - } - resbuf[nread] = '\0'; - printf("%s\n",resbuf); - - close(sock); - - // Delete the path to limit clutter in /tmp. - sprintf(rmcmd,"rm -f %s",rspPath); - system(rmcmd); -} diff --git a/apps/README.DatabaseCreation b/apps/README.DatabaseCreation new file mode 100644 index 0000000..90d3639 --- /dev/null +++ b/apps/README.DatabaseCreation @@ -0,0 +1,14 @@ +OpenBTS requires a configuration database, OpenBTS.db. + +In an intitial installation, OpenBTS.db is created from OpenBTS.example.sql. + +To do that: + +sh> sqlite3 OpenBTS.db +sqlite3> .read OpenBTS.example.sql +sqlite3> .quit + +Done! + +BTW: If OpenBTS.db already exists, you will need to delete that file before doing this procedure. + diff --git a/config/Makefile.am b/config/Makefile.am index 69263e3..6cf3293 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/configure.ac b/configure.ac index 035da31..36e36f2 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,6 @@ dnl dnl Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +dnl Copyright 2014 Range Networks, Inc. dnl dnl This software is distributed under the terms of the GNU Public License. dnl See the COPYING file in the main directory for details. @@ -18,7 +19,7 @@ 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,4.0TRUNK) +AC_INIT(openbts,5.0.0-prealpha) AC_PREREQ(2.57) AC_CONFIG_SRCDIR([config/Makefile.am]) AC_CONFIG_AUX_DIR([.]) @@ -97,18 +98,18 @@ AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "xyes"]) 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]) + AC_MSG_ERROR([/usr/local/include/zmq.h not found. Install the libzmq3-dev package manually or run $ 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]) + AC_MSG_ERROR([/usr/local/include/zmq.hpp not found. Install the libzmq3-dev package manually or run $ 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_CHECK_LIB(zmq, zmq_init, ,[AC_MSG_ERROR([Cannot link with -lzmq. Install the libzmq3-dev package manually or run $ 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])]) + [AC_MSG_ERROR([no. Install the libzmq3-dev package manually or run $ sudo ./NodeManager/install_libzmq.sh])]) dnl Check for liba53 if test ! -r "/usr/include/a53.h" -a ! -r "/usr/local/include/a53.h"; then @@ -187,7 +188,6 @@ AC_CONFIG_FILES([\ Peering/Makefile \ NodeManager/Makefile \ Scanning/Makefile \ - sqlite3/Makefile \ ]) AC_OUTPUT diff --git a/debian/changelog b/debian/changelog index 932e11d..b33e404 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -openbts (4.1) unstable; urgency=low +openbts (5.0) unstable; urgency=low * Test diff --git a/debian/control b/debian/control index 32c0a37..9f86995 100755 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: comm Priority: optional Maintainer: Range Networks, Inc. Homepage: http://www.rangenetworks.com/ -Build-Depends: build-essential, debhelper (>= 7), pkg-config, autoconf, libtool, range-libzmq, liba53, libortp-dev, libusb-1.0-0-dev, libreadline-dev +Build-Depends: build-essential, debhelper (>= 7), pkg-config, autoconf, libtool, libortp-dev, libsqlite3-dev, libusb-1.0-0-dev, libreadline-dev, libssl-dev, libzmq3-dev, libzmq3, liba53 Standards-Version: 3.7.3 Package: openbts @@ -11,5 +11,5 @@ Section: comm Priority: optional Architecture: any Essential: no -Depends: sqlite3, libusb-1.0-0, libortp8, libc6, pkg-config, range-libzmq, liba53, libreadline6 +Depends: sqlite3, libusb-1.0-0, libortp8, libc6, pkg-config, libzmq3, liba53, libreadline6, libssl1.0.0 Description: Range Networks - OpenBTS GSM+GPRS Radio Access Network Node diff --git a/debian/postinst b/debian/postinst index d66d132..c5e39bb 100755 --- a/debian/postinst +++ b/debian/postinst @@ -30,10 +30,7 @@ fi 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 } diff --git a/debian/rules b/debian/rules index 6bfa704..c3c9be3 100755 --- a/debian/rules +++ b/debian/rules @@ -109,7 +109,7 @@ binary-common: # dh_perl dh_makeshlibs dh_installdeb -# dh_shlibdeps + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info dh_gencontrol dh_md5sums dh_builddeb diff --git a/doc/Makefile.am b/doc/Makefile.am index 887b2d9..badcb9c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,5 +1,6 @@ # # Copyright 2008 Free Software Foundation, Inc. +# Copyright 2014 Range Networks, 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/sqlite3 b/sqlite3 deleted file mode 160000 index effc8fe..0000000 --- a/sqlite3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit effc8fe4744285c07e3710ab97231537a27b7997