/**@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 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 "ControlCommon.h" #include "CBS.h" #include #include #include #include #include #include // (pat) See GSM 03.41. SMSCB is a broadcast message service on a dedicated broadcast channel and unrelated to anything else. // Broadcast messages are completely unacknowledged. They are repeated perpetually. namespace Control { static sqlite3 *sCBSDB = NULL; static const char* createSMSCBTable = { "CREATE TABLE IF NOT EXISTS SMSCB (" "GS INTEGER NOT NULL, " "MESSAGE_CODE INTEGER NOT NULL, " "UPDATE_NUMBER INTEGER NOT NULL, " "MSGID INTEGER NOT NULL, " "LANGUAGE_CODE INTEGER NOT NULL, " // (pat) A 2 character string encoded as a 2 byte integer. "MESSAGE TEXT NOT NULL, " "SEND_TIME INTEGER DEFAULT 0, " "SEND_COUNT INTEGER DEFAULT 0" ")" }; static sqlite3* CBSConnectDatabase(bool whine) { string path = gConfig.getStr("Control.SMSCB.Table"); if (path.length() == 0) { return NULL; } if (sCBSDB) { return sCBSDB; } int rc = sqlite3_open(path.c_str(),&sCBSDB); if (rc) { if (whine) LOG(EMERG) << "Cannot open Cell Broadcast Service database on path " << path << ": " << sqlite3_errmsg(sCBSDB); sqlite3_close(sCBSDB); sCBSDB = NULL; return NULL; } if (!sqlite3_command(sCBSDB,createSMSCBTable)) { if (whine) LOG(EMERG) << "Cannot create Cell Broadcast Service table"; return NULL; } // Set high-concurrency WAL mode. if (!sqlite3_command(sCBSDB,enableWAL)) { if (whine) LOG(EMERG) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(sCBSDB); } return sCBSDB; } static int cbsRunQuery(string query) { if (!CBSConnectDatabase(true)) { return 0; } LOG(DEBUG) << LOGVAR(query); if (! sqlite_command(sCBSDB,query.c_str())) { LOG(INFO) << "CBS SQL query failed"< &result, string text) { if (!CBSConnectDatabase(true)) { return false; } result.clear(); sqlite3_stmt *stmt = NULL; string query = format("SELECT %s FROM SMSCB ",crackRowNames); if (text.size()) { query += format("WHERE MESSAGE=='%s'",text); } int rc; if ((rc = sqlite3_prepare_statement(sCBSDB,&stmt,query.c_str()))) { LOG(DEBUG) << "sqlite3_prepare_statement failed code="< &fields,string separator) { string result; int cnt = 0; for (vector::iterator it = fields.begin(); it != fields.end(); it++, cnt++) { if (cnt) result.append(separator); result.append(*it); } return result; } static void update1field(const char *col, unsigned uval, vector*cols, vector*vals, vector*both) { if (cols) { cols->push_back(col); } if (vals) { vals->push_back(format("%u",uval)); } if (both) { both->push_back(format("%s=%u",col,uval)); } } static void update1field(const char *col, string sval, vector*cols, vector*vals, vector*both) { if (cols) { cols->push_back(col); } if (vals) { vals->push_back(format("'%s'",sval)); } if (both) { both->push_back(format("%s='%s'",col,sval)); } } // The all flag is for INSERT which must update all the DB fields with the "NOT NULL" option. Oops. static void CBMessage2SQLFields(CBMessage &msg, vector*cols, vector*vals, vector*both, bool all) { if (all || msg.mGS_change) { update1field("GS",msg.mGS,cols,vals,both); } if (all || msg.mMessageCode_change) { update1field("MESSAGE_CODE",msg.mMessageCode,cols,vals,both); } if (all || msg.mUpdateNumber_change) { update1field("UPDATE_NUMBER",msg.mUpdateNumber,cols,vals,both); } if (all || msg.mMessageId_change) { update1field("MSGID",msg.mMessageId,cols,vals,both); } if (all || msg.mLanguageCode_change) { update1field("LANGUAGE_CODE",msg.mLanguageCode,cols,vals,both); } //if (all || msg.mLanguage_change) { update1field("LANGUAGE_CODE",msg.getLanguageCode(),cols,vals,both); } if (all || msg.mMessageText.size()) { update1field("MESSAGE",msg.mMessageText,cols,vals,both); } // ROWID is a synthetic field. if (msg.mRowId_change) { update1field("ROWID",msg.mRowId,cols,vals,both); } } // Deletes all messages that match msg, which must have at least one field set. int CBSDeleteMessage(CBMessage &msg) { vector fields; CBMessage2SQLFields(msg,NULL,NULL,&fields,false); if (fields.size()) { string query = format("DELETE FROM SMSCB WHERE %s",strJoin(fields,",")); return cbsRunQuery(query); } else { return 0; // If the CBMessage contained no fields, dont delete all messages, just return 0. } } int CBSAddMessage(CBMessage &msg, string &errorMsg) { if (msg.mMessageText.size() == 0) { errorMsg = string("Attempt to add message with no text"); return 0; } if (!CBSConnectDatabase(true)) { errorMsg = string("could not write to database"); return 0; } // Does the message exist already? vector existing; CBSGetMessages(existing,msg.mMessageText); for (vector::iterator it = existing.begin(); it != existing.end(); it++) { if (msg.match(*it)) { errorMsg = string("Attempt to add duplicate message"); return 0; } } // TODO: If the message matches an existing we should increment the update_number. // like this: INSERT OR REPLACE INTO SMSCB (GS,MESSAGE_CODE,UPDATE_NUMBER,MSGID,LANGUAGE_CODE,MESSAGE) VALUES (0,0,0,0,0,'whatever') // Cannot use REPLACE: REPLACE only works if INSERT would generate a constraint conflict, ie, duplicate UNIQ field. vector cols, vals; // We must update all the fields with the history "NOT NULL" value in the database. Oops. CBMessage2SQLFields(msg, &cols, &vals, NULL,true); string query = format("INSERT INTO SMSCB (%s) VALUES (%s)",strJoin(cols,","),strJoin(vals,",")); return cbsRunQuery(query); } static void encode7(char mc, int &shift, unsigned int &dp, int &buf, char *thisPage) { buf |= (mc & 0x7F) << shift--; if (shift < 0) { shift = 7; } else { thisPage[dp++] = buf & 0xFF; buf = buf >> 8; } } // (pat 8-2014) I added the CBMessage class and added CLI cbscmd to manipulate the database, // but I did not change the basic encoding and transmit logic below nor did I enable the language option. static void CBSSendMessage(CBMessage &msg, GSM::CBCHLogicalChannel* CBCH) { // Figure out how many pages to send. const unsigned maxLen = 40*15; unsigned messageLen = msg.mMessageText.length(); if (messageLen>maxLen) { LOG(ALERT) << "SMSCB message ID " << msg.mMessageId << " to long; truncating to " << maxLen << " char."; messageLen = maxLen; } unsigned numPages = messageLen / 40; if (messageLen % 40) numPages++; unsigned mp = 0; LOG(INFO) << "sending message ID=" << msg.mMessageId << " code=" << msg.mMessageCode << " in " << numPages << " pages: " << msg.mMessageText; // Break into pages and send each page. for (unsigned page=0; page> 8; thisPage[dp++] = msg.mLanguageCode & 0x0ff; while (dp<82 && mp> 8, shift, dp, buf, thisPage); // encode7(languageCode & 0xFF, shift, dp, buf, thisPage); // encode7('\r', shift, dp, buf, thisPage); while (dp<81 && mpl2sendm(message); } } string CBMessage::cbtext() { ostringstream os; os <