File Coverage

blib/lib/Net/CIMD.pm
Criterion Covered Total %
statement 36 133 27.0
branch 0 42 0.0
condition 0 9 0.0
subroutine 11 28 39.2
pod 7 12 58.3
total 54 224 24.1


line stmt bran cond sub pod time code
1             # Net::CIMD.pm - CIMD over TCP, pure perl implementation
2             # Copyright (c) 2013 Badr Zarhri , , All rights reserved.
3             # This code may be distributed under same terms as perl. NO WARRANTY.
4             # The implementation of this module is based on CIMD interface specification available in the internet.
5             # I should mention that Net::SMPP of Sampo Kellomaki was of great help in implementing this protocol.
6             # Writing this module in perl makes it independant of other packages (it requires only a working perl installation).
7             # This module was tested in perl 5.8.8 (redhat EL 5), and 5.16.3 (centos 6.3).
8             # Please feel free to contact me if you've any remarks or ideas to improve this work.
9             # 23-June-2013, Created the module -- Badr
10             # 27-June-2013, fixed decode RE to handle nack --Badr
11             # 11-July-2013, added server support -- Badr
12             # 12-July-2013, added 7bit encoding functions -- Badr
13              
14             package Net::CIMD;
15              
16 1     1   9727 use 5.008008;
  1         3  
  1         34  
17 1     1   5 use strict;
  1         1  
  1         29  
18 1     1   4 use warnings;
  1         12  
  1         27  
19 1     1   4 use Carp;
  1         1  
  1         77  
20 1     1   805 use IO::Socket;
  1         29814  
  1         4  
21 1     1   3514 use Data::Dumper;
  1         11892  
  1         265  
22              
23             require Exporter;
24              
25             our @ISA = qw(Exporter);
26              
27             # Items to export into callers namespace by default. Note: do not export
28             # names by default without a very good reason. Use EXPORT_OK instead.
29             # Do not simply export all your public functions/methods/constants.
30              
31             # This allows declaration use Net::CIMD ':all';
32             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
33             # will save memory.
34             our %EXPORT_TAGS = ( 'all' => [ qw( pack_7bit unpack_7bit
35            
36             ) ] );
37              
38             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
39              
40             our @EXPORT = qw(
41            
42             );
43              
44             our $VERSION = '0.01_03';
45              
46              
47             my $trace = 0;
48             my %inv_op;
49             my %inv_par;
50 1         160 use constant Defaults => {
51             host => "127.0.0.1",
52             port => 9971,
53             timeout => 300,
54             queue_size => 120
55 1     1   11 };
  1         2  
56             # CIMD interface specification 3.5 operation codes
57 1         190 use constant operation => {
58             login => "01",
59             login_resp => "51",
60             logout => "02",
61             logout_resp => "52",
62             submit => "03",
63             submit_resp => "53",
64             submit_status_report => "13",
65             submit_status_report_resp => "63",
66             enquire_message_status => "04",
67             enquire_message_status_resp => "54",
68             delivery_request => "05",
69             delivery_request_resp => "55",
70             cancel => "06",
71             cancel_resp => "56",
72             deliver_message => "20",
73             deliver_message_resp => "70",
74             deliver_status_report => "23",
75             deliver_status_report_resp => "73",
76             set_parameters => "08",
77             set_parameters_resp => "58",
78             get_parameters => "09",
79             get_parameters_resp => "59",
80             alive => "40",
81             alive_resp => "90",
82             general_error_resp => "98",
83             nack => "99"
84 1     1   6 };
  1         2  
