File Coverage

blib/lib/BGPmon/Fetch/Client.pm
Criterion Covered Total %
statement 96 222 43.2
branch 0 42 0.0
condition n/a
subroutine 32 45 71.1
pod 13 13 100.0
total 141 322 43.7


line stmt bran cond sub pod time code
1             package BGPmon::Fetch::Client;
2             our $VERSION = '2.0';
3              
4 1     1   17738 use 5.14.0;
  1         4  
  1         49  
5 1     1   5 use strict;
  1         3  
  1         667  
6 1     1   7 use warnings;
  1         3  
  1         36  
7 1     1   1297 use IO::Socket;
  1         40331  
  1         5  
8 1     1   2246 use IO::Select;
  1         2595  
  1         74  
9 1     1   10 use POSIX qw/strftime/;
  1         3  
  1         9  
10              
11             require Exporter;
12             our $AUTOLOAD;
13             our @ISA = qw(Exporter);
14             our @EXPORT_OK = qw(connect_bgpmon read_xml_message close_connection
15             is_connected messages_read uptime connection_endtime init_bgpdata
16             get_error_code get_error_message get_error_msg set_timeout);
17              
18             # connection status
19             our $msgs_read = 0;
20             our $start_time = time;
21             our $end_time = time;
22             our $connected = 0;
23              
24             # socket and persistent buffer for storing partial messages
25             our $sock;
26             our $sel;
27             our $read_buffer = "";
28             our $chunksize = 1024;
29             our $socket_timeout = 600; # Socket times out after 10 min.
30             our %socket_buffer = ();
31              
32             # Variables to store error codes and messages.
33             my %error_code;
34             my %error_msg;
35              
36             # Error messages
37 1     1   979 use constant NO_ERROR_CODE => 0;
  1         2  
  1         104  
38 1     1   6 use constant NO_ERROR_MSG => 'No error.';
  1         2  
  1         43  
39 1     1   5 use constant CONNECT_ERROR_CODE => 201;
  1         2  
  1         52  
40 1     1   26 use constant CONNECT_ERROR_MSG => 'Could not connect to BGPmon.';
  1         2  
  1         59  
41 1     1   4 use constant NOT_CONNECTED_CODE => 202;
  1         2  
  1         41  
42 1     1   5 use constant NOT_CONNECTED_MSG => 'Not connected to a BGPmon instance.';
  1         7  
  1         44  
43 1     1   4 use constant ALREADY_CONNECTED_CODE => 203;
  1         2  
  1         49  
44 1     1   5 use constant ALREADY_CONNECTED_MSG => 'Already connected to a BGPmon instance.';
  1         1  
  1         40  
45 1     1   5 use constant XML_MSG_READ_ERROR_CODE => 204;
  1         1  
  1         44  
46 1     1   4 use constant XML_MSG_READ_ERROR_MSG => 'Error reading XML message from stream.';
  1         2  
  1         48  
47 1     1   4 use constant CONNECTION_CLOSED_CODE => 205;
  1         2  
  1         42  
48 1     1   4 use constant CONNECTION_CLOSED_MSG => 'Connection to BGPmon closed.';
  1         2  
  1         116  
49 1     1   5 use constant INVALID_NUM_BYTES_ERROR_CODE => 206;
  1         1  
  1         120  
50 1         40 use constant INVALID_NUM_BYTES_ERROR_MSG =>
51 1     1   5 'Attempting to read invalid number of bytes.';
  1         1  
52 1     1   5 use constant READ_ERROR_CODE => 207;
  1         2  
  1         43  
53 1         36 use constant READ_ERROR_MSG =>
54 1     1   16 'Could not read from file handle. Error in sysread.';
  1         2  
55 1     1   4 use constant SOCKET_TIMEOUT_CODE => 208;
  1         1  
  1         41  
56 1     1   4 use constant SOCKET_TIMEOUT_MSG => 'Socket timed out.';
  1         2  
  1         35  
57 1     1   4 use constant NON_PRINTABLE_CHARS_CODE => 209;
  1         1  
  1         50  
58 1         43 use constant NON_PRINTABLE_CHARS_MSG =>
59 1     1   5 'Received XML messages contains non-printable characters';
  1         1  
60 1     1   4 use constant ARGUMENT_ERROR_CODE => 297;
  1         2  
  1         37  
61 1     1   4 use constant ARGUMENT_ERROR_MSG => 'Invalid number of arguments.';
  1         1  
  1         41  
