File Coverage

blib/lib/Ser/BinRPC.pm
Criterion Covered Total %
statement 138 321 42.9
branch 41 142 28.8
condition 7 18 38.8
subroutine 16 21 76.1
pod 6 16 37.5
total 208 518 40.1


line stmt bran cond sub pod time code
1             # Ser::BinRPC.pm
2             #
3             # Copyright (c) 2010 Tomas Mandys . All rights reserved.
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6              
7             package Ser::BinRPC;
8              
9 1     1   32706 use strict;
  1         2  
  1         32  
10             #no strict "refs";
11              
12 1     1   4 use warnings;
  1         3  
  1         23  
13 1     1   5 use Socket;
  1         6  
  1         565  
14 1     1   831 use IO::Socket;
  1         31116  
  1         9  
15              
16             require Exporter;
17              
18             our @ISA = qw(Exporter);
19              
20             # Items to export into callers namespace by default. Note: do not export
21             # names by default without a very good reason. Use EXPORT_OK instead.
22             # Do not simply export all your public functions/methods/constants.
23              
24             # This allows declaration use Ser::BinRPC ':all';
25             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
26             # will save memory.
27             our %EXPORT_TAGS = ( 'all' => [ qw(
28            
29             ) ] );
30              
31             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
32              
33             our @EXPORT = qw(
34            
35             );
36              
37             our $VERSION = '0.01';
38              
39             require XSLoader;
40             XSLoader::load('Ser::BinRPC', $VERSION);
41              
42             my %data_type = (
43             INT => 0,
44             STR => 1, # 0 term, for easier parsing
45             DOUBLE => 2,
46             STRUCT => 3,
47             ARRAY => 4,
48             AVP => 5, # allowed only in structs
49             BYTES => 6 # like STR, but not 0 term
50             );
51              
52             my %message_type = (
53             REQ => 0,
54             REPL => 1,
55             FAULT => 3
56             );
57              
58             my %sock_type_by_proto = (
59             udp => SOCK_DGRAM,
60             tcp => SOCK_STREAM
61             );
62              
63             # ------------------------ module subroutines ------------------------
64              
65             sub binrpc_get_int_len($) {
66 23     23 0 25 my ($i) = @_;
67 23         23 my $size;
68 23   66     243 for ($size=4; $size && (($i & (0xff<<24))==0); $i<<=8, $size--) {};
69 23         48 return $size;
70             }
71              
72             sub binrpc_write_int(\@$) {
73 7     7 0 11 my ($arr, $i) = @_;
74 7         8 my $size;
75 7   66     94 for ($size=4; $size && (($i & (0xff<<24))==0); $i<<=8, $size--) {};
76 7         10 my $n;
77 7         20 for ($n=$size; $n; $n--){
78 7         12 push(@$arr, (($i>>24) & 0xFF));
79 7         16 $i<<=8;
80             }
81 7         13 return $size;
82             }
83              
84             sub binrpc_read_int(\@$\$) {
85 0     0 0 0 my ($arr, $len, $i) = @_;
86              
87 0 0       0 if ($len > scalar(@$arr)) {
88 0         0 return 0;
89             }
90 0         0 $$i = 0;
91 0         0 for (; $len>0; $len--) {
92 0         0 $$i <<= 8;
93 0         0 $$i |= shift(@$arr);
94             }
95 0         0 return 1;
96             }
97              
98             sub binrpc_add_str(\@$) {
99 10     10 0 15 my ($arr, $s) = @_;
100              
101 10         16 my $l = length($s)+1;
102 10         18 my $size;
103 10 100       27 if ($l < 8) {
104 3         8 push(@$arr, ($l << 4) | $data_type{STR});
105             }
106             else {
107 7         18 push(@$arr, (binrpc_get_int_len($l) << 4) | 0x80 | $data_type{STR});
108 7         26 binrpc_write_int(@$arr, $l);
109             }
110 10         27 for my $i (0 .. length($s)-1) {
111 79         163 push(@$arr, unpack('C', substr($s, $i, 1)));
112             }
113 10         27 push(@$arr, 0);
114             }
115              
116             sub binrpc_add_int(\@$) {
117 0     0 0 0 my ($arr, $x) = @_;
118 0         0 push(@$arr, (binrpc_get_int_len($x) << 4) | $data_type{INT});
119 0         0 binrpc_write_int(@$arr, $x);
120             }
121              
122             sub binrpc_add_double(\@$) {
123 0     0 0 0 my ($arr, $x) = @_;
124 0         0 $x = int($x*1000);
125 0         0 push(@$arr, binrpc_get_int_len($x) <<4 | $data_type{DOUBLE});
126 0         0 binrpc_write_int(@$arr, $x);
127             }
128              
129             sub binrpc_read_record(\@$\$\$\$); # forward declaration to introduce prototype for recursive usage
130              
131             sub binrpc_read_record(\@$\$\$\$) {
132 0     0 0 0 my ($arr, $nesting, $val_type, $val, $name) = @_;
133 0         0 my $end_tag = 0;
134              
135 0 0       0 if (scalar(@$arr) == 0) {
136 0         0 return 0;
137             }
138 0         0 my $len = shift(@$arr);
139 0 0 0     0 if (defined $$val_type && (($len & 0x0F) != $$val_type)) {
140 0         0 return 0;
141             }
142 0         0 $$val_type = $len & 0x0F;
143 0         0 $len >>= 4;
144 0 0       0 if ($len & 0x08) {
145 0         0 $end_tag=1; # possible end mark for array or structs
146 0 0       0 unless (binrpc_read_int(@$arr, $len & 0x07, $len)) { # we have to read len bytes and use them as the new len */
147 0         0 return 0;
148             }
149             }
150 0 0       0 if ($len > scalar(@$arr)) {
151 0         0 return 0;
152             }
153 0 0       0 if ($nesting eq 'S') {
154 0 0       0 if ($$val_type == $data_type{STRUCT} ) {
    0          
155 0 0       0 if ($end_tag) {
156 0         0 undef $$val_type; # end of struct
157 0         0 return 1;
158             }
159             else {
160 0         0 return 0;
161             }
162             } elsif ($$val_type == $data_type{AVP} ) {
163 0         0 $$name = '';
164 0         0 for my $i (0 .. $len-2) {
165 0         0 $$name .= pack('C', shift @$arr);
166             }
167 0         0 shift @$arr; # zero term
168 0 0       0 if (scalar(@$arr) == 0) {
169 0         0 return 0;
170             }
171              
172 0         0 $$val_type = $$arr[0] & 0x0F;
173 0 0 0     0 if ($$val_type == $data_type{AVP} || $$val_type == $data_type{ARRAY}) {
174 0         0 return 0;
175             }
176 0         0 my ($dummy);
177 0 0       0 unless (binrpc_read_record(@$arr, '', $$val_type, $$val, $dummy)) {
178 0         0 return 0;
179             }
180 0         0 return 1;
181              
182             } else {
183 0         0 return 0;
184             }
185             }
186             else {
187 0 0       0 if ($$val_type == $data_type{INT} ) {
    0          
    0          
    0          
    0          
    0          
188 0 0       0 unless (binrpc_read_int(@$arr, $len, $$val)) { # we have to read len bytes and use them as the new len */
189 0         0 return 0;
190             }
191              
192             } elsif ($$val_type == $data_type{DOUBLE} ) {
193 0 0       0 unless (binrpc_read_int(@$arr, $len, $$val)) { # we have to read len bytes and use them as the new len */
194 0         0 return 0;
195             }
196 0         0 $$val = ($$val*1.00)/1000;
197              
198             } elsif ($$val_type == $data_type{STR} ) {
199 0         0 $$val = '';
200 0         0 for my $i (0 .. $len-2) {
201 0         0 $$val .= pack('C', shift @$arr);
202             }
203 0         0 shift @$arr; # zero term
204              
205             } elsif ($$val_type == $data_type{BYTES} ) {
206 0         0 $$val = '';
207 0         0 for my $i (0 .. $len-1) {
208 0         0 $$val .= pack('C', shift @$arr);
209             }
210              
211             } elsif ($$val_type == $data_type{STRUCT} ) {
212 0 0       0 if ($end_tag) {
213 0         0 return 0;
214             }
215 0         0 my %s = ();
216 0         0 my ($val_type2, $val2, $name2);
217 0         0 do {
218 0         0 undef $val_type2;
219 0 0       0 unless (binrpc_read_record(@$arr, 'S', $val_type2, $val2, $name2)) {
220 0         0 return 0;
221             }
222 0 0       0 if (defined $val_type2) {
223 0         0 $s{$name2} = $val2;
224             }
225             } while (defined $val_type2);
226 0         0 $$val = \%s;
227              
228             } elsif ($$val_type == $data_type{ARRAY} ) {
229 0 0       0 if ($end_tag) {
230 0 0       0 if ($nesting eq 'A') {
231 0         0 undef $$val_type;
232 0         0 return 1;
233             } else {
234 0         0 return 0;
235             }
236             }
237 0         0 my @a2 = ();
238 0         0 my ($val_type2, $val2, $name2);
239 0         0 do {
240 0         0 undef $val_type2;
241 0 0       0 unless (binrpc_read_record(@$arr, 'A', $val_type2, $val2, $name2)) {
242 0         0 return 0;
243             }
244 0 0       0 if (defined $val_type2) {
245 0         0 push (@a2, $val2);
246             }
247             } while (defined $val_type2);
248 0         0 $$val = \@a2;
249              
250             } else {
251 0         0 return 0;
252             }
253             }
254 0         0 return 1;
255             }
256              
257             # ----------------------------- begin of object ------------------------------------
258              
259             sub new {
260 2     2 1 17 my $class = shift;
261 2         1097 my $self = {
262             verbose=>0,
263             errs=>'',
264              
265             sock_domain=>PF_UNIX,
266             sock_type=>SOCK_STREAM,
267             unix_sock=>'/tmp/ser_ctl',
268             remote_host=>'localhost',
269             remote_port=>2049,
270             proto=>getprotobyname('udp')
271             };
272 2         15 return bless($self, $class);
273             }
274              
275             sub parse_connection_string($$) {
276 10     10 1 2687 my ( $self, $s ) = @_;
277 10         31 $self->dbg("parse_connection_string($s)");
278 10         35 my @flds = split(/:/, $s);
279 10 100 100     62 if ($flds[0] eq 'unix') {
    100          
280 2         5 $self->{sock_domain} = PF_UNIX;
281 2 100       8 $self->{unix_sock} = $flds[1] if $flds[1];
282 2         3 $self->{sock_type} = SOCK_STREAM;
283             }
284             elsif ($flds[0] eq 'udp' || $flds[0] eq 'tcp') {
285 7         10 my $type;
286 7         16 $self->{sock_domain} = PF_INET;
287 7 100       22 $self->{remote_host} = $flds[1] if $flds[1];
288 7 100       18 $self->{remote_port} = $flds[2] if $flds[2];
289 7         605 $self->{proto} = (getprotobyname($flds[0]))[2];
290 7         28 $self->{sock_type} = $sock_type_by_proto{$flds[0]};
291             }
292             else {
293 1         6 $self->err("Bad protocol in \'$s\'\n");
294 1         5 return 0;
295             }
296 9         45 return 1;
297             }
298              
299             sub dbg($$) {
300 39     39 0 59 my ( $self, $s ) = @_;
301 39 50       137 if ($self->{verbose}) {
302 0         0 print STDERR "DBG: $s\n";
303             }
304             }
305              
306              
307             sub dbg_dump_arr ($$\@) { # method prototypes has no effect !
308 16     16 0 25 my ($self, $name, $arr) = @_;
309 16 50       49 if ($self->{verbose}) {
310 0         0 $self->dbg(sprintf("$name (length: %d):", scalar(@$arr)));
311 0         0 my $s1 = '';
312 0         0 my $s2 = '';
313 0         0 my $i = 16;
314 0         0 for my $j (0 .. $#$arr) {
315 0         0 $b = $$arr[$j];
316 0         0 $s1 .= sprintf('%0.2x ', $b);
317 0 0 0     0 if (($b >= 0x20) and ($b<0x80)) {
318 0         0 $s2 .= pack('C', $b);
319             }
320             else {
321 0         0 $s2 .= '.';
322             }
323 0         0 $i--;
324 0 0       0 if (($i & 0x0F) == 0) {
325 0         0 $self->dbg("$s1 $s2");
326 0         0 $s1 = '';
327 0         0 $s2 = '';
328 0         0 $i = 16;
329             }
330             }
331 0 0       0 if ($i & 0x0F) {
332 0         0 while ($i > 0) {
333 0         0 $s1 .= ' ';
334 0         0 $i--;
335             }
336 0         0 $self->dbg("$s1 $s2");
337             }
338 0         0 $self->dbg("END");
339             }
340             }
341              
342             sub err($$) {
343 17     17 0 30 my ($self, $s) = @_;
344              
345 17         123 my @stack = caller(1);
346 17         56 $self->{errs} = "$stack[3]: $s";
347 17         56 $self->dbg("ERROR: $self->{errs}");
348             }
349              
350             sub open($) {
351 4     4 1 9 my ($self) = @_;
352 4         22 my $sock;
353 4 50       17 if (defined $self->{socket}) {
354 0         0 $self->err("Socket is already opened");
355 0         0 return 0;
356             }
357 4 100       16 if ($self->{sock_domain} == PF_UNIX) {
    50          
358 1         15 $sock = IO::Socket::UNIX->new(
359             Type=>$self->{sock_type},
360             Peer=>$self->{unix_sock}
361             #Local=>
362             #Listem=>
363             );
364 1 50       358 unless ($sock) {
365 1         7 $self->err("socket: $!");
366 1         5 return 0;
367             }
368             # unless (connect(SOCKET, sockaddr_un($conn_params->{'file'}))) {
369             # err(%$conn, "connect: $!");
370             # close SOCKET;
371             # return 0;
372             # }
373 0         0 $self->{socket} = $sock;
374             }
375             elsif($self->{sock_domain} == PF_INET) {
376 3         608 my $iaddr = inet_aton($self->{remote_host});
377 3 50       12 unless ($iaddr) {
378 0         0 $self->err("no destination address for \'$self->{remote_host}\'");
379 0         0 return 0;
380             }
381 3         38 $sock = IO::Socket::INET->new(
382             PeerAddr=>$self->{remote_host},
383             PeerPort=>$self->{remote_port},
384             Proto=>$self->{proto},
385             Type=>$self->{sock_type}
386             );
387 3 100       1218 unless ($sock) {
388 1         5 $self->err("socket: $!");
389 1         3 return 0;
390             }
391             # unless (connect(SOCKET, sockaddr_in($conn_params->{'port'}, $iaddr))) {
392             # err(%$conn, "connect: $!");
393             # $sock->close;
394             # return 0;
395             # }
396 2         5 $self->{socket} = $sock;
397             } else {
398 0         0 $self->err("Unknown domain");
399 0         0 return 0;
400             }
401 2         6 return 1;
402             }
403              
404             sub close($) {
405 6     6 1 1208 my ($self) = @_;
406 6 100       17 if (! defined $self->{socket}) {
407 3         16 return 1;
408             }
409 3         7 my $sock = $self->{socket};
410 3 100       29 unless ($sock->close) {
411 2         107 $self->err("close: $!");
412 2         102 return 0;
413             }
414 1         42 undef $self->{socket};
415 1         13 return 1;
416             }
417              
418             sub DESTROY {
419 2     2   4 my ($self) = @_;
420 2         6 $self->close();
421             }
422              
423             sub command($$\@\@) { # method prototypes has no effect !
424 12     12 1 7832 my ($self, $cmd, $params, $result) = @_;
425 12         20 my $magic = 0xA;
426 12         13 my $version = 1;
427              
428 12 100       34 if (!defined @$params) {
429 10         17 my @a = ();
430 10         21 $params = \@a;
431             }
432 12         56 $self->dbg("command: $cmd(@$params)");
433 12 50       31 unless ($cmd) {
434 0         0 $self->err("Command not specified");
435 0         0 return 0;
436             }
437 12 100       33 unless (defined $self->{socket}) {
438 4         9 $self->err("Socket is not opened");
439 4         16 return 0;
440             }
441             # prepare body
442 8         17 my @body = ();
443              
444 8         26 binrpc_add_str(@body, $cmd);
445 8         21 foreach (@$params) {
446 2         3 my $item = $_;
447 2         5 my $data_type;
448 2 50       15 if ($item =~ /^\d+$/) {
    50          
449 0         0 binrpc_add_int(@body, $item);
450             }
451             elsif ($item =~ /^\d+\.\d+$/) {
452 0         0 binrpc_add_double(@body, $item);
453             } else {
454 2 100       9 if ($item =~ /^s:/ ) {
455 1         3 $item = substr($item, 2);
456             }
457 2         5 binrpc_add_str(@body, $item);
458             }
459             }
460              
461              
462 8         58 my $cookie = int(rand(0xFFFFFFFF));
463 8         13 my $body_len = $#body+1;
464 8         15 my $type = $message_type{REQ};
465 8 50       19 if ($body_len > 0xFFFFFFFF) {
466 0         0 $self->err("Body length exceeded");
467 0         0 return -1;
468             }
469 8         16 my $len_len = binrpc_get_int_len($body_len);
470 8         10 my $c_len = binrpc_get_int_len($cookie);
471 8 50       18 if ($len_len==0) {
472 0         0 $len_len=1; # we can't have 0 len
473             }
474 8 50       30 if ($c_len==0) {
475 0         0 $c_len=1; # we can't have 0 len
476             }
477 8         12 my @hdr = ();
478 8         16 push(@hdr, ($magic << 4) | $version);
479 8         19 push(@hdr, ($type<<4)|(($len_len-1)<<2)|($c_len-1));
480             #$self->dbg(sprintf("Cookie: %x, len_len=$len_len, c_len=$c_len, body_len=$body_len", $cookie));
481 8         23 for (; $len_len>0; $len_len--){
482 8         28 push(@hdr, ($body_len>>(($len_len-1)*8)) & 0xFF);
483             }
484 8         28 for (; $c_len>0; $c_len--){
485 32         76 push(@hdr, ($cookie>>(($c_len-1)*8)) & 0xFF);
486             }
487              
488 8         25 $self->dbg_dump_arr('header', \@hdr);
489 8         16 $self->dbg_dump_arr('body', \@body);
490              
491             # flush read buffer
492 8         13 my $sock = $self->{'socket'};
493 8         144 $sock->flush;
494              
495             # send request
496 8         13 my $buf = '';
497 8         18 for my $i (0 .. $#hdr) {
498 56         8389 $buf .= pack('C', $hdr[$i]);
499             }
500 8         28 for my $i (0 .. $#body) {
501 106         141 $buf .= pack('C', $body[$i]);
502             }
503 8 100       60 unless ($sock->write($buf, length($buf))) {
504 4         115 $self->err("Send error: $!");
505 4         22 return 0;
506             }
507              
508             # read header
509 4 50       346 unless ($sock->read($buf, 2)) {
510 4         95 $self->err("Header recv error: $!");
511 4         22 return 0;
512             }
513             # validate
514 0           my @arr = unpack('C*', $buf);
515              
516 0           $self->dbg_dump_arr('magic', \@arr);
517 0 0         if (($arr[0] >> 4) != $magic) {
518 0           $self->err("Bad magic");
519 0           return 0;
520             }
521 0 0         if (($arr[0] & 0x0F) != $version) {
522 0           $self->err("Bad version");
523 0           return 0;
524             }
525 0           $type = $arr[1] >> 4;
526 0           $len_len = (($arr[1] >> 2) & 0x03)+1;
527 0           $c_len = ($arr[1] & 0x03)+1;
528              
529 0 0         unless ($sock->read($buf, $len_len+$c_len)) {
530 0           $self->err("Header length recv error: $!");
531 0           return 0;
532             }
533            
534             # read body len & cookie
535 0           my $cookie2;
536 0           @arr = unpack('C*', $buf);
537 0           $self->dbg_dump_arr('cookie', \@arr);
538 0           binrpc_read_int(@arr, $len_len, $body_len);
539 0           binrpc_read_int(@arr, $c_len, $cookie2);
540              
541 0 0         if ($cookie != $cookie2) {
542 0           $self->err("Bad cookie ($cookie!=$cookie2");
543 0           return 0;
544             }
545             # read body
546 0 0         unless ($sock->read($buf, $body_len)) {
547 0           $self->err("Body recv error: $!");
548 0           return 0;
549             }
550              
551             # parse result
552 0           @arr = unpack('C*', $buf);
553 0           $self->dbg_dump_arr('result', \@arr);
554              
555 0           @$result = ();
556              
557 0           while (scalar(@arr)) {
558 0           my ($val_type, $val, $dummy);
559 0           undef $val_type; # any type
560 0 0         unless (binrpc_read_record(@arr, '', $val_type, $val, $dummy)) {
561 0           $self->err("Parsing result error");
562 0           return 0;
563             }
564 0           push(@$result, $val);
565             }
566              
567 0 0         if ($type == $message_type{REPL}) {
    0          
568 0           return 1;
569             }
570             elsif ($type == $message_type{FAULT}) {
571 0           return -1;
572             }
573             else {
574 0           $self->err("Bad reply type ($type)");
575 0           return 0;
576             }
577             }
578              
579             sub print_result($$\@$) {
580 0     0 1   my ($self, $stream, $result, $indent) = @_;
581              
582 0 0         $indent = '' unless (defined $indent);
583            
584 0 0         if (ref($result) eq 'ARRAY') {
    0          
585 0           for my $i (0 .. $#$result) {
586 0 0         if (ref($$result[$i]) eq 'ARRAY') {
    0          
587 0           printf $stream "%s(\n", $indent;
588 0           $self->print_result($stream, $$result[$i], "$indent ");
589 0           printf $stream "%s)\n", $indent;
590             } elsif (ref($$result[$i]) eq 'HASH') {
591 0           printf $stream "%s\{\n", $indent;
592 0           $self->print_result($stream, $$result[$i], "$indent ");
593 0           printf $stream "%s}\n", $indent;
594              
595             } else {
596 0           printf $stream "%s%s\n", $indent, $$result[$i];
597             }
598              
599             }
600             } elsif (ref($result) eq 'HASH') {
601 0           foreach my $k (keys %$result) {
602 0           printf $stream "%s%s: %s\n", $indent, $k, $result->{$k};
603             }
604             } else {
605 0           printf $stream "%s%s\n", $indent, $$result;
606             }
607            
608             }
609              
610              
611             # ----------------------------- end of object ------------------------------------
612              
613             # Preloaded methods go here.
614              
615             1;
616             __END__