85             # CIMD specification 7.2 parameters.
86 1         192 use constant parameter => {
87             user_identity => "010",
88             password => "011",
89             subaddr => "012",
90             window_size => "019",
91             destination_address => "021",
92             originating_address => "023",
93             originating_IMSI => "026",
94             alphanumeric_ariginating_address => "027",
95             originated_visited_MSC_address => "028",
96             data_coding_scheme => "030",
97             user_data_header => "032",
98             user_data => "033",
99             user_data_binary => "034",
100             more_messages_to_send => "044",
101             validity_Period_Relative => "050",
102             validity_Period_Absolute => "051",
103             protocol_identifier => "052",
104             first_delivery_time_relative => "053",
105             first_delivery_time_absolute => "054",
106             reply_path => "055",
107             status_report_request => "056",
108             cancel_enabled => "058",
109             cancel_mode => "059",
110             service_centre_time_stamp => "060",
111             status_code => "061",
112             status_error_code => "062",
113             discharge_time => "063",
114             tariff_class => "064",
115             service_description => "065",
116             message_count => "066",
117             priority => "067",
118             delivery_request_mode => "068",
119             service_center_address => "069",
120             IP_address => "071",
121             get_parameter => "500",
122             SMS_center_time => "501",
123             error_code => "900",
124             error_text => "901"
125 1     1   7 };
  1         3  
