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