3 /// Record parsing class for the SMS database.
7 Copyright (C) 2005-2012, 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.
24 #include "record-internal.h"
25 #include "protostructs.h"
34 #include "ios_state.h"
37 using namespace Barry::Protocol
;
41 ///////////////////////////////////////////////////////////////////////////////
45 #define SMSFC_METADATA 0x01
46 #define SMSFC_ADDRESS 0x02
47 #define SMSFC_BODY 0x04
49 // SMS Field Sizes and Header Sizes
50 #define SMS_ADDRESS_HEADER_SIZE 0x04
52 #define MILLISECONDS_IN_A_SECOND 1000
54 time_t Sms::GetTime() const
56 return (time_t)(Timestamp
/ MILLISECONDS_IN_A_SECOND
);
59 time_t Sms::GetServiceCenterTime() const
61 return (time_t)(ServiceCenterTimestamp
/ MILLISECONDS_IN_A_SECOND
);
64 void Sms::SetTime(const time_t timestamp
, const unsigned milliseconds
)
66 Timestamp
= (uint64_t)timestamp
* MILLISECONDS_IN_A_SECOND
+ milliseconds
;
69 void Sms::SetServiceCenterTime(const time_t timestamp
, const unsigned milliseconds
)
71 ServiceCenterTimestamp
= (uint64_t)timestamp
* MILLISECONDS_IN_A_SECOND
+ milliseconds
;
83 const unsigned char* Sms::ParseField(const unsigned char *begin
,
84 const unsigned char *end
,
87 const CommonField
*field
= (const CommonField
*)begin
;
89 // advance and check size
90 begin
+= COMMON_FIELD_HEADER_SIZE
+ btohs(field
->size
);
91 if (begin
> end
) // if begin==end, we are ok
94 if (!btohs(field
->size
)) // if field has no size, something's up
101 if (btohs(field
->size
) < SMS_METADATA_SIZE
)
102 break; // size not match
104 const SMSMetaData
&metadata
= field
->u
.sms_metadata
;
105 NewConversation
= metadata
.flags
& SMS_FLG_NEW_CONVERSATION
;
106 Saved
= metadata
.flags
& SMS_FLG_SAVED
;
107 Deleted
= metadata
.flags
& SMS_FLG_DELETED
;
108 Opened
= metadata
.flags
& SMS_FLG_OPENED
;
110 IsNew
= metadata
.new_flag
;
112 uint32_t status
= btohl(metadata
.status
);
116 case SMS_STA_RECEIVED
:
117 MessageStatus
= Received
;
120 MessageStatus
= Draft
;
123 MessageStatus
= Sent
; // consider all others as sent
126 ErrorId
= btohl(metadata
.error_id
);
128 Timestamp
= btohll(metadata
.timestamp
);
129 ServiceCenterTimestamp
= btohll(metadata
.service_center_timestamp
);
131 switch (metadata
.dcs
)
134 DataCodingScheme
= SevenBit
;
137 DataCodingScheme
= EightBit
;
140 DataCodingScheme
= UCS2
;
143 DataCodingScheme
= SevenBit
; // consider all unknowns as 7bit
151 uint16_t length
= btohs(field
->size
);
152 if (length
< SMS_ADDRESS_HEADER_SIZE
+ 1) // trailing '\0'
155 length
-= SMS_ADDRESS_HEADER_SIZE
;
156 const char *address
= (const char *)field
->u
.raw
+ SMS_ADDRESS_HEADER_SIZE
;
157 Addresses
.push_back(std::string(address
, strnlen(address
, length
)));
164 // Some SMS bodies contain a null terminator
165 // in the middle, and it is unknown at the moment
166 // why this is. For regular 8bit char strings,
167 // we just strip out the nulls. For UCS2
168 // 16bit char strings, we strip out the
171 // Any further information on why these null
172 // terminators appear is welcome.
174 const char *str
= (const char *)field
->u
.raw
;
175 uint16_t maxlen
= btohs(field
->size
);
176 if (DataCodingScheme
!= UCS2
) {
177 for (uint16_t i
= 0; i
< maxlen
; ++i
) {
178 if (str
[i
]) // if not null, push it
183 for (uint16_t i
= 0; maxlen
&& i
< (maxlen
-1); i
+= 2) {
184 if (str
[i
] || str
[i
+ 1]) // if not null, push it
185 Body
+= std::string(str
+ i
, 2);
189 if (DataCodingScheme
== SevenBit
) {
190 // SevenBit -> UTF-8 -> ic's tocode
191 IConvHandle
utf8("UTF-8", *ic
);
192 Body
= ic
->Convert(utf8
, ConvertGsmToUtf8(Body
)); // convert the Body string from GSM 03.38 defined to UTF-8
194 else if (DataCodingScheme
== EightBit
)
195 Body
= ic
->FromBB(Body
);
197 IConvHandle
ucs2("UCS-2BE", *ic
);
198 Body
= ic
->Convert(ucs2
, Body
);
205 // if still not handled, add to the Unknowns list
207 uf
.type
= field
->type
;
208 uf
.data
.assign((const char*)field
->u
.raw
, btohs(field
->size
));
209 Unknowns
.push_back(uf
);
211 // return new pointer for next field
215 void Sms::ParseHeader(const Data
&data
, size_t &offset
)
217 // no header in SMS records
220 void Sms::ParseFields(const Data
&data
, size_t &offset
, const IConverter
*ic
)
222 const unsigned char *finish
= ParseCommonFields(*this,
223 data
.GetData() + offset
, data
.GetData() + data
.GetSize(), ic
);
224 offset
+= finish
- (data
.GetData() + offset
);
227 void Sms::Validate() const
231 void Sms::BuildHeader(Data
&data
, size_t &offset
) const
233 // not yet implemented
236 void Sms::BuildFields(Data
&data
, size_t &offset
, const IConverter
*ic
) const
238 // not yet implemented
243 RecType
= GetDefaultRecType();
246 MessageStatus
= Unknown
;
247 DeliveryStatus
= NoReport
;
249 IsNew
= NewConversation
= Saved
= Deleted
= Opened
= false;
251 Timestamp
= ServiceCenterTimestamp
= 0;
253 DataCodingScheme
= SevenBit
;
263 const FieldHandle
<Sms
>::ListT
& Sms::GetFieldHandles()
265 static FieldHandle
<Sms
>::ListT fhv
;
270 #undef CONTAINER_OBJECT_NAME
271 #define CONTAINER_OBJECT_NAME fhv
273 #undef RECORD_CLASS_NAME
274 #define RECORD_CLASS_NAME Sms
276 FHP(RecType
, "Record Type Code");
277 FHP(RecordId
, "Unique Record ID");
279 FHE(mt
, MessageType
, MessageStatus
, "Message Status");
280 FHE_CONST(mt
, Unknown
, "Unknown");
281 FHE_CONST(mt
, Received
, "Received");
282 FHE_CONST(mt
, Sent
, "Sent");
283 FHE_CONST(mt
, Draft
, "Draft");
285 FHE(dt
, DeliveryType
, DeliveryStatus
, "Delivery Status");
286 FHE_CONST(dt
, NoReport
, "No Report");
287 FHE_CONST(dt
, Failed
, "Failed");
288 FHE_CONST(dt
, Succeeded
, "Succeeded");
290 FHP(IsNew
, "Is New?");
291 FHP(NewConversation
, "New Conversation");
293 FHP(Deleted
, "Deleted");
294 FHP(Opened
, "Opened");
296 FHP(Timestamp
, "Timestamp in Milliseconds");
297 FHP(ServiceCenterTimestamp
, "Service Center Timestamp");
299 FHE(dcst
, DataCodingSchemeType
, DataCodingScheme
, "Data Coding Scheme");
300 FHE_CONST(dcst
, SevenBit
, "7bit");
301 FHE_CONST(dcst
, EightBit
, "8bit");
302 FHE_CONST(dcst
, UCS2
, "UCS2");
304 FHP(ErrorId
, "Error ID");
306 FHD(Addresses
, "Addresses", SMSFC_ADDRESS
, true);
307 FHD(Body
, "Body", SMSFC_BODY
, true);
309 FHP(Unknowns
, "Unknown Fields");
314 std::string
Sms::GetDescription() const
316 if( Addresses
.size() )
319 return "Unknown destination";
322 void Sms::Dump(std::ostream
&os
) const
324 ios_format_state
state(os
);
326 os
<< "SMS record: 0x" << setbase(16) << RecordId
327 << " (" << (unsigned int)RecType
<< ")\n";
328 time_t t
= GetTime();
329 os
<< "\tTimestamp: " << ctime(&t
);
331 if (MessageStatus
== Received
) {
332 t
= GetServiceCenterTime();
333 os
<< "\tService Center Timestamp: " << ctime(&t
);
337 os
<< "\tSend Error: 0x" << setbase(16) << ErrorId
<< "\n";
339 switch (MessageStatus
)
342 os
<< "\tReceived From:\n";
345 os
<< "\tSent to:\n";
348 os
<< "\tDraft for:\n";
351 os
<< "\tUnknown status for:\n";
356 os
<< Addresses
<< "\n";
358 if (IsNew
|| Opened
|| Saved
|| Deleted
|| NewConversation
) {
368 os
<< "Message" << (NewConversation
? " that starts a new conversation" : "") << "\n";
370 os
<< "\tContent: " << Body
<< "\n";
375 // This function helps to convert GSM 03.38 defined 7-bit
377 // Detailed information can be found in:
378 // ftp://ftp.3gpp.org/Specs/html-info/0338.htm (Official)
379 // http://en.wikipedia.org/wiki/SMS#GSM
382 std::string
Sms::ConvertGsmToUtf8(const std::string
&s
)
385 // This array stores the GSM 03.38 defined encoding's
386 // corresponding UTF-8 values.
387 // For example: GsmTable[0] = "\x40", which refers to
388 // a "@" in UTF-8 encoding.
389 // The 0x1b item, leads to the extension table, using
390 // the char right next to it as the index.
391 // According to the official specification, when not
392 // able to handle it, it should be treated simply as
393 // a space, which is denoted in UTF-8 as "\x20".
395 static const std::string GsmTable
[0x80] = {
396 // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
397 "\x40", "\xc2\xa3", "\x24", "\xc2\xa5", "\xc3\xa8", "\xc3\xa9", "\xc3\xb9", "\xc3\xac", // 0x00
398 "\xc3\xb2", "\xc3\x87", "\x0a", "\xc3\x98", "\xc3\xb8", "\x0d", "\xc3\x85", "\xc3\xa5", // 0x08
399 "\xce\x94", "\x5f", "\xce\xa6", "\xce\x93", "\xce\x9b", "\xce\xa9", "\xce\xa0", "\xce\xa8", // 0x10
400 "\xce\xa3", "\xce\x98", "\xce\x9e", "\x20", "\xc3\x86", "\xc3\xa6", "\xc3\x9f", "\xc3\x89", // 0x18
401 "\x20", "\x21", "\x22", "\x23", "\xc2\xa4", "\x25", "\x26", "\x27", // 0x20
402 "\x28", "\x29", "\x2a", "\x2b", "\x2c", "\x2d", "\x2e", "\x2f", // 0x28
403 "\x30", "\x31", "\x32", "\x33", "\x34", "\x35", "\x36", "\x37", // 0x30
404 "\x38", "\x39", "\x3a", "\x3b", "\x3c", "\x3d", "\x3e", "\x3f", // 0x38
405 "\xc2\xa1", "\x41", "\x42", "\x43", "\x44", "\x45", "\x46", "\x47", // 0x40
406 "\x48", "\x49", "\x4a", "\x4b", "\x4c", "\x4d", "\x4e", "\x4f", // 0x48
407 "\x50", "\x51", "\x52", "\x53", "\x54", "\x55", "\x56", "\x57", // 0x50
408 "\x58", "\x59", "\x5a", "\xc3\x84", "\xc3\x96", "\xc3\x91", "\xc3\x9c", "\xc2\xa7", // 0x58
409 "\xc2\xbf", "\x61", "\x62", "\x63", "\x64", "\x65", "\x66", "\x67", // 0x60
410 "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f", // 0x68
411 "\x70", "\x71", "\x72", "\x73", "\x74", "\x75", "\x76", "\x77", // 0x70
412 "\x78", "\x79", "\x7a", "\xc3\xa4", "\xc3\xb6", "\xc3\xb1", "\xc3\xbc", "\xc3\xa0" // 0x78
415 // This sparse array stores the GSM 03.38 defined
416 // encoding extension table.
417 // The \x1b item is also preserved, for possibly
418 // another extension table.
420 static const std::string GsmExtensionTable
[0x80] = {
421 // 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
422 "", "", "", "", "", "", "", "", // 0x00
423 "", "", "\x0c", "", "", "", "", "", // 0x08
424 "", "", "", "", "\x5e", "", "", "", // 0x10
425 "", "", "", " ", "", "", "", "", // 0x18
426 "", "", "", "", "", "", "", "", // 0x20
427 "\x7b", "\x7d", "", "", "", "", "", "\x5c", // 0x28
428 "", "", "", "", "", "", "", "", // 0x30
429 "", "", "", "", "\x5b", "\x7e", "\x5d", "", // 0x38
430 "\x7c", "", "", "", "", "", "", "", // 0x40
431 "", "", "", "", "", "", "", "", // 0x48
432 "", "", "", "", "", "", "", "", // 0x50
433 "", "", "", "", "", "", "", "", // 0x58
434 "", "", "", "", "", "\xe2\x82\xac", "", "", // 0x60
435 "", "", "", "", "", "", "", "", // 0x68
436 "", "", "", "", "", "", "", "", // 0x70
437 "", "", "", "", "", "", "", "" // 0x78
440 unsigned len
= s
.length();
441 for (unsigned i
= 0; i
< len
; ++i
) {
442 unsigned char c
= (unsigned char) s
[i
];
443 if (c
> 0x7f) // prevent from illegal index
445 else if (c
== 0x1b) { // go to extension table
447 c
= (unsigned char) s
[++i
];
448 if (c
<= 0x7f) // prevent from illegal index
449 ret
+= GsmExtensionTable
[c
];