File Coverage

blib/lib/Device/Gsm/Sms.pm
Criterion Covered Total %
statement 76 184 41.3
branch 19 82 23.1
condition 3 21 14.2
subroutine 13 34 38.2
pod 12 23 52.1
total 123 344 35.7


line stmt bran cond sub pod time code
1             # Device::Gsm::Sms - SMS message simple class that represents a text SMS message
2             # Copyright (C) 2002-2015 Cosimo Streppone, cosimo@cpan.org
3             # Copyright (C) 2006-2015 Grzegorz Wozniak, wozniakg@gmail.com
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it only under the terms of Perl itself.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # Perl licensing terms for details.
12             #
13             # Commercial support is available. Write me if you are
14             # interested in new features or software support.
15              
16             package Device::Gsm::Sms;
17              
18 7     7   30 use strict;
  7         9  
  7         159  
19 7     7   2833 use integer;
  7         52  
  7         28  
20              
21 7     7   168 use constant SMS_DELIVER => 0x00;
  7         6  
  7         291  
22 7     7   25 use constant SMS_SUBMIT => 0x01;
  7         8  
  7         244  
23 7     7   25 use constant SMS_STATUS => 0x02;
  7         9  
  7         242  
24              
25 7     7   2425 use Device::Gsm::Pdu;
  7         17  
  7         135  
26 7     7   2260 use Device::Gsm::Sms::Structure;
  7         16  
  7         165  
27 7     7   36 use Device::Gsm::Sms::Token;
  7         9  
  7         9813  
