line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Palm::Treo680MessagesDB; |
2
|
|
|
|
|
|
|
|
3
|
8
|
|
|
8
|
|
173014
|
use strict; |
|
8
|
|
|
|
|
16
|
|
|
8
|
|
|
|
|
291
|
|
4
|
8
|
|
|
8
|
|
34
|
use warnings; |
|
8
|
|
|
|
|
9
|
|
|
8
|
|
|
|
|
245
|
|
5
|
|
|
|
|
|
|
|
6
|
8
|
|
|
8
|
|
3566
|
use Palm::Raw(); |
|
8
|
|
|
|
|
2544
|
|
|
8
|
|
|
|
|
147
|
|
7
|
8
|
|
|
8
|
|
6469
|
use DateTime; |
|
8
|
|
|
|
|
941141
|
|
|
8
|
|
|
|
|
322
|
|
8
|
8
|
|
|
8
|
|
4730
|
use Data::Hexdumper (); |
|
8
|
|
|
|
|
11474
|
|
|
8
|
|
|
|
|
227
|
|
9
|
|
|
|
|
|
|
|
10
|
8
|
|
|
8
|
|
47
|
use vars qw($VERSION @ISA $timezone $incl_raw $debug $multipart); |
|
8
|
|
|
|
|
9
|
|
|
8
|
|
|
|
|
1302
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
$VERSION = '1.02'; |
13
|
|
|
|
|
|
|
@ISA = qw(Palm::Raw); |
14
|
|
|
|
|
|
|
$timezone = 'Europe/London'; |
15
|
|
|
|
|
|
|
$debug = 0; |
16
|
|
|
|
|
|
|
$incl_raw = 0; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$multipart = {}; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub import { |
21
|
8
|
|
|
8
|
|
66
|
my $class = shift; |
22
|
8
|
|
|
|
|
17
|
my %opts = @_; |
23
|
8
|
100
|
|
|
|
34
|
$timezone = $opts{timezone} if(exists($opts{timezone})); |
24
|
8
|
100
|
|
|
|
19
|
$incl_raw = $opts{incl_raw} if(exists($opts{incl_raw})); |
25
|
8
|
100
|
|
|
|
27
|
$debug = $opts{debug} if(exists($opts{debug})); |
26
|
8
|
|
|
|
|
42
|
Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [MsSt => 'MsDb']); |
27
|
|
|
|
|
|
|
|
28
|
8
|
100
|
|
|
|
1868
|
if(!$debug) { |
29
|
8
|
|
|
8
|
|
69
|
no warnings; |
|
8
|
|
|
|
|
13
|
|
|
8
|
|
|
|
|
13190
|
|
30
|
7
|
|
|
|
|
16
|
my $orig_Load = \&Palm::PDB::Load; |
31
|
|
|
|
|
|
|
*Palm::PDB::Load = sub { |
32
|
6
|
|
|
6
|
|
657
|
$orig_Load->(@_); |
33
|
3594
|
100
|
66
|
|
|
16572
|
$_[0]->{records} = [ |
34
|
|
|
|
|
|
|
grep { |
35
|
6
|
|
|
|
|
42
|
$_->{type} ne 'unknown' && |
36
|
|
|
|
|
|
|
!(exists($_->{epoch}) && $_->{epoch} < 946684800) # 2000-01-01 00:00 |
37
|
6
|
50
|
33
|
|
|
2336
|
} @{$_[0]->{records}} |
38
|
|
|
|
|
|
|
] if( |
39
|
|
|
|
|
|
|
$_[0]->{creator} eq 'MsSt' && |
40
|
|
|
|
|
|
|
$_[0]->{type} eq 'MsDb' |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
} |
43
|
7
|
|
|
|
|
11652
|
} |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 NAME |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Palm::Treo680MessagesDB - Handler for Treo 680 SMS message databases |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 SYNOPSIS |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
use Palm::PDB; |
53
|
|
|
|
|
|
|
use Palm::Treo680MessagesDB timezone => 'Europe/London'; |
54
|
|
|
|
|
|
|
use Data::Dumper; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
my $pdb = Palm::PDB->new(); |
57
|
|
|
|
|
|
|
$pdb->Load("MessagesDB.pdb"); |
58
|
|
|
|
|
|
|
print Dumper(@{$pdb->{records}}); |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head1 DESCRIPTION |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
This is a helper class for the Palm::PDB package, which parses the |
63
|
|
|
|
|
|
|
database generated by a Treo 680 as a record of all your SMSes. |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head1 OPTIONS |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
You can set some global options when you 'use' the module: |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=over |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=item timezone |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Defaults to 'Europe/London'. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=item incl_raw |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Whether to include the raw binary blob of data in the parsed records. |
78
|
|
|
|
|
|
|
Defaults to false. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item debug |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Defaults to false. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
If false, unknown record-types and those which look like they weren't |
85
|
|
|
|
|
|
|
parsed properly (eg they have an impossible timestamp) are suppressed. |
86
|
|
|
|
|
|
|
This is done by over-riding Palm::PDB's C method. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
If true, include a hexadecimal dump of each record in the 'debug' |
89
|
|
|
|
|
|
|
field, and don't suppress unknown or badly parsed records. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=back |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head1 METHODS |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
This class inherits from Palm::Raw, so has all of its methods. The |
96
|
|
|
|
|
|
|
folliwing are over-ridden, and differ from that in the parent class |
97
|
|
|
|
|
|
|
thus: |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head2 ParseRecord |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Returns data structures with the following keys: |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=over |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=item rawdata |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
The raw data blob passed to the method. This is only present if the |
108
|
|
|
|
|
|
|
incl_raw option is true. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=item date |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
The date of the message, if available, in YYYY-MM-DD format |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=item time |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
The time of the message, if available, in HH:MM format |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=item epoch or timestamp (it's available under both names) |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
The epoch time of the message, if available. Note that because |
121
|
|
|
|
|
|
|
the database doesn't |
122
|
|
|
|
|
|
|
store the timezone, we assume 'Europe/London' when converting this |
123
|
|
|
|
|
|
|
to the seperate date and time fields. If you want to change |
124
|
|
|
|
|
|
|
that, then suppy a timezone option when you 'use' the module. |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Note that this is always the Unix epoch time, even though PalmOS |
127
|
|
|
|
|
|
|
uses an epoch based on 1904. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=item name |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
The name of the other party, which the Treo extracts from the SIM |
132
|
|
|
|
|
|
|
phone-book or from the Palm address book at the time the SMS is saved. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item number or phone |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
The number of the other party. This is not normalised so you might see |
137
|
|
|
|
|
|
|
the same number in different formats, eg 07979866975 and +447979866975. |
138
|
|
|
|
|
|
|
I may add number normalisation in the future. |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=item direction |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Either 'incoming', or 'outgoing'. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=back |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
Other fields may be added in the future. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub ParseRecord { |
151
|
4193
|
|
|
4193
|
1
|
143291
|
my $self = shift; |
152
|
4193
|
|
|
|
|
12643
|
my %record = @_; |
153
|
|
|
|
|
|
|
|
154
|
4193
|
|
|
|
|
7024
|
$record{rawdata} = delete($record{data}); |
155
|
4193
|
|
|
|
|
7091
|
my $parsed = _parseblob($record{rawdata}); |
156
|
4193
|
100
|
|
|
|
10451
|
delete $record{rawdata} unless($incl_raw); |
157
|
|
|
|
|
|
|
|
158
|
4193
|
|
|
|
|
8192
|
return {%record, %{$parsed}}; |
|
4193
|
|
|
|
|
35409
|
|
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub _parseblob { |
162
|
4193
|
|
|
4193
|
|
3641
|
my $buf = shift; |
163
|
4193
|
|
|
|
|
4445
|
my %record = (); |
164
|
|
|
|
|
|
|
|
165
|
4193
|
|
|
|
|
8634
|
my $type = 256 * ord(substr($buf, 10, 1)) + ord(substr($buf, 11, 1)); |
166
|
4193
|
|
|
|
|
5247
|
my($dir, $num, $name, $msg) = ('', '', '', ''); |
167
|
4193
|
100
|
66
|
|
|
16493
|
if($type == 0x400C || $type == 0x4009) { # 4009 not used by 680? |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
168
|
2247
|
50
|
|
|
|
3790
|
$dir = ($type == 0x400C) ? 'inbound' : 'outbound'; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# ASCIIZ number starting at 0x22 |
171
|
2247
|
|
|
|
|
13075
|
($num = substr($buf, 0x22)) =~ s/\00.*//s; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# immediately followed by ASCIIZ name, with some trailing 0s |
174
|
2247
|
|
|
|
|
4541
|
$name = substr($buf, length($num) + 1 + 0x22); |
175
|
2247
|
|
|
|
|
7945
|
$name =~ /^([^\00]*?)\00+(.*)$/s; |
176
|
2247
|
|
|
|
|
6674
|
($name, my $after_name) = ($1, $2); |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# four unknown bytes, then ASCIIZ message |
179
|
2247
|
|
|
|
|
6846
|
($msg = substr($after_name, 4)) =~ s/\00.*//s; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# two unknown bytes, then 32-bit time_t, but with 1904 epoch |
182
|
2247
|
|
|
|
|
3851
|
my $epoch = substr($after_name, 4 + length($msg) + 1 + 2, 4); |
183
|
|
|
|
|
|
|
|
184
|
2247
|
|
|
|
|
5960
|
$record{epoch} = |
185
|
|
|
|
|
|
|
0x1000000 * ord(substr($epoch, 0, 1)) + |
186
|
|
|
|
|
|
|
0x10000 * ord(substr($epoch, 1, 1)) + |
187
|
|
|
|
|
|
|
0x100 * ord(substr($epoch, 2, 1)) + |
188
|
|
|
|
|
|
|
ord(substr($epoch, 3, 1)) - |
189
|
|
|
|
|
|
|
2082844800; # offset from Palm epoch (1904) to Unix |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# if is because DateTime::from_epoch seems to DTwrongT on Win32 |
192
|
|
|
|
|
|
|
# when you get a negative epoch |
193
|
2247
|
100
|
|
|
|
4427
|
if($record{epoch} > 0) { |
194
|
2009
|
|
|
|
|
6697
|
my $dt = DateTime->from_epoch( |
195
|
|
|
|
|
|
|
epoch => $record{epoch}, |
196
|
|
|
|
|
|
|
time_zone => $timezone |
197
|
|
|
|
|
|
|
); |
198
|
2009
|
|
|
|
|
829924
|
$record{date} = sprintf('%04d-%02d-%02d', $dt->year(), $dt->month(), $dt->day()); |
199
|
2009
|
|
|
|
|
24145
|
$record{time} = sprintf('%02d:%02d', $dt->hour(), $dt->minute()); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
} elsif($type == 0x0002) { |
202
|
1505
|
|
|
|
|
1714
|
$dir = 'outbound'; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# ASCIIZ number starting at 0x46 |
205
|
1505
|
|
|
|
|
8559
|
($num = substr($buf, 0x46)) =~ s/\00.*//s; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# immediately followed by ASCIIZ name, with some trailing 0s |
208
|
|
|
|
|
|
|
# some Trsm gibberish, then an ASCIIZ message |
209
|
|
|
|
|
|
|
# $name = substr($buf, length($num) + 1 + 0x46); |
210
|
|
|
|
|
|
|
# $name =~ /^([^\00]+)\00+.Trsm....([^\00]+)\00.*$/s; |
211
|
|
|
|
|
|
|
# ($name, $msg) = ($1, $2); |
212
|
1505
|
|
|
|
|
5513
|
($name = substr($buf, length($num) + 1 + 0x46)) =~ s/\00.*//s; |
213
|
1505
|
100
|
|
|
|
2864
|
$name = undef unless(length($name)); |
214
|
1505
|
100
|
100
|
|
|
5295
|
$name .= " (may be truncated)" if($name && length($name) == 31); |
215
|
1505
|
|
|
|
|
9854
|
($msg = $buf) =~ s/^.*?Trsm....(([^\00]+)\00.*)$/$2/s; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# 32-bit time_t, but with 1904 epoch |
218
|
1505
|
|
|
|
|
2180
|
my $epoch = substr($buf, 0x24, 4); |
219
|
1505
|
|
|
|
|
4583
|
$record{epoch} = |
220
|
|
|
|
|
|
|
0x1000000 * ord(substr($epoch, 0, 1)) + |
221
|
|
|
|
|
|
|
0x10000 * ord(substr($epoch, 1, 1)) + |
222
|
|
|
|
|
|
|
0x100 * ord(substr($epoch, 2, 1)) + |
223
|
|
|
|
|
|
|
ord(substr($epoch, 3, 1)) - |
224
|
|
|
|
|
|
|
2082844800; |
225
|
1505
|
|
|
|
|
5031
|
my $dt = DateTime->from_epoch( |
226
|
|
|
|
|
|
|
epoch => $record{epoch}, |
227
|
|
|
|
|
|
|
time_zone => $timezone |
228
|
|
|
|
|
|
|
); |
229
|
1505
|
|
|
|
|
626458
|
$record{date} = sprintf('%04d-%02d-%02d', $dt->year(), $dt->month(), $dt->day()); |
230
|
1505
|
|
|
|
|
18060
|
$record{time} = sprintf('%02d:%02d', $dt->hour(), $dt->minute()); |
231
|
|
|
|
|
|
|
|
232
|
1505
|
100
|
66
|
|
|
15528
|
if($msg eq "\01N@" && length($1) == 14) { # no real body. bleh |
233
|
7
|
|
|
|
|
25
|
delete @record{qw(epoch date time)}; |
234
|
7
|
|
|
|
|
40
|
$type = 'unknown'; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
} elsif($type == 0x0001) { |
237
|
7
|
|
|
|
|
19
|
$dir = 'outbound'; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# number field at 0x4C, possibly including some leading crap |
240
|
|
|
|
|
|
|
# then an ASCIIZ number |
241
|
7
|
|
|
|
|
58
|
($num = substr($buf, 0x4C)) =~ s/(^\00*[^\00]+)\00.*/$1/s; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# immediately followed by ASCIIZ name, with some trailing 0s |
244
|
7
|
|
|
|
|
48
|
($name = substr($buf, length($num) + 0x4C + 1)) =~ s/\00.*//s; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# ASCIIZ message, prefixed by 0x20 0x02 16-bit length word |
247
|
7
|
|
|
|
|
34
|
$msg = substr($buf, length($num) + 0x4C + 1 + length($name) + 1); |
248
|
7
|
|
|
|
|
73
|
$msg =~ s/^.*\x20\x02..|\00.*$//g; |
249
|
|
|
|
|
|
|
|
250
|
7
|
|
|
|
|
27
|
$num =~ s/^[^0-9+]+//; # clean leading rubbish from number |
251
|
|
|
|
|
|
|
|
252
|
7
|
|
|
|
|
17
|
my $epoch = substr($buf, 0x24, 4); |
253
|
7
|
|
|
|
|
41
|
$record{epoch} = |
254
|
|
|
|
|
|
|
0x1000000 * ord(substr($epoch, 0, 1)) + |
255
|
|
|
|
|
|
|
0x10000 * ord(substr($epoch, 1, 1)) + |
256
|
|
|
|
|
|
|
0x100 * ord(substr($epoch, 2, 1)) + |
257
|
|
|
|
|
|
|
ord(substr($epoch, 3, 1)) - |
258
|
|
|
|
|
|
|
2082844800; |
259
|
7
|
|
|
|
|
44
|
my $dt = DateTime->from_epoch( |
260
|
|
|
|
|
|
|
epoch => $record{epoch}, |
261
|
|
|
|
|
|
|
time_zone => $timezone |
262
|
|
|
|
|
|
|
); |
263
|
7
|
|
|
|
|
2947
|
$record{date} = sprintf('%04d-%02d-%02d', $dt->year(), $dt->month(), $dt->day()); |
264
|
7
|
|
|
|
|
99
|
$record{time} = sprintf('%02d:%02d', $dt->hour(), $dt->minute()); |
265
|
|
|
|
|
|
|
|
266
|
7
|
50
|
|
|
|
80
|
if($num eq '') { |
267
|
0
|
|
|
|
|
0
|
delete @record{qw(epoch date time)}; |
268
|
0
|
|
|
|
|
0
|
$type = 'unknown'; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} elsif($type == 0x0000 && substr($buf, 0x0040, 1) ne "\00") { |
271
|
14
|
|
|
|
|
22
|
$dir = 'outbound'; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# message first, preceded by 0x2002 and 16 bit length |
274
|
14
|
|
|
|
|
130
|
($msg = $buf) =~ s/^.*\040\02..//s; |
275
|
14
|
|
|
|
|
54
|
$msg =~ s/\00.*//s; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# then some cruft, ASCIIZ number and name |
278
|
|
|
|
|
|
|
# find number by finding *last* sequence of 6 or more digits, then |
279
|
|
|
|
|
|
|
# going back 1 to find a + if it's there |
280
|
14
|
|
|
|
|
490
|
($num, $name) = split(/\00/, ($buf =~ /(\+?\d{6,}\00[^\00]+\00)/g)[-1]); |
281
|
|
|
|
|
|
|
|
282
|
14
|
|
|
|
|
58
|
my $epoch = substr($buf, index($buf, "\x80\00") + 2, 4); |
283
|
14
|
|
|
|
|
61
|
$record{epoch} = |
284
|
|
|
|
|
|
|
0x1000000 * ord(substr($epoch, 0, 1)) + |
285
|
|
|
|
|
|
|
0x10000 * ord(substr($epoch, 1, 1)) + |
286
|
|
|
|
|
|
|
0x100 * ord(substr($epoch, 2, 1)) + |
287
|
|
|
|
|
|
|
ord(substr($epoch, 3, 1)) - |
288
|
|
|
|
|
|
|
2082844800; |
289
|
14
|
|
|
|
|
70
|
my $dt = DateTime->from_epoch( |
290
|
|
|
|
|
|
|
epoch => $record{epoch}, |
291
|
|
|
|
|
|
|
time_zone => $timezone |
292
|
|
|
|
|
|
|
); |
293
|
14
|
|
|
|
|
5831
|
$record{date} = sprintf('%04d-%02d-%02d', $dt->year(), $dt->month(), $dt->day()); |
294
|
14
|
|
|
|
|
180
|
$record{time} = sprintf('%02d:%02d', $dt->hour(), $dt->minute()); |
295
|
|
|
|
|
|
|
|
296
|
14
|
50
|
|
|
|
151
|
if($num eq '') { |
297
|
0
|
|
|
|
|
0
|
delete @record{qw(epoch date time)}; |
298
|
0
|
|
|
|
|
0
|
$type = 'unknown'; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
} elsif($type == 0x0000) { |
301
|
343
|
|
|
|
|
503
|
$dir = 'outbound'; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# number field at 0x4C, possibly including some leading crap |
304
|
|
|
|
|
|
|
# then an ASCIIZ number |
305
|
343
|
|
|
|
|
2853
|
($num = substr($buf, 0x4C)) =~ s/(^\00*[^\00]+)\00.*/$1/s; |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# immediately followed by ASCIIZ name, with some trailing 0s |
308
|
343
|
|
|
|
|
1581
|
($name = substr($buf, length($num) + 0x4C + 1)) =~ s/\00.*//s; |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# ASCIIZ message, prefixed by 0x20 0x02 16-bit length word |
311
|
343
|
|
|
|
|
827
|
$msg = substr($buf, length($num) + 0x4C + 1 + length($name) + 1); |
312
|
343
|
|
|
|
|
3851
|
$msg =~ s/^.*\x20\x02..|\00.*$//g; |
313
|
|
|
|
|
|
|
|
314
|
343
|
|
|
|
|
688
|
$num =~ s/^[^0-9+]+//; # clean leading rubbish from number |
315
|
|
|
|
|
|
|
|
316
|
343
|
|
|
|
|
535
|
my $epoch = substr($buf, 0x24, 4); |
317
|
343
|
|
|
|
|
1310
|
$record{epoch} = |
318
|
|
|
|
|
|
|
0x1000000 * ord(substr($epoch, 0, 1)) + |
319
|
|
|
|
|
|
|
0x10000 * ord(substr($epoch, 1, 1)) + |
320
|
|
|
|
|
|
|
0x100 * ord(substr($epoch, 2, 1)) + |
321
|
|
|
|
|
|
|
ord(substr($epoch, 3, 1)) - |
322
|
|
|
|
|
|
|
2082844800; |
323
|
343
|
|
|
|
|
1280
|
my $dt = DateTime->from_epoch( |
324
|
|
|
|
|
|
|
epoch => $record{epoch}, |
325
|
|
|
|
|
|
|
time_zone => $timezone |
326
|
|
|
|
|
|
|
); |
327
|
343
|
|
|
|
|
237187
|
$record{date} = sprintf('%04d-%02d-%02d', $dt->year(), $dt->month(), $dt->day()); |
328
|
343
|
|
|
|
|
4430
|
$record{time} = sprintf('%02d:%02d', $dt->hour(), $dt->minute()); |
329
|
|
|
|
|
|
|
|
330
|
343
|
100
|
|
|
|
3582
|
if($num eq '') { |
331
|
7
|
|
|
|
|
32
|
delete @record{qw(epoch date time)}; |
332
|
7
|
|
|
|
|
30
|
$type = 'unknown'; |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
} else { |
335
|
77
|
|
|
|
|
100
|
$type = 'unknown'; |
336
|
|
|
|
|
|
|
} |
337
|
4193
|
100
|
|
|
|
23971
|
$record{debug} = "\n".Data::Hexdumper::hexdump(suppress_warnings => 1, data => $buf) if($debug); |
338
|
4193
|
|
|
|
|
1793793
|
$record{device} = 'Treo 680'; |
339
|
4193
|
|
|
|
|
5101
|
$record{direction} = $dir; # inbound or outbound |
340
|
4193
|
|
|
|
|
5839
|
$record{phone} = $record{number} = $num; |
341
|
4193
|
|
|
|
|
6793
|
$record{timestamp} = $record{epoch}; |
342
|
4193
|
|
|
|
|
5199
|
$record{name} = $name; |
343
|
4193
|
|
|
|
|
4457
|
$record{text} = $msg; |
344
|
4193
|
100
|
|
|
|
12150
|
$record{type} = $type eq 'unknown' ? $type : sprintf('0x%04X', $type); |
345
|
4193
|
|
|
|
|
8800
|
return \%record; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head1 BUGS, LIMITATIONS and FEEDBACK |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
The database structure is undocumented. Consequently it has had to be |
351
|
|
|
|
|
|
|
reverse-engineered. There appear to be several message formats in |
352
|
|
|
|
|
|
|
the database. Some have a superficial resemblance to those used by |
353
|
|
|
|
|
|
|
the 650 (and which is partially documented by Palm) but there is no |
354
|
|
|
|
|
|
|
publicly available documentation that I could find for the others - |
355
|
|
|
|
|
|
|
if you know where I can get docs, please let me know! |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
I can only reverse-engineer record formats that appear on my phone, so |
358
|
|
|
|
|
|
|
there may be some missing. In addition, I may decode some formats |
359
|
|
|
|
|
|
|
incorrectly because they're not quite what I thought they were. If |
360
|
|
|
|
|
|
|
this affects you, please please please send me the offending data. |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
There is currently no support for creating a new database, or for |
363
|
|
|
|
|
|
|
editing the contents of an existing database. If you need that |
364
|
|
|
|
|
|
|
functionality, please submit a patch with tests. I will *not* write |
365
|
|
|
|
|
|
|
this myself unless I need it. Behaviour if you try to create or |
366
|
|
|
|
|
|
|
edit a database is currently undefined, but editing a database will |
367
|
|
|
|
|
|
|
almost certainly break it. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
If you find any bugs please report them either using |
370
|
|
|
|
|
|
|
L or by email. Ideally, I would like to receive |
371
|
|
|
|
|
|
|
sample data and a test file, which fails with the latest version of |
372
|
|
|
|
|
|
|
the module but will pass when I fix the bug. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
Sample data can be either in the form of a complete database, or a |
375
|
|
|
|
|
|
|
dump of just a single record structure, which *must* include the |
376
|
|
|
|
|
|
|
raw binary data - |
377
|
|
|
|
|
|
|
use the 'incl_raw' option when you load the module, and save the |
378
|
|
|
|
|
|
|
data structure to a file using Data::Dumper. |
379
|
|
|
|
|
|
|
Feel free to obscure |
380
|
|
|
|
|
|
|
real names, phone numbers, and messages in the data, but you |
381
|
|
|
|
|
|
|
should ensure that phone numbers are correctly formed, and that |
382
|
|
|
|
|
|
|
you don't change the length of any parts of the message. Also, |
383
|
|
|
|
|
|
|
please don't change any non-human-readable parts of the record. |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=head1 SEE ALSO |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
L, which handles SMS messages databases on some other models |
388
|
|
|
|
|
|
|
of Treo. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
L |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
L |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=head1 THANKS TO |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Michal Seliga, for sample MMS data |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=head1 AUTHOR, COPYRIGHT and LICENCE |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
Copyright 2008 David Cantrell Edavid@cantrell.org.ukE |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
This software is free-as-in-speech software, and may be used, |
403
|
|
|
|
|
|
|
distributed, and modified under the terms of either the GNU |
404
|
|
|
|
|
|
|
General Public Licence version 2 or the Artistic Licence. It's |
405
|
|
|
|
|
|
|
up to you which one you use. The full text of the licences can |
406
|
|
|
|
|
|
|
be found in the files GPL2.txt and ARTISTIC.txt, respectively. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head1 CONSPIRACY |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
This module is also free-as-in-mason software. |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=cut |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
1; |