126              
127             BEGIN {
128 1     1   3 foreach( keys %{&operation})
  1         9  
129             {
130 26         64 $inv_op{operation->{$_}}=$_;
131             }
132 1         4 foreach( keys %{¶meter} )
  1         26  
133             {
134 38         1952 $inv_par{parameter->{$_}}=$_;
135             }
136             }
137             sub new_connect {
138 0     0 0   my %arg = @_;
139 0 0         my %parms=(
    0          
    0          
140             PeerAddr => exists $arg{host} ? $arg{host} : Defaults->{host},
141             PeerPort => exists $arg{port} ? $arg{port} : Defaults->{port},
142             Proto => 'tcp',
143             Timeout => exists $arg{timeout} ? $arg{timeout} : Defaults->{timeout} );
144 0 0         my $s = IO::Socket::INET->new(%parms) # pass any extra args to constructor
145             or croak "Can't connect $! $@\n";
146 0 0         return undef unless defined $s;
147 0           $s->autoflush(1);
148 0           return $s;
149             }
150              
151             # buffer here is for future use.
152             sub receive_packet {
153 0     0 1   my $me=shift;
154 0           my $temp;
155 0           while ($me->{tunnel}->sysread($temp, 1)) {
156 0           $me->{buffer} .= $temp;
157 0 0         last if($me->{buffer} =~ /\x09(?:..)?\x03$/);
158             }
159 0           my $result=$me->{buffer};
160 0           $me->{buffer}="";
161 0           return $result;
162             }
163              
164             sub send_packet {
165 0     0 0   my $me = shift;
166 0           my $data = shift;
167 0           $me->{seq}=sprintf("%03d", (($me->{seq}+2)%1000));
168 0 0         carp "sending packet :\n".hexdump($data,"\t") if $trace;
169 0 0         $me->{tunnel}->syswrite($data) or return undef;
170 0           return 'Ok';
171             }
172              
173             sub login {
174 0     0 1   my $type = shift;
175 0   0       $type=ref($type) || $type;
176 0           my $tunnel = new_connect(@_);
177 0 0         return undef if !defined $tunnel;
178 0           my $me= bless {
179             seq => "001",
180             buffer => "",
181             checksum => 0,
182             tunnel => $tunnel
183             }, $type;
184 0           my %args=@_;
185 0           $me->send_packet($me->encode_packet("login", 'user_identity',$args{user_identity},'password',$args{password}));
186 0           my $resp=$me->receive_packet();
187 0 0         print "received\n".Dumper($resp) if $trace;
188 0           return $me;
189             }
190              
191             sub AUTOLOAD {
192 0     0     my $me = shift;
193 0           my $operation=our $AUTOLOAD;
194 0           $operation =~ s/^.*::([^:]+)$/$1/;
195 0 0         return undef unless defined (operation->{$operation});
196 0           $me->send_packet($me->encode_packet($operation,@_));
197 0           return "Ok";
198             }
199              
200             sub encode_packet {
201 0     0 0   my $me=shift;
202 0           my $op=shift;
203 0           my %args=@_;
204 0 0         my $res="\x02".
205             operation->{$op}.":".(defined $args{seq}?$args{seq}:$me->{seq})."\t";
206 0           foreach (sort { parameter->{$a} <=> parameter->{$b} } keys %args)
  0            
207             {
208 0 0         $res.=parameter->{$_}.":".$args{$_}."\t" if(defined parameter->{$_});
209             }
210 0           return $res.&checksum($res)."\x03";
211             #return $res."\x03";
212             }
213              
214             sub decode_packet {
215 0     0 1   my $me=shift;
216 0           my $data=shift;
217 0 0         return undef unless($data =~ /^\x02([^:]+):([^\x09]+)\x09(.*\x09)?(..)?\x03/);
218 0           my ($op, $seq)=($1, $2);
219 0           $data=$3;
220 0           my $checksum=$4;
221 0           my %parms;
222 0 0         $data =~ s/([^:]+):([^\x09]*)\x09/$parms{$inv_par{$1}}=$2/eg if(defined $data);
  0            
223 0           return bless {"operation", $inv_op{$op}, "sequence", $seq, %parms}, 'Net::CIMD::PDU';
224             }
225              
226             sub checksum {
227 0     0 0   my $hash = 0;
228 0           foreach (split //, shift) {
229 0           $hash += ord($_);
230 0           $hash &= 0xFF;
231             }
232 0           return sprintf("%02x",$hash);
233             }
234             sub read_sync()
235             {
236 0     0 1   my $me=shift;
237 0           my $req=$me->decode_packet($me->receive_packet());
238 0 0         $me->send_packet($me->encode_packet($req->{"operation"}."_resp",'seq', $req->{"sequence"})) if defined (operation->{$req->{"operation"}."_resp"});
239 0           return $req;
240             }
241             sub read_async()
242             {
243 0     0 1   my $me=shift;
244 0           return $me->decode_packet($me->receive_packet());
245             }
246             sub DESTROY
247             {
248 0     0     my $me=shift;
249 0           $me->logout();
250             }
251              
252             # Server commands
253             sub listen {
254 0     0 0   my $type=shift;
255 0   0       $type=ref($type) || $type;
256 0           my %arg=@_;
257 0 0         my %parms=(
    0          
258             Listen => exists $arg{queue_size} ? $arg{queue_size} : Defaults->{queue_size},
259             LocalPort => exists $arg{port} ? $arg{port} : Defaults->{port},
260             Proto => 'tcp',
261             ReuseAddr => 'true',
262             );
263 0           return Net::CIMD::Listener->new(%parms);
264             }
265              
266             sub pack_7bit {
267 0     0 1   my ($s) = @_;
268 0           $s = unpack 'b*', $s;
269 0           $s =~ s/(.{7})./$1/g; # Zap the high order (8th) bits
270 0           $s= pack 'b*', $s;
271 0           $s=unpack("H*", $s);
272 0           return uc($s);
273             }
274              
275             sub unpack_7bit {
276 0     0 1   my ($s) = @_;
277 0           $s=pack("H*", $s);
278 0           $s = unpack 'b*', $s;
279 0           $s =~ s/(.{7})/${1}0/g; # Stuff in high order (8th) bits
280 0           $s = pack 'b*', $s;
281 0 0         chop $s if substr($s, -1, 1) eq "\x00";
282 0           return $s;
283             }
284              
285             package Net::CIMD::PDU;
286             sub new
287             {
288 0     0     my $class=shift;
289 0           return bless {@_}, $class;
290             }
291              
292             package Net::CIMD::Listener;
293 1     1   8 use Carp;
  1         3  
  1         313  
294              
295             sub new {
296 0     0     my $type=shift;
297 0   0       $type=ref($type) || $type;
298 0           print "Listening : ".join(";", @_)." ...\n";
299 0 0         my $s = IO::Socket::INET->new(@_) or croak "Can't Listen $! $@\n";
300 0 0         return undef unless defined $s;
301 0           $s->autoflush(1);
302 0           return bless {'listener' => $s}, $type;
303             }
304             sub accept {
305 0     0     my $self=shift;
306 0           my $tunnel = $self->{'listener'}->accept();
307 0           return bless {
308             seq => "002",
309             buffer => "",
310             tunnel => $tunnel
311             }, 'Net::CIMD';
312             }
313              
314             1;
315             __END__