28              
29 0     0   0 sub _log { print @_, "\n"; }
30 0     0   0 sub _parent { $_[0]->{_parent} }
31              
32             #
33             # new(
34             # header => '+CMGL: .....',
35             # pdu => '[encoded pdu string]',
36             # )
37             #
38             # creates message object
39             #
40             sub new {
41 16     16 1 6562 my ($proto, %opt) = @_;
42 16   33     67 my $class = ref $proto || $proto;
43              
44             # Create new message object
45 16         16 my $self = {};
46              
47             # Store gsm parent object reference
48 16 50       35 if (exists $opt{'parent'}) {
49 0         0 $self->{'_parent'} = $opt{'parent'};
50              
51             # Assume default storage for sms message
52 0   0     0 $opt{'storage'} ||= $self->{'_parent'}->storage();
53             }
54              
55             # Store options into main object
56 16         71 $self->{'options'} = \%opt;
57              
58             # Hash to contain token objects after decoding (must be accessible by name)
59 16         23 $self->{'tokens'} = {};
60              
61 16 50 33     50 return undef unless (exists $opt{'header'} && exists $opt{'pdu'});
62              
63             #_log("NEW SMS OBJECT");
64             #_log("Header [$opt{header}]");
65             #_log("PDU [$opt{pdu}]");
66              
67             # Check for valid msg header (thanks to Pierre Hilson for his patch
68             # to make this regex work also for Alcatel gsm software)
69 16 100       135 if ($opt{'header'} =~ /\+CMGL:\s*(\d+),\s*(\d+),\s*(\w*),\s*(\d+)/o) {
    50          
70              
71 14         38 $self->{'index'} = $1; # Position of message in SIM card
72 14         22 $self->{'status'}
73             = $2; # Status of message (REC READ/UNREAD, STO, ...);
74 14         20 $self->{'alpha'} = $3; # Alphanumeric representation of sender
75 14         20 $self->{'length'} = $4; # Final length of message
76 14         16 $self->{'pdu'} = $opt{'pdu'}; # PDU content
77 14         28 $self->{'storage'} = $opt{'storage'}; # Storage (SM or ME)
78              
79 14         16 bless $self, $class;
80              
81 14 50       26 if ($self->decode(Device::Gsm::Sms::SMS_DELIVER)) {
    0          
82              
83             # _log('OK, message decoded correctly!');
84             }
85             elsif ($self->decode(Device::Gsm::Sms::SMS_STATUS)) {
86              
87             }
88             else {
89              
90             # _log('CASINO!');
91 0         0 undef $self;
92             }
93              
94             }
95             elsif ($opt{'header'} =~ /\+CDS:\s*(\d+)/o) {
96              
97 0         0 $self->{'mr'} = $1; # Message number
98 0         0 $self->{'pdu'} = $opt{'pdu'}; # PDU content
99              
100 0         0 bless $self, $class;
101              
102 0 0       0 if ($self->decode(Device::Gsm::Sms::SMS_STATUS)) {
103             # _log('OK, message decoded correctly!');
104              
105             }
106             else {
107              
108             # _log('CASINO!');
109 0         0 undef $self;
110             }
111              
112             }
113             else {
114              
115             # Warning: could not parse message header
116 2         4 undef $self;
117              
118             }
119              
120 16         30 return $self;
121             }
122              
123             #
124             # time(): returns message time in ascii format
125             #
126             sub time {
127 0     0 0 0 my $self = shift;
128 0 0       0 if (my $t = $self->token('SCTS')) {
129 0         0 return $t->toString();
130             }
131 0         0 return '';
132             }
133              
134             #
135             # time_dt (): returns status message discharge time in ascii format
136             #
137             sub time_dt {
138 0     0 0 0 my $self = shift;
139 0 0       0 if (my $t = $self->token('DT')) {
140 0         0 return $t->toString();
141             }
142 0         0 return '';
143             }
144              
145             #
146             # message_ref(): returns message reference of status message
147             #
148             sub message_ref {
149 0     0 0 0 my $self = shift;
150 0 0       0 if (my $t = $self->token('MR')) {
151 0         0 return $t->toString();
152             }
153 0         0 return '';
154             }
155              
156             #
157             # type(): returns message type in ascii readable format
158             #
159             {
160              
161             # List of allowed status strings
162             my @status
163             = ('UNKNOWN', 'REC UNREAD', 'REC READ', 'SENT UNREAD', 'SENT READ');
164              
165             sub status () {
166 0     0 1 0 my $self = shift;
167 0 0       0 return $status[ defined $self->{'status'} ? $self->{'status'} : 0 ];
168             }
169              
170             }
171              
172             #
173             # decode( CMGL_header, pdu_string )
174             #
175             # creates a new Device::Gsm::Sms object from
176             # PDU encoded message string returned by +CMGL commands
177             #
178             # If some error occurs, returns undef.
179             #
180             #
181             sub _old_decode {
182 0     0   0 my ($header, $pdu) = @_;
183 0         0 my %msg = ();
184 0         0 my $errors = 0;
185              
186             # Copy original header/pdu strings
187 0         0 $msg{'_HEADER'} = $header;
188 0         0 $msg{'_PDU'} = $pdu;
189              
190             #
191             # Decode header string
192             #
193 0 0       0 if ($header =~ /\+CMGL:\s*(\d+),(\d+),(\d*),(\d+)/) {
194 0         0 $msg{'index'} = $1;
195 0         0 $msg{'type'} = $2;
196 0         0 $msg{'xxx'} = $3; # XXX
197 0         0 $msg{'length'} = $4;
198             }
199              
200             #
201             # Decode all parts of PDU message
202             #
203              
204             # ----------------------------------- SCA (service center address)
205 0         0 my $sca_length = hex(substr $pdu, 0, 2);
206 0 0       0 if ($sca_length == 0) {
207              
208             # No SCA provided, take default
209 0         0 $msg{'SCA'} = undef;
210             }
211             else {
212              
213             # Parse SCA address
214             #print STDERR "SCA length = ", $sca_length, "; ";
215             #print STDERR "Parsing address ", substr( $pdu, 0, ($sca_length+1) << 1 );
216 0         0 $msg{'SCA'} = Device::Gsm::Pdu::decode_address(
217             substr($pdu, 0, ($sca_length + 1) << 1));
218              
219             #print STDERR ' = `', $msg{'SCA'}, "'\n";
220             }
221              
222             # ----------------------------------- PDU type
223 0         0 $pdu = substr $pdu => (($sca_length + 1) << 1);
224 0         0 $msg{'PDU_TYPE'} = substr $pdu, 0, 2;
225 0         0 undef $sca_length;
226              
227             # ----------------------------------- OA (originating address)
228 0         0 $pdu = substr $pdu => 2;
229 0         0 my $oa_length = hex(substr $pdu, 0, 2);
230              
231 0         0 $msg{'OA'} = Device::Gsm::Pdu::decode_address(
232             substr($pdu, 0, ($oa_length + 1) << 1));
233 0         0 undef $oa_length;
234              
235             # PID (protocol identifier)
236             # DCS (data coding scheme)
237             # SCTS (service center time stamp)
238             # UDL + UD (user data)
239 0         0 @msg{qw/PID DCS SCTS UDL UD/} = unpack 'A2 A2 A14 A2 A*', $pdu;
240              
241             #map { $msg{$_} = hex $msg{$_} } qw/PID DCS UDL/;
242             #
243             # Decode USER DATA in 7/8 bit encoding
244             #
245 0 0       0 if ($msg{'DCS'} eq '00') { # DCS_7BIT
    0          
246 0         0 Device::Gsm::Pdu::decode_text7($msg{'UD'});
247             }
248             elsif ($msg{'DCS'} eq 'F6') { # DCS_8BIT
249 0         0 Device::Gsm::Pdu::decode_text8($msg{'UD'});
250             }
251              
252             # XXX DEBUG
253             #foreach( sort keys %msg ) {
254             # print STDERR 'MSG[', $_, '] = `'.$msg{$_}.'\'', "\n";
255             #}
256              
257 0         0 bless \%msg, 'Device::Gsm::Sms';
258             }
259              
260             sub decode {
261 14     14 1 14 my ($self, $type) = @_;
262 14         22 $self->{'type'} = $type;
263              
264             # Get list of tokens for this message (from ::Sms::Structure)
265 14         15 my $cPdu = $self->{'pdu'};
266              
267             # Check that PDU is not empty
268 14 50       24 return 0 unless $cPdu;
269              
270             # Backup copy for "backtracking"
271 14         12 my $cPduCopy = $cPdu;
272              
273 14         35 my @token_names = $self->structure();
274 14         17 my $decoded = 1;
275              
276             #is udh in pdu?
277 14         10 my $udh_parsed = 0;
278 14         24 while (@token_names) {
279              
280             # Create new token object
281             my $token = new Sms::Token(shift @token_names,
282 104         266 { messageTokens => $self->{'tokens'} });
283 104 50       197 if (!defined $token) {
284 0         0 $decoded = 0;
285 0         0 last;
286             }
287              
288             # If decoding is completed successfully, add token object to message
289             #_log('PDU BEFORE ['.$cPdu.']', length($cPdu) );
290              
291 104 50       217 if ($token->decode(\$cPdu)) {
292              
293             # Store token object into SMS message
294 104         212 $self->{'tokens'}->{ $token->name() } = $token;
295              
296             # Catch message type indicator (MTI) and re-load structure
297             # We must also skip message types 0x02 and 0x03 because we don't handle them currently
298 104 100       170 if ($token->name() eq 'PDUTYPE') {
299              
300 16         24 my $mti = $token->MTI();
301 16         27 my $udhi = $token->UDHI();
302              
303             # # If MTI has bit 1 on, this could be a SMS-STATUS message (0x02), or (0x03???)
304             # if( $mti >= SMS_STATUS ) {
305             # _log('skipping unhandled message type ['.$mti.']');
306             # return undef;
307             # }
308              
309 16 100       24 if ($mti != $type) {
310              
311             #_log('token PDUTYPE, data='.$token->data().' MTI='.$token->get('MTI').' ->MTI()='.$token->MTI());
312             #
313             # This is a SMS-SUBMIT message, so:
314             #
315             # 1) change type
316             # 2) restore original PDU message
317             # 3) reload token structure
318             # 4) restart decoding
319             #
320 2         3 $self->type($type = $mti);
321              
322 2         5 $cPdu = $cPduCopy;
323 2         3 @token_names = $self->structure();
324              
325             #_log('RESTARTING DECODING AFTER MTI DETECTION'); #;
326 2         4 redo;
327             }
328              
329 14 50 33     68 if ($udh_parsed == 0 and $udhi == 1) {
330 0         0 $cPdu = $cPduCopy;
331 0         0 @token_names = $self->structure();
332 0         0 $udh_parsed = 1;
333 0         0 redo;
334             }
335              
336             #_log(' ', $token->name(), ' DATA = ', $token->toString() );
337              
338             }
339              
340             }
341              
342             #_log('PDU AFTER ['.$cPdu.']', length($cPdu) );
343              
344             }
345              
346             #_log("\n", 'PRESS ENTER TO CONTINUE'); ;
347              
348 14         31 return $decoded;
349              
350             }
351              
352             #
353             # Delete an sms message
354             #
355             sub delete {
356 0     0 1 0 my $self = $_[0];
357 0         0 my $gsm = $self->_parent();
358 0         0 my $ok;
359              
360             # Try to delete message
361 0         0 my $msg_index = $self->index();
362 0         0 my $storage = $self->storage();
363              
364             # Issue delete command
365 0 0 0     0 if (ref $gsm && $storage && $msg_index >= 0) {
      0        
366 0         0 $ok = $gsm->delete_sms($msg_index, $storage);
367 0 0       0 $gsm->log->write('info',
368             'Delete sms n.'
369             . $msg_index
370             . ' in storage '
371             . $storage . ' => '
372             . ($ok ? 'OK' : '*ERROR'));
373             }
374             else {
375 0         0 $gsm->log->write('warn',
376             'Could not delete sms n.'
377             . $msg_index
378             . ' in storage '
379             . $storage
380             . '. Internal error.');
381 0         0 $ok = undef;
382             }
383              
384 0         0 return $ok;
385             }
386              
387             #
388             # Returns message own index number (position)
389             #
390             sub index {
391 0     0 1 0 my $self = $_[0];
392 0         0 return $self->{'index'};
393             }
394              
395             #
396             # Returns message storage (SM - SIM card or ME - phone memory)
397             #
398             sub storage {
399 0     0 1 0 my $self = $_[0];
400 0         0 return $self->{'storage'};
401             }
402              
403             #
404             # Only valid for SMS_SUBMIT and SMS_STATUS messages
405             #
406             sub recipient {
407 0     0 1 0 my $self = shift;
408 0 0 0     0 if ($self->type() == SMS_SUBMIT or $self->type() == SMS_STATUS) {
409 0         0 my $t = $self->token('DA');
410 0 0       0 return $t->toString() if $t;
411             }
412             }
413              
414             #
415             # Only valid for SMS_STATUS messages?
416             #
417             sub message_reference {
418 0     0 0 0 my $self = shift;
419 0 0       0 if ( $self->type() == SMS_STATUS) {
420 0         0 my $t = $self->token('MR');
421 0 0       0 return $t->toString() if $t;
422             }
423             }
424              
425              
426             #
427             #Only valid for SMS_STATUS messages returns status code(in hex) extracted from status message
428             #Codes are explained in ST.pm
429             #
430             sub delivery_status {
431 0     0 0 0 my $self = shift;
432 0 0       0 if ($self->type() == SMS_STATUS) {
433 0         0 my $t = $self->token('ST');
434 0 0       0 return $t->toString() if $t;
435             }
436             }
437              
438             #
439             # Only valid for SMS_DELIVER messages (?)
440             #
441             sub sender {
442 0     0 1 0 my $self = shift;
443 0 0       0 if ($self->type() == SMS_DELIVER) {
444 0         0 my $t = $self->token('OA');
445 0 0       0 return $t->toString() if $t;
446             }
447             }
448              
449             # Alias for text()
450             sub content {
451 0     0 1 0 return $_[0]->text();
452             }
453              
454             sub text {
455 12     12 1 2320 my $self = shift;
456 12         21 my $t = $self->token('UD');
457 12 50       42 return $t->toString() if $t;
458             }
459              
460             #
461             #only valid for SMS_DELIVER messages, retuns presence of UDH
462             #
463             sub is_udh {
464 0     0 0 0 my $self = shift;
465 0 0       0 if ($self->type() == SMS_DELIVER) {
466 0         0 return $self->{'tokens'}->{'PDUTYPE'}->{'_UDHI'};
467             }
468             }
469              
470             #
471             #only valid for SMS_DELIVER messages with UDH, returns if sms is csms
472             #
473             sub is_csms {
474 0     0 0 0 my $self = shift;
475 0 0       0 if ($self->is_udh()) {
476 0         0 return $self->{'tokens'}->{'UDH'}->{'_IS_CSMS'};
477             }
478             }
479              
480             #
481             #only valid for SMS_DELIVER messages with UDH, retuns CSM reference number
482             #
483             sub csms_ref_num {
484 0     0 0 0 my $self = shift;
485 0 0       0 if ($self->is_csms()) {
486 0         0 return $self->{'tokens'}->{'UDH'}->{'_REF_NUM'};
487             }
488             }
489              
490             #
491             #only valid for SMS_DELIVER messages with UDH, retuns CSM reference number
492             #
493             sub csms_ref_hex {
494 0     0 0 0 my $self = shift;
495 0 0       0 if ($self->is_csms()) {
496 0         0 return $self->{'tokens'}->{'UDH'}->{'_REF_HEX'};
497             }
498             }
499              
500             #
501             #only valid for SMS_DELIVER messages with UDH, retuns CSM parts count
502             #
503             sub csms_parts {
504 0     0 0 0 my $self = shift;
505 0 0       0 if ($self->is_csms()) {
506 0         0 return $self->{'tokens'}->{'UDH'}->{'_PARTS'};
507             }
508             }
509              
510             #
511             #only valid for SMS_DELIVER messages with UDH, retuns CSM current part number
512             #
513             sub csms_part_num {
514 0     0 0 0 my $self = shift;
515 0 0       0 if ($self->is_csms()) {
516 0         0 return $self->{'tokens'}->{'UDH'}->{'_PART_NUM'};
517             }
518             }
519              
520             sub token ($) {
521 12     12 1 13 my ($self, $token_name) = @_;
522 12 50       24 return undef unless $token_name;
523              
524 12 50       22 if (exists $self->{'tokens'}->{$token_name}) {
525 12         19 return $self->{'tokens'}->{$token_name};
526             }
527             else {
528 0         0 warn('undefined token ' . $token_name . ' for this sms');
529 0         0 return undef;
530             }
531             }
532              
533             #
534             # Returns type of sms (SMS_DELIVER || SMS_SUBMIT)
535             #
536             sub type {
537 20     20 1 16 my $self = shift;
538 20 100       33 if (@_) {
539 2         3 $self->{'type'} = shift;
540             }
541 20         44 $self->{'type'};
542             }
543              
544             =pod
545              
546             =head1 NAME
547              
548             Device::Gsm::Sms - SMS message internal class that represents a single text SMS message
549              
550             =head1 SYNOPSIS
551              
552             # A list of Device::Gsm::Sms messages is returned by
553             # Device::Gsm messages() method.
554              
555             use Device::Gsm;
556             ...
557             @sms = $gsm->messages();
558              
559             if( @sms ) {
560             foreach( @sms ) {
561             print $msg->storage() , "\n";
562             print $msg->recipient() , "\n";
563             print $msg->sender() , "\n";
564             print $msg->content() , "\n";
565             print $msg->time() , "\n";
566             print $msg->type() , "\n";
567             }
568             }
569              
570             # Or you can instance a sms message from raw PDU data
571             my $msg = new Device::Gsm::Sms(
572             header => '+CMGL: ...',
573             pdu => `[encoded pdu data]',
574             storage=> 'ME', # or 'SC'
575             );
576              
577             if( defined $msg ) {
578             print $msg->recipient() , "\n";
579             print $msg->sender() , "\n";
580             print $msg->content() , "\n"; # or $msg->text()
581             print $msg->time() , "\n";
582             print $msg->type() , "\n";
583             }
584              
585             $msg->delete();
586              
587             =head1 DESCRIPTION
588              
589             C class implements very basic SMS message object,
590             that can be used to decode C<+CMGL> GSM command response to build a more
591             friendly high-level object.
592              
593             =head1 METHODS
594              
595             The following is a list of methods applicable to C objects.
596              
597             =head2 content()
598              
599             See text() method.
600              
601             =head2 decode()
602              
603             Starts the decoding process of pdu binary data. If decoding process
604             ends in success, return value is true and sms object is filled with
605             all proper values.
606              
607             If decoding process has errors or pdu data is not provided, return
608             value is 0 (zero).
609              
610              
611             =head2 delete()
612              
613             Delete the current SMS message from sim card.
614             Example:
615              
616             $gsm = Device::Gsm->new();
617             ...
618             my @msg = $gsm->messages();
619             $msg[0] && $msg[0]->delete();
620              
621             =head2 new()
622              
623             Basic constructor. You can build a new C object from the
624             raw B<+CMGL> header and B data. Those data is then decoded and a new
625             sms object is instanced and all information filled, to be available
626             for subsequent method calls.
627              
628             The allowed parameters to new() method are:
629              
630             =over 4
631              
632             =item header
633              
634             This is the raw B<+CMGL> header string as modem outputs when you
635             issue a B<+CMGL> command
636              
637             =item pdu
638              
639             Binary encoded sms data
640              
641             =item storage
642              
643             Tells which storage to delete the message from. Check the documentation of your
644             phone to know valid storage values. Default values are:
645              
646             =over 4
647              
648             =item C
649              
650             Deletes messages from gsm phone memory.
651              
652             =item C
653              
654             Deletes messages from sim card.
655              
656             =back
657              
658             =back
659              
660             =head2 index()
661              
662             Returns the sms message index number, that is the position of message in the
663             internal device memory or sim card.
664             This number is used for example to delete the message.
665              
666             my $gsm = Device::Gsm->new(port=>'/dev/ttyS0');
667             ...
668             my @messages = $gsm->messages();
669             ...
670             # Delete the first returned message
671             my $msg = shift @messages;
672             $gsm->delete_sms( $msg->index() );
673              
674             =head2 recipient()
675              
676             Returns the sms recipient number (destination address = DA)
677             as string (ex.: C<+39012345678>).
678              
679             =head2 sender()
680              
681             Returns the sms sender number (originating address = OA) as string.
682              
683             =head2 status()
684              
685             Status of the message can be one value from the following list:
686              
687             =for html
688            
689              
690             =for pod
691             'UNKNOWN', 'REC UNREAD', 'REC READ', 'SENT UNREAD', 'SENT READ'
692              
693             =head2 storage()
694              
695             Returns the storage where SMS has been read from.
696              
697             =head2 text()
698              
699             Returns the textual content of sms message.
700              
701             =head2 token()
702              
703             Returns the given PDU token of the decoded message (internal usage).
704              
705             =head2 type()
706              
707             SMS messages can be of two types: SMS_SUBMIT and SMS_DELIVER, that are defined by
708             two constants with those names. type() method returns one of these two values.
709              
710             Example:
711              
712             if( $sms->type() == Device::Gsm::Sms::SMS_DELIVER ) {
713             # ...
714             }
715             elsif( $sms->type() == Device::Gsm::Sms::SMS_SUBMIT ) {
716             # ...
717             }
718              
719             =head1 REQUIRES
720              
721             =over 4
722              
723             =item *
724              
725             Device::Gsm
726              
727             =back
728              
729             =head1 EXPORTS
730              
731             None
732              
733             =head1 TODO
734              
735             =over 4
736              
737             =item *
738              
739             Complete and proof-read documentation and examples
740              
741             =back
742              
743             =head1 COPYRIGHT
744              
745             Device::Gsm::Sms - SMS message simple class that represents a text SMS message
746              
747             Copyright (C) 2002-2015 Cosimo Streppone, cosimo@cpan.org
748              
749             This program is free software; you can redistribute it and/or modify
750             it only under the terms of Perl itself.
751              
752             This program is distributed in the hope that it will be useful,
753             but WITHOUT ANY WARRANTY; without even the implied warranty of
754             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
755             Perl licensing terms for details.
756              
757             =head1 AUTHOR
758              
759             Cosimo Streppone, cosimo@cpan.org
760              
761             =head1 SEE ALSO
762              
763             L, perl(1)
764              
765             =cut