| 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 |  | 32344 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 46 |  | 
| 21 | 1 |  |  | 1 |  | 7 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 22 | 1 |  |  | 1 |  | 656 | use Palm::Raw(); | 
|  | 1 |  |  |  |  | 550 |  | 
|  | 1 |  |  |  |  | 31 |  | 
| 23 | 1 |  |  | 1 |  | 7 | use vars qw($VERSION @ISA @folders); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 1599 |  | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | $VERSION = 0.04; | 
| 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 |  | 12 | &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, | 
| 212 |  |  |  |  |  |  | [ "SMS!", "DATA" ], | 
| 213 |  |  |  |  |  |  | ); | 
| 214 | 1 |  |  |  |  | 21 | &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, | 
| 215 |  |  |  |  |  |  | [ "HsCh", "SMct" ], | 
| 216 |  |  |  |  |  |  | ); | 
| 217 | 1 | 50 |  | 1 |  | 263 | eval "use Palm::Treo680MessagesDB" || | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 1 |  |  |  |  | 78 |  | 
| 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 | 1266 | my $self = shift; | 
| 338 | 2 |  |  |  |  | 8 | my %record = @_; | 
| 339 | 2 |  |  |  |  | 3 | 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 |  |  |  |  | 6 | ($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 |  |  |  | 4 | 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 |  |  |  |  | 2 | my $unpack; | 
| 415 |  |  |  |  |  |  |  | 
| 416 | 1 |  |  |  |  | 10 | ($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 |  |  |  |  | 3 | $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 |  |  |  |  | 4 | $timestamp -= $EPOCH_1904; | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # Assign extracted values to record | 
| 440 | 2 | 50 |  |  |  | 7 | if($self->{creator} ne 'MsSt') { | 
| 441 | 2 |  |  |  |  | 4 | $record{name}      = $name; | 
| 442 | 2 |  |  |  |  | 4 | $record{firstName} = $firstName; | 
| 443 | 2 |  |  |  |  | 5 | $record{phone}     = $phone; | 
| 444 | 2 |  |  |  |  | 3 | $record{timestamp} = $timestamp; | 
| 445 | 2 |  |  |  |  | 3 | $record{folder}    = $record{category}; | 
| 446 | 2 |  |  |  |  | 4 | $record{text}      = $text; | 
| 447 |  |  |  |  |  |  |  | 
| 448 | 2 |  |  |  |  | 4 | $record{smsh}      = $smsh; | 
| 449 | 2 |  |  |  |  | 2 | $record{unknown1}  = $unknown1; | 
| 450 | 2 |  |  |  |  | 4 | $record{unknown2}  = $unknown2; | 
| 451 | 2 |  |  |  |  | 4 | $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__ |