line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# SMS.pm |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Perl module for reading, manipulating, and writing the .pdb files |
4
|
|
|
|
|
|
|
# used by Handspring SMS application on PalmOS devices such as |
5
|
|
|
|
|
|
|
# Handspring Treo 270. |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# This code is provided "as is" with no warranty. The exact terms |
8
|
|
|
|
|
|
|
# under which you may use and (re)distribute it are detailed |
9
|
|
|
|
|
|
|
# in the GNU General Public License, in the file COPYING. |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# Copyright (C) 2005 Free Software Foundation, Inc. |
12
|
|
|
|
|
|
|
# |
13
|
|
|
|
|
|
|
# Written by Lorenzo Cappelletti |
14
|
|
|
|
|
|
|
# |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# $Id: SMS.pm,v 1.1 2009/01/10 16:17:59 drhyde Exp $ |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
package Palm::SMS; |
19
|
|
|
|
|
|
|
|
20
|
1
|
|
|
1
|
|
36916
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
43
|
|
21
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
31
|
|
22
|
1
|
|
|
1
|
|
924
|
use Palm::Raw(); |
|
1
|
|
|
|
|
466
|
|
|
1
|
|
|
|
|
24
|
|
23
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION @ISA @folders); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1522
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
$VERSION = 0.03; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
@ISA = qw(Palm::Raw); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 NAME |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Palm::SMS - parse SMS database files |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 SYNOPSIS |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use Palm::PDB; |
36
|
|
|
|
|
|
|
use Palm::SMS; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $pdb = new Palm::PDB; |
39
|
|
|
|
|
|
|
$pdb->Load("sms.pdb"); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $record = $pdb->{records}[0]; |
42
|
|
|
|
|
|
|
print "$record->{text}\n"; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 DESCRIPTION |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
The SMS PDB handler is a helper class for the Palm::PDB module. It is |
47
|
|
|
|
|
|
|
intended for reading, manipulating, and writing the .pdb files used by |
48
|
|
|
|
|
|
|
Handspring SMS application on PalmOS devices such as Handspring Treo |
49
|
|
|
|
|
|
|
270. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Palm::SMS module is the result of a reverse engineering attempt of |
52
|
|
|
|
|
|
|
trasforming a Handspring Treo 270 SMS PDB file into a plain text file. |
53
|
|
|
|
|
|
|
The PDB test file was produced by Handspring's application SMS |
54
|
|
|
|
|
|
|
v. 3.5H. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
Due to lack of knowledge about how PDB files work and what format SMS |
57
|
|
|
|
|
|
|
database files conform to, at present this module is not suitable for |
58
|
|
|
|
|
|
|
from-scratch SMS generation. Conversely, you may find it extremely |
59
|
|
|
|
|
|
|
useful if you intend to extract SMS messages from merged PDB files and |
60
|
|
|
|
|
|
|
convert them to a human readable form. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head2 Fields |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
$record = $pdb->{records}[N]; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
$record->{name} |
67
|
|
|
|
|
|
|
$record->{firstName} |
68
|
|
|
|
|
|
|
$record->{phone} |
69
|
|
|
|
|
|
|
$record->{folder} |
70
|
|
|
|
|
|
|
$record->{timestamp} |
71
|
|
|
|
|
|
|
$record->{text} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
$record->{smsh} |
74
|
|
|
|
|
|
|
$record->{unknown1} |
75
|
|
|
|
|
|
|
$record->{unknown2} |
76
|
|
|
|
|
|
|
$record->{unknown3} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
The fields provided for each record are the following: |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=over |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item name |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
A string containing the name of the person who wrote the message. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item firstname |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
A string containing the first name of the person who wrote the |
89
|
|
|
|
|
|
|
message. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item phone |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
A string containing the phone number. |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=item timestamp |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
An integer which represents the number of seconds elapsed from Unix |
98
|
|
|
|
|
|
|
epoch to message creation time. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
It is worth noticing that there is no way of retrieving neither the TZ |
101
|
|
|
|
|
|
|
nor the DST out of data stored in the PDB file. This timestamp always |
102
|
|
|
|
|
|
|
expresses the time of your handheld's clock at message creation time. |
103
|
|
|
|
|
|
|
Hence, I suggest passing the value C as third argument to |
104
|
|
|
|
|
|
|
L's time2str() to get the right timestamp |
105
|
|
|
|
|
|
|
rappresentation: |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
use Date::Format; |
108
|
|
|
|
|
|
|
... |
109
|
|
|
|
|
|
|
$timestamp = time2str("%T %%Z", $record->{timestamp}, "GMT"); |
110
|
|
|
|
|
|
|
$timestamp = time2str($timestamp, $record->{timestamp} ); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item folder |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
An integer which represents in which folder the message was stored. |
115
|
|
|
|
|
|
|
English folder names (such as I or I) are available as |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
$Palm::SMS::folders[$record->{folder}]; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item text |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
A string containing the message body. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=back |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
The module provides additional fields which will probably be less |
126
|
|
|
|
|
|
|
commonly used. |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=over |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=item smsh |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
This string of four bytes ("I") is present at the start of each |
133
|
|
|
|
|
|
|
record. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=item unknown1 |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item unknown2 |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=item unknown3 |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
These fields contain a chunk of bytes whose function is not yet known. |
142
|
|
|
|
|
|
|
Please, refer to the L method. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=back |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head2 Fields for the Treo 680 |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
The Treo 680 uses different software, and Palm have not documented its |
149
|
|
|
|
|
|
|
message format. Consequently only some information is available, and |
150
|
|
|
|
|
|
|
some records that are extracted are extracted incorrectly. The |
151
|
|
|
|
|
|
|
following fields are available, and have been reverse-engineered from |
152
|
|
|
|
|
|
|
a single sample database. Consequently, you should treat their |
153
|
|
|
|
|
|
|
values with suspicion. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Treo 680 databases are read-only. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
If the Palm::Treo680MessagesDB module is available, then that will be |
158
|
|
|
|
|
|
|
used instead. Over time, that module is intended to do a better job, |
159
|
|
|
|
|
|
|
as and when I figure out new bits of the puzzle. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=over |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item device |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
This will always be "Treo 680" |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item direction |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
The direction of the SMS relative to your phone. This will be either |
170
|
|
|
|
|
|
|
'inbound' or 'outbound' |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item number |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
The other party's phone number (same as for any other device) |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=item name |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
The other party's name (same as for any other device) |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item text |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
The text of the message (same as for any other device) |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item type |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
A number representing the type of message. If this is 'unknown' then |
187
|
|
|
|
|
|
|
none of the above fields will be populated. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item rawdata |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
The raw binary data of the record |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=back |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head1 METHODS |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=cut |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
@folders = ( # SMS folder names |
200
|
|
|
|
|
|
|
"Inbox", |
201
|
|
|
|
|
|
|
"Sent", |
202
|
|
|
|
|
|
|
"Pending", # guessed |
203
|
|
|
|
|
|
|
); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
my $EPOCH_1904 = 2082844800; # Difference between Palm's |
206
|
|
|
|
|
|
|
# epoch (Jan. 1, 1904) and |
207
|
|
|
|
|
|
|
# Unix's epoch (Jan. 1, 1970), |
208
|
|
|
|
|
|
|
# in seconds. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub import { |
211
|
1
|
|
|
1
|
|
15
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, |
212
|
|
|
|
|
|
|
[ "SMS!", "DATA" ], |
213
|
|
|
|
|
|
|
); |
214
|
1
|
|
|
|
|
22
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, |
215
|
|
|
|
|
|
|
[ "HsCh", "SMct" ], |
216
|
|
|
|
|
|
|
); |
217
|
1
|
50
|
|
1
|
|
510
|
eval "use Palm::Treo680MessagesDB" || |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
1
|
|
|
|
|
91
|
|
218
|
|
|
|
|
|
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, # GSM Treo 680 |
219
|
|
|
|
|
|
|
[ "MsSt", "MsDb" ], # Messaging app v 2.6.1 |
220
|
|
|
|
|
|
|
); |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 new |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
$pdb = new Palm::SMS; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Creates a new PDB, initialized with the various Palm::SMS fields |
228
|
|
|
|
|
|
|
and an empty record list. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Use this method if you're creating a SMS PDB from scratch. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
#' |
235
|
|
|
|
|
|
|
sub new { |
236
|
0
|
|
|
0
|
1
|
0
|
my $classname = shift; |
237
|
0
|
|
|
|
|
0
|
my $self = $classname->SUPER::new(@_); # no need to rebless |
238
|
0
|
|
|
|
|
0
|
$self->{name} = "SMS Messages"; # default |
239
|
0
|
|
|
|
|
0
|
$self->{creator} = "SMS!"; |
240
|
0
|
|
|
|
|
0
|
$self->{type} = "DATA"; |
241
|
0
|
|
|
|
|
0
|
$self->{attributes}{resource} = 0; # not a resource db |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
|
244
|
0
|
|
|
|
|
0
|
$self->{sort} = undef; # empty sort block |
245
|
|
|
|
|
|
|
|
246
|
0
|
|
|
|
|
0
|
$self->{records} = []; # empty list of records |
247
|
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
0
|
return $self; |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 new_Record |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
$record = $pdb->new_Record; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
$record->{phone} = "1234567890"; |
256
|
|
|
|
|
|
|
$record->{folder} = 1; |
257
|
|
|
|
|
|
|
... |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
$phone = $record->{phone}; |
260
|
|
|
|
|
|
|
$folder = $Palm::SMS::folders[$record->{folder}]; |
261
|
|
|
|
|
|
|
... |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
Creates a new SMS record, with blank values for all of the fields. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
C does B add the new record to C<$pdb>. For that, |
266
|
|
|
|
|
|
|
you want C<$pdb-Eappend_Record>. |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Default field values are: |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
name : undef |
271
|
|
|
|
|
|
|
firstName: undef |
272
|
|
|
|
|
|
|
phone : undef |
273
|
|
|
|
|
|
|
timestamp: localtime() |
274
|
|
|
|
|
|
|
folder : 1 |
275
|
|
|
|
|
|
|
text : undef |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
smsh : "SMSh" |
278
|
|
|
|
|
|
|
unknown1 : undef |
279
|
|
|
|
|
|
|
unknown2 : undef |
280
|
|
|
|
|
|
|
unknown3 : undef |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=cut |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub new_Record { |
285
|
0
|
|
|
0
|
1
|
0
|
my $classname = shift; |
286
|
0
|
|
|
|
|
0
|
my $retval = $classname->SUPER::new_Record(@_); |
287
|
|
|
|
|
|
|
|
288
|
0
|
|
|
|
|
0
|
$retval->{name} = undef; |
289
|
0
|
|
|
|
|
0
|
$retval->{firstName} = undef; |
290
|
0
|
|
|
|
|
0
|
$retval->{phone} = undef; |
291
|
0
|
|
|
|
|
0
|
$retval->{timestamp} = localtime; |
292
|
0
|
|
|
|
|
0
|
$retval->{folder} = 1; |
293
|
0
|
|
|
|
|
0
|
$retval->{text} = undef; |
294
|
|
|
|
|
|
|
|
295
|
0
|
|
|
|
|
0
|
$retval->{smsh} = "SMSh"; |
296
|
0
|
|
|
|
|
0
|
$retval->{unknown1} = undef; |
297
|
0
|
|
|
|
|
0
|
$retval->{unknown2} = undef; |
298
|
0
|
|
|
|
|
0
|
$retval->{unknown2} = undef; |
299
|
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
0
|
return $retval; |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head2 ParseRecord |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
ParseRecord() returns a parsed representation of the record, typically |
306
|
|
|
|
|
|
|
as a reference to a record object or anonymous hash. It is |
307
|
|
|
|
|
|
|
automatically called from within L and, as such, is not |
308
|
|
|
|
|
|
|
intented to be used directly from applications. |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
The record structure which an SMS posses is: |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
smsh : 4-byte ASCII string |
313
|
|
|
|
|
|
|
unknown1 : 2-byte data whose function is unknown |
314
|
|
|
|
|
|
|
timestamp: 32-bit, big-endian, unsigned integer rappresenting |
315
|
|
|
|
|
|
|
the number of seconds since 1904 |
316
|
|
|
|
|
|
|
unknown2 : 26-byte data whose function is unknown |
317
|
|
|
|
|
|
|
phone : Null terminated string |
318
|
|
|
|
|
|
|
name : Null terminated string |
319
|
|
|
|
|
|
|
firstname: Null terminated string |
320
|
|
|
|
|
|
|
unknown3 : 16-byte data whose function is unknown |
321
|
|
|
|
|
|
|
text : Null terminated (sent messages only) string |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
I field value is copied from I field which is |
324
|
|
|
|
|
|
|
computed by Palm::PDB and then delted since there is no application |
325
|
|
|
|
|
|
|
info block (see L) in the PDB file. |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
I is empty for messages belonging to category 1 (folder |
328
|
|
|
|
|
|
|
I). |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
It is worth noticing that length, offset, and even availability of |
331
|
|
|
|
|
|
|
I data are not preserved between module version when their |
332
|
|
|
|
|
|
|
meaning becomes clear. |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
=cut |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub ParseRecord { |
337
|
2
|
|
|
2
|
1
|
1787
|
my $self = shift; |
338
|
2
|
|
|
|
|
7
|
my %record = @_; |
339
|
2
|
|
|
|
|
4
|
my @unpack; |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
my $smsh; # each record starts with "SMSh": SMS handler? |
342
|
|
|
|
|
|
|
# not on Treo 680 |
343
|
0
|
|
|
|
|
0
|
my $unknown1; |
344
|
0
|
|
|
|
|
0
|
my $timestamp; |
345
|
0
|
|
|
|
|
0
|
my $unknown2; |
346
|
0
|
|
|
|
|
0
|
my $name; |
347
|
0
|
|
|
|
|
0
|
my $firstName; |
348
|
0
|
|
|
|
|
0
|
my $unknown3; |
349
|
0
|
|
|
|
|
0
|
my $phone; |
350
|
0
|
|
|
|
|
0
|
my $folder; |
351
|
0
|
|
|
|
|
0
|
my $text; |
352
|
|
|
|
|
|
|
|
353
|
2
|
50
|
|
|
|
16
|
if ($self->{creator} eq "HsCh") { |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
354
|
0
|
|
|
|
|
0
|
($smsh, |
355
|
|
|
|
|
|
|
$unknown1, |
356
|
|
|
|
|
|
|
$timestamp, |
357
|
|
|
|
|
|
|
$unknown2, |
358
|
|
|
|
|
|
|
$text, |
359
|
|
|
|
|
|
|
) = unpack("a2 A4 N a24 Z* a*", $record{data}); |
360
|
0
|
0
|
|
|
|
0
|
if ($timestamp eq "") {$timestamp=$EPOCH_1904;} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
361
|
|
|
|
|
|
|
else {$timestamp -= 14400;} |
362
|
0
|
0
|
|
|
|
0
|
if ($smsh eq "\0\0" ) { $phone="Target"; } |
|
0
|
|
|
|
|
0
|
|
363
|
0
|
|
|
|
|
0
|
else { $phone="Me"; } |
364
|
|
|
|
|
|
|
} elsif($self->{creator} eq 'MsSt') { # Treo 680 |
365
|
0
|
|
|
|
|
0
|
my $buf = $record{data}; |
366
|
0
|
|
|
|
|
0
|
my $type = 256 * ord(substr($buf, 10, 1)) + ord(substr($buf, 11, 1)); |
367
|
0
|
|
|
|
|
0
|
my($dir, $num, $name, $msg) = ('', '', '', ''); |
368
|
0
|
0
|
0
|
|
|
0
|
if($type == 0x400C || $type == 0x4009) { # 4009 not used by 680? |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
369
|
0
|
0
|
|
|
|
0
|
$dir = ($type == 0x400C) ? 'inbound' : 'outbound'; |
370
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = (split(/\00+/, substr($buf, 34)))[0, 1, 3]; |
371
|
0
|
|
|
|
|
0
|
$msg = substr($msg, 1); |
372
|
|
|
|
|
|
|
} elsif($type == 0) { |
373
|
0
|
|
|
|
|
0
|
$dir = 'outbound'; |
374
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = split(/\00+/, substr($buf, 0x4C), 3); |
375
|
0
|
|
|
|
|
0
|
$msg =~ s/^.{9}//s; |
376
|
0
|
|
|
|
|
0
|
$msg =~ s/\00.*$//s; |
377
|
|
|
|
|
|
|
} elsif($type == 0x0002) { |
378
|
0
|
|
|
|
|
0
|
$dir = 'outbound'; |
379
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = split(/\00+/, substr($buf, 0x46), 3); |
380
|
0
|
|
|
|
|
0
|
$msg =~ s/^.Trsm....//s; |
381
|
0
|
|
|
|
|
0
|
$msg =~ s/\00.*$//s; |
382
|
|
|
|
|
|
|
} else { |
383
|
0
|
|
|
|
|
0
|
$type = 'unknown'; |
384
|
|
|
|
|
|
|
} |
385
|
0
|
|
|
|
|
0
|
@record{qw(device type direction phone name text rawdata)} = |
386
|
|
|
|
|
|
|
("Treo 680", $type, $dir, $num, $name, $msg, $buf); |
387
|
|
|
|
|
|
|
} elsif ($record{category} == 0) { |
388
|
|
|
|
|
|
|
### Inbox folder ### |
389
|
1
|
|
|
|
|
2
|
my $nameFlag; # whether name and firstName are available |
390
|
|
|
|
|
|
|
my $extra; # temporary string |
391
|
|
|
|
|
|
|
|
392
|
1
|
|
|
|
|
7
|
($smsh, |
393
|
|
|
|
|
|
|
$unknown1, |
394
|
|
|
|
|
|
|
$timestamp, |
395
|
|
|
|
|
|
|
$unknown2, |
396
|
|
|
|
|
|
|
$phone, |
397
|
|
|
|
|
|
|
$extra, |
398
|
|
|
|
|
|
|
) = unpack("A4 a2 N a26 Z* a*", $record{data}); |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# unknown2 tells whether name and firstName are available |
401
|
1
|
|
|
|
|
4
|
$nameFlag = unpack("x7 H", $unknown2); |
402
|
1
|
50
|
|
|
|
5
|
if ($nameFlag eq "4") { |
403
|
1
|
|
|
|
|
5
|
($name, |
404
|
|
|
|
|
|
|
$firstName, |
405
|
|
|
|
|
|
|
$extra, |
406
|
|
|
|
|
|
|
) = unpack("Z* Z* a*", $extra); |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# $extra's head contains unknown3 followed by "\d\0" |
410
|
1
|
|
|
|
|
12
|
($unknown3, $text) = $extra =~ m/(.*?\d\0)([^\0]+)$/; |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
} elsif ($record{category} == 1) { |
413
|
|
|
|
|
|
|
### Sent folder ### |
414
|
1
|
|
|
|
|
1
|
my $unpack; |
415
|
|
|
|
|
|
|
|
416
|
1
|
|
|
|
|
23
|
($smsh, |
417
|
|
|
|
|
|
|
$unknown1, |
418
|
|
|
|
|
|
|
$timestamp, |
419
|
|
|
|
|
|
|
$unknown2, |
420
|
|
|
|
|
|
|
$phone, |
421
|
|
|
|
|
|
|
$name, |
422
|
|
|
|
|
|
|
$firstName, |
423
|
|
|
|
|
|
|
$text, |
424
|
|
|
|
|
|
|
) = unpack("A4 a2 N a26 Z* Z* Z* Z*", $record{data}); |
425
|
1
|
|
|
|
|
4
|
$unknown3 = ""; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
} elsif ($record{category} == 2) { |
428
|
|
|
|
|
|
|
### Pending folder ### |
429
|
0
|
|
|
|
|
0
|
die "Never tried to parse a message from Pending folder"; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
} else { |
432
|
0
|
|
|
|
|
0
|
die "Unknown category"; |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# Work out common extracted values |
437
|
2
|
|
|
|
|
3
|
$timestamp -= $EPOCH_1904; |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# Assign extracted values to record |
440
|
2
|
50
|
|
|
|
7
|
if($self->{creator} ne 'MsSt') { |
441
|
2
|
|
|
|
|
3
|
$record{name} = $name; |
442
|
2
|
|
|
|
|
4
|
$record{firstName} = $firstName; |
443
|
2
|
|
|
|
|
6
|
$record{phone} = $phone; |
444
|
2
|
|
|
|
|
4
|
$record{timestamp} = $timestamp; |
445
|
2
|
|
|
|
|
5
|
$record{folder} = $record{category}; |
446
|
2
|
|
|
|
|
3
|
$record{text} = $text; |
447
|
|
|
|
|
|
|
|
448
|
2
|
|
|
|
|
5
|
$record{smsh} = $smsh; |
449
|
2
|
|
|
|
|
4
|
$record{unknown1} = $unknown1; |
450
|
2
|
|
|
|
|
9
|
$record{unknown2} = $unknown2; |
451
|
2
|
|
|
|
|
5
|
$record{unknown3} = $unknown3; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
2
|
|
|
|
|
4
|
delete $record{data}; |
455
|
|
|
|
|
|
|
|
456
|
2
|
|
|
|
|
7
|
return \%record; |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=head2 PackRecord |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
This is the converse of L. PackRecord() |
462
|
|
|
|
|
|
|
takes a record as returned by ParseRecord() and returns a string of |
463
|
|
|
|
|
|
|
raw data that can be written to the database file. As |
464
|
|
|
|
|
|
|
L, this function is not intended to be |
465
|
|
|
|
|
|
|
used directly from applications. |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Because there are chunk of record data whose function is unknown (see |
468
|
|
|
|
|
|
|
L), this method may produce an invalid |
469
|
|
|
|
|
|
|
result, expecially when passed record was created from scratch via |
470
|
|
|
|
|
|
|
L. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
This method is granted to work if the record being packed has been |
473
|
|
|
|
|
|
|
unpacked from an existing PDB and no information has been added. |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=cut |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub PackRecord { |
478
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
479
|
0
|
|
|
|
|
|
my $record = shift; |
480
|
0
|
|
|
|
|
|
my $retval; |
481
|
|
|
|
|
|
|
my $pack; |
482
|
|
|
|
|
|
|
|
483
|
0
|
|
|
|
|
|
$pack = "A4 a2 N a26 Z*"; |
484
|
|
|
|
|
|
|
|
485
|
0
|
0
|
|
|
|
|
if ($record->{folder} == 0) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
486
|
|
|
|
|
|
|
### Inbox folder ### |
487
|
0
|
0
|
0
|
|
|
|
if (not ($record->{name} or $record->{firstName})) { |
488
|
0
|
|
|
|
|
|
$pack .= " a* A*"; |
489
|
0
|
|
|
|
|
|
$retval = pack($pack, |
490
|
|
|
|
|
|
|
$record->{smsh}, |
491
|
|
|
|
|
|
|
$record->{unknown1}, |
492
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
493
|
|
|
|
|
|
|
$record->{unknown2}, |
494
|
|
|
|
|
|
|
$record->{phone}, |
495
|
|
|
|
|
|
|
$record->{unknown3}, |
496
|
|
|
|
|
|
|
$record->{text}); |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
} else { |
499
|
0
|
|
|
|
|
|
$pack .= " Z* Z* a* A*"; |
500
|
0
|
|
|
|
|
|
$retval = pack($pack, |
501
|
|
|
|
|
|
|
$record->{smsh}, |
502
|
|
|
|
|
|
|
$record->{unknown1}, |
503
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
504
|
|
|
|
|
|
|
$record->{unknown2}, |
505
|
|
|
|
|
|
|
$record->{phone}, |
506
|
|
|
|
|
|
|
$record->{name}, |
507
|
|
|
|
|
|
|
$record->{firstName}, |
508
|
|
|
|
|
|
|
$record->{unknown3}, |
509
|
|
|
|
|
|
|
$record->{text}); |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
} elsif ($record->{folder} ==1) { |
514
|
|
|
|
|
|
|
### Sent folder ### |
515
|
0
|
|
|
|
|
|
$pack .= " Z* Z* Z*"; |
516
|
0
|
|
|
|
|
|
$retval = pack($pack, |
517
|
|
|
|
|
|
|
$record->{smsh}, |
518
|
|
|
|
|
|
|
$record->{unknown1}, |
519
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
520
|
|
|
|
|
|
|
$record->{unknown2}, |
521
|
|
|
|
|
|
|
$record->{phone}, |
522
|
|
|
|
|
|
|
$record->{name}, |
523
|
|
|
|
|
|
|
$record->{firstName}, |
524
|
|
|
|
|
|
|
$record->{text}); |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
} elsif ($record->{folder} == 2) { |
527
|
|
|
|
|
|
|
### Pending folder ### |
528
|
0
|
|
|
|
|
|
die "Never tried to pack a message to Pending folder"; |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
} else { |
531
|
0
|
|
|
|
|
|
die "Unknown category"; |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
|
return $retval; |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
1; |
539
|
|
|
|
|
|
|
__END__ |