62 1     1   4 use constant INVALID_FUNCTION_SPECIFIED_CODE => 298;
  1         2  
  1         40  
63 1         34 use constant INVALID_FUNCTION_SPECIFIED_MSG =>
64 1     1   5 'Invalid function name specified.';
  1         2  
65 1     1   4 use constant UNKNOWN_ERROR_CODE => 299;
  1         27  
  1         37  
66 1     1   5 use constant UNKNOWN_ERROR_MSG => 'Unknown error occurred.';
  1         1  
  1         2539  
67              
68             # Initially, all functions are error-free.
69             my @functions = qw(init_bgpdata connect_bgpmon read_xml_message read_n_bytes
70             close_connection is_connected messages_read uptime connection_endtime);
71             for my $function (@functions) {
72             $error_code{$function} = NO_ERROR_CODE;
73             $error_msg{$function} = NO_ERROR_MSG;
74             }
75              
76             =head1 NAME
77              
78             BGPmon::Fetch::Client
79              
80             The BGPmon Client module, to connect to BGPmon and receive XML messages
81             one at a time.
82              
83             =cut
84              
85             =head1 SYNOPSIS
86              
87             The BGPmon::Client module provides functionality to connect to a bgpmon
88             instance and read one XML message at a time.
89              
90             use BGPmon::Fetch::Client;
91             my $ret = connect_bgpmon();
92             set_timeout($time_out_seconds);
93             my $xml_msg = read_xml_message();
94             my $ret = is_connected();
95             my $num_read = messages_read();
96             my $uptime = uptime();
97             my $ret = close_connection();
98             my $downtime = connection_endtime();
99             =cut
100              
101             =head1 EXPORT
102              
103             init_bgpdata
104             connect_bgpmon
105             set_timeout
106             read_xml_message
107             close_connection
108             is_connected
109             messages_read
110             uptime
111             connection_endtime
112              
113             =cut
114              
115             =head1 SUBROUTINES/METHODS
116              
117             =head2 init_bgpdata
118              
119             Initializes the client module. Takes no arguments.
120              
121             =cut
122              
123             sub init_bgpdata {
124 0     0 1   return 1;
125             }
126              
127             =head2 set_timeout
128              
129             Sets the socket timeout value in seconds. Takes one argument -
130             the timeout value in seconds.
131              
132             =cut
133              
134             sub set_timeout {
135 0     0 1   $socket_timeout = shift;
136             }
137              
138             =head2 connect_bgpmon
139              
140             This function connects to the BGPmon server. If the connection succeeds, the
141             function attempts to read the starting tag from the BGPmon server. If
142             that succeeds, the created socket is returned. If not, the function returns
143             undef.
144              
145             =cut
146              
147             sub connect_bgpmon {
148 0     0 1   my ($server, $port) = @_;
149 0           my $fname = 'connect_bgpmon';
150              
151 0 0         if ($connected == 1) {
152 0           $error_code{$fname} = ALREADY_CONNECTED_CODE;
153 0           $error_msg{$fname} = ALREADY_CONNECTED_MSG;
154 0           return -1;
155             }
156              
157             # Connect to the BGPmon instance to receive XML xml_stream
158 0           $sock = new IO::Socket::INET(
159             PeerAddr => $server, PeerPort => $port, Proto => 'tcp');
160 0 0         if(!defined $sock) {
161 0           $error_code{$fname} = CONNECT_ERROR_CODE;
162 0           $error_msg{$fname} = CONNECT_ERROR_MSG;
163 0           return -2;
164             }
165              
166             # Create select object, to be used later.
167 0           $sel = new IO::Select->new();
168 0           $sel->add($sock);
169              
170 0           my $data;
171 0           my @ready = $sel->can_read($socket_timeout);
172 0 0         if (scalar(@ready) > 0) { #We have data on socket.
173 0           $data = read_n_bytes(5);
174 0 0         if (!defined $data) {
175 0           $error_code{$fname} = XML_MSG_READ_ERROR_CODE;
176 0           $error_msg{$fname} = XML_MSG_READ_ERROR_MSG;
177 0           $sel->remove($sock);
178 0           close($sock);
179 0           return -3;
180             }
181 0 0         if ($data ne "") {
182 0           $error_code{$fname} = XML_MSG_READ_ERROR_CODE;
183 0           $error_msg{$fname} = XML_MSG_READ_ERROR_MSG;
184 0           $sel->remove($sock);
185 0           close($sock);
186 0           return -4;
187             }
188 0           $connected = 1;
189 0           $start_time = time;
190 0           $msgs_read = 0;
191              
192 0           $error_code{$fname} = NO_ERROR_CODE;
193 0           $error_msg{$fname} = NO_ERROR_MSG;
194 0           return 0;
195             }
196              
197             # Socket timed out.
198 0           $error_code{$fname} = SOCKET_TIMEOUT_CODE;
199 0           $error_msg{$fname} = SOCKET_TIMEOUT_MSG;
200 0           $sel->remove($sock);
201 0           close($sock);
202 0           return -5;
203             }
204              
205             =head2 read_xml_message
206              
207             This function reads one xml message at a time from the BGPmon XML stream.
208              
209             =cut
210              
211             sub read_xml_message {
212 0     0 1   my $complete_xml_msg = undef;
213 0           my $fname = 'read_xml_message';
214              
215             # Check if we are connected to a server
216 0 0         unless ($connected == 1) {
217 0           $error_code{$fname} = NOT_CONNECTED_CODE;
218 0           $error_msg{$fname} = NOT_CONNECTED_MSG;
219 0           return undef;
220             }
221              
222 0           my $tempbuf;
223 0           my $bytesread = 0;
224 0           my @ready = $sel->can_read($socket_timeout);
225              
226             # If there is a socket with data, read data from the socket.
227 0 0         if (scalar(@ready) > 0) {
228 0           while ($read_buffer !~ //) {
229 0           $bytesread = sysread($sock, $tempbuf, $chunksize);
230 0 0         if (!defined $bytesread) {
231 0           $error_code{$fname} = XML_MSG_READ_ERROR_CODE;
232 0           $error_msg{$fname} = XML_MSG_READ_ERROR_MSG;
233 0           close_connection();
234 0           return undef;
235             }
236 0 0         if ($bytesread == 0) {
237 0           $error_code{$fname} = CONNECTION_CLOSED_CODE;
238 0           $error_msg{$fname} = CONNECTION_CLOSED_MSG;
239 0           close_connection();
240 0           return undef;
241             }
242 0           $read_buffer .= $tempbuf;
243             }
244              
245             # check if message contains only ASCII printable characters
246 0 0         unless ($read_buffer =~ /[[:print:]]/) {
247 0           $error_code{$fname} = NON_PRINTABLE_CHARS_CODE;
248 0           $error_msg{$fname} = NON_PRINTABLE_CHARS_MSG;
249 0           return undef;
250             }
251              
252             # at this point I have a complete message OR my socket closed
253 0 0         if ($read_buffer =~ /^()/) {
254 0           $complete_xml_msg = $1;
255 0           $msgs_read++;
256 0           $read_buffer =~ s/^//;
257             } else {
258 0           $error_code{$fname} = UNKNOWN_ERROR_CODE;
259 0           $error_msg{$fname} = UNKNOWN_ERROR_MSG;
260             }
261 0           return $complete_xml_msg;
262             }
263             # Timeout.
264 0           $error_code{$fname} = SOCKET_TIMEOUT_CODE;
265 0           $error_msg{$fname} = SOCKET_TIMEOUT_MSG;
266 0           return undef;
267             }
268              
269             =head2 close_connection
270              
271             Function to close open files and sockets.
272              
273             =cut
274              
275             sub close_connection {
276 0     0 1   $connected = 0;
277 0           $end_time = time;
278 0 0         if ($sock) {
279 0           $sel->remove($sock);
280 0           $sock->close();
281             }
282             }
283              
284             =head2 is_connected
285              
286             Function to report whether currently connected to BGPmon.
287              
288             =cut
289              
290             sub is_connected {
291 0     0 1   return $connected;
292             }
293              
294             =head2 messages_read
295              
296             Get number of messages read.
297              
298             =cut
299              
300             sub messages_read {
301 0     0 1   return $msgs_read;
302             }
303              
304             =head2 uptime
305              
306             Returns number of seconds the connection has been up.
307             If the connection is down, return 0.
308              
309             =cut
310              
311             sub uptime {
312 0 0   0 1   if ($connected) {
313 0           return time() - $start_time;
314             }
315 0           return 0;
316              
317             }
318              
319             =head2 connection_endtime
320              
321             Returns the time the connection ended .
322             If the connection is up, return 0.
323              
324             =cut
325              
326             sub connection_endtime {
327 0 0   0 1   if ($connected) {
328 0           return 0;
329             }
330 0           return $end_time;
331              
332             }
333              
334             =head2 read_n_bytes
335              
336             This function reads exactly n bytes from a connection.
337             returns undef on any error or connection close
338              
339             =cut
340              
341             sub read_n_bytes {
342 0     0 1   my ($bytesneeded) = shift;
343 0           my $fname = 'read_n_bytes';
344 0 0         if ($bytesneeded < 0) {
345 0           $error_code{$fname} = INVALID_NUM_BYTES_ERROR_CODE;
346 0           $error_msg{$fname} = INVALID_NUM_BYTES_ERROR_MSG;
347 0           return undef;
348             }
349 0           my $data = "";
350 0           my $buf = "";
351 0           my $bytesread = 0;
352 0           while($bytesneeded > 0) {
353 0           $bytesread = sysread($sock, $buf, $bytesneeded);
354 0 0         if (!defined $bytesread) {
355 0           $error_code{$fname} = READ_ERROR_CODE;
356 0           $error_msg{$fname} = READ_ERROR_MSG . ": $!";
357 0           return undef;
358             }
359 0 0         if ($bytesread == 0) {
360 0           $error_code{$fname} = CONNECTION_CLOSED_CODE;
361 0           $error_msg{$fname} = CONNECTION_CLOSED_MSG;
362 0           return undef;
363             }
364 0           $data .= $buf;
365 0           $bytesneeded = $bytesneeded - $bytesread;
366             }
367 0           return $data;
368             }
369              
370             =head2 get_error_code
371              
372             Get the error code for a given function
373             Input : the name of the function whose error code we should report
374             Output: the function's error code
375             or ARGUMENT_ERROR if the user did not supply a function
376             or INVALID_FUNCTION_SPECIFIED if the user provided an invalid function
377             Usage: my $err_code = get_error_code("connect_archive");
378              
379             =cut
380             sub get_error_code {
381 0     0 1   my $function = shift;
382 0 0         unless (defined $function) {
383 0           return ARGUMENT_ERROR_CODE;
384             }
385              
386 0 0         return $error_code{$function} if (defined $error_code{$function});
387 0           return INVALID_FUNCTION_SPECIFIED_CODE;
388             }
389              
390             =head2 get_error_message {
391              
392             Get the error message of a given function
393             Input : the name of the function whose error message we should report
394             Output: the function's error message
395             or ARGUMENT_ERROR if the user did not supply a function
396             or INVALID_FUNCTION_SPECIFIED if the user provided an invalid function
397             Usage: my $err_msg = get_error_message("read_xml_message");
398              
399             =cut
400             sub get_error_message {
401 0     0 1   my $function = shift;
402 0 0         unless (defined $function) {
403 0           return ARGUMENT_ERROR_MSG;
404             }
405              
406 0 0         return $error_msg{$function} if (defined $error_msg{$function});
407 0           return INVALID_FUNCTION_SPECIFIED_MSG;
408             }
409              
410             =head2 get_error_msg
411              
412             Shorthand call for get_error_message
413              
414             =cut
415              
416             sub get_error_msg{
417 0     0 1   my $fname = shift;
418 0           return get_error_message($fname);
419             }
420              
421             =head1 AUTHOR
422              
423             Kaustubh Gadkari, C<< >>
424              
425             =head1 BUGS
426              
427             Please report any bugs or feature requests to
428             C, or through the web interface at
429             L.
430              
431              
432             =head1 SUPPORT
433              
434             You can find documentation for this module with the perldoc command.
435              
436             perldoc BGPmon::Client
437              
438             =cut
439              
440             =head1 LICENSE AND COPYRIGHT
441             Copyright (c) 2012 Colorado State University
442              
443             Permission is hereby granted, free of charge, to any person
444             obtaining a copy of this software and associated documentation
445             files (the "Software"), to deal in the Software without
446             restriction, including without limitation the rights to use,
447             copy, modify, merge, publish, distribute, sublicense, and/or
448             sell copies of the Software, and to permit persons to whom
449             the Software is furnished to do so, subject to the following
450             conditions:
451              
452             The above copyright notice and this permission notice shall be
453             included in all copies or substantial portions of the Software.
454              
455             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
456             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
457             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
458             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
459             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
460             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
461             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
462             OTHER DEALINGS IN THE SOFTWARE.\
463              
464              
465             File: Client.pm
466             Authors: Kaustubh Gadkari, Dan Massey, Cathie Olschanowsky
467             Date: 13 October 2012
468              
469             =cut
470             1; # End of BGPmon::Client