File Coverage

blib/lib/Net/Peep/BC.pm
Criterion Covered Total %
statement 153 266 57.5
branch 24 74 32.4
condition 9 30 30.0
subroutine 29 37 78.3
pod 0 18 0.0
total 215 425 50.5


line stmt bran cond sub pod time code
1             package Net::Peep::BC;
2              
3             require 5.00503;
4 3     3   61 use strict;
  3         5  
  3         113  
5             # use warnings; # commented out for 5.005 compatibility
6 3     3   17 use Carp;
  3         6  
  3         221  
7              
8             require Exporter;
9              
10 3     3   15 use vars qw{ @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT $VERSION };
  3         6  
  3         524  
11              
12             @ISA = qw(Exporter);
13             %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
14             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
15             @EXPORT = qw( );
16             $VERSION = do { my @r = (q$Revision: 1.10 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
17              
18 3     3   17 use Socket;
  3         5  
  3         2064  
19 3     3   1799 use Sys::Hostname;
  3         17233  
  3         230  
20 3     3   1916 use Net::Peep::Parser;
  3         8  
  3         164  
21 3     3   18 use Net::Peep::Conf;
  3         5  
  3         109  
22 3     3   16 use Net::Peep::Log;
  3         5  
  3         102  
23 3     3   1133 use Net::Peep::Scheduler;
  3         8  
  3         147  
24              
25 3     3   17 use vars qw{ %Leases %Servers %Defaults $Scheduler $Alarmtime };
  3         7  
  3         439  
26              
27             %Leases = %Servers = ();
28              
29             %Defaults = (
30             type => 0,
31             location => 128,
32             priority => 0,
33             volume => 128,
34             dither => 0,
35             sound => 0
36             );
37              
38             $Scheduler = new Net::Peep::Scheduler;
39              
40             $Alarmtime = 30;
41              
42             # Peep protocol constants
43 3     3   17 use constant PROT_MAJORVER => 1;
  3         18  
  3         196  
44 3     3   15 use constant PROT_MINORVER => 0;
  3         6  
  3         123  
45 3     3   15 use constant PROT_BCSERVER => 0;
  3         5  
  3         107  
46 3     3   16 use constant PROT_BCCLIENT => 1;
  3         11  
  3         130  
47 3     3   17 use constant PROT_SERVERSTILLALIVE => 2;
  3         4  
  3         127  
48 3     3   14 use constant PROT_CLIENTSTILLALIVE => 3;
  3         6  
  3         125  
49 3     3   15 use constant PROT_CLIENTEVENT => 4;
  3         4  
  3         319  
50 3     3   16 use constant PROT_CLASSDELIM => '!';
  3         7  
  3         11483  
51              
52             sub new {
53              
54 3     3 0 7 my $self = shift;
55 3   33     17 my $class = ref($self) || $self;
56 3         8 my $this = {};
57 3         10 bless $this, $class;
58              
59 3         19 $this->initialize(@_);
60              
61 3         16 return $this;
62              
63             } # end sub new
64              
65             sub initialize {
66              
67 3     3 0 7 my $self = shift;
68 3   33     11 my $client = shift || confess "Error: Client not found";
69 3   33     12 my $configuration = shift || confess "Error: Configuration not found";
70              
71             # put the configuration object in a place where all the other
72             # methods can find it
73 3         14 $self->setConfiguration($configuration);
74              
75 3         18 my %options = $configuration->getOptionsHash($client);
76              
77             # FIXIT: This is silly and redundant. The object that instantiates
78             # the Net::Peep::BC object already has all of the options set and a
79             # configuration object. Net::Peep::BC shouldn't need to build its own
80             # stable of options. Deal with it later ....
81              
82             # Populate the object attributes either by the class default
83             # attributes or the options arguments passed in
84              
85 3         22 for my $key (keys %Defaults) {
86 18 100       45 if ($key ne 'dither') {
87 15 50       30 if (exists $options{$key}) {
88 0         0 $self->setOption($key,$options{$key});
89             } else {
90 15         51 $self->setOption($key,$Defaults{$key});
91             }
92             }
93             }
94              
95             # Make allowances for the two possible meanings of dither
96             # dither is based exclusively on the value of 'type'
97 3 50       16 $self->getOption('type') ? $self->setOption('dither',255) : $self->setOption('dither',0);
98              
99             # Now initialize our socket
100 3         20 my $port = $configuration->getClientPort($client);
101 3         42 $self->logger()->debug(7,"Initializing socket on port $port ...");
102 3         9 my $addr = INADDR_ANY;
103 3         8255 my $proto = getprotobyname('udp');
104 3         8911 my $paddr = sockaddr_in($port, $addr);
105 3 50       447 socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) or confess "Socket error: $!";
106 3 50       26 if ($configuration->getOption($client,'autodiscovery')) {
107 0 0       0 bind(SOCKET, $paddr) or confess "Bind error: $!";
108             }
109 3         17 $self->logger()->debug(7,"\tSocket initialized.");
110              
111             #Set the socket option for the broadcast
112 3         31 setsockopt SOCKET, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, 1;
113              
114             #Let everyone know we're alive
115 3         27 $self->hello( PROT_BCCLIENT );
116              
117             #Start up the alarm. Once this handler gets started, we'll have it
118             #work concurrently with the program to handle host lists
119 3 50       16 if ($configuration->getOption($client,'autodiscovery')) {
120             $Scheduler->schedulerAddEvent(
121             $self->getConfiguration()->client(),
122             $Alarmtime,
123             0.0,
124             'client',
125 0     0   0 sub { $self->handlealarm( PROT_CLIENTSTILLALIVE ) },
126 0         0 '',
127             1 # 1 => repeated event, 0 => single event
128             );
129             }
130              
131             } # end sub initialize
132              
133             sub hello {
134              
135 3     3 0 6 my $self = shift;
136 3         7 my $constant = shift;
137 3   33     15 my $configuration = $self->getConfiguration() || confess "Error: Configuration not found";
138              
139             # Send out our broadcast to let everybody know we're alive
140             # Note - we want to send these broadcasts to the servers within
141             # the class definition. So, we use getServer() - Mike
142 3         24 for my $class ($configuration->getClassList()) {
143 3         11 $self->logger()->debug(7,"Getting broadcast for class [$class]");
144 3         20 my $broadcasts = $configuration->getServer($class);
145              
146 3         39 for my $broadcast (@$broadcasts) {
147 3         14 my ($zone, $port) = ($broadcast->{'name'}, $broadcast->{'port'});
148 3         10 $self->logger()->debug(7,"Socketing to zone [$zone] and port [$port] ...");
149 3         875 my $iaddr = inet_aton($zone);
150 3         19 my $bcaddr = sockaddr_in($port, $iaddr);
151              
152             #Assemble the packet and send it
153 3         40 my $packet = $self->assemble_bc_packet($constant);
154 3 50 33     27 if (defined($constant) && $constant == PROT_CLIENTSTILLALIVE) {
155 0         0 $self->logger()->debug(7,"Letting [$zone:$port] know we're still alive ...");
156             } else {
157 3         12 $self->logger()->debug(7,"Sending a friendly hello to address [$zone:$port] ...");
158             }
159 3 50       283 if (defined(send(SOCKET, $packet, 0, $bcaddr))) {
160 3         14 $self->logger()->debug(9,"\tPacket of length ".length($packet)." sent.");
161             } else {
162 0         0 $self->logger()->log("Send broadcast error: $!");
163             }
164             }
165             }
166              
167             } # end sub hello
168              
169             sub getConfiguration {
170              
171 9     9 0 15 my $self = shift;
172 9 50       27 confess "Error retrieving configuration: The configuration has not been set yet."
173             unless exists $self->{"__CONFIGURATOR"};
174 9         39 return $self->{"__CONFIGURATOR"};
175              
176             } # end sub getConfiguration
177              
178             sub setConfiguration {
179              
180 3     3 0 7 my $self = shift;
181 3 50       12 if (@_) {
182 3         22 $self->{"__CONFIGURATOR"} = shift;
183             } else {
184 0         0 confess "Cannot set configuration: No configuration object found.";
185             }
186 3         14 return 1;
187              
188             } # end sub setConfiguration
189              
190             # Function to assemble a broadcast packet with an appropriate
191             # identifier string
192             sub assemble_bc_packet {
193              
194 3     3 0 8 my $self = shift;
195 3         6 my $constant = shift;
196 3         9 my $configuration = $self->getConfiguration();
197 3         13 my $identifier = join PROT_CLASSDELIM, ($configuration->getClassList());
198 3         8 $identifier .= PROT_CLASSDELIM;
199 3         31 return pack("CCCCA128",
200             PROT_MAJORVER,
201             PROT_MINORVER,
202             $constant,
203             0,
204             $identifier);
205              
206             } # end sub assemble_bc_packet
207              
208             # returns a logging object
209             sub logger {
210              
211 30     30 0 44 my $self = shift;
212 30 100       78 if ( ! exists $self->{'__LOGGER'} ) { $self->{'__LOGGER'} = new Net::Peep::Log }
  3         25  
213 30         253 return $self->{'__LOGGER'};
214              
215             } # end sub logger
216              
217             sub close {
218              
219 0     0 0 0 my $self = shift;
220 0         0 close SOCKET;
221              
222             } # end sub close
223              
224             # Send out a packet
225             sub send {
226              
227 3     3 0 5 my $self = shift;
228 3         11 my $client = shift;
229 3         28 my %options = @_;
230              
231 3         26 $self->logger()->debug(7,"Sending packet to server(s) ...");
232              
233 3         9 my $configuration = $self->getConfiguration();
234              
235 3 50       21 my $type = exists($options{'type'}) ? $options{'type'} : $self->getOption('type');
236 3 50       15 my $location = exists($options{'location'}) ? $options{'location'} : $self->getOption('location');
237 3 50       12 my $priority = exists($options{'priority'}) ? $options{'priority'} : $self->getOption('priority');
238 3 50       14 my $volume = exists($options{'volume'}) ? $options{'volume'} : $self->getOption('volume');
239 3 50       12 my $dither = exists($options{'dither'}) ? $options{'dither'} : $self->getOption('dither');
240 3 50       12 my $sound = exists($options{'sound'}) ? $options{'sound'} : $self->getOption('sound');
241              
242 3         12 $self->logger()->debug(9,"type=[$type] location=[$location] priority=[$priority] volume=[$volume] dither=[$dither] sound=[$sound]");
243              
244             #Now convert the sound name into the number if it isn't a number already
245 3 50       23 if ($sound !~ /\d+/) {
246 0         0 my $hash;
247 0 0       0 $hash = $configuration->getEvent($sound) if $configuration->isEvent($sound);
248 0 0       0 $hash = $configuration->getState($sound) if $configuration->isState($sound);
249              
250 0 0       0 if (ref($hash)) {
251 0         0 my $index = $hash->{'index'};
252 0         0 $self->logger()->debug(5,"Sound [$sound] reassigned: Now it is [$index]");
253 0         0 $sound = $index;
254             } else {
255 0         0 $self->logger()->log(ref($self),": Warning: Asking Peep to play a non existent sound: [$sound]");
256 0         0 return;
257             }
258             }
259              
260 3 50       12 if ($configuration->getOption($client,'autodiscovery')) {
261              
262             # Now sendout to all the servers in our server list
263 0         0 for my $server (keys %Servers) {
264 0         0 my ($serverport,$serverip) = unpack_sockaddr_in($server);
265 0         0 $serverip = inet_ntoa($serverip);
266 0         0 $self->logger()->debug(7,"Notifying server [$serverip:$serverport] of event or sound [$sound] ...");
267 0         0 $self->sendout($type, $sound, $location, $priority, $volume, $dither, $server);
268             }
269              
270             } else {
271              
272             # Just send a packet to the server and port specified on the command-line
273 3   33     15 my $port = $configuration->getOption($client,'port') || confess "Error: Expecting nonzero port!";
274 3   33     14 my $host = $configuration->getOption($client,'server') || confess "Error: Expecting nonzero host!";
275 3         12 $self->logger()->debug(7,"Notifying server [$host:$port] of event or sound [$sound] ...");
276 3         277 $host = inet_aton($host);
277 3         14 my $server = sockaddr_in($port,$host);
278 3         42 $self->sendout($type, $sound, $location, $priority, $volume, $dither, $server);
279              
280             }
281              
282             } # end sub send
283              
284             sub sendout {
285              
286 3     3 0 9 my $self = shift;
287 3         13 my ($type,$sound,$location,$priority,$volume,$dither,$server)
288             = @_;
289 3         8 my $mix_in_time = 0;
290              
291 3         16 my ($serverport,$serverip) = unpack_sockaddr_in($server);
292 3         27 $serverip = inet_ntoa($serverip);
293 3         13 $self->logger()->debug(7,"type=[$type] sound=[$sound] location=[$location] priority=[$priority] volume=[$volume] dither=[$dither] server=[$serverip:$serverport]") ;
294              
295             #Now we need to build the appropriate network packet
296 3         18 my $packet = pack("CCCCC8",
297             PROT_MAJORVER,
298             PROT_MINORVER,
299             PROT_CLIENTEVENT,
300             0,
301             $type, $sound, $location, $priority, $volume, $dither);
302              
303 3 50       109 if (not defined(CORE::send(SOCKET, $packet, 0, $server))) {
304 0         0 $self->logger()->debug(7,"Error sending packet to [$serverip:$serverport]: $!");
305 0         0 $self->logger()->debug(7,"You may want to check that the server is accepting connections on port [$serverport].");
306             }
307              
308 3         26 return 1;
309              
310             } # end sub sendout
311              
312             sub setOption {
313              
314 18     18 0 24 my $self = shift;
315 18   33     51 my $option = shift || confess "option not found";
316 18         21 my $value = shift;
317 18 50       38 confess "value not found" unless defined $value;
318 18         46 $self->{"__OPTIONS"}->{$option} = $value;
319 18         38 return 1;
320              
321             } # end sub setOption
322              
323             sub getOption {
324              
325 3     3 0 6 my $self = shift;
326 3   33     13 my $option = shift || confess "option not found";
327              
328 3 50       22 if (exists $self->{"__OPTIONS"}->{$option}) {
329 3         20 return $self->{"__OPTIONS"}->{$option};
330             } else {
331 0           confess "Cannot get the option '$option': It has not yet been set.";
332             }
333              
334 0           return 0;
335              
336             } # end sub setOption
337              
338             sub handlealarm {
339              
340             #Every tick, we wait until we have some input to respond to, then update
341             #our server list. Finally, we purge the server list of any impurities and
342             #carry on with out business
343 0     0 0   my $self = shift;
344 0           my $constant = shift;
345              
346 0           $self->hello($constant);
347 0           $self->updateserverlist();
348 0           $self->purgeserverlist();
349              
350 0 0         if (scalar(keys %Servers)) {
351 0           $self->logger()->debug(9,"Known servers:");
352 0           for my $server (sort keys %Servers) {
353 0           my ($serverport,$serverip) = unpack_sockaddr_in($server);
354 0           $serverip = inet_ntoa($serverip);
355 0           $self->logger()->debug(9,"\t[$serverip:$serverport]");
356             }
357             } else {
358 0           $self->logger()->debug(9,"There are currently no known servers.");
359             }
360              
361 0           return 1;
362              
363             } # end sub handlealarm
364              
365             sub updateserverlist {
366              
367             #Poll to see if we've received anything so we can update the server list
368             #before we send. Then, send out the packet.
369 0     0 0   my $self = shift;
370              
371 0           $self->logger()->debug(9,"Updating server list ...");
372              
373 0           my $rin = "";
374 0           my $rout;
375 0           vec($rin, fileno(SOCKET), 1) = 1;
376              
377 0 0         if (select($rout = $rin, undef, undef, 0.1)) {
378 0           my $packet;
379              
380 0           $self->logger()->debug(9,"\tReading from socket ...");
381              
382 0           my $server = recv(SOCKET, $packet, 256, 0); # 256 is safe amount to read
383             # Adding a defined argument here because recv can produce errors if
384             # a broadcast isn't responded to. Plus, we want to continue anyway.
385 0 0 0       if (defined($server) and $server ne '') {
386 0           my ($serverport,$serverip) = unpack_sockaddr_in($server);
387 0           $serverip = inet_ntoa($serverip);
388              
389 0           $self->logger()->debug(7,"\tJust received a packet from [$serverip:$serverport] ...");
390              
391             #Verify that this is a server bc packet
392 0           my ($majorver, $minorver, $type, $padding) = unpack("CCCC", $packet);
393              
394 0 0         $self->addnewserver($server, $packet) if $type == PROT_BCSERVER;
395 0 0         $self->logger()->debug(7,"\tUpdating server with profile [$majorver:$minorver:$type]") if $type == PROT_SERVERSTILLALIVE;
396 0 0         $self->updateserver($server, $packet) if $type == PROT_SERVERSTILLALIVE;
397             }
398             }
399              
400             } # end updateserverlist
401              
402             sub purgeserverlist {
403              
404 0     0 0   my $self = shift;
405              
406 0           $self->logger()->debug(9,"Purging server list ...");
407              
408 0           for my $server (keys %Servers) {
409 0 0         if ($Servers{$server}->{'expires'} <= time()) {
410 0           delete $Servers{$server};
411 0           $self->logger()->debug(7,"\tServer purged. Number of known servers: " . scalar (keys %Servers));
412              
413 0           for my $known (keys %Net::Peep::Servers) {
414 0           my ($serverport,$serverip) = unpack_sockaddr_in($server);
415 0           $serverip = inet_ntoa($serverip);
416 0           $self->logger()->debug(7,"\t\t$serverip:$serverport");
417             }
418             }
419             }
420              
421             } # end sub purgeserverlist
422              
423             sub addnewserver {
424              
425 0     0 0   my $self = shift;
426              
427 0           my ($server, $packet) = @_;
428              
429 0           my $configuration = $self->getConfiguration();
430              
431 0           my ($serverport,$serverip) = unpack_sockaddr_in($server);
432              
433 0           $serverip = inet_ntoa($serverip);
434              
435             # Check if this server already exists - because then we shouldn't be
436             # doing an add... so abort. This can happen because when the client
437             # registers with the server, the server always sends a BC response
438             # directly back to the client to make sure that the client really
439             # has the server in its hostlist
440 0 0         if (exists $Servers{$server}) {
441 0           $self->logger()->debug(7,"\tServer [$serverip:$serverport] won't be added to the server list: It is already in the list.");
442 0           return;
443             }
444              
445 0           my ($majorver, $minorver, $type, $padding, $min, $sec, $id) = unpack("CCCCCCA128", $packet);
446 0           my $delim = PROT_CLASSDELIM;
447              
448             #Clean up the ID string
449 0           $id =~ /([A-Za-z0-9!\-]*)/;
450 0           my $realid = $1;
451              
452 0           foreach my $class ($configuration->getClassList()) {
453 0           my $str = quotemeta($class.$delim);
454 0           $self->logger()->debug(7,"\tChecking server id [$realid] against class descriptor [$class$delim] ....");
455              
456 0 0         if ($realid =~ /$str/) {
457 0           $self->logger()->debug(7,"\tMatch found: Adding server [$serverip:$serverport] to the server list.");
458 0           $self->addserver($server, $min, $sec);
459             } else {
460 0           $self->logger()->debug(7,"\tNo match found. Nothing added to server list.");
461             }
462             }
463              
464 0           return 1;
465              
466             } # end sub addnewserver
467              
468             sub addserver {
469              
470 0     0 0   my $self = shift;
471 0           my ($server,$leasemin,$leasesec) = @_;
472              
473 0           $Servers{$server}->{'IP'} = $server;
474 0           $Servers{$server}->{'expires'} = time() + $leasemin*60 + $leasesec;
475              
476 0           $self->logger()->debug(7,"\tServer added. Number of known servers: " . scalar(keys %Servers));
477 0           for my $known (keys %Net::Peep::Servers) {
478 0           my ($serverport,$serverip) = unpack_sockaddr_in($known);
479 0           $serverip = inet_ntoa($serverip);
480 0           $self->logger()->debug(7,"\t\t$serverip:$serverport");
481             }
482              
483             #Let's send it a "BC" to tell it to add us as well
484 0           my ($serverport,$serverip) = unpack_sockaddr_in($server);
485 0           $serverip = inet_ntoa($serverip);
486 0           $self->logger()->debug(7,"\tSending client BC packet to [$serverip:$serverport] ...");
487 0 0         defined(CORE::send(SOCKET, $self->assemble_bc_packet(PROT_BCCLIENT), 0, $server)) or confess "Send clientbc error: $!";
488 0           $self->logger()->debug(7,"\tClient BC packet sent successfully.");
489              
490 0           return 1;
491              
492             } # end sub addserver
493              
494             sub updateserver {
495              
496 0     0 0   my $self = shift;
497 0           my $server = shift;
498 0           my $packet = shift;
499 0           my ($majorver, $minorver, $type, $padding, $min, $sec) = unpack("CCCCCC", $packet);
500              
501 0           $self->logger()->debug(7,"\tServer updated. Number of known servers: " . scalar(keys %Servers));
502              
503 0           $Servers{$server}->{'expires'} = time() + $min*60 + $sec;
504              
505             # New send out a client alive
506 0           my $net_packet = pack ("CCCC",
507             PROT_MAJORVER,
508             PROT_MINORVER,
509             PROT_CLIENTSTILLALIVE,
510             0);
511              
512 0           $self->logger()->debug(7,"\tSending client still alive packet ...");
513 0 0         defined(CORE::send(SOCKET, $net_packet, 0, $server)) or confess "Send client still alive error: $!";
514 0           $self->logger()->debug(7,"\tClient still alive packet sent successfully.");
515              
516 0           return 1;
517              
518             } # end sub updateserver
519              
520             1;
521              
522             __END__