-
Notifications
You must be signed in to change notification settings - Fork 372
Expand file tree
/
Copy pathCBS.cpp
More file actions
executable file
·380 lines (332 loc) · 12.7 KB
/
CBS.cpp
File metadata and controls
executable file
·380 lines (332 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/**@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 <GSMLogicalChannel.h>
#include <GSMConfig.h>
#include <GSMSMSCBL3Messages.h>
#include <Reporting.h>
#include <sqlite3.h>
#include <sqlite3util.h>
// (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"<<LOGVAR(query);
return 0;
}
int changes = sqlite3_changes(sCBSDB);
return changes;
}
// The crackRowNames, crackCBMessageFromDB and CBSGetMessages must be kept matching.
// These row names match crackCBMessageFromDB.
static const char *crackRowNames = "GS,MESSAGE_CODE,UPDATE_NUMBER,MSGID,LANGUAGE_CODE,MESSAGE,SEND_COUNT,SEND_TIME,ROWID";
static void crackCBMessageFromDB(CBMessage &result, sqlite3_stmt* stmt)
{
result.setGS((CBMessage::GeographicalScope)sqlite3_column_int(stmt,0));
result.setMessageCode((unsigned)sqlite3_column_int(stmt,1));
result.setUpdateNumber((unsigned)sqlite3_column_int(stmt,2));
result.setMessageId((unsigned)sqlite3_column_int(stmt,3));
result.setLanguageCode((unsigned)sqlite3_column_int(stmt,4));
result.setMessageText(string((const char*)sqlite3_column_text(stmt,5)));
result.mSendCount = (unsigned)sqlite3_column_int(stmt,6);
result.mSendTime = (unsigned)sqlite3_column_int(stmt,7);
result.mRowId = (unsigned)sqlite3_column_int(stmt,8);
}
// Return false on error; return true on success, which means we accessed the table - the result size is the number of entries.
bool CBSGetMessages(vector<CBMessage> &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="<<rc;
return false;
}
while (SQLITE_ROW == (rc=sqlite3_run_query(sCBSDB,stmt))) {
CBMessage msg;
crackCBMessageFromDB(msg, stmt);
result.push_back(msg);
LOG(DEBUG) <<LOGVAR(rc) <<LOGVAR(msg.cbtext());
}
sqlite3_finalize(stmt); // Finalize ASAP to unlock the database.
LOG(DEBUG) <<"final"<<LOGVAR(rc);
return true;
}
int CBSClearMessages()
{
return cbsRunQuery("DELETE FROM SMSCB WHERE 1");
}
static string strJoin(vector<string> &fields,string separator)
{
string result;
int cnt = 0;
for (vector<string>::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<string>*cols, vector<string>*vals, vector<string>*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<string>*cols, vector<string>*vals, vector<string>*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<string>*cols, vector<string>*vals, vector<string>*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<string> 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<CBMessage> existing;
CBSGetMessages(existing,msg.mMessageText);
for (vector<CBMessage>::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<string> 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<numPages; page++) {
// Encode the mesage into pages.
// We use UCS2 encoding for the message,
// even though the input text is ASCII for now.
char thisPage[82];
unsigned dp = 0;
int codingScheme;
// (pat) If we want to implement languages we should support DCS of GSM 3.38 first, not UCS2.
if (false && msg.mLanguageCode) {
codingScheme = 0x11; // UCS2
thisPage[dp++] = msg.mLanguageCode >> 8;
thisPage[dp++] = msg.mLanguageCode & 0x0ff;
while (dp<82 && mp<messageLen) {
// UCS2 uses 16-bit characters.
// (pat) Setting the high byte to 0 is just wrong - the user would want to put the 16-bit characters in the database,
// that is the point of using UCS2.
thisPage[dp++] = 0;
thisPage[dp++] = msg.mMessageText[mp++];
}
while (dp<82) { thisPage[dp++] = 0; thisPage[dp++]='\r'; }
} else {
// 03.38 section 5
codingScheme = 0x10; // 'default' codiing scheme
int buf = 0;
int shift = 0;
// The spec (above) says to put this language stuff in, but it doesn't work on my samsung galaxy y. (dbrown)
// encode7(languageCode >> 8, shift, dp, buf, thisPage);
// encode7(languageCode & 0xFF, shift, dp, buf, thisPage);
// encode7('\r', shift, dp, buf, thisPage);
while (dp<81 && mp<messageLen) {
encode7(msg.mMessageText[mp++], shift, dp, buf, thisPage);
}
while (dp<81) { encode7('\r', shift, dp, buf, thisPage); }
thisPage[dp++] = buf;
}
// Format the page into an L3 message.
GSM::L3SMSCBMessage message(
GSM::L3SMSCBSerialNumber(msg.mGS,msg.mMessageCode,msg.mUpdateNumber),
GSM::L3SMSCBMessageIdentifier(msg.mMessageId),
GSM::L3SMSCBDataCodingScheme(codingScheme),
GSM::L3SMSCBPageParameter(page+1,numPages),
GSM::L3SMSCBContent(thisPage)
);
// Send it.
LOG(DEBUG) << "sending L3 message page " << page+1 << ": " << message;
CBCH->l2sendm(message);
}
}
string CBMessage::cbtext()
{
ostringstream os;
os <<LOGVARM(mGS)<<LOGVARM(mMessageCode)<<LOGVARM(mUpdateNumber)<<LOGVARM(mMessageId)<<LOGVAR(mMessageText);
return os.str();
}
void CBMessage::cbtext(std::ostream &os)
{
os <<cbtext();
}
// (pat) The SMSCB name is misleading; this has nothing to do with SMS. It is Cell Broadcast Service.
void* SMSCBSender(void*)
{
// Connect to the database.
// Just keep trying until it connects.
bool whine = true;
while (!CBSConnectDatabase(whine)) { sleep(2); whine = false; }
LOG(NOTICE) << "SMSCB service starting";
// Get a channel.
GSM::CBCHLogicalChannel* CBCH = gBTS.getCBCH();
while (1) {
// Get the next message ready to send.
// (pat) The "ROWID" is not a column name, it is sql-lite magic to return the row number.
int sqlresult;
CBMessage msg;
{
string query = format("SELECT %s FROM SMSCB WHERE SEND_TIME==(SELECT min(SEND_TIME) FROM SMSCB)",crackRowNames);
sqlite3_stmt *stmt;
if (sqlite3_prepare_statement(sCBSDB,&stmt,query.c_str())) {
LOG(ALERT) << "Cannot access SMSCB database: " << sqlite3_errmsg(sCBSDB);
sleep(1);
continue;
}
// Send the message or sleep briefly.
sqlresult = sqlite3_run_query(sCBSDB,stmt);
if (sqlresult == SQLITE_ROW) {
crackCBMessageFromDB(msg, stmt);
}
// Finalize ASAP to unlock the database.
sqlite3_finalize(stmt);
}
LOG(DEBUG) <<LOGVAR(sqlresult) <<LOGVAR(msg.cbtext());
switch (sqlresult) {
case SQLITE_ROW: {
CBSSendMessage(msg,CBCH);
// Update send count and send time in the database.
char query[100];
snprintf(query,100,"UPDATE SMSCB SET SEND_TIME = %u, SEND_COUNT = %u WHERE ROWID == %u",
(unsigned)time(NULL), msg.mSendCount+1, (unsigned)msg.mRowId);
if (!sqlite3_command(sCBSDB,query)) LOG(ALERT) << "SMSCB database timestamp update failed: " << sqlite3_errmsg(sCBSDB);
continue;
}
case SQLITE_DONE:
// Empty database.
break;
default:
LOG(ALERT) << "SCSCB database failure: " << sqlite3_errmsg(sCBSDB);
break;
}
sleep(1);
}
// keep the compiler from whining
return NULL;
}
}; // namespace
// vim: ts=4 sw=4