Bumped copyright dates for 2013
[barry.git] / src / r_sms.cc
blobd5a32c3dbecbd804c4475d490b86713f8f9b1442
1 ///
2 /// \file r_sms.cc
3 /// Record parsing class for the SMS database.
4 ///
6 /*
7 Copyright (C) 2005-2013, Net Direct Inc. (http://www.netdirect.ca/)
8 Copyright (C) 2009, Ryan Li(ryan@ryanium.com)
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 See the GNU General Public License in the COPYING file at the
20 root directory of this project for more details.
23 #include "i18n.h"
24 #include "r_sms.h"
25 #include "record-internal.h"
26 #include "protostructs.h"
27 #include "data.h"
28 #include "time.h"
29 #include "debug.h"
30 #include "iconv.h"
31 #include "strnlen.h"
32 #include <ostream>
33 #include <iomanip>
34 #include <string.h>
35 #include "ios_state.h"
37 using namespace std;
38 using namespace Barry::Protocol;
40 namespace Barry {
42 ///////////////////////////////////////////////////////////////////////////////
43 // Sms Class
45 // SMS Field Codes
46 #define SMSFC_METADATA 0x01
47 #define SMSFC_ADDRESS 0x02
48 #define SMSFC_BODY 0x04
50 // SMS Field Sizes and Header Sizes
51 #define SMS_ADDRESS_HEADER_SIZE 0x04
53 #define MILLISECONDS_IN_A_SECOND 1000
55 time_t Sms::GetTime() const
57 return (time_t)(Timestamp / MILLISECONDS_IN_A_SECOND);
60 time_t Sms::GetServiceCenterTime() const
62 return (time_t)(ServiceCenterTimestamp / MILLISECONDS_IN_A_SECOND);
65 void Sms::SetTime(const time_t timestamp, const unsigned milliseconds)
67 Timestamp = (uint64_t)timestamp * MILLISECONDS_IN_A_SECOND + milliseconds;
70 void Sms::SetServiceCenterTime(const time_t timestamp, const unsigned milliseconds)
72 ServiceCenterTimestamp = (uint64_t)timestamp * MILLISECONDS_IN_A_SECOND + milliseconds;
75 Sms::Sms()
77 Clear();
80 Sms::~Sms()
84 #define CHECK_FLAG(flags, bitfield) ((flags & (bitfield)) != 0)
86 const unsigned char* Sms::ParseField(const unsigned char *begin,
87 const unsigned char *end,
88 const IConverter *ic)
90 const CommonField *field = (const CommonField *)begin;
92 // advance and check size
93 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
94 if (begin > end) // if begin==end, we are ok
95 return begin;
97 if (!btohs(field->size)) // if field has no size, something's up
98 return begin;
100 switch (field->type)
102 case SMSFC_METADATA:
104 if (btohs(field->size) < SMS_METADATA_SIZE)
105 break; // size not match
107 const SMSMetaData &metadata = field->u.sms_metadata;
108 NewConversation = CHECK_FLAG(metadata.flags, SMS_FLG_NEW_CONVERSATION);
109 Saved = CHECK_FLAG(metadata.flags, SMS_FLG_SAVED);
110 Deleted = CHECK_FLAG(metadata.flags, SMS_FLG_DELETED);
111 Opened = CHECK_FLAG(metadata.flags, SMS_FLG_OPENED);
113 IsNew = metadata.new_flag != 0;
115 uint32_t status = btohl(metadata.status);
117 switch (status)
119 case SMS_STA_RECEIVED:
120 MessageStatus = Received;
121 break;
122 case SMS_STA_DRAFT:
123 MessageStatus = Draft;
124 break;
125 default:
126 MessageStatus = Sent; // consider all others as sent
129 ErrorId = btohl(metadata.error_id);
131 Timestamp = btohll(metadata.timestamp);
132 ServiceCenterTimestamp = btohll(metadata.service_center_timestamp);
134 switch (metadata.dcs)
136 case SMS_DCS_7BIT:
137 DataCodingScheme = SevenBit;
138 break;
139 case SMS_DCS_8BIT:
140 DataCodingScheme = EightBit;
141 break;
142 case SMS_DCS_UCS2:
143 DataCodingScheme = UCS2;
144 break;
145 default:
146 DataCodingScheme = SevenBit; // consider all unknowns as 7bit
149 return begin;
152 case SMSFC_ADDRESS:
154 uint16_t length = btohs(field->size);
155 if (length < SMS_ADDRESS_HEADER_SIZE + 1) // trailing '\0'
156 break; // too short
158 length -= SMS_ADDRESS_HEADER_SIZE;
159 const char *address = (const char *)field->u.raw + SMS_ADDRESS_HEADER_SIZE;
160 Addresses.push_back(std::string(address, strnlen(address, length)));
161 return begin;
164 case SMSFC_BODY:
167 // Some SMS bodies contain a null terminator
168 // in the middle, and it is unknown at the moment
169 // why this is. For regular 8bit char strings,
170 // we just strip out the nulls. For UCS2
171 // 16bit char strings, we strip out the
172 // 16bit nulls.
174 // Any further information on why these null
175 // terminators appear is welcome.
177 const char *str = (const char *)field->u.raw;
178 uint16_t maxlen = btohs(field->size);
179 if (DataCodingScheme != UCS2) {
180 for (uint16_t i = 0; i < maxlen; ++i) {
181 if (str[i]) // if not null, push it
182 Body += str[i];
185 else {
186 for (uint16_t i = 0; maxlen && i < (maxlen-1); i += 2) {
187 if (str[i] || str[i + 1]) // if not null, push it
188 Body += std::string(str + i, 2);
191 if (ic) {
192 if (DataCodingScheme == SevenBit) {
193 // SevenBit -> UTF-8 -> ic's tocode
194 IConvHandle utf8("UTF-8", *ic);
195 Body = ic->Convert(utf8, ConvertGsmToUtf8(Body)); // convert the Body string from GSM 03.38 defined to UTF-8
197 else if (DataCodingScheme == EightBit)
198 Body = ic->FromBB(Body);
199 else {
200 IConvHandle ucs2("UCS-2BE", *ic);
201 Body = ic->Convert(ucs2, Body);
204 return begin;
208 // if still not handled, add to the Unknowns list
209 UnknownField uf;
210 uf.type = field->type;
211 uf.data.assign((const char*)field->u.raw, btohs(field->size));
212 Unknowns.push_back(uf);
214 // return new pointer for next field
215 return begin;
218 void Sms::ParseHeader(const Data &data, size_t &offset)
220 // no header in SMS records
223 void Sms::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
225 const unsigned char *finish = ParseCommonFields(*this,
226 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
227 offset += finish - (data.GetData() + offset);
230 void Sms::Validate() const
234 void Sms::BuildHeader(Data &data, size_t &offset) const
236 // not yet implemented
239 void Sms::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
241 // not yet implemented
244 void Sms::Clear()
246 RecType = GetDefaultRecType();
247 RecordId = 0;
249 MessageStatus = Unknown;
250 DeliveryStatus = NoReport;
252 IsNew = NewConversation = Saved = Deleted = Opened = false;
254 Timestamp = ServiceCenterTimestamp = 0;
256 DataCodingScheme = SevenBit;
258 ErrorId = 0;
260 Addresses.clear();
261 Body.clear();
263 Unknowns.clear();
266 const FieldHandle<Sms>::ListT& Sms::GetFieldHandles()
268 static FieldHandle<Sms>::ListT fhv;
270 if( fhv.size() )
271 return fhv;
273 #undef CONTAINER_OBJECT_NAME
274 #define CONTAINER_OBJECT_NAME fhv
276 #undef RECORD_CLASS_NAME
277 #define RECORD_CLASS_NAME Sms
279 FHP(RecType, _("Record Type Code"));
280 FHP(RecordId, _("Unique Record ID"));
282 FHE(mt, MessageType, MessageStatus, _("Message Status"));
283 FHE_CONST(mt, Unknown, _("Unknown"));
284 FHE_CONST(mt, Received, _("Received"));
285 FHE_CONST(mt, Sent, _("Sent"));
286 FHE_CONST(mt, Draft, _("Draft"));
288 FHE(dt, DeliveryType, DeliveryStatus, _("Delivery Status"));
289 FHE_CONST(dt, NoReport, _("No Report"));
290 FHE_CONST(dt, Failed, _("Failed"));
291 FHE_CONST(dt, Succeeded, _("Succeeded"));
293 FHP(IsNew, _("Is New?"));
294 FHP(NewConversation, _("New Conversation"));
295 FHP(Saved, _("Saved"));
296 FHP(Deleted, _("Deleted"));
297 FHP(Opened, _("Opened"));
299 FHP(Timestamp, _("Timestamp in Milliseconds"));
300 FHP(ServiceCenterTimestamp, _("Service Center Timestamp"));
302 FHE(dcst, DataCodingSchemeType, DataCodingScheme, _("Data Coding Scheme"));
303 FHE_CONST(dcst, SevenBit, _("7bit"));
304 FHE_CONST(dcst, EightBit, _("8bit"));
305 FHE_CONST(dcst, UCS2, _("UCS2"));
307 FHP(ErrorId, _("Error ID"));
309 FHD(Addresses, _("Addresses"), SMSFC_ADDRESS, true);
310 FHD(Body, _("Body"), SMSFC_BODY, true);
312 FHP(Unknowns, _("Unknown Fields"));
314 return fhv;
317 std::string Sms::GetDescription() const
319 if( Addresses.size() )
320 return Addresses[0];
321 else
322 return _("Unknown destination");
325 void Sms::Dump(std::ostream &os) const
327 ios_format_state state(os);
329 os << _("SMS record: ") << "0x" << setbase(16) << RecordId
330 << " (" << (unsigned int)RecType << ")\n";
331 time_t t = GetTime();
332 os << "\t" << _("Timestamp: ") << ctime(&t);
334 if (MessageStatus == Received) {
335 t = GetServiceCenterTime();
336 os << "\t" << _("Service Center Timestamp: ") << ctime(&t);
339 if (ErrorId)
340 os << "\t" << _("Send Error: ") << "0x" << setbase(16) << ErrorId << "\n";
342 switch (MessageStatus)
344 case Received:
345 os << "\t" << _("Received From:\n");
346 break;
347 case Sent:
348 os << "\t" << _("Sent to:\n");
349 break;
350 case Draft:
351 os << "\t" << _("Draft for:\n");
352 break;
353 case Unknown:
354 os << "\t" << _("Unknown status for:\n");
355 break;
358 os << "\t";
359 os << Addresses << "\n";
361 if (IsNew || Opened || Saved || Deleted || NewConversation) {
362 os << "\t";
363 if (IsNew)
364 os << _("New ");
365 if (Opened)
366 os << _("Opened ");
367 if (Saved)
368 os << _("Saved ");
369 if (Deleted)
370 os << _("Deleted ");
371 os << _("Message") << (NewConversation ? _(" that starts a new conversation") : "") << "\n";
373 os << "\t" << _("Content: ") << Body << "\n";
374 os << "\n";
378 // This function helps to convert GSM 03.38 defined 7-bit
379 // SMS to UTF-8.
380 // Detailed information can be found in:
381 // ftp://ftp.3gpp.org/Specs/html-info/0338.htm (Official)
382 // http://en.wikipedia.org/wiki/SMS#GSM
385 std::string Sms::ConvertGsmToUtf8(const std::string &s)
388 // This array stores the GSM 03.38 defined encoding's
389 // corresponding UTF-8 values.
390 // For example: GsmTable[0] = "\x40", which refers to
391 // a "@" in UTF-8 encoding.
392 // The 0x1b item, leads to the extension table, using
393 // the char right next to it as the index.
394 // According to the official specification, when not
395 // able to handle it, it should be treated simply as
396 // a space, which is denoted in UTF-8 as "\x20".
398 static const std::string GsmTable[0x80] = {
399 // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
400 "\x40", "\xc2\xa3", "\x24", "\xc2\xa5", "\xc3\xa8", "\xc3\xa9", "\xc3\xb9", "\xc3\xac", // 0x00
401 "\xc3\xb2", "\xc3\x87", "\x0a", "\xc3\x98", "\xc3\xb8", "\x0d", "\xc3\x85", "\xc3\xa5", // 0x08
402 "\xce\x94", "\x5f", "\xce\xa6", "\xce\x93", "\xce\x9b", "\xce\xa9", "\xce\xa0", "\xce\xa8", // 0x10
403 "\xce\xa3", "\xce\x98", "\xce\x9e", "\x20", "\xc3\x86", "\xc3\xa6", "\xc3\x9f", "\xc3\x89", // 0x18
404 "\x20", "\x21", "\x22", "\x23", "\xc2\xa4", "\x25", "\x26", "\x27", // 0x20
405 "\x28", "\x29", "\x2a", "\x2b", "\x2c", "\x2d", "\x2e", "\x2f", // 0x28
406 "\x30", "\x31", "\x32", "\x33", "\x34", "\x35", "\x36", "\x37", // 0x30
407 "\x38", "\x39", "\x3a", "\x3b", "\x3c", "\x3d", "\x3e", "\x3f", // 0x38
408 "\xc2\xa1", "\x41", "\x42", "\x43", "\x44", "\x45", "\x46", "\x47", // 0x40
409 "\x48", "\x49", "\x4a", "\x4b", "\x4c", "\x4d", "\x4e", "\x4f", // 0x48
410 "\x50", "\x51", "\x52", "\x53", "\x54", "\x55", "\x56", "\x57", // 0x50
411 "\x58", "\x59", "\x5a", "\xc3\x84", "\xc3\x96", "\xc3\x91", "\xc3\x9c", "\xc2\xa7", // 0x58
412 "\xc2\xbf", "\x61", "\x62", "\x63", "\x64", "\x65", "\x66", "\x67", // 0x60
413 "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f", // 0x68
414 "\x70", "\x71", "\x72", "\x73", "\x74", "\x75", "\x76", "\x77", // 0x70
415 "\x78", "\x79", "\x7a", "\xc3\xa4", "\xc3\xb6", "\xc3\xb1", "\xc3\xbc", "\xc3\xa0" // 0x78
418 // This sparse array stores the GSM 03.38 defined
419 // encoding extension table.
420 // The \x1b item is also preserved, for possibly
421 // another extension table.
423 static const std::string GsmExtensionTable[0x80] = {
424 // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
425 "", "", "", "", "", "", "", "", // 0x00
426 "", "", "\x0c", "", "", "", "", "", // 0x08
427 "", "", "", "", "\x5e", "", "", "", // 0x10
428 "", "", "", " ", "", "", "", "", // 0x18
429 "", "", "", "", "", "", "", "", // 0x20
430 "\x7b", "\x7d", "", "", "", "", "", "\x5c", // 0x28
431 "", "", "", "", "", "", "", "", // 0x30
432 "", "", "", "", "\x5b", "\x7e", "\x5d", "", // 0x38
433 "\x7c", "", "", "", "", "", "", "", // 0x40
434 "", "", "", "", "", "", "", "", // 0x48
435 "", "", "", "", "", "", "", "", // 0x50
436 "", "", "", "", "", "", "", "", // 0x58
437 "", "", "", "", "", "\xe2\x82\xac", "", "", // 0x60
438 "", "", "", "", "", "", "", "", // 0x68
439 "", "", "", "", "", "", "", "", // 0x70
440 "", "", "", "", "", "", "", "" // 0x78
442 std::string ret;
443 unsigned len = s.length();
444 for (unsigned i = 0; i < len; ++i) {
445 unsigned char c = (unsigned char) s[i];
446 if (c > 0x7f) // prevent from illegal index
447 continue;
448 else if (c == 0x1b) { // go to extension table
449 if (i < len - 1) {
450 c = (unsigned char) s[++i];
451 if (c <= 0x7f) // prevent from illegal index
452 ret += GsmExtensionTable[c];
455 else
456 ret += GsmTable[c];
458 return ret;
461 } // namespace Barry