|
- #!/usr/bin/perl
- #
- # etherSimulator.pl
- # Simulates the luminiferous ether for RH_Simulator.
- # Connects multiple instances of RH_Simulator clients together and passes
- # simulated messages between them.
-
- use Getopt::Long;
- use strict;
-
- # Configurable variables
- my $help;
- my $config;
- my $port = 4000;
- $port = $main::opt_p
- if $main::opt_p;
- my $bps = 10000;
- $bps = $main::opt_b
- if $main::opt_b;
-
- # Config that shows probability of successful transmission between nodes
- # Read from config file
- my %netconfig;
-
- use warnings;
- use POE qw(Component::Server::TCP Filter::Block);
- use strict;
-
- my @options =
- (
- 'h' => \$help, # Help, show usage
- 'c=s' => \$config, # Config file
- 'b=n' => \$bps, # Bits per second simulated baud rate
- 'p=n' => \$port, # port number
- );
-
- &GetOptions(@options) || &usage;
- &usage if $help;
-
- readConfig($config) if defined $config;
-
- sub usage
- {
- print "usage: $0 [-h] [-c configfile] [-b bitspersec] [-p portnumber]\n";
- exit;
- }
-
- # config file for etherSimulator.pl
- # Specify the probability of correct delivery between nodea and nodeb (bidirectional)
- # probability:nodea:nodeb:probability
- # nodea and nodeb are integers 0 to 255
- # probability is a float range 0.0 to 1.0
- # In this example, the probability of successful transmission
- # between nodes 10 and 2 (and vice versa) is given as 0.5 (ie 50% chance)
- # probability:10:2:0.5
- sub readConfig
- {
- my ($config) = @_;
-
- if (open(CONFIG, $config))
- {
- while (<CONFIG>)
- {
- if (/^probability:(\d{1,3}):(\d{1,3}):(\d+(\.\d+))/)
- {
- $netconfig{$1}{$2} = $3;
- $netconfig{$2}{$1} = $3; # Bidirectional
- }
- }
- close(CONFIG);
- }
- else
- {
- print STDERR "Could not open config file $config: $!\n";
- exit;
- }
- }
-
- # See RHTcpProtocol.h
- # messages to and from us are preceded by the payload length as uint32_t in network byte order
- sub encoder
- {
- my $stuff = shift;
- substr($$stuff, 0, 0) = pack('N', length($$stuff));
- return;
- }
-
- sub decoder
- {
- my $stuff = shift;
- return if (length($$stuff) < 4);
- my ($length) = unpack('N', $$stuff);
- return if (length($$stuff) < $length+4);
- return $length + 4;
- }
-
- # Filter to assemble and disassemble messages accordiong to precending length
- my $filter = POE::Filter::Block->new( LengthCodec => [ \&encoder, \&decoder ] );
-
- # Message types
- # See RH_TcpProtocol.h
- my $RH_TCP_MESSAGE_TYPE_NOP = 0; # Not used
- my $RH_TCP_MESSAGE_TYPE_THISADDRESS = 1; # Specifies the thisAddress of the connected sketch
- my $RH_TCP_MESSAGE_TYPE_PACKET = 2; # Message to/from the connected sketch
-
- my %clients;
-
- # Look up the source and dest nodes in the netconfig and return the 0.0 to 1.0 probability
- # of successful delivery
- sub probabilityOfSuccessfulDelivery
- {
- my ($from, $to) = @_;
-
- return $netconfig{$from}{$to}
- if exists $netconfig{$from}{$to};
- # If no explicit probability, use 1.0 (certainty)
- return 1.0;
- }
-
- # Return true if the message is simulted to have been received successfully
- # taking into account the probability of sucessful delivery
- sub willDeliverFromTo
- {
- my ($from, $to) = @_;
-
- my $prob = probabilityOfSuccessfulDelivery($from, $to);
- return 1
- if rand() < $prob;
- return 0;
- }
-
- sub deliverMessages
- {
- my ($key, $value);
- while (($key, $value) = each(%clients))
- {
- next unless defined $$value{'packet'}; # No packet waiting for delivery
- # Find how long since the message was transmitted and see it its time to
- # deliver it to the client.
- # We are waiting here for the transmission time of the message to elapse
- # given the message length and the bits per second
- my $elapsed = Time::HiRes::tv_interval([$$value{'packetreceived'}], [Time::HiRes::gettimeofday]);
- if ($elapsed > length($$value{'packet'}) * 8 / $bps)
- {
- $$value{'client'}->put(pack('Ca*', $RH_TCP_MESSAGE_TYPE_PACKET, $$value{'packet'}));
- delete $$value{'packet'}; # Delivered, forget it
- }
- }
- }
-
- POE::Session->create(
- inline_states => {
- _start => sub {
- $_[KERNEL]->delay(tick => 1);
- },
-
- tick => sub {
- deliverMessages();
- $_[KERNEL]->delay(tick => 0.001);
- },
- },
- );
-
- POE::Component::Server::TCP->new(
- Port => $port,
-
- ClientConnected => sub {
- my $client = $_[HEAP]{client};
- # Create a new object to hold data about RH_TCP messages to and from this client
- $clients{$client} = {'client' => $client};
- },
-
- ClientInput => sub {
- my $client = $_[HEAP]{client};
- my $client_input = $_[ARG0];
- my $client_id = $_[ARG1];
- my ($length, $type) = unpack('NC', $client_input);
- if ($type == $RH_TCP_MESSAGE_TYPE_THISADDRESS)
- {
- # Client notifies us of its node ID
- my ($length, $type, $thisaddress) = unpack('NCC', $client_input);
- # Set the client objects thisaddress
- $clients{$client}{'thisaddress'} = $thisaddress;
- }
- elsif ($type == $RH_TCP_MESSAGE_TYPE_PACKET)
- {
- # New packet for transmission
- my ($length, $type, $packet) = unpack('NCa*', $client_input);
- # Try to deliver the packet to all the other clients
- my ($key, $value);
- while (($key, $value) = each(%clients))
- {
- next if ($key eq $client); # Dont deliver back to the same client
-
- # Check the network config and see if delivery to this node is possible
- next unless willDeliverFromTo($clients{$client}{'thisaddress'}, $$value{thisaddress});
-
- # The packet reached this destination, see if it collided with
- # another packet
- if (defined $$value{'packet'})
- {
- # Collision with waiting packet, delete it
- delete $$value{'packet'};
- }
- else
- {
- # New packet, queue it for delivery to the client after the
- # nominal transmission time is complete
- $$value{'packet'} = $packet;
- $$value{'packetreceived'} = Time::HiRes::gettimeofday();
- }
- }
- }
- },
-
- ClientDisconnected => sub {
- my $client = $_[HEAP]{client};
- delete $clients{$client};
- },
- ClientFilter => $filter, # Handles prepended lengths to
- );
-
- POE::Kernel->run;
- exit;
|