File Coverage

blib/lib/Net/EMI/Client.pm
Criterion Covered Total %
statement 43 167 25.7
branch 0 116 0.0
condition 0 27 0.0
subroutine 15 30 50.0
pod 5 5 100.0
total 63 345 18.2


line stmt bran cond sub pod time code
1             package Net::EMI::Client;
2 1     1   12020 use strict;
  1         3  
  1         47  
3 1     1   7 use Carp;
  1         3  
  1         210  
4              
5 1     1   7 use vars qw($VERSION);
  1         7  
  1         71  
6             $VERSION='1.02';
7              
8 1     1   1085 use IO::Socket;
  1         35034  
  1         4  
9 1     1   1535 use Net::EMI::Common;
  1         842  
  1         33  
10              
11 1     1   7 use constant ACK=>'A';
  1         1  
  1         46  
12 1     1   5 use constant TRUE=>1;
  1         2  
  1         50  
13              
14 1     1   2046 BEGIN{*logout=*close_link;}
15              
16             ###########################################################################################################
17 0     0 1   sub new {bless({},shift())->_init(@_);}
18              
19             ###########################################################################################################
20             # login to SMSC
21             sub login {
22 0     0 1   my$self=shift();
23 0           my%args=(
24             SMSC_ID=>'',
25             SMSC_PW=>'',
26             @_);
27              
28             # Conditionally open the socket unless already opened.
29 0 0         $self->open_link() unless(defined($self->{SOCK}));
30 0 0         unless(defined($self->{SOCK})) {
31 0 0         return(defined(wantarray)?wantarray?(undef,0,''):undef:undef);
    0          
32             }
33              
34 0 0 0       defined($args{SMSC_ID})&&length($args{SMSC_ID})||do {
35 0 0         $self->{WARN}&&warn("Missing mandatory parameter 'SMSC_ID' when trying to login. Login failed");
36 0 0         return(defined(wantarray)?wantarray?(undef,0,''):undef:undef);
    0          
37             };
38              
39 0 0 0       defined($args{SMSC_PW})&&length($args{SMSC_PW})||do {
40 0 0         $self->{WARN}&&warn("Missing mandatory parameter 'SMSC_PW' when trying to login. Login failed");
41 0 0         return(defined(wantarray)?wantarray?(undef,0,''):undef:undef);
    0          
42             };
43              
44 0           my $data=$args{SMSC_ID}. # OAdC
45             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
46             '6'. # OTON (short number alias)
47             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
48             '5'. # ONPI (private)
49             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
50             '1'. # STYP (open session)
51             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
52             $self->{OBJ_EMI_COMMON}->ia5_encode($args{SMSC_PW}). # PWD
53             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
54             ''. # NPWD
55             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
56             '0100'. # VERS (version)
57             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
58             ''. # LAdC
59             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
60             ''. # LTON
61             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
62             ''. # LNPI
63             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
64             ''. # OPID
65             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
66             ''; # RES1
67              
68 0           my $header=sprintf("%02d",$self->{TRN_OBJ}->next_trn()). # Transaction counter.
69             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
70             $self->{OBJ_EMI_COMMON}->data_len($data). # Length.
71             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
72             'O'. # Type (operation).
73             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
74             '60'; # OT (Session management).
75              
76 0           my $checksum=$self->{OBJ_EMI_COMMON}->checksum($header.
77             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
78             $data.
79             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER);
80 0           $self->_transmit_msg($header.
81             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
82             $data.
83             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
84             $checksum,
85             $self->{TIMEOUT_OBJ}->timeout());
86             }
87              
88             #############################################################################################
89             # This method will also conditionally be called from the login() method.
90             sub open_link {
91 0     0 1   my$self=shift;
92              
93 0           $self->{SOCK}=IO::Socket::INET->new(PeerAddr=>$self->{SMSC_HOST},
94             PeerPort=>$self->{SMSC_PORT},
95             Proto=>'tcp');
96 0 0         defined($self->{SOCK})||do {
97 0 0         $self->{WARN}&&warn("Failed to establish a socket connection with host $self->{SMSC_HOST} on port $self->{SMSC_PORT}");
98 0           return;
99             };
100 0           TRUE;
101             }
102              
103             #############################################################################################
104             # To avoid keeping the socket open if not used any more.
105             sub close_link {
106 0     0 1   my$self=shift;
107              
108 0 0         defined($self->{SOCK})||return;
109              
110 0           close($self->{SOCK});
111 0           $self->{SOCK}=undef;
112 0           $self->{TRN_OBJ}->reset_trn();
113 0           TRUE;
114             }
115              
116             ###########################################################################################################
117             # send the SMS
118             sub send_sms {
119 0     0 1   my$self=shift();
120 0           my%args=(
121             RECIPIENT=>'',
122             MESSAGE_TEXT=>'',
123             SENDER_TEXT=>'',
124             TIMEOUT=>undef,
125             @_);
126 0           my$timeout;
127              
128 0 0         if(defined($args{TIMEOUT})) {
129 0           my$tv=TimeoutValue->new(TIMEOUT=>$args{TIMEOUT});
130 0           $timeout=$tv->timeout();
131             }
132             else {
133 0           $timeout=$self->{TIMEOUT_OBJ}->timeout();
134             }
135              
136 0 0 0       defined($args{RECIPIENT})&&length($args{RECIPIENT})||do {
137 0 0         $self->{WARN}&&warn("Missing mandatory parameter 'RECIPIENT' when trying to send message. Transmission failed");
138 0 0         return(defined(wantarray)?wantarray?(undef,0,''):undef:undef);
    0          
139             };
140              
141 0           $args{RECIPIENT}=~s/^\+/00/;
142 0 0         $args{RECIPIENT}=~/^\d+$/||do{
143 0 0         $self->{WARN}&&warn("The recipient address contains illegal (non-numerical) characters: $args{RECIPIENT}\nMessage not sent ");
144 0 0         return(defined(wantarray)?wantarray?(undef,0,''):undef:undef);
    0          
145             };
146              
147             # It's OK to send an empty message, but not to use undef.
148 0 0         defined($args{MESSAGE_TEXT})||($args{MESSAGE_TEXT}='');
149              
150 0 0 0       my $data=$args{RECIPIENT}. # AdC (Address Code)
151             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
152             # OAdC (Originators Adress Code)
153             # If given, use it. Otherwise use the one given to the constructor.
154             (defined($args{SENDER_TEXT})&&length($args{SENDER_TEXT})?
155             $self->{OBJ_EMI_COMMON}->encode_7bit($args{SENDER_TEXT}):
156             $self->{OBJ_EMI_COMMON}->encode_7bit($self->{SENDER_TEXT})).
157             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
158             ''. # $AC.
159             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
160             ''. # NRq (Notfication Request 1).
161             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
162             ''. # $NAdC.
163             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
164             ''. # NT (Notification Type 3).
165             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
166             ''. # $NPID.
167             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
168             ''. # $LRq.
169             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
170             ''. # LRAd (Last Resort Address).
171             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
172             ''. # $LPID.
173             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
174             ''. # $DD.
175             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
176             ''. # $DDT.
177             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
178             ''. # $VP.
179             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
180             ''. # $RPID.
181             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
182             ''. # $SCTS.
183             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
184             ''. # $Dst.
185             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
186             ''. # $Rsn.
187             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
188             ''. # $DSCTS.
189             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
190             '3'. # MT (message type, alphanumeric).
191             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
192             ''. # $NB.
193             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
194             $self->{OBJ_EMI_COMMON}->ia5_encode($args{MESSAGE_TEXT}).
195             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
196             ''. # $MMS.
197             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
198             ''. # $PR.
199             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
200             ''. # $DCs.
201             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
202             ''. # $MCLs.
203             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
204             ''. # $RPI.
205             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
206             ''. # $CPg.
207             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
208             ''. # $RPLy.
209             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
210             '5039'. # OTOA (Originator Type of Address).
211             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
212             ''. # $HPLMN.
213             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
214             ''. # $XSer.
215             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
216             ''. # $RES4.
217             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
218             ''; # $RES5;
219              
220 0           my $header=sprintf("%02d",$self->{TRN_OBJ}->next_trn()). # Transaction counter.
221             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
222             $self->{OBJ_EMI_COMMON}->data_len($data).
223             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
224             'O'. # Type.
225             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
226             '51'; # OT (submit message)
227              
228 0           my $message_string=$header.
229             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
230             $data.
231             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
232             $self->{OBJ_EMI_COMMON}->checksum($header.
233             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER.
234             $data.
235             $self->{OBJ_EMI_COMMON}->UCP_DELIMITER);
236              
237 0           $self->_transmit_msg($message_string,$timeout);
238             }
239              
240             ###########################################################################################################
241             ###########################################################################################################
242             #
243             # 'Internal' subs. Don't call these since they may, and will, change without notice.
244             #
245             ###########################################################################################################
246             ###########################################################################################################
247              
248             ###########################################################################################################
249             sub _init {
250 0     0     my$self=shift();
251 0           $self->{OBJ_EMI_COMMON}=Net::EMI::Common->new();
252 0           my%args=(
253             SMSC_HOST=>'',
254             SMSC_PORT=>$self->{OBJ_EMI_COMMON}->DEF_SMSC_PORT,
255             SENDER_TEXT=>'',
256             WARN=>0,
257             TIMEOUT=>undef,
258             @_);
259              
260 0 0         $self->{WARN}=defined($args{WARN})?$args{WARN}?1:0:0;
    0          
261 0           $self->{TIMEOUT_OBJ}=TimeoutValue->new(TIMEOUT=>$args{TIMEOUT},
262             WARN=>$self->{WARN});
263 0           $self->{TRN_OBJ}=TranNbr->new();
264              
265 0 0 0       defined($args{SMSC_HOST})&&length($args{SMSC_HOST})||do{
266 0 0         $self->{WARN}&&warn("Mandatory entity 'SMSC_HOST' was missing when creating an object of class ".
267             __PACKAGE__.
268             ". Object not created");
269 0           return; # Failed to instantiate this object.
270             };
271 0 0 0       defined($args{SMSC_PORT})&&length($args{SMSC_PORT})||do{
272 0 0         $self->{WARN}&&warn("Mandatory entity 'SMSC_PORT' was missing when creating an object of class ".
273             __PACKAGE__.
274             ". Object not created");
275 0           return; # Failed to instantiate this object.
276             };
277 0 0         $args{SMSC_PORT}=~/^\d+$/||do{
278 0 0         $self->{WARN}&&warn("Non-numerical data found in entity 'SMSC_PORT' when creating an object of class ".
279             __PACKAGE__.
280             ". Object not created");
281 0           return; # Failed to instantiate this object.
282             };
283              
284 0           $self->{SMSC_HOST}=$args{SMSC_HOST};
285 0           $self->{SMSC_PORT}=$args{SMSC_PORT};
286 0 0 0       $self->{SENDER_TEXT}=defined($args{SENDER_TEXT})&&length($args{SENDER_TEXT})?$args{SENDER_TEXT}:__PACKAGE__;
287              
288 0           $self->{SOCK}=undef;
289              
290             # Some systems have not implemented alarm().
291             # On such systems, calling alarm() will create a run-time error.
292             # Determine if we dare calling alarm() or not.
293 0           eval{alarm(0)};
  0            
294 0 0         $self->{CAN_ALARM}=$@?0:1;
295              
296 0           $self;
297             }
298              
299             ###########################################################################################################
300             # one step in UCP communication
301             sub _transmit_msg {
302 0     0     my($self,$message_string,$timeout)=@_;
303 0           my($rd,$buffer,$response,$acknack,$errcode,$errtxt,$ack);
304              
305 0 0         defined($timeout)||do{$timeout=0};
  0            
306              
307 0   0       print {$self->{SOCK}} ($self->{OBJ_EMI_COMMON}->STX.$message_string.$self->{OBJ_EMI_COMMON}->ETX) ||do{
  0            
308             $errtxt="Failed to print to SMSC socket. Remote end closed?";
309             $self->{WARN}&&warn($errtxt);
310             return(defined(wantarray)?wantarray?(undef,0,$errtxt):undef:undef);
311             };
312              
313 0           $self->{SOCK}->flush();
314              
315 0           do {
316             # If this system implements alarm(), we will do a non-blocking read.
317 0 0         if($self->{CAN_ALARM}) {
318 0           eval {
319 0           $rd=undef;
320 0     0     local($SIG{ALRM})=sub{die("alarm\n")}; # NB: \n required
  0            
321 0           alarm($timeout);
322 0           $rd=read($self->{SOCK},$buffer,1);
323 0           alarm(0);
324             };
325             # Propagate unexpected errors.
326 0 0 0       $@&&$@ne"alarm\n"&&die($@);
327             }
328             else {
329             # No alarm() implemented. Must do a (potentially) blocking call to read().
330 0           $rd=read($self->{SOCK},$buffer,1);
331             }
332 0 0         defined($rd)||do{ # undef, read error.
333 0           $errtxt="Failed to read from SMSC socket. Never received ETX. Remote end closed?";
334 0 0         $self->{WARN}&&warn($errtxt);
335 0 0         return(defined(wantarray)?wantarray?(undef,0,$errtxt):undef:undef);
    0          
336             };
337 0 0         $rd||do{ # Zero, end of 'file'.
338 0           $errtxt="Never received ETX from SMSC. Remote end closed?";
339 0 0         $self->{WARN}&&warn($errtxt);
340 0 0         return(defined(wantarray)?wantarray?(undef,0,$errtxt):undef:undef);
    0          
341             };
342 0           $response.=$buffer;
343             } until($buffer eq $self->{OBJ_EMI_COMMON}->ETX);
344              
345 0           (undef,undef,undef,undef,$acknack,$errcode,$errtxt,undef)=split($self->{OBJ_EMI_COMMON}->UCP_DELIMITER,$response);
346 0 0         if($acknack eq ACK) {
347 0           ($ack,$errcode,$errtxt)=(TRUE,0,'');
348             }
349             else {
350 0           $ack=0;
351 0           $errtxt=~s/^\s+//;
352 0           $errtxt=~s/\s+$//;
353             }
354 0 0         defined(wantarray)?wantarray?($ack,$errcode,$errtxt):$ack:undef;
    0          
355             }
356              
357             ###########################################################################################################
358             package TimeoutValue;
359 1     1   8 use strict;
  1         2  
  1         39  
360 1     1   5 use Carp;
  1         1  
  1         82  
361              
362 1     1   5 use constant MIN_TIMEOUT=>0; # No timeout at all!
  1         2  
  1         69  
363 1     1   5 use constant DEFAULT_TIMEOUT=>15;
  1         3  
  1         44  
364 1     1   5 use constant MAX_TIMEOUT=>60;
  1         1  
  1         326  
365              
366             ###########################################################################################################
367 0     0     sub new {bless({},shift())->_init(@_);}
368              
369             ###########################################################################################################
370 0     0     sub timeout {$_[0]->{TIMEOUT};}
371              
372             ###########################################################################################################
373             sub _init {
374 0     0     my$self=shift();
375 0           my%args=(
376             TIMEOUT=>undef,
377             WARN=>0,
378             @_);
379              
380 0 0         $self->{WARN}=defined($args{WARN})?$args{WARN}?1:0:0;
    0          
381 0           $self->{TIMEOUT}=DEFAULT_TIMEOUT;
382              
383 0 0         if(defined($args{TIMEOUT})) {
384 0 0         if($args{TIMEOUT}=~/\D/) {
    0          
385 0 0         $self->{WARN}&&warn("Non-numerical data found in entity 'TIMEOUT' when creating an object of class ".
386             __PACKAGE__.
387             '. '.
388             'Input data: >'.
389             $args{TIMEOUT}.
390             '< Given TIMEOUT value ignored and default value '.DEFAULT_TIMEOUT.' used instead');
391             }
392             # The commented code will never be executed until we let the MIN_TIMEOUT be greater than zero (since the '-' is non-numeric).
393             # elsif($args{TIMEOUT}
394             # $self->{WARN}&&warn("Entity 'TIMEOUT' contains a value smaller than the smallest value allowed (".
395             # MIN_TIMEOUT.
396             # ") when creating an object of class ".
397             # __PACKAGE__.
398             # '. Given TIMEOUT value ignored and default value '.DEFAULT_TIMEOUT.' used instead');
399             # }
400             elsif($args{TIMEOUT}>MAX_TIMEOUT) {
401 0 0         $self->{WARN}&&warn("Entity 'TIMEOUT' contains a value greater than the largest value allowed (".
402             MAX_TIMEOUT.
403             ") when creating an object of class ".
404             __PACKAGE__.
405             '. Given TIMEOUT value ignored and default value '.DEFAULT_TIMEOUT.' used instead');
406             }
407             else {
408 0           $self->{TIMEOUT}=$args{TIMEOUT};
409             }
410             }
411              
412 0           $self;
413             }
414              
415             ###########################################################################################################
416             package TranNbr;
417 1     1   6 use strict;
  1         2  
  1         38  
418 1     1   5 use constant HIGHEST_NBR=>99;
  1         2  
  1         185  
419              
420             ###########################################################################################################
421 0     0     sub new {bless({},shift())->_init(@_);}
422              
423             ###########################################################################################################
424             sub next_trn {
425 0     0     my$self=shift;
426 0 0         ($self->{TRN}>HIGHEST_NBR)&&do{$self->{TRN}=0};
  0            
427 0           $self->{TRN}++;
428             }
429              
430             ###########################################################################################################
431             sub reset_trn {
432 0     0     $_[0]->{TRN}=0;
433             }
434              
435             ###########################################################################################################
436             sub _init {
437 0     0     $_[0]->reset_trn();
438 0           $_[0];
439             }
440              
441             'Choppers rule';
442             __END__