summaryrefslogtreecommitdiffstats
path: root/3rdparty/openpgm-svn-r1135/pgm/test
diff options
context:
space:
mode:
authorSebastien Braun2010-10-03 16:14:44 +0200
committerSebastien Braun2010-10-03 16:14:44 +0200
commitffee0868ef1341cfb7622821431cb73c52590962 (patch)
treebc96be65e0346ea25a8effb2118de59b08d54466 /3rdparty/openpgm-svn-r1135/pgm/test
parentAdd patch for OpenPGM to fix switch() fallthrough (diff)
downloadpvs-ffee0868ef1341cfb7622821431cb73c52590962.tar.gz
pvs-ffee0868ef1341cfb7622821431cb73c52590962.tar.xz
pvs-ffee0868ef1341cfb7622821431cb73c52590962.zip
Assorted Multicast Fixes:
- Upgrade bundled OpenPGM to SVN r1135 - Timing fixes: Make all rate-limited and timer-pending operation wait for at least 1ms to avoid busy-waiting - No distinction between sending and receiving sockets when setting up socket options (Receivers need to be able to send anyway when using PGMCC). - Switch from fixed-rate transmission to using PGMCC for congestion control. - Remove some obnoxious debugging outputs - Some white space fixes - Introduce a short waiting time before actually starting file transmission in order to allow enough SPM messages to be sent so that receivers can initialize properly. - Fix MCASTFTANNOUNCE message to include full file name instead of basename. - Fix generateMcastTransferID in order to gather more random IDs. PVSGUI may become confused if transfer IDs are reused. - Properly dispose of clientFileReceiveDialog when multicast transfer is finished. - Properly display transfer size in clientFileReceiveDialog
Diffstat (limited to '3rdparty/openpgm-svn-r1135/pgm/test')
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/PGM/Test.pm394
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/SConscript15
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/ambient_spm.pl87
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/apdu.pl58
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/apdu_parity.pl59
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/app.c1068
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/async.c579
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/async.h76
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/dump-json.c1292
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/dump-json.h33
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/heartbeat_spm.pl58
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/monitor.c349
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/nak.pl68
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/nak_cancellation.pl163
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/nak_list.pl72
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/nak_parity.pl75
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/nak_repeat.pl71
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/ncf.pl68
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/ncf_cancellation.pl149
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/ncf_list.pl74
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/ncf_suppression.pl103
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata.pl45
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_completion.pl89
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_jump.pl73
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_jump_parity.pl83
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_number.pl60
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_rate.pl69
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/odata_reception.pl61
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/on-demand_spm.pl49
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/outofwindow_ncf.pl105
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion.pl89
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity.pl97
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity_var_pktlen.pl97
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/rdata_jump.pl73
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/rdata_reception.pl61
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/sim.c2301
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spm.pl43
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spm_jump.pl62
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spm_jump2.pl61
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spm_reception.pl60
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spmr.pl75
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spmr_after_spm.pl80
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spmr_from_odata.pl55
-rwxr-xr-x3rdparty/openpgm-svn-r1135/pgm/test/spmr_suppression.pl60
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/sudoers.example26
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/test/test.conf.pl27
46 files changed, 8812 insertions, 0 deletions
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/PGM/Test.pm b/3rdparty/openpgm-svn-r1135/pgm/test/PGM/Test.pm
new file mode 100644
index 0000000..cb2ee6d
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/PGM/Test.pm
@@ -0,0 +1,394 @@
+package PGM::Test;
+
+use strict;
+our($VERSION);
+use Carp;
+use IO::File;
+use IPC::Open2;
+use Net::SSH qw(sshopen2);
+use Sys::Hostname;
+use POSIX ":sys_wait_h";
+use JSON;
+
+$VERSION = '1.00';
+
+=head1 NAME
+
+PGM::Test - PGM test module
+
+=head1 SYNOPSIS
+
+ $test = PGM::Test->new();
+
+=cut
+
+my $json = new JSON;
+
+sub new {
+ my $class = shift;
+ my $self = {};
+ my %params = @_;
+
+ $self->{tag} = exists $params{tag} ? $params{tag} : confess "tag parameter is required";
+ $self->{host} = exists $params{host} ? $params{host} : confess "host parameter is required";
+ $self->{cmd} = exists $params{cmd} ? $params{cmd} : confess "cmd parameter is required";
+
+ $self->{in} = IO::File->new();
+ $self->{out} = IO::File->new();
+ $self->{pid} = undef;
+
+ bless $self, $class;
+ return $self;
+}
+
+sub connect {
+ my $self = shift;
+ my $host = hostname;
+
+ if ($self->{host} =~ /^(localhost|127\.1|127\.0\.0\.1|$host)$/)
+ {
+ print "$self->{tag}: opening local connection\n";
+ $self->{pid} = open2 ($self->{in},
+ $self->{out},
+ "uname -a && sudo $self->{cmd}")
+ or croak "open2 failed $!";
+ }
+ else
+ {
+ print "$self->{tag}: opening SSH connection to $self->{host} ...\n";
+ $self->{pid} = sshopen2 ($self->{host},
+ $self->{in},
+ $self->{out},
+ "uname -a && sudo $self->{cmd}")
+ or croak "SSH failed: $!";
+ }
+
+ print "$self->{tag}: connected.\n";
+ $self->wait_for_ready;
+}
+
+sub disconnect {
+ my($self,$quiet) = @_;
+ my $out = $self->{out};
+
+ print "$self->{tag}: sending quit command ...\n";
+ eval {
+ local($SIG{ALRM}) = sub { die "alarm\n"; };
+ alarm 10;
+ print $out "quit\n";
+ while (readline($self->{in})) {
+ chomp;
+ print "$self->{tag} [$_]\n" if (!$quiet);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ print "$self->{tag}: alarm raised on quit command.\n";
+ } else {
+ print "$self->{tag}: eof.\n";
+ }
+
+ print "$self->{tag}: closing SSH connection ...\n";
+ close ($self->{in});
+ close ($self->{out});
+ print "$self->{tag}: closed.\n";
+}
+
+sub DESTROY {
+ my $self = shift;
+
+ if ($self->{pid}) {
+ print "$self->{tag}: waiting child to terminate ...\n";
+ eval {
+ local($SIG{ALRM}) = sub { die "alarm\n"; };
+ alarm 10;
+ waitpid $self->{pid}, 0;
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ local($SIG{CHLD}) = 'IGNORE';
+ print "$self->{tag}: killing child ...\n";
+ kill 'INT' => $self->{pid};
+ print "$self->{tag}: killed.\n";
+ } else {
+ print "$self->{tag}: terminated.\n";
+ }
+ }
+}
+
+sub wait_for_ready {
+ my $self = shift;
+
+ while (readline($self->{in})) {
+ chomp;
+ print "$self->{tag} [$_]\n";
+ last if /^READY/;
+ }
+}
+
+sub wait_for_block {
+ my $self = shift;
+ my $fh = $self->{in};
+ my $b = '';
+ my $state = 0;
+
+ while (<$fh>) {
+ chomp();
+ my $l = $_;
+ if ($state == 0) {
+ if ($l =~ /^{$/) {
+ $state = 1;
+ } else {
+ print "$self->{tag} [$l]\n";
+ }
+ }
+
+ if ($state == 1) {
+ $b .= $l;
+
+ if ($l =~ /^}$/) {
+ $state = 0;
+ return $b;
+ }
+ }
+ }
+}
+
+sub wait_for_spm {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /SPM$/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for spm.\n";
+ }
+
+ return $obj;
+}
+
+sub wait_for_spmr {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /SPMR/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for spmr.\n";
+ }
+
+ return $obj;
+}
+
+sub die_on_spmr {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /SPMR/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ return $obj;
+ }
+
+ confess "$self->{tag}: spmr received during blackout.\n";
+}
+
+# data to {app}
+sub wait_for_data {
+ my $self = shift;
+ my $fh = $self->{in};
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $data = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ while (<$fh>) {
+ chomp;
+ if (/^DATA: (.+)$/) {
+ $data = $1;
+ last;
+ }
+ print "$self->{tag} [$_]\n";
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for data.\n";
+ }
+
+ return $data;
+}
+
+sub wait_for_odata {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /ODATA/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for odata.\n";
+ }
+
+ return $obj;
+}
+
+sub wait_for_rdata {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /RDATA/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for rdata.\n";
+ }
+
+ return $obj;
+}
+
+sub die_on_nak {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /NAK/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ return $obj;
+ }
+
+ confess "$self->{tag}: nak received during blackout.\n";
+}
+
+sub wait_for_nak {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /NAK/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for nak.\n";
+ }
+
+ return $obj;
+}
+
+sub wait_for_ncf {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $obj = undef;
+
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n"; };
+ alarm $timeout;
+ for (;;) {
+ my $block = $self->wait_for_block;
+ $obj = $json->jsonToObj($block);
+ last if ($obj->{PGM}->{type} =~ /NCF/);
+ }
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised waiting for ncf.\n";
+ }
+
+ return $obj;
+}
+
+sub print {
+ my $self = shift;
+ my $timeout = ref($_[0]) ? $_[0]->{'timeout'} : 10;
+ my $out = $self->{out};
+
+ print "$self->{tag}> @_";
+ eval {
+ local($SIG{ALRM}) = sub { die "alarm\n"; };
+ alarm $timeout;
+ print $out "@_";
+ $self->wait_for_ready;
+ alarm 0;
+ };
+ if ($@) {
+ die unless $@ eq "alarm\n";
+ confess "$self->{tag}: alarm raised.\n";
+ }
+}
+
+sub say {
+ my $self = shift;
+ $self->print ("@_\n");
+}
+
+1;
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/SConscript b/3rdparty/openpgm-svn-r1135/pgm/test/SConscript
new file mode 100644
index 0000000..7ca9926
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/SConscript
@@ -0,0 +1,15 @@
+# -*- mode: python -*-
+# OpenPGM build script
+# $Id$
+
+Import('env')
+e = env.Clone();
+e.MergeFlags(env['GLIB_FLAGS']);
+e.Append(LIBS = ['libpgm', 'libpgmex']);
+e.Append(CCFLAGS = '-DGETTEXT_PACKAGE=\'"pgm"\'');
+
+e.Program(['monitor.c', 'dump-json.c'])
+e.Program(['app.c', 'async.c'])
+e.Program(['sim.c', 'dump-json.c', 'async.c'])
+
+# end of file
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/ambient_spm.pl b/3rdparty/openpgm-svn-r1135/pgm/test/ambient_spm.pl
new file mode 100755
index 0000000..90de632
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/ambient_spm.pl
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+# ambient_spm.pl
+# 5.1.4. Ambient SPMs
+
+use strict;
+use PGM::Test;
+use IO::Handle;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!";
+FROM_PARENT->autoflush(1);
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ close FROM_PARENT; close TO_CHILD;
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+if (my $pid = fork) {
+# parent
+ close FROM_PARENT;
+
+ print "mon: wait for odata ...\n";
+ $mon->wait_for_odata;
+ print "mon: odata received.\n";
+ print "mon: wait for spm ...\n";
+ $mon->wait_for_spm ({ 'timeout' => 45 });
+ print "mon: received spm.\n";
+
+ print TO_CHILD "die\n";
+
+ close TO_CHILD;
+ waitpid($pid,0);
+} else {
+# child
+ die "cannot fork: $!" unless defined $pid;
+ close TO_CHILD;
+ print "app: loop sending data.\n";
+ vec(my $rin, fileno(FROM_PARENT), 1) = 1;
+ my $rout = undef;
+
+# hide stdout
+ open(OLDOUT, ">&STDOUT");
+ open(STDOUT, ">/dev/null") or die "Can't redirect stdout: $!";
+
+# send every ~50ms
+ while (! select($rout = $rin, undef, undef, 0.05))
+ {
+ $app->say ("send ao ringo");
+ }
+
+# restore stdout
+ close(STDOUT) or die "Can't close STDOUT: $!";
+ open(STDOUT, ">&OLDOUT") or die "Can't restore stdout: $!";
+ close(OLDOUT) or die "Can't close OLDOUT: $!";
+
+ print "app: loop finished.\n";
+ close FROM_PARENT;
+ exit;
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/apdu.pl b/3rdparty/openpgm-svn-r1135/pgm/test/apdu.pl
new file mode 100755
index 0000000..b26a3f2
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/apdu.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+# apdu.pl
+# 6.1. Data Reception
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("set network $config{app}{network}");
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("set network $config{sim}{network}");
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish APDU.\n";
+$sim->say ("send ao ringo x 1000");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+my $ref_data = "ringo" x 1000;
+die "incoming data corrupt\n" unless ($data == $ref_data);
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/apdu_parity.pl b/3rdparty/openpgm-svn-r1135/pgm/test/apdu_parity.pl
new file mode 100755
index 0000000..eb4cf27
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/apdu_parity.pl
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+# apdu_parity.pl
+# 6.1. Data Reception
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$app->say ("create ao");
+##$app->say ("set ao FEC RS(255,4)");
+$app->say ("bind ao");
+$app->say ("listen ao");
+
+$sim->say ("create ao");
+$sim->say ("set ao FEC RS(255,4)");
+$sim->say ("bind ao");
+
+print "sim: publish APDU.\n";
+$sim->say ("send brokn ao ringo x 1200");
+
+print "sim: insert parity NAK from app.\n";
+
+#print "sim: wait for NAK.\n";
+#my $nak = $sim->wait_for_nak;
+#die "Selective NAK received, parityPacket=false\n" unless $nak->{PGM}->{options}->{parityPacket};
+#print "Parity NAK received.\n";
+
+print "sim: insert parity RDATA from sim.\n";
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+my $ref_data = "ringo" x 1200;
+die "incoming data corrupt\n" unless ($data == $ref_data);
+
+print "test completed successfully.\n";
+
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/app.c b/3rdparty/openpgm-svn-r1135/pgm/test/app.c
new file mode 100644
index 0000000..ea6a2b7
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/app.c
@@ -0,0 +1,1068 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * PGM conformance test application.
+ *
+ * Copyright (c) 2006-2010 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <regex.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <glib.h>
+
+#include <pgm/pgm.h>
+#include <pgm/backtrace.h>
+#include <pgm/log.h>
+#include <pgm/gsi.h>
+#include <pgm/signal.h>
+
+#include "async.h"
+
+
+/* typedefs */
+
+struct idle_source {
+ GSource source;
+ guint64 expiration;
+};
+
+struct app_session {
+ char* name;
+ pgm_sock_t* sock;
+ pgm_async_t* async;
+};
+
+/* globals */
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "app"
+
+static int g_port = 7500;
+static const char* g_network = ";239.192.0.1";
+
+static guint g_max_tpdu = 1500;
+static guint g_sqns = 100 * 1000;
+
+static GHashTable* g_sessions = NULL;
+static GMainLoop* g_loop = NULL;
+static GIOChannel* g_stdin_channel = NULL;
+
+
+static void on_signal (int, gpointer);
+static gboolean on_startup (gpointer);
+static gboolean on_mark (gpointer);
+static void destroy_session (gpointer, gpointer, gpointer);
+static int on_data (gpointer, guint, gpointer);
+static gboolean on_stdin_data (GIOChannel*, GIOCondition, gpointer);
+
+
+G_GNUC_NORETURN static
+void
+usage (const char* bin)
+{
+ fprintf (stderr, "Usage: %s [options]\n", bin);
+ fprintf (stderr, " -n <network> : Multicast group or unicast IP address\n");
+ fprintf (stderr, " -s <port> : IP port\n");
+ exit (1);
+}
+
+int
+main (
+ int argc,
+ char *argv[]
+ )
+{
+ pgm_error_t* err = NULL;
+
+/* pre-initialise PGM messages module to add hook for GLib logging */
+ pgm_messages_init();
+ log_init ();
+ g_message ("app");
+
+ if (!pgm_init (&err)) {
+ g_error ("Unable to start PGM engine: %s", (err && err->message) ? err->message : "(null)");
+ pgm_error_free (err);
+ pgm_messages_shutdown();
+ return EXIT_FAILURE;
+ }
+
+/* parse program arguments */
+ const char* binary_name = strrchr (argv[0], '/');
+ int c;
+ while ((c = getopt (argc, argv, "s:n:h")) != -1)
+ {
+ switch (c) {
+ case 'n': g_network = optarg; break;
+ case 's': g_port = atoi (optarg); break;
+
+ case 'h':
+ case '?':
+ pgm_messages_shutdown();
+ usage (binary_name);
+ }
+ }
+
+ g_loop = g_main_loop_new (NULL, FALSE);
+
+/* setup signal handlers */
+ signal (SIGSEGV, on_sigsegv);
+ signal (SIGHUP, SIG_IGN);
+ pgm_signal_install (SIGINT, on_signal, g_loop);
+ pgm_signal_install (SIGTERM, on_signal, g_loop);
+
+/* delayed startup */
+ g_message ("scheduling startup.");
+ g_timeout_add (0, (GSourceFunc)on_startup, NULL);
+
+/* dispatch loop */
+ g_message ("entering main event loop ... ");
+ g_main_loop_run (g_loop);
+
+ g_message ("event loop terminated, cleaning up.");
+
+/* cleanup */
+ g_main_loop_unref(g_loop);
+ g_loop = NULL;
+
+ if (g_sessions) {
+ g_message ("destroying sessions.");
+ g_hash_table_foreach_remove (g_sessions, (GHRFunc)destroy_session, NULL);
+ g_hash_table_unref (g_sessions);
+ g_sessions = NULL;
+ }
+
+ if (g_stdin_channel) {
+ puts ("unbinding stdin.");
+ g_io_channel_unref (g_stdin_channel);
+ g_stdin_channel = NULL;
+ }
+
+ g_message ("PGM engine shutdown.");
+ pgm_shutdown();
+ g_message ("finished.");
+ pgm_messages_shutdown();
+ return EXIT_SUCCESS;
+}
+
+static
+void
+destroy_session (
+ gpointer key, /* session name */
+ gpointer value, /* transport_session object */
+ G_GNUC_UNUSED gpointer user_data
+ )
+{
+ struct app_session* sess = (struct app_session*)value;
+
+ g_message ("closing socket \"%s\"", (char*)key);
+ pgm_close (sess->sock, TRUE);
+ sess->sock = NULL;
+
+ if (sess->async) {
+ g_message ("destroying asynchronous session on \"%s\"", (char*)key);
+ pgm_async_destroy (sess->async);
+ sess->async = NULL;
+ }
+
+ g_free (sess->name);
+ sess->name = NULL;
+ g_free (sess);
+}
+
+static
+void
+on_signal (
+ int signum,
+ gpointer user_data
+ )
+{
+ GMainLoop* loop = (GMainLoop*)user_data;
+ g_message ("on_signal (signum:%d user-data:%p)", signum, user_data);
+ g_main_loop_quit (loop);
+}
+
+static
+gboolean
+on_startup (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ g_message ("startup.");
+
+ g_sessions = g_hash_table_new (g_str_hash, g_str_equal);
+
+/* add stdin to event manager */
+ g_stdin_channel = g_io_channel_unix_new (fileno(stdin));
+ printf ("binding stdin with encoding %s.\n", g_io_channel_get_encoding(g_stdin_channel));
+
+ g_io_add_watch (g_stdin_channel, G_IO_IN | G_IO_PRI, on_stdin_data, NULL);
+
+/* period timer to indicate some form of life */
+// TODO: Gnome 2.14: replace with g_timeout_add_seconds()
+ g_timeout_add(10 * 1000, (GSourceFunc)on_mark, NULL);
+
+ puts ("READY");
+ fflush (stdout);
+ return FALSE;
+}
+
+static
+int
+on_data (
+ gpointer data,
+ G_GNUC_UNUSED guint len,
+ G_GNUC_UNUSED gpointer user_data
+ )
+{
+ printf ("DATA: %s\n", (char*)data);
+ fflush (stdout);
+ return 0;
+}
+
+static
+void
+session_create (
+ char* session_name
+ )
+{
+ pgm_error_t* pgm_err = NULL;
+
+/* check for duplicate */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess != NULL) {
+ printf ("FAILED: duplicate session name '%s'\n", session_name);
+ return;
+ }
+
+/* create new and fill in bits */
+ sess = g_new0(struct app_session, 1);
+ sess->name = g_memdup (session_name, strlen(session_name)+1);
+
+ if (!pgm_socket (&sess->sock, AF_INET, SOCK_SEQPACKET, IPPROTO_PGM, &pgm_err)) {
+ printf ("FAILED: pgm_socket(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ goto err_free;
+ }
+
+/* success */
+ g_hash_table_insert (g_sessions, sess->name, sess);
+ printf ("created new session \"%s\"\n", sess->name);
+ puts ("READY");
+
+ return;
+
+err_free:
+ g_free(sess->name);
+ g_free(sess);
+}
+
+static
+void
+session_set_nak_bo_ivl (
+ char* session_name,
+ guint milliseconds
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (pgm_msecs (milliseconds) > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int nak_bo_ivl = pgm_msecs (milliseconds);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_BO_IVL, &nak_bo_ivl, sizeof(nak_bo_ivl)))
+ printf ("FAILED: set NAK_BO_IVL = %dms\n", milliseconds);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_nak_rpt_ivl (
+ char* session_name,
+ guint milliseconds
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (pgm_msecs (milliseconds) > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int nak_rpt_ivl = pgm_msecs (milliseconds);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RPT_IVL, &nak_rpt_ivl, sizeof(nak_rpt_ivl)))
+ printf ("FAILED: set NAK_RPT_IVL = %dms\n", milliseconds);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_nak_rdata_ivl (
+ char* session_name,
+ guint milliseconds
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (pgm_msecs (milliseconds) > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int nak_rdata_ivl = pgm_msecs (milliseconds);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RDATA_IVL, &nak_rdata_ivl, sizeof(nak_rdata_ivl)))
+ printf ("FAILED: set NAK_RDATA_IVL = %dms\n", milliseconds);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_nak_ncf_retries (
+ char* session_name,
+ guint retry_count
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (retry_count > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int nak_ncf_retries = retry_count;
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_NCF_RETRIES, &nak_ncf_retries, sizeof(nak_ncf_retries)))
+ printf ("FAILED: set NAK_NCF_RETRIES = %d\n", retry_count);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_nak_data_retries (
+ char* session_name,
+ guint retry_count
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (retry_count > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int nak_data_retries = retry_count;
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_DATA_RETRIES, &nak_data_retries, sizeof(nak_data_retries)))
+ printf ("FAILED: set NAK_DATA_RETRIES = %d\n", retry_count);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_txw_max_rte (
+ char* session_name,
+ guint bitrate
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (bitrate > INT_MAX) {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const int txw_max_rte = bitrate;
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_TXW_MAX_RTE, &txw_max_rte, sizeof(txw_max_rte)))
+ printf ("FAILED: set TXW_MAX_RTE = %d\n", bitrate);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_set_fec (
+ char* session_name,
+ guint block_size,
+ guint group_size
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (block_size > UINT8_MAX ||
+ group_size > UINT8_MAX)
+ {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const struct pgm_fecinfo_t fecinfo = {
+ .block_size = block_size,
+ .proactive_packets = 0,
+ .group_size = group_size,
+ .ondemand_parity_enabled = TRUE,
+ .var_pktlen_enabled = TRUE
+ };
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_USE_FEC, &fecinfo, sizeof(fecinfo)))
+ printf ("FAILED: set FEC = RS(%d, %d)\n", block_size, group_size);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_bind (
+ char* session_name
+ )
+{
+ pgm_error_t* pgm_err = NULL;
+
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* Use RFC 2113 tagging for PGM Router Assist */
+ const int no_router_assist = 0;
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_IP_ROUTER_ALERT, &no_router_assist, sizeof(no_router_assist)))
+ puts ("FAILED: disable IP_ROUTER_ALERT");
+
+/* set PGM parameters */
+ const int send_and_receive = 0,
+ active = 0,
+ mtu = g_max_tpdu,
+ txw_sqns = g_sqns,
+ rxw_sqns = g_sqns,
+ ambient_spm = pgm_secs (30),
+ heartbeat_spm[] = { pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (1300),
+ pgm_secs (7),
+ pgm_secs (16),
+ pgm_secs (25),
+ pgm_secs (30) },
+ peer_expiry = pgm_secs (300),
+ spmr_expiry = pgm_msecs (250),
+ nak_bo_ivl = pgm_msecs (50),
+ nak_rpt_ivl = pgm_secs (2),
+ nak_rdata_ivl = pgm_secs (2),
+ nak_data_retries = 50,
+ nak_ncf_retries = 50;
+
+ g_assert (G_N_ELEMENTS(heartbeat_spm) > 0);
+
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SEND_ONLY, &send_and_receive, sizeof(send_and_receive)))
+ puts ("FAILED: set bi-directional transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_RECV_ONLY, &send_and_receive, sizeof(send_and_receive)))
+ puts ("FAILED: set bi-directional transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_PASSIVE, &active, sizeof(active)))
+ puts ("FAILED: set active transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MTU, &mtu, sizeof(mtu)))
+ printf ("FAILED: set MAX_TPDU = %d bytes\n", mtu);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_TXW_SQNS, &txw_sqns, sizeof(txw_sqns)))
+ printf ("FAILED: set TXW_SQNS = %d\n", txw_sqns);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_RXW_SQNS, &rxw_sqns, sizeof(rxw_sqns)))
+ printf ("FAILED: set RXW_SQNS = %d\n", rxw_sqns);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_AMBIENT_SPM, &ambient_spm, sizeof(ambient_spm)))
+ printf ("FAILED: set AMBIENT_SPM = %ds\n", (int)pgm_to_secs (ambient_spm));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_HEARTBEAT_SPM, &heartbeat_spm, sizeof(heartbeat_spm)))
+ {
+ char buffer[1024];
+ sprintf (buffer, "%d", heartbeat_spm[0]);
+ for (unsigned i = 1; i < G_N_ELEMENTS(heartbeat_spm); i++) {
+ char t[1024];
+ sprintf (t, ", %d", heartbeat_spm[i]);
+ strcat (buffer, t);
+ }
+ printf ("FAILED: set HEARTBEAT_SPM = { %s }\n", buffer);
+ }
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_PEER_EXPIRY, &peer_expiry, sizeof(peer_expiry)))
+ printf ("FAILED: set PEER_EXPIRY = %ds\n",(int) pgm_to_secs (peer_expiry));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SPMR_EXPIRY, &spmr_expiry, sizeof(spmr_expiry)))
+ printf ("FAILED: set SPMR_EXPIRY = %dms\n", (int)pgm_to_msecs (spmr_expiry));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_BO_IVL, &nak_bo_ivl, sizeof(nak_bo_ivl)))
+ printf ("FAILED: set NAK_BO_IVL = %dms\n", (int)pgm_to_msecs (nak_bo_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RPT_IVL, &nak_rpt_ivl, sizeof(nak_rpt_ivl)))
+ printf ("FAILED: set NAK_RPT_IVL = %dms\n", (int)pgm_to_msecs (nak_rpt_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RDATA_IVL, &nak_rdata_ivl, sizeof(nak_rdata_ivl)))
+ printf ("FAILED: set NAK_RDATA_IVL = %dms\n", (int)pgm_to_msecs (nak_rdata_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_DATA_RETRIES, &nak_data_retries, sizeof(nak_data_retries)))
+ printf ("FAILED: set NAK_DATA_RETRIES = %d\n", nak_data_retries);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_NCF_RETRIES, &nak_ncf_retries, sizeof(nak_ncf_retries)))
+ printf ("FAILED: set NAK_NCF_RETRIES = %d\n", nak_ncf_retries);
+
+/* create global session identifier */
+ struct pgm_sockaddr_t addr;
+ memset (&addr, 0, sizeof(addr));
+ addr.sa_port = g_port;
+ addr.sa_addr.sport = 0;
+ if (!pgm_gsi_create_from_hostname (&addr.sa_addr.gsi, &pgm_err)) {
+ printf ("FAILED: pgm_gsi_create_from_hostname(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ }
+
+{
+ char buffer[1024];
+ pgm_tsi_print_r (&addr.sa_addr, buffer, sizeof(buffer));
+ printf ("pgm_bind (sock:%p addr:{port:%d tsi:%s} err:%p)\n",
+ (gpointer)sess->sock,
+ addr.sa_port, buffer,
+ (gpointer)&pgm_err);
+}
+ if (!pgm_bind (sess->sock, &addr, sizeof(addr), &pgm_err)) {
+ printf ("FAILED: pgm_bind(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ } else
+ puts ("READY");
+}
+
+static
+void
+session_connect (
+ char* session_name
+ )
+{
+ struct pgm_addrinfo_t hints = {
+ .ai_family = AF_INET
+ }, *res = NULL;
+ pgm_error_t* pgm_err = NULL;
+
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (!pgm_getaddrinfo (g_network, &hints, &res, &pgm_err)) {
+ printf ("FAILED: pgm_getaddrinfo(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ return;
+ }
+
+/* join IP multicast groups */
+ for (unsigned i = 0; i < res->ai_recv_addrs_len; i++)
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_JOIN_GROUP, &res->ai_recv_addrs[i], sizeof(struct group_req)))
+ {
+ char group[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&res->ai_recv_addrs[i].gsr_group, sizeof(struct sockaddr_in),
+ group, sizeof(group),
+ NULL, 0,
+ NI_NUMERICHOST);
+ printf ("FAILED: join group (#%u %s)\n", (unsigned)res->ai_recv_addrs[i].gsr_interface, group);
+ }
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SEND_GROUP, &res->ai_send_addrs[0], sizeof(struct group_req)))
+ {
+ char group[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&res->ai_send_addrs[0].gsr_group, sizeof(struct sockaddr_in),
+ group, sizeof(group),
+ NULL, 0,
+ NI_NUMERICHOST);
+ printf ("FAILED: send group (#%u %s)\n", (unsigned)res->ai_send_addrs[0].gsr_interface, group);
+ }
+ pgm_freeaddrinfo (res);
+
+/* set IP parameters */
+ const int non_blocking = 1,
+ no_multicast_loop = 0,
+ multicast_hops = 16,
+ dscp = 0x2e << 2; /* Expedited Forwarding PHB for network elements, no ECN. */
+
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MULTICAST_LOOP, &no_multicast_loop, sizeof(no_multicast_loop)))
+ puts ("FAILED: disable multicast loop");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MULTICAST_HOPS, &multicast_hops, sizeof(multicast_hops)))
+ printf ("FAILED: set TTL = %d\n", multicast_hops);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_TOS, &dscp, sizeof(dscp)))
+ printf ("FAILED: set TOS = 0x%x\n", dscp);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NOBLOCK, &non_blocking, sizeof(non_blocking)))
+ puts ("FAILED: set non-blocking sockets");
+
+ if (!pgm_connect (sess->sock, &pgm_err)) {
+ printf ("FAILED: pgm_connect(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ } else
+ puts ("READY");
+}
+
+static
+void
+session_send (
+ char* session_name,
+ char* string
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* send message */
+ int status;
+ gsize stringlen = strlen(string) + 1;
+ int n_fds = 1;
+ struct pollfd fds[ n_fds ];
+ struct timeval tv;
+ int timeout;
+again:
+printf ("pgm_send (sock:%p string:\"%s\" stringlen:%" G_GSIZE_FORMAT " NULL)\n", (gpointer)sess->sock, string, stringlen);
+ status = pgm_send (sess->sock, string, stringlen, NULL);
+ switch (status) {
+ case PGM_IO_STATUS_NORMAL:
+ puts ("READY");
+ break;
+ case PGM_IO_STATUS_TIMER_PENDING:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (sess->sock, IPPROTO_PGM, PGM_TIME_REMAIN, &tv, &optlen);
+ }
+ goto block;
+ case PGM_IO_STATUS_RATE_LIMITED:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (sess->sock, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &optlen);
+ }
+/* fall through */
+ case PGM_IO_STATUS_WOULD_BLOCK:
+block:
+ timeout = PGM_IO_STATUS_WOULD_BLOCK == status ? -1 : ((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
+ memset (fds, 0, sizeof(fds));
+ pgm_poll_info (sess->sock, fds, &n_fds, POLLOUT);
+ poll (fds, n_fds, timeout /* ms */);
+ goto again;
+ default:
+ puts ("FAILED: pgm_send()");
+ break;
+ }
+}
+
+static
+void
+session_listen (
+ char* session_name
+ )
+{
+ GError* err = NULL;
+
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* listen */
+printf ("pgm_async_create (async:%p sock:%p err:%p)\n", (gpointer)&sess->async, (gpointer)sess->sock, (gpointer)&err);
+ if (!pgm_async_create (&sess->async, sess->sock, &err)) {
+ printf ("FAILED: pgm_async_create(): %s", err->message);
+ g_error_free (err);
+ return;
+ }
+ pgm_async_add_watch (sess->async, on_data, sess);
+ puts ("READY");
+}
+
+static
+void
+session_destroy (
+ char* session_name
+ )
+{
+/* check that session exists */
+ struct app_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* remove from hash table */
+ g_hash_table_remove (g_sessions, session_name);
+
+/* stop any async thread */
+ if (sess->async) {
+ pgm_async_destroy (sess->async);
+ sess->async = NULL;
+ }
+
+ pgm_close (sess->sock, TRUE);
+ sess->sock = NULL;
+ g_free (sess->name);
+ sess->name = NULL;
+ g_free (sess);
+
+ puts ("READY");
+}
+
+/* process input commands from stdin/fd
+ */
+
+static
+gboolean
+on_stdin_data (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ gchar* str = NULL;
+ gsize len = 0;
+ gsize term = 0;
+ GError* err = NULL;
+
+ g_io_channel_read_line (source, &str, &len, &term, &err);
+ if (len > 0) {
+ if (term) str[term] = 0;
+
+/* quit */
+ if (strcmp(str, "quit") == 0)
+ {
+ g_main_loop_quit(g_loop);
+ goto out;
+ }
+
+ regex_t preg;
+ regmatch_t pmatch[10];
+
+/* create socket */
+ const char *re = "^create[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_create (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set NAK_BO_IVL */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+NAK_BO_IVL[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint nak_bo_ivl = strtol (p, &p, 10);
+
+ session_set_nak_bo_ivl (name, nak_bo_ivl);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set NAK_RPT_IVL */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+NAK_RPT_IVL[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint nak_rpt_ivl = strtol (p, &p, 10);
+
+ session_set_nak_rpt_ivl (name, nak_rpt_ivl);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set NAK_RDATA_IVL */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+NAK_RDATA_IVL[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint nak_rdata_ivl = strtol (p, &p, 10);
+
+ session_set_nak_rdata_ivl (name, nak_rdata_ivl);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set NAK_NCF_RETRIES */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+NAK_NCF_RETRIES[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint nak_ncf_retries = strtol (p, &p, 10);
+
+ session_set_nak_ncf_retries (name, nak_ncf_retries);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set NAK_DATA_RETRIES */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+NAK_DATA_RETRIES[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint nak_data_retries = strtol (p, &p, 10);
+
+ session_set_nak_data_retries (name, nak_data_retries);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set TXW_MAX_RTE */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+TXW_MAX_RTE[[:space:]]+([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ guint txw_max_rte = strtol (p, &p, 10);
+
+ session_set_txw_max_rte (name, txw_max_rte);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* enable Reed-Solomon Forward Error Correction */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+FEC[[:space:]]+RS[[:space:]]*\\([[:space:]]*([0-9]+)[[:space:]]*,[[:space:]]*([0-9]+)[[:space:]]*\\)$";
+ regcomp (&preg, re, REG_EXTENDED);
+
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ *(str + pmatch[2].rm_eo) = 0;
+ guint n = strtol (p, &p, 10);
+ p = str + pmatch[3].rm_so;
+ *(str + pmatch[3].rm_eo) = 0;
+ guint k = strtol (p, &p, 10);
+ session_set_fec (name, n, k);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* bind socket */
+ re = "^bind[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_bind (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* connect socket */
+ re = "^connect[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_connect (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send packet */
+ re = "^send[[:space:]]+([[:alnum:]]+)[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *string = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ string[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ session_send (name, string);
+
+ g_free (name);
+ g_free (string);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* listen */
+ re = "^listen[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_listen (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* destroy transport */
+ re = "^destroy[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_destroy (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set PGM network */
+ re = "^set[[:space:]]+network[[:space:]]+([[:print:]]*;[[:print:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char* pgm_network = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ pgm_network[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+ g_network = pgm_network;
+ puts ("READY");
+
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+ printf ("unknown command: %s\n", str);
+ }
+
+out:
+ fflush (stdout);
+ g_free (str);
+ return TRUE;
+}
+
+/* idle log notification
+ */
+
+static
+gboolean
+on_mark (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ g_message ("-- MARK --");
+ return TRUE;
+}
+
+/* eof */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/async.c b/3rdparty/openpgm-svn-r1135/pgm/test/async.c
new file mode 100644
index 0000000..3be1458
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/async.c
@@ -0,0 +1,579 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * Asynchronous queue for receiving packets in a separate managed thread.
+ *
+ * Copyright (c) 2006-2010 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <pgm/pgm.h>
+#include "async.h"
+
+
+//#define ASYNC_DEBUG
+
+#ifndef ASYNC_DEBUG
+# define g_trace(...) while (0)
+#else
+#include <ctype.h>
+# define g_trace(...) g_debug(__VA_ARGS__)
+#endif
+
+
+/* globals */
+
+
+/* global locals */
+
+typedef struct pgm_event_t pgm_event_t;
+
+struct pgm_event_t {
+ gpointer data;
+ guint len;
+};
+
+
+/* external: Glib event loop GSource of pgm contiguous data */
+struct pgm_watch_t {
+ GSource source;
+ GPollFD pollfd;
+ pgm_async_t* async;
+};
+
+typedef struct pgm_watch_t pgm_watch_t;
+
+
+static gboolean pgm_src_prepare (GSource*, gint*);
+static gboolean pgm_src_check (GSource*);
+static gboolean pgm_src_dispatch (GSource*, GSourceFunc, gpointer);
+
+static GSourceFuncs g_pgm_watch_funcs = {
+ .prepare = pgm_src_prepare,
+ .check = pgm_src_check,
+ .dispatch = pgm_src_dispatch,
+ .finalize = NULL,
+ .closure_callback = NULL
+};
+
+
+static inline gpointer pgm_event_alloc (pgm_async_t* const) G_GNUC_MALLOC;
+static PGMAsyncError pgm_async_error_from_errno (const gint);
+
+
+static inline
+gpointer
+pgm_event_alloc (
+ pgm_async_t* const async
+ )
+{
+ g_return_val_if_fail (async != NULL, NULL);
+ return g_slice_alloc (sizeof(pgm_event_t));
+}
+
+/* release event memory for custom async queue dispatch handlers
+ */
+
+static inline
+void
+pgm_event_unref (
+ pgm_async_t* const async,
+ pgm_event_t* const event
+ )
+{
+ g_return_if_fail (async != NULL);
+ g_return_if_fail (event != NULL);
+ g_slice_free1 (sizeof(pgm_event_t), event);
+}
+
+/* internal receiver thread, sits in a loop processing incoming packets
+ */
+
+static
+gpointer
+pgm_receiver_thread (
+ gpointer data
+ )
+{
+ g_assert (NULL != data);
+
+ pgm_async_t* async = (pgm_async_t*)data;
+ g_async_queue_ref (async->commit_queue);
+
+/* incoming message buffer */
+ struct pgm_msgv_t msgv;
+ gsize bytes_read = 0;
+ struct timeval tv;
+
+ do {
+/* blocking read */
+ const int status = pgm_recvmsg (async->sock, &msgv, 0, &bytes_read, NULL);
+ switch (status) {
+ case PGM_IO_STATUS_NORMAL:
+ {
+/* queue a copy to receiver */
+ pgm_event_t* event = pgm_event_alloc (async);
+ event->data = bytes_read > 0 ? g_malloc (bytes_read) : NULL;
+ event->len = bytes_read;
+ gpointer dst = event->data;
+ guint i = 0;
+ while (bytes_read) {
+ const struct pgm_sk_buff_t* skb = msgv.msgv_skb[i++];
+ g_assert (NULL != skb);
+ g_assert (skb->len > 0);
+ g_assert (skb->len <= bytes_read);
+ memcpy (dst, skb->data, skb->len);
+ dst = (char*)dst + skb->len;
+ bytes_read -= skb->len;
+ }
+/* prod pipe on edge */
+ g_async_queue_lock (async->commit_queue);
+ g_async_queue_push_unlocked (async->commit_queue, event);
+ if (g_async_queue_length_unlocked (async->commit_queue) == 1)
+ pgm_notify_send (&async->commit_notify);
+ g_async_queue_unlock (async->commit_queue);
+ break;
+ }
+
+ case PGM_IO_STATUS_TIMER_PENDING:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (async->sock, IPPROTO_PGM, PGM_TIME_REMAIN, &tv, &optlen);
+ }
+ goto block;
+
+ case PGM_IO_STATUS_RATE_LIMITED:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (async->sock, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &optlen);
+ }
+/* fall through */
+ case PGM_IO_STATUS_WOULD_BLOCK:
+block:
+ {
+#ifdef CONFIG_HAVE_POLL
+ const int timeout = PGM_IO_STATUS_WOULD_BLOCK == status ? -1 : ((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
+ int n_fds = 3;
+ struct pollfd fds[1+n_fds];
+ memset (fds, 0, sizeof(fds));
+ fds[0].fd = pgm_notify_get_fd (&async->destroy_notify);
+ fds[0].events = POLLIN;
+ if (-1 == pgm_poll_info (async->sock, &fds[1], &n_fds, POLLIN)) {
+ g_trace ("poll_info returned errno=%i",errno);
+ goto cleanup;
+ }
+ const int ready = poll (fds, 1 + n_fds, timeout);
+#else /* HAVE_SELECT */
+ fd_set readfds;
+ int fd = pgm_notify_get_fd (&async->destroy_notify), n_fds = 1 + fd;
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+ if (-1 == pgm_select_info (async->sock, &readfds, NULL, &n_fds)) {
+ g_trace ("select_info returned errno=%i",errno);
+ goto cleanup;
+ }
+ const int ready = select (n_fds, &readfds, NULL, NULL, PGM_IO_STATUS_RATE_LIMITED == status ? &tv : NULL);
+#endif
+ if (-1 == ready) {
+ g_trace ("block returned errno=%i",errno);
+ goto cleanup;
+ }
+#ifdef CONFIG_HAVE_POLL
+ if (ready > 0 && fds[0].revents)
+#else
+ if (ready > 0 && FD_ISSET(fd, &readfds))
+#endif
+ goto cleanup;
+ break;
+ }
+
+ case PGM_IO_STATUS_ERROR:
+ case PGM_IO_STATUS_EOF:
+ goto cleanup;
+
+ case PGM_IO_STATUS_RESET:
+ {
+ int is_abort_on_reset;
+ socklen_t optlen = sizeof (is_abort_on_reset);
+ pgm_getsockopt (async->sock, IPPROTO_PGM, PGM_ABORT_ON_RESET, &is_abort_on_reset, &optlen);
+ if (is_abort_on_reset)
+ goto cleanup;
+ break;
+ }
+
+/* TODO: report to user */
+ case PGM_IO_STATUS_FIN:
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+ } while (!async->is_destroyed);
+
+cleanup:
+ g_async_queue_unref (async->commit_queue);
+ return NULL;
+}
+
+/* create asynchronous thread handler
+ *
+ * on success, 0 is returned. on error, -1 is returned, and errno set appropriately.
+ * on invalid parameters, -EINVAL is returned.
+ */
+
+gboolean
+pgm_async_create (
+ pgm_async_t** async,
+ pgm_sock_t* const sock,
+ GError** error
+ )
+{
+ pgm_async_t* new_async;
+
+ g_return_val_if_fail (NULL != async, FALSE);
+ g_return_val_if_fail (NULL != sock, FALSE);
+
+ g_trace ("create (async:%p sock:%p error:%p)",
+ (gpointer)async, (gpointer)sock, (gpointer)error);
+
+ if (!g_thread_supported())
+ g_thread_init (NULL);
+
+ new_async = g_new0 (pgm_async_t, 1);
+ new_async->sock = sock;
+ if (0 != pgm_notify_init (&new_async->commit_notify) ||
+ 0 != pgm_notify_init (&new_async->destroy_notify))
+ {
+ g_set_error (error,
+ PGM_ASYNC_ERROR,
+ pgm_async_error_from_errno (errno),
+ _("Creating async notification channels: %s"),
+ g_strerror (errno));
+ g_free (new_async);
+ return FALSE;
+ }
+ new_async->commit_queue = g_async_queue_new();
+/* setup new thread */
+ new_async->thread = g_thread_create_full (pgm_receiver_thread,
+ new_async,
+ 0,
+ TRUE,
+ TRUE,
+ G_THREAD_PRIORITY_HIGH,
+ error);
+ if (NULL == new_async->thread) {
+ g_async_queue_unref (new_async->commit_queue);
+ pgm_notify_destroy (&new_async->commit_notify);
+ g_free (new_async);
+ return FALSE;
+ }
+
+/* return new object */
+ *async = new_async;
+ return TRUE;
+}
+
+/* tell async thread to stop, wait for it to stop, then cleanup.
+ *
+ * on success, 0 is returned. if async is invalid, -EINVAL is returned.
+ */
+
+gboolean
+pgm_async_destroy (
+ pgm_async_t* const async
+ )
+{
+ g_return_val_if_fail (NULL != async, FALSE);
+ g_return_val_if_fail (!async->is_destroyed, FALSE);
+
+ async->is_destroyed = TRUE;
+ pgm_notify_send (&async->destroy_notify);
+ if (async->thread)
+ g_thread_join (async->thread);
+ if (async->commit_queue) {
+ g_async_queue_unref (async->commit_queue);
+ async->commit_queue = NULL;
+ }
+ pgm_notify_destroy (&async->destroy_notify);
+ pgm_notify_destroy (&async->commit_notify);
+ g_free (async);
+ return TRUE;
+}
+
+/* queue to GSource and GMainLoop */
+
+GSource*
+pgm_async_create_watch (
+ pgm_async_t* async
+ )
+{
+ g_return_val_if_fail (async != NULL, NULL);
+
+ GSource *source = g_source_new (&g_pgm_watch_funcs, sizeof(pgm_watch_t));
+ pgm_watch_t *watch = (pgm_watch_t*)source;
+
+ watch->async = async;
+ watch->pollfd.fd = pgm_async_get_fd (async);
+ watch->pollfd.events = G_IO_IN;
+
+ g_source_add_poll (source, &watch->pollfd);
+
+ return source;
+}
+
+/* pgm transport attaches to the callees context: the default context instead of
+ * any internal contexts.
+ */
+
+int
+pgm_async_add_watch_full (
+ pgm_async_t* async,
+ gint priority,
+ pgm_eventfn_t function,
+ gpointer user_data,
+ GDestroyNotify notify
+ )
+{
+ g_return_val_if_fail (async != NULL, -EINVAL);
+ g_return_val_if_fail (function != NULL, -EINVAL);
+
+ GSource* source = pgm_async_create_watch (async);
+
+ if (priority != G_PRIORITY_DEFAULT)
+ g_source_set_priority (source, priority);
+
+ g_source_set_callback (source, (GSourceFunc)function, user_data, notify);
+
+ guint id = g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ return id;
+}
+
+int
+pgm_async_add_watch (
+ pgm_async_t* async,
+ pgm_eventfn_t function,
+ gpointer user_data
+ )
+{
+ return pgm_async_add_watch_full (async, G_PRIORITY_HIGH, function, user_data, NULL);
+}
+
+/* returns TRUE if source has data ready, i.e. async queue is not empty
+ *
+ * called before event loop poll()
+ */
+
+static
+gboolean
+pgm_src_prepare (
+ GSource* source,
+ gint* timeout
+ )
+{
+ pgm_watch_t* watch = (pgm_watch_t*)source;
+
+/* infinite timeout */
+ *timeout = -1;
+
+ return ( g_async_queue_length(watch->async->commit_queue) > 0 );
+}
+
+/* called after event loop poll()
+ *
+ * return TRUE if ready to dispatch.
+ */
+
+static
+gboolean
+pgm_src_check (
+ GSource* source
+ )
+{
+ pgm_watch_t* watch = (pgm_watch_t*)source;
+
+ return ( g_async_queue_length(watch->async->commit_queue) > 0 );
+}
+
+/* called when TRUE returned from prepare or check
+ */
+
+static gboolean
+pgm_src_dispatch (
+ GSource* source,
+ GSourceFunc callback,
+ gpointer user_data
+ )
+{
+ g_trace ("pgm_src_dispatch (source:%p callback:() user-data:%p)",
+ (gpointer)source, user_data);
+
+ const pgm_eventfn_t function = (pgm_eventfn_t)callback;
+ pgm_watch_t* watch = (pgm_watch_t*)source;
+ pgm_async_t* async = watch->async;
+
+/* empty pipe */
+ pgm_notify_read (&async->commit_notify);
+
+/* purge only one message from the asynchronous queue */
+ pgm_event_t* event = g_async_queue_try_pop (async->commit_queue);
+ if (event)
+ {
+/* important that callback occurs out of lock to allow PGM layer to add more messages */
+ (*function) (event->data, event->len, user_data);
+
+/* return memory to receive window */
+ if (event->len) g_free (event->data);
+ pgm_event_unref (async, event);
+ }
+
+ return TRUE;
+}
+
+/* synchronous reading from the queue.
+ *
+ * returns GIOStatus with success, error, again, or eof.
+ */
+
+GIOStatus
+pgm_async_recv (
+ pgm_async_t* const async,
+ gpointer data,
+ const gsize len,
+ gsize* const bytes_read,
+ const int flags, /* MSG_DONTWAIT for non-blocking */
+ GError** error
+ )
+{
+ g_return_val_if_fail (NULL != async, G_IO_STATUS_ERROR);
+ if (len) g_return_val_if_fail (NULL != data, G_IO_STATUS_ERROR);
+
+ g_trace ("pgm_async_recv (async:%p data:%p len:%" G_GSIZE_FORMAT" bytes-read:%p flags:%d error:%p)",
+ (gpointer)async, data, len, (gpointer)bytes_read, flags, (gpointer)error);
+
+ pgm_event_t* event = NULL;
+ g_async_queue_lock (async->commit_queue);
+ if (g_async_queue_length_unlocked (async->commit_queue) == 0)
+ {
+ g_async_queue_unlock (async->commit_queue);
+ if (flags & MSG_DONTWAIT || async->is_nonblocking)
+ return G_IO_STATUS_AGAIN;
+#ifdef CONFIG_HAVE_POLL
+ struct pollfd fds[1];
+ int ready;
+ do {
+ memset (fds, 0, sizeof(fds));
+ fds[0].fd = pgm_notify_get_fd (&async->commit_notify);
+ fds[0].events = POLLIN;
+ ready = poll (fds, G_N_ELEMENTS(fds), -1);
+ if (-1 == ready || async->is_destroyed) /* errno = EINTR */
+ return G_IO_STATUS_ERROR;
+ } while (ready <= 0);
+#else
+ fd_set readfds;
+ int n_fds, ready, fd = pgm_notify_get_fd (&async->commit_notify);
+ do {
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+ n_fds = fd + 1;
+ ready = select (n_fds, &readfds, NULL, NULL, NULL);
+ if (-1 == ready || async->is_destroyed) /* errno = EINTR */
+ return G_IO_STATUS_ERROR;
+ } while (ready <= 0);
+#endif
+ pgm_notify_read (&async->commit_notify);
+ g_async_queue_lock (async->commit_queue);
+ }
+ event = g_async_queue_pop_unlocked (async->commit_queue);
+ g_async_queue_unlock (async->commit_queue);
+
+/* pass data back to callee */
+ if (event->len > len) {
+ *bytes_read = len;
+ memcpy (data, event->data, *bytes_read);
+ g_set_error (error,
+ PGM_ASYNC_ERROR,
+ PGM_ASYNC_ERROR_OVERFLOW,
+ _("Message too large to be stored in buffer."));
+ pgm_event_unref (async, event);
+ return G_IO_STATUS_ERROR;
+ }
+
+ if (bytes_read)
+ *bytes_read = event->len;
+ memcpy (data, event->data, event->len);
+
+/* cleanup */
+ if (event->len) g_free (event->data);
+ pgm_event_unref (async, event);
+ return G_IO_STATUS_NORMAL;
+}
+
+gboolean
+pgm_async_set_nonblocking (
+ pgm_async_t* const async,
+ const gboolean nonblocking
+ )
+{
+ g_return_val_if_fail (NULL != async, FALSE);
+ async->is_nonblocking = nonblocking;
+ return TRUE;
+}
+
+GQuark
+pgm_async_error_quark (void)
+{
+ return g_quark_from_static_string ("pgm-async-error-quark");
+}
+
+static
+PGMAsyncError
+pgm_async_error_from_errno (
+ const gint err_no
+ )
+{
+ switch (err_no) {
+#ifdef EFAULT
+ case EFAULT:
+ return PGM_ASYNC_ERROR_FAULT;
+ break;
+#endif
+
+#ifdef EMFILE
+ case EMFILE:
+ return PGM_ASYNC_ERROR_MFILE;
+ break;
+#endif
+
+#ifdef ENFILE
+ case ENFILE:
+ return PGM_ASYNC_ERROR_NFILE;
+ break;
+#endif
+
+ default :
+ return PGM_ASYNC_ERROR_FAILED;
+ break;
+ }
+}
+
+/* eof */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/async.h b/3rdparty/openpgm-svn-r1135/pgm/test/async.h
new file mode 100644
index 0000000..d801abd
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/async.h
@@ -0,0 +1,76 @@
+/* vim:ts=8:sts=4:sw=4:noai:noexpandtab
+ *
+ * Asynchronous receive thread helper
+ *
+ * Copyright (c) 2006-2009 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __PGM_ASYNC_H__
+#define __PGM_ASYNC_H__
+
+#include <errno.h>
+#include <glib.h>
+#include <impl/framework.h>
+
+
+#define PGM_ASYNC_ERROR pgm_async_error_quark ()
+
+typedef enum
+{
+ /* Derived from errno */
+ PGM_ASYNC_ERROR_FAULT,
+ PGM_ASYNC_ERROR_MFILE,
+ PGM_ASYNC_ERROR_NFILE,
+ PGM_ASYNC_ERROR_OVERFLOW,
+ PGM_ASYNC_ERROR_FAILED
+} PGMAsyncError;
+
+typedef struct pgm_async_t pgm_async_t;
+
+struct pgm_async_t {
+ pgm_sock_t* sock;
+ GThread* thread;
+ GAsyncQueue* commit_queue;
+ pgm_notify_t commit_notify;
+ pgm_notify_t destroy_notify;
+ gboolean is_destroyed;
+ gboolean is_nonblocking;
+};
+
+typedef int (*pgm_eventfn_t)(gpointer, guint, gpointer);
+
+
+G_BEGIN_DECLS
+
+int pgm_async_create (pgm_async_t**, pgm_sock_t* const, GError**);
+int pgm_async_destroy (pgm_async_t* const);
+GIOStatus pgm_async_recv (pgm_async_t* const, gpointer, const gsize, gsize* const, const int, GError**);
+gboolean pgm_async_set_nonblocking (pgm_async_t* const, const gboolean);
+GSource* pgm_async_create_watch (pgm_async_t* const) G_GNUC_WARN_UNUSED_RESULT;
+int pgm_async_add_watch_full (pgm_async_t*, gint, pgm_eventfn_t, gpointer, GDestroyNotify);
+int pgm_async_add_watch (pgm_async_t*, pgm_eventfn_t, gpointer);
+GQuark pgm_async_error_quark (void);
+
+static inline int pgm_async_get_fd (pgm_async_t* async)
+{
+ g_return_val_if_fail (async != NULL, -EINVAL);
+ return pgm_notify_get_fd (&async->commit_notify);
+}
+
+G_END_DECLS
+
+#endif /* __PGM_ASYNC_H__ */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.c b/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.c
new file mode 100644
index 0000000..adbc217
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.c
@@ -0,0 +1,1292 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * JSON packet dump.
+ *
+ * Copyright (c) 2006-2008 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <glib.h>
+
+#include <impl/framework.h>
+#include <impl/packet_test.h>
+
+#include "dump-json.h"
+
+
+/* globals */
+
+#define OPTIONS_TOTAL_LEN(x) *(guint16*)( ((char*)(x)) + sizeof(guint16) )
+
+
+int verify_ip_header (struct pgm_ip*, guint);
+void print_ip_header (struct pgm_ip*);
+int verify_pgm_header (struct pgm_header*, guint);
+void print_pgm_header (struct pgm_header*);
+int verify_spm (struct pgm_header*, char*, guint);
+void print_spm (struct pgm_header*, char*);
+int verify_poll (struct pgm_header*, char*, guint);
+void print_poll (struct pgm_header*, char*);
+int verify_polr (struct pgm_header*, char*, guint);
+void print_polr (struct pgm_header*, char*);
+int verify_odata (struct pgm_header*, char*, guint);
+void print_odata (struct pgm_header*, char*);
+int verify_rdata (struct pgm_header*, char*, guint);
+void print_rdata (struct pgm_header*, char*);
+static int generic_verify_nak (const char*, struct pgm_header*, char*, guint);
+static void generic_print_nak (const char*, struct pgm_header*, char*);
+int verify_nak (struct pgm_header*, char*, guint);
+void print_nak (struct pgm_header*, char*);
+int verify_nnak (struct pgm_header*, char*, guint);
+void print_nnak (struct pgm_header*, char*);
+int verify_ncf (struct pgm_header*, char*, guint);
+void print_ncf (struct pgm_header*, char*);
+int verify_spmr (struct pgm_header*, char*, guint);
+void print_spmr (struct pgm_header*, char*);
+int verify_options (char*, guint);
+void print_options (char*);
+
+
+int
+monitor_packet (
+ char* data,
+ guint len
+ )
+{
+ static int count = 0;
+
+ puts ("{");
+ printf ("\t\"id\": %i,\n", ++count);
+
+ int retval = 0;
+
+ struct pgm_ip* ip = (struct pgm_ip*)data;
+ if (verify_ip_header (ip, len) < 0) {
+ puts ("\t\"valid\": false");
+ retval = -1;
+ goto out;
+ }
+
+ struct pgm_header* pgm = (struct pgm_header*)(data + (ip->ip_hl * 4));
+ guint pgm_len = len - (ip->ip_hl * 4);
+ if (verify_pgm_header (pgm, pgm_len) < 0) {
+ puts ("\t\"valid\": false");
+ retval = -1;
+ goto out;
+ }
+
+ char* pgm_data = (char*)(pgm + 1);
+ guint pgm_data_len = pgm_len - sizeof(struct pgm_header);
+ switch (pgm->pgm_type) {
+ case PGM_SPM: retval = verify_spm (pgm, pgm_data, pgm_data_len); break;
+ case PGM_POLL: retval = verify_poll (pgm, pgm_data, pgm_data_len); break;
+ case PGM_POLR: retval = verify_polr (pgm, pgm_data, pgm_data_len); break;
+ case PGM_ODATA: retval = verify_odata (pgm, pgm_data, pgm_data_len); break;
+ case PGM_RDATA: retval = verify_rdata (pgm, pgm_data, pgm_data_len); break;
+ case PGM_NAK: retval = verify_nak (pgm, pgm_data, pgm_data_len); break;
+ case PGM_NNAK: retval = verify_nnak (pgm, pgm_data, pgm_data_len); break;
+ case PGM_NCF: retval = verify_ncf (pgm, pgm_data, pgm_data_len); break;
+ case PGM_SPMR: retval = verify_spmr (pgm, pgm_data, pgm_data_len); break;
+ }
+
+ if (retval < 0) {
+ puts ("\t\"valid\": false");
+ goto out;
+ }
+
+/* packet verified correct */
+ puts ("\t\"valid\": true,");
+
+ print_ip_header (ip);
+ print_pgm_header (pgm);
+
+ switch (pgm->pgm_type) {
+ case PGM_SPM: print_spm (pgm, pgm_data); break;
+ case PGM_POLL: print_poll (pgm, pgm_data); break;
+ case PGM_POLR: print_polr (pgm, pgm_data); break;
+ case PGM_ODATA: print_odata (pgm, pgm_data); break;
+ case PGM_RDATA: print_rdata (pgm, pgm_data); break;
+ case PGM_NAK: print_nak (pgm, pgm_data); break;
+ case PGM_NNAK: print_nnak (pgm, pgm_data); break;
+ case PGM_NCF: print_ncf (pgm, pgm_data); break;
+ case PGM_SPMR: print_spmr (pgm, pgm_data); break;
+ }
+
+out:
+ puts ("}");
+ return retval;
+}
+
+
+int
+verify_ip_header (
+ struct pgm_ip* ip,
+ guint len
+ )
+{
+/* minimum size should be IP header plus PGM header */
+ if (len < (sizeof(struct pgm_ip) + sizeof(struct pgm_header)))
+ {
+ printf ("\t\"message\": \"IP: packet size too small: %i bytes, expecting at least %" G_GSIZE_FORMAT " bytes.\",\n", len, sizeof(struct pgm_header));
+ return -1;
+ }
+
+/* IP packet header: IPv4
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version| HL | ToS | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Fragment ID |R|D|M| Fragment Offset |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | TTL | Protocol | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source IP Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Destination IP Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | IP Options when present ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+ ...
+ * | Data ...
+ * +-+-+- ...
+ *
+ * IPv6: n/a
+ */
+
+/* decode IP header */
+ if (ip->ip_v != 4 && ip->ip_v != 6) { /* IP version, 4 or 6 */
+ printf ("\t\"message\": \"IP: unknown IP version %i.\",\n", ip->ip_v);
+ return -1;
+ }
+
+ guint ip_header_length = ip->ip_hl * 4; /* IP header length in 32bit octets */
+ if (ip_header_length < sizeof(struct pgm_ip)) {
+ printf ("\t\"message\": \"IP: bad IP header length %i, should be at least %" G_GSIZE_FORMAT "lu bytes.\",\n", ip_header_length, sizeof(struct pgm_ip));
+ return -1;
+ }
+
+/* ip_len can equal packet_length - ip_header_length in FreeBSD/NetBSD
+ * Stevens/Fenner/Rudolph, Unix Network Programming Vol.1, p.739
+ *
+ * RFC3828 allows partial packets such that len < packet_length with UDP lite
+ */
+ guint packet_length = g_ntohs(ip->ip_len); /* total packet length */
+ if (len < packet_length) { /* redundant: often handled in kernel */
+ printf ("\t\"message\": \"IP: truncated IP packet: header reports %i actual length %i bytes.\",\n", (int)len, (int)packet_length);
+ return -1;
+ }
+
+/* TCP Segmentation Offload (TSO) might have zero length here */
+ if (packet_length < ip_header_length) {
+ printf ("\t\"message\": \"IP: header reports %i less than IP header length %i.\",\n", (int)packet_length, (int)ip_header_length);
+ return -1;
+ }
+
+/* packets that fail checksum will generally not be passed upstream except with rfc3828
+ */
+ int sum = pgm_inet_checksum((char*)ip, ip_header_length, 0);
+ if (sum != 0) {
+ int ip_sum = g_ntohs(ip->ip_sum);
+ printf ("\t\"message\": \"IP: IP header checksum incorrect: 0x%x.\",\n", ip_sum);
+ return -2;
+ }
+
+ if (ip->ip_p != IPPROTO_PGM) {
+ printf ("\t\"message\": \"IP: packet IP protocol not PGM: %i.\",\n", ip->ip_p);
+ return -1;
+ }
+
+/* fragmentation offset, bit 0: 0, bit 1: do-not-fragment, bit 2: more-fragments */
+ int offset = g_ntohs(ip->ip_off);
+ if ((offset & 0x1fff) != 0) {
+ printf ("\t\"message\": \"IP: fragmented IP packet, ignoring.\",\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+print_ip_header (
+ struct pgm_ip* ip
+ )
+{
+ puts ("\t\"IP\": {");
+ printf ("\t\t\"version\": %i,\n",
+ ip->ip_v
+ );
+ printf ("\t\t\"headerLength\": %i,\n",
+ ip->ip_hl
+ );
+ printf ("\t\t\"ToS\": %i,\n",
+ ip->ip_tos & 0x3
+ );
+ printf ("\t\t\"length\": %i,\n",
+ g_ntohs(ip->ip_len)
+ );
+ printf ("\t\t\"fragmentId\": %i,\n",
+ g_ntohs(ip->ip_id)
+ );
+ printf ("\t\t\"DF\": %s,\n",
+ (g_ntohs(ip->ip_off) & 0x4000) ? "true" : "false"
+ );
+ printf ("\t\t\"MF\": %s,\n",
+ (g_ntohs(ip->ip_off) & 0x2000) ? "true" : "false"
+ );
+ printf ("\t\t\"fragmentOffset\": %i,\n",
+ g_ntohs(ip->ip_off) & 0x1fff
+ );
+ printf ("\t\t\"TTL\": %i,\n",
+ ip->ip_ttl
+ );
+ printf ("\t\t\"protocol\": %i,\n",
+ ip->ip_p
+ );
+ printf ("\t\t\"sourceIp\": \"%s\",\n",
+ inet_ntoa(*(struct in_addr*)&ip->ip_src)
+ );
+ printf ("\t\t\"destinationIp\": \"%s\",\n",
+ inet_ntoa(*(struct in_addr*)&ip->ip_dst)
+ );
+ puts ("\t\t\"IpOptions\": {");
+ puts ("\t\t}");
+ puts ("\t},");
+}
+
+int
+verify_pgm_header (
+ struct pgm_header* pgm,
+ guint pgm_len
+ )
+{
+
+/* PGM payload, header looks as follows:
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Port | Destination Port |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Options | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Global Source ID ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ... Global Source ID | TSDU Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type specific data ...
+ * +-+-+-+-+-+-+-+-+-+- ...
+ */
+ if (pgm_len < sizeof(pgm)) {
+ printf ("\t\"message\": \"PGM: packet size less than PGM header: %i bytes.\",\n", pgm_len);
+ return -1;
+ }
+
+ if (pgm->pgm_checksum)
+ {
+ int sum = pgm->pgm_checksum;
+ pgm->pgm_checksum = 0;
+ int pgm_sum = pgm_csum_fold (pgm_csum_partial ((const char*)pgm, pgm_len, 0));
+ pgm->pgm_checksum = sum;
+ if (pgm_sum != sum) {
+ printf ("\t\"message\": \"PGM: PGM packet checksum incorrect, packet 0x%x calculated 0x%x.\",\n", sum, pgm_sum);
+ return -2;
+ }
+ } else {
+ if (pgm->pgm_type != PGM_ODATA && pgm->pgm_type != PGM_RDATA) {
+ printf ("\t\"message\": \"PGM: No PGM checksum value, mandatory for ODATA/RDATA.\",\n");
+ return -1;
+ }
+ }
+
+ if ( pgm->pgm_type != PGM_SPM &&
+ pgm->pgm_type != PGM_POLL &&
+ pgm->pgm_type != PGM_POLR &&
+ pgm->pgm_type != PGM_ODATA &&
+ pgm->pgm_type != PGM_RDATA &&
+ pgm->pgm_type != PGM_NAK &&
+ pgm->pgm_type != PGM_NNAK &&
+ pgm->pgm_type != PGM_NCF &&
+ pgm->pgm_type != PGM_SPMR )
+ {
+ printf ("\t\"message\": \"PGM: Not a valid PGM packet type: %i.\",\n", pgm->pgm_type);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* note: output trails tsdu length line to allow for comma
+ */
+
+void
+print_pgm_header (
+ struct pgm_header* pgm
+ )
+{
+ puts ("\t\"PGM\": {");
+ printf ("\t\t\"sourcePort\": %i,\n", g_ntohs(pgm->pgm_sport));
+ printf ("\t\t\"destinationPort\": %i,\n", g_ntohs(pgm->pgm_dport));
+ printf ("\t\t\"type\": \"%s\",\n", pgm_type_string(pgm->pgm_type & 0xf));
+ printf ("\t\t\"version\": %i,\n", (pgm->pgm_type & 0xc0) >> 6);
+ puts ("\t\t\"options\": {");
+ printf ("\t\t\t\"networkSignificant\": %s,\n", (pgm->pgm_options & PGM_OPT_NETWORK) ? "true" : "false");
+ printf ("\t\t\t\"parityPacket\": %s,\n", (pgm->pgm_options & PGM_OPT_PARITY) ? "true" : "false");
+ printf ("\t\t\t\"variableLength\": %s\n", (pgm->pgm_options & PGM_OPT_VAR_PKTLEN) ? "true" : "false");
+ puts ("\t\t},");
+ printf ("\t\t\"checksum\": %i,\n", pgm->pgm_checksum);
+ printf ("\t\t\"gsi\": \"%i.%i.%i.%i.%i.%i\",\n",
+ pgm->pgm_gsi[0],
+ pgm->pgm_gsi[1],
+ pgm->pgm_gsi[2],
+ pgm->pgm_gsi[3],
+ pgm->pgm_gsi[4],
+ pgm->pgm_gsi[5]);
+ printf ("\t\t\"tsduLength\": %i", g_ntohs(pgm->pgm_tsdu_length));
+}
+
+/* 8.1. Source Path Messages (SPM)
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | SPM's Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Trailing Edge Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Leading Edge Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | NLA AFI | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Path NLA ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+
+ * | Option Extensions when present ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ... -+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * NLA = Network Layer Address
+ * NLA AFI = NLA Address Family Indicator: rfc 1700 (ADDRESS FAMILY NUMBERS)
+ * => Path NLA = IP address of last network element
+ */
+
+int
+verify_spm (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+/* truncated packet */
+ if (len < sizeof(struct pgm_spm)) {
+ printf ("\t\"message\": \"SPM: packet length: %i less than minimum SPM length: %" G_GSIZE_FORMAT "lu bytes.\",\n", len, sizeof(struct pgm_spm));
+ retval = -1;
+ goto out;
+ }
+
+ struct pgm_spm* spm = (struct pgm_spm*)data;
+ char* opt_offset = (char*)(spm + 1);
+ guint opt_len = len - sizeof(spm);
+
+ switch (g_ntohs(spm->spm_nla_afi)) {
+ case AFI_IP6:
+ if (len < sizeof(struct pgm_spm6)) {
+ printf ("\t\"message\": \"SPM: packet length: %i less than minimum IPv6 SPM length: %" G_GSIZE_FORMAT "lu bytes.\",\n", len, sizeof(struct pgm_spm6));
+ retval = -1;
+ goto out;
+ }
+ opt_offset += sizeof(struct pgm_spm6) - sizeof(struct pgm_spm);
+ opt_len -= sizeof(struct pgm_spm6) - sizeof(struct pgm_spm);
+
+ case AFI_IP:
+ break;
+
+ default:
+ printf ("\t\"message\": \"SPM: invalid AFI of source NLA: %i.\",\n", g_ntohs(spm->spm_nla_afi));
+ retval = -1;
+ goto out;
+ }
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ retval = verify_options (opt_offset, opt_len);
+ }
+
+out:
+ return retval;
+}
+
+void
+print_spm (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ struct pgm_spm* spm = (struct pgm_spm*)data;
+ struct pgm_spm6* spm6 = (struct pgm_spm6*)data;
+ char* opt_offset = (char*)(spm + 1);
+
+ puts (",");
+ printf ("\t\t\"spmSqn\": %i,\n", g_ntohl(spm->spm_sqn));
+ printf ("\t\t\"spmTrail\": %i,\n", g_ntohl(spm->spm_trail));
+ printf ("\t\t\"spmLead\": %i,\n", g_ntohl(spm->spm_lead));
+ printf ("\t\t\"spmNlaAfi\": %i,\n", g_ntohs (spm->spm_nla_afi));
+
+ char s[INET6_ADDRSTRLEN];
+ switch (g_ntohs(spm->spm_nla_afi)) {
+ case AFI_IP:
+ inet_ntop ( AF_INET, &spm->spm_nla, s, sizeof (s) );
+ break;
+
+ case AFI_IP6:
+ inet_ntop ( AF_INET6, &spm6->spm6_nla, s, sizeof (s) );
+ opt_offset += sizeof(struct pgm_spm6) - sizeof(struct pgm_spm);
+ break;
+ }
+
+ printf ("\t\t\"spmNla\": \"%s\"", s);
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ puts (",");
+ print_options (opt_offset);
+ }
+
+ puts ("\n\t}");
+}
+
+/* 14.7.1. Poll Request
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | POLL's Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | POLL's Round | POLL's Sub-type |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | NLA AFI | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Path NLA ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+
+ * | POLL's Back-off Interval |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Random String |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Matching Bit-Mask |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Option Extensions when present ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ... -+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Sent to ODATA multicast group with IP Router Alert option.
+ */
+
+#define PGM_MIN_POLL_SIZE ( sizeof(struct pgm_poll) )
+
+int
+verify_poll (
+ G_GNUC_UNUSED struct pgm_header* header,
+ G_GNUC_UNUSED char* data,
+ G_GNUC_UNUSED guint len
+ )
+{
+ return -1;
+}
+
+void
+print_poll (
+ G_GNUC_UNUSED struct pgm_header* header,
+ G_GNUC_UNUSED char* data
+ )
+{
+}
+
+/* 14.7.2. Poll Response
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | POLR's Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | POLR's Round | reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Option Extensions when present ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ... -+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+int
+verify_polr (
+ G_GNUC_UNUSED struct pgm_header* header,
+ G_GNUC_UNUSED char* data,
+ G_GNUC_UNUSED guint len
+ )
+{
+ return -1;
+}
+
+void
+print_polr (
+ G_GNUC_UNUSED struct pgm_header* header,
+ G_GNUC_UNUSED char* data
+ )
+{
+}
+
+/* 8.2. Data Packet
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data Packet Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Trailing Edge Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Option Extensions when present ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ... -+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data ...
+ * +-+-+- ...
+ */
+
+int
+verify_odata (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+ if (len < sizeof(struct pgm_data)) {
+ printf ("\t\"message\": \"ODATA: packet length: %i less than minimum ODATA length: %" G_GSIZE_FORMAT " bytes.\",\n", len, sizeof(struct pgm_data));
+ retval = -1;
+ goto out;
+ }
+
+ char* tsdu = data + sizeof(struct pgm_data);
+ guint tsdu_len = len - sizeof(struct pgm_data);
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ retval = verify_options (tsdu, tsdu_len);
+
+ guint opt_total_len = g_ntohs( OPTIONS_TOTAL_LEN(tsdu) );
+ tsdu += opt_total_len;
+ tsdu_len -= opt_total_len;
+ }
+
+ if (!retval && g_ntohs(header->pgm_tsdu_length) != tsdu_len) {
+ printf ("\t\"message\": \"ODATA: TSDU truncated expected %i, found %i bytes.\",\n", g_ntohs(header->pgm_tsdu_length), tsdu_len);
+ retval = -1;
+ }
+out:
+ return retval;
+}
+
+void
+print_odata (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ struct pgm_data* odata = (struct pgm_data*)data;
+ char* tsdu = data + sizeof(struct pgm_data);
+
+ puts (",");
+ printf ("\t\t\"odSqn\": %lu,\n", (gulong)g_ntohl(odata->data_sqn));
+ printf ("\t\t\"odTrail\": %lu,\n", (gulong)g_ntohl(odata->data_trail));
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ print_options (tsdu);
+ tsdu += g_ntohs( OPTIONS_TOTAL_LEN(tsdu) );
+ puts (",");
+ }
+
+/* data */
+ printf ("\t\t\"data\": \"");
+ char* end = tsdu + g_ntohs (header->pgm_tsdu_length);
+ while (tsdu < end) {
+ if (isprint(*tsdu))
+ putchar(*tsdu);
+ else
+ putchar('.');
+ tsdu++;
+ }
+
+ puts ("\"");
+ puts ("\t}");
+}
+
+/* 8.2. Repair Data
+ */
+
+int
+verify_rdata (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+ if (len < sizeof(struct pgm_data)) {
+ printf ("\t\"message\": \"RDATA: packet length: %i less than minimum RDATA length: %" G_GSIZE_FORMAT " bytes.\",\n", len, sizeof(struct pgm_data));
+ retval = -1;
+ goto out;
+ }
+
+ char* tsdu = data + sizeof(struct pgm_data);
+ guint tsdu_len = len - sizeof(struct pgm_data);
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ retval = verify_options (tsdu, tsdu_len);
+
+ guint opt_total_len = g_ntohs( OPTIONS_TOTAL_LEN(tsdu) );
+ tsdu += opt_total_len;
+ tsdu_len -= opt_total_len;
+ }
+
+ if (!retval && g_ntohs(header->pgm_tsdu_length) != tsdu_len) {
+ printf ("\t\"message\": \"RDATA: tsdu truncated expected %i, found %i bytes.\",\n", g_ntohs(header->pgm_tsdu_length), tsdu_len);
+ retval = -1;
+ }
+out:
+ return retval;
+}
+
+void
+print_rdata (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ struct pgm_data* rdata = (struct pgm_data*)data;
+ char* tsdu = data + sizeof(struct pgm_data);
+
+ puts (",");
+ printf ("\t\t\"rdSqn\": %lu,\n", (gulong)g_ntohl(rdata->data_sqn));
+ printf ("\t\t\"rdTrail\": %lu,\n", (gulong)g_ntohl(rdata->data_trail));
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ print_options (tsdu);
+ tsdu += g_ntohs( OPTIONS_TOTAL_LEN(tsdu) );
+ puts (",");
+ }
+
+/* data */
+ printf ("\t\t\"data\": \"");
+ char* end = tsdu + g_ntohs (header->pgm_tsdu_length);
+ while (tsdu < end) {
+ if (isprint(*tsdu))
+ putchar(*tsdu);
+ else
+ putchar('.');
+ tsdu++;
+ }
+
+ puts ("\"");
+ puts ("\t}");
+}
+
+/* 8.3. NAK
+ *
+ * Technically the AFI of the source and multicast group can be different
+ * but that would be very wibbly wobbly. One example is using a local DLR
+ * with a IPv4 address to reduce NAK cost for recovery on wide IPv6
+ * distribution.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Requested Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | NLA AFI | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source NLA ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+
+ * | NLA AFI | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Multicast Group NLA ... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+
+ * | Option Extensions when present ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ...
+ */
+
+int
+verify_nak (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ return generic_verify_nak ("NAK", header, data, len);
+}
+
+int
+verify_ncf (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ return generic_verify_nak ("NCF", header, data, len);
+}
+
+int
+verify_nnak (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ return generic_verify_nak ("NNAK", header, data, len);
+}
+
+static int
+generic_verify_nak (
+ const char* name, /* upper case */
+ G_GNUC_UNUSED struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+/* truncated packet */
+ if (len < sizeof(struct pgm_nak)) {
+ printf ("\t\"message\": \"%s: packet length: %i less than minimum %s length: %" G_GSIZE_FORMAT " bytes.\",\n",
+ name, len, name, sizeof(struct pgm_nak));
+ retval = -1;
+ goto out;
+ }
+
+ struct pgm_nak* nak = (struct pgm_nak*)data;
+ int nak_src_nla_afi = g_ntohs (nak->nak_src_nla_afi);
+ int nak_grp_nla_afi = -1;
+
+/* check source NLA: unicast address of the ODATA sender */
+ switch (nak_src_nla_afi) {
+ case AFI_IP:
+ nak_grp_nla_afi = g_ntohs (nak->nak_grp_nla_afi);
+ break;
+
+ case AFI_IP6:
+ nak_grp_nla_afi = g_ntohs (((struct pgm_nak6*)nak)->nak6_grp_nla_afi);
+ break;
+
+ default:
+ printf ("\t\"message\": \"%s: invalid AFI of source NLA: %i.\",\n",
+ name, nak_src_nla_afi);
+ retval = -1;
+ goto out;
+ }
+
+/* check multicast group NLA */
+ switch (nak_grp_nla_afi) {
+ case AFI_IP6:
+ switch (nak_src_nla_afi) {
+/* IPv4 + IPv6 NLA */
+ case AFI_IP:
+ if (len < ( sizeof(struct pgm_nak) + sizeof(struct in6_addr) - sizeof(struct in_addr) )) {
+ printf ("\t\"message\": \"%s: packet length: %i less than joint IPv4/6 %s length: %" G_GSIZE_FORMAT " bytes.\",\n",
+ name, len, name, ( sizeof(struct pgm_nak) + sizeof(struct in6_addr) - sizeof(struct in_addr) ));
+ retval = -1;
+ }
+ break;
+
+/* IPv6 + IPv6 NLA */
+ case AFI_IP6:
+ if (len < sizeof(struct pgm_nak6)) {
+ printf ("\t\"message\": \"%s: packet length: %i less than IPv6 %s length: %" G_GSIZE_FORMAT " bytes.\",\n",
+ name, len, name, sizeof(struct pgm_nak6));
+ retval = -1;
+ }
+ break;
+ }
+ break;
+
+ case AFI_IP:
+ if (nak_src_nla_afi == AFI_IP6) {
+ if (len < ( sizeof(struct pgm_nak) + sizeof(struct in6_addr) - sizeof(struct in_addr) )) {
+ printf ("\t\"message\": \"%s: packet length: %i less than joint IPv6/4 %s length: %" G_GSIZE_FORMAT " bytes.\",\n",
+ name, len, name, ( sizeof(struct pgm_nak) + sizeof(struct in6_addr) - sizeof(struct in_addr) ));
+ retval = -1;
+ }
+ }
+ break;
+
+ default:
+ printf ("\t\"message\": \"%s: invalid AFI of group NLA: %i.\",\n",
+ name, nak_grp_nla_afi);
+ retval = -1;
+ break;
+ }
+
+out:
+ return retval;
+}
+
+void
+print_nak (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ generic_print_nak ("nak", header, data);
+}
+
+void
+print_ncf (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ generic_print_nak ("ncf", header, data);
+}
+
+void
+print_nnak (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ generic_print_nak ("nnak", header, data);
+}
+
+static void
+generic_print_nak (
+ const char* name, /* lower case */
+ struct pgm_header* header,
+ char* data
+ )
+{
+ struct pgm_nak* nak = (struct pgm_nak*)data;
+ struct pgm_nak6* nak6 = (struct pgm_nak6*)data;
+ char* opt_offset = (char*)(nak + 1);
+
+ puts (",");
+ printf ("\t\t\"%sSqn\": %lu,\n", name, (gulong)g_ntohl(nak->nak_sqn));
+
+ guint16 nak_src_nla_afi = g_ntohs (nak->nak_src_nla_afi);
+ guint16 nak_grp_nla_afi = 0;
+ char s[INET6_ADDRSTRLEN];
+
+ printf ("\t\t\"%sSourceNlaAfi\": %i,\n", name, nak_src_nla_afi);
+
+/* source nla */
+ switch (nak_src_nla_afi) {
+ case AFI_IP:
+ inet_ntop ( AF_INET, &nak->nak_src_nla, s, sizeof(s) );
+ nak_grp_nla_afi = g_ntohs (nak->nak_grp_nla_afi);
+ break;
+
+ case AFI_IP6:
+ inet_ntop ( AF_INET6, &nak6->nak6_src_nla, s, sizeof(s) );
+ nak_grp_nla_afi = g_ntohs (nak6->nak6_grp_nla_afi);
+ opt_offset += sizeof(struct in6_addr) - sizeof(struct in_addr);
+ break;
+ }
+
+ printf ("\t\t\"%sSourceNla\": \"%s\",\n", name, s);
+ printf ("\t\t\"%sGroupNlaAfi\": %i,\n", name, nak_grp_nla_afi);
+
+ switch (nak_grp_nla_afi) {
+ case AFI_IP6:
+ switch (nak_src_nla_afi) {
+/* IPv4 + IPv6 NLA */
+ case AFI_IP:
+ inet_ntop ( AF_INET6, &nak->nak_grp_nla, s, sizeof(s) );
+ break;
+
+/* IPv6 + IPv6 NLA */
+ case AFI_IP6:
+ inet_ntop ( AF_INET6, &nak6->nak6_grp_nla, s, sizeof(s) );
+ break;
+ }
+ opt_offset += sizeof(struct in6_addr) - sizeof(struct in_addr);
+ break;
+
+ case AFI_IP:
+ switch (nak_src_nla_afi) {
+/* IPv4 + IPv4 NLA */
+ case AFI_IP:
+ inet_ntop ( AF_INET, &nak->nak_grp_nla, s, sizeof(s) );
+ break;
+
+/* IPv6 + IPv4 NLA */
+ case AFI_IP6:
+ inet_ntop ( AF_INET, &nak6->nak6_grp_nla, s, sizeof(s) );
+ break;
+ }
+ break;
+ }
+
+ printf ("\t\t\"%sGroupNla\": \"%s\"", name, s);
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ puts (",");
+ print_options (opt_offset);
+ }
+
+ puts ("\n\t}");
+}
+
+
+/* 13.6. SPM Request
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Option Extensions when present ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- ...
+ */
+
+int
+verify_spmr (
+ struct pgm_header* header,
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+ char* opt_offset = data;
+ guint opt_len = len;
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ retval = verify_options (opt_offset, opt_len);
+ }
+
+ return retval;
+}
+
+void
+print_spmr (
+ struct pgm_header* header,
+ char* data
+ )
+{
+ char* opt_offset = data;
+
+/* option extensions */
+ if (header->pgm_options & PGM_OPT_PRESENT)
+ {
+ print_options (opt_offset);
+ puts ("");
+ }
+
+ puts ("\t}");
+}
+
+/* Parse PGM options fields:
+ *
+ * assume at least two options, one the mandatory OPT_LENGTH
+ */
+
+#define PGM_MIN_OPT_SIZE ( sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_length) )
+
+int
+verify_options (
+ char* data,
+ guint len
+ )
+{
+ int retval = 0;
+
+ if (len < PGM_MIN_OPT_SIZE) {
+ printf ("\t\"message\": \"PGM options: packet size too small for options.\",\n");
+ retval = -1;
+ goto out;
+ }
+
+/* OPT_LENGTH first */
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)data;
+ if ((opt_len->opt_type & PGM_OPT_MASK) != PGM_OPT_LENGTH) {
+ printf ("\t\"message\": \"PGM options: first option not OPT_LENGTH.\",\n");
+ retval = -1;
+ goto out;
+ }
+
+ if (opt_len->opt_length != sizeof(struct pgm_opt_length)) {
+ printf ("\t\"message\": \"PGM options: OPT_LENGTH incorrect option length: %i, expecting %" G_GSIZE_FORMAT " bytes.\",\n", opt_len->opt_length, sizeof(struct pgm_opt_length));
+ retval = -1;
+ goto out;
+ }
+
+ if (g_ntohs(opt_len->opt_total_length) < PGM_MIN_OPT_SIZE) {
+ printf ("\t\"message\": \"PGM options: OPT_LENGTH total length too short: %i bytes.\",\n", g_ntohs(opt_len->opt_total_length));
+ retval = -1;
+ goto out;
+ }
+
+ if (g_ntohs(opt_len->opt_total_length) > len) {
+ printf ("\t\"message\": \"PGM options: OPT_LENGTH total length longer than packet allows: %i bytes.\",\n", g_ntohs(opt_len->opt_total_length));
+ retval = -1;
+ goto out;
+ }
+
+/* iterate through options (max 16) */
+ guint count = 0;
+ guint total_length = g_ntohs(opt_len->opt_total_length);
+
+ guint opt_counters[256];
+ memset (&opt_counters, 0, sizeof(opt_counters));
+
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)data;
+ for (;;) {
+ total_length -= opt_header->opt_length;
+ if (total_length < sizeof(struct pgm_opt_header)) {
+ printf ("\t\"message\": \"PGM options: option #%i shorter than minimum option size.\",\n", count + 1);
+ retval = -1;
+ goto out;
+ }
+ opt_header = (struct pgm_opt_header*)( ((char*)opt_header) + opt_header->opt_length );
+ if (((int)total_length - (int)opt_header->opt_length) < 0) {
+ printf ("\t\"message\": \"PGM options: option #%i shorter than embedded size.\",\n", count + 1);
+ retval = -1;
+ goto out;
+ }
+
+ if (opt_counters[opt_header->opt_type]++) {
+ printf ("\t\"message\": \"PGM options: duplicate option %i.\",\n", opt_header->opt_type);
+ retval = -1;
+ goto out;
+ }
+
+/* check option types */
+ switch (opt_header->opt_type & PGM_OPT_MASK) {
+ case PGM_OPT_FRAGMENT:
+ {
+ if (opt_header->opt_length != sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_fragment)) {
+ printf ("\t\"message\": \"PGM options: OPT_FRAGMENT incorrect size: %i bytes.\",\n", opt_header->opt_length);
+ retval = -1;
+ goto out;
+ }
+
+ struct pgm_opt_fragment* opt_fragment = (struct pgm_opt_fragment*)(opt_header + 1);
+ if (g_ntohl(opt_fragment->opt_frag_off) > g_ntohl(opt_fragment->opt_frag_len)) {
+ printf ("\t\"message\": \"PGM options: fragment offset longer than original packet.\",\n");
+ retval = -1;
+ goto out;
+ }
+ break;
+ }
+
+ case PGM_OPT_NAK_LIST:
+ {
+ guint list_len = opt_header->opt_length - sizeof(struct pgm_opt_header) - sizeof(guint8);
+ if (list_len & 1) {
+ printf ("\t\"message\": \"PGM options: OPT_NAK_LIST invalid odd length: %i bytes.\",\n", opt_header->opt_length);
+ retval = -1;
+ goto out;
+ }
+
+ list_len /= 2;
+ if (list_len == 0) {
+ printf ("\t\"message\": \"PGM options: OPT_NAK_LIST empty.\",\n");
+ retval = -1;
+ goto out;
+ }
+
+ if (list_len > 62) {
+ printf ("\t\"message\": \"PGM options: OPT_NAK_LIST too long: %i sqns.\",\n", list_len);
+ retval = -1;
+ goto out;
+ }
+ break;
+ }
+
+ case PGM_OPT_PARITY_PRM:
+ {
+ struct pgm_opt_parity_prm* opt_parity_prm = (struct pgm_opt_parity_prm*)(opt_header + 1);
+ if ((opt_parity_prm->opt_reserved & PGM_PARITY_PRM_MASK) == 0) {
+ printf ("\t\"message\": \"PGM options: neither pro-active or on-demand parity set in OPT_PARITY_PRM.\",\n");
+ retval = -1;
+ goto out;
+ }
+ guint32 parity_prm_tgs = g_ntohl (opt_parity_prm->parity_prm_tgs);
+ if (parity_prm_tgs < 2 || parity_prm_tgs > 128) {
+ printf ("\t\"message\": \"PGM options: transmission group size out of bounds: %i.\",\n", parity_prm_tgs);
+ retval = -1;
+ goto out;
+ }
+ break;
+ }
+
+ default:
+/* unknown option, skip */
+ break;
+ }
+/* end option types */
+
+ if (opt_header->opt_type & PGM_OPT_END) {
+ break;
+ }
+
+ if (count++ == 16) {
+ printf ("\t\"message\": \"PGM options: more than 16 options found.\",\n");
+ retval = -1;
+ goto out;
+ }
+ }
+
+out:
+ return retval;
+}
+
+static const char *opx_text[4] = {
+ "OPX_IGNORE",
+ "OPX_INVALIDATE",
+ "OPX_DISCARD",
+ "OPX_UNKNOWN"
+};
+
+void
+print_options (
+ char* data
+ )
+{
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)data;
+
+ puts ("\t\t\"pgmOptions\": [");
+ puts ("\t\t\t{");
+ printf ("\t\t\t\t\"length\": 0x%x,\n", opt_len->opt_length);
+ puts ("\t\t\t\t\"type\": \"OPT_LENGTH\",");
+ printf ("\t\t\t\t\"totalLength\": %i\n", g_ntohs (opt_len->opt_total_length));
+ printf ("\t\t\t}");
+
+/* iterate through options */
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)data;
+ do {
+ opt_header = (struct pgm_opt_header*)( ((char*)opt_header) + opt_header->opt_length );
+
+ puts (",");
+ puts ("\t\t\t{");
+ printf ("\t\t\t\t\"length\": 0x%x,\n", opt_header->opt_length);
+
+ switch (opt_header->opt_type & PGM_OPT_MASK) {
+ case PGM_OPT_FRAGMENT:
+ {
+ struct pgm_opt_fragment* opt_fragment = (struct pgm_opt_fragment*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"OPT_FRAGMENT%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s,\n", (opt_fragment->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ printf ("\t\t\t\t\"firstSqn\": %i,\n", g_ntohl(opt_fragment->opt_sqn));
+ printf ("\t\t\t\t\"fragmentOffset\": %i,\n", g_ntohl(opt_fragment->opt_frag_off));
+ printf ("\t\t\t\t\"originalLength\": %i\n", g_ntohl(opt_fragment->opt_frag_len));
+ break;
+ }
+
+ case PGM_OPT_NAK_LIST:
+ {
+ struct pgm_opt_nak_list* opt_nak_list = (struct pgm_opt_nak_list*)(opt_header + 1);
+ char* end = (char*)opt_header + opt_header->opt_length;
+ printf ("\t\t\t\t\"type\": \"OPT_NAK_LIST%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s,\n", (opt_nak_list->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ char sqns[1024] = "";
+ guint i = 0;
+ do {
+ char sqn[1024];
+ sprintf (sqn, "%s%i", (i>0)?", ":"", g_ntohl(opt_nak_list->opt_sqn[i]));
+ strcat (sqns, sqn);
+ i++;
+ } while ((char*)&opt_nak_list->opt_sqn[i] < end);
+ printf ("\t\t\t\t\"sqn\": [%s]\n", sqns);
+ break;
+ }
+
+ case PGM_OPT_PARITY_PRM:
+ {
+ struct pgm_opt_parity_prm* opt_parity_prm = (struct pgm_opt_parity_prm*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"OPT_PARITY_PRM%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s,\n", (opt_parity_prm->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ printf ("\t\t\t\t\"P-bit\": %s,\n", (opt_parity_prm->opt_reserved & PGM_PARITY_PRM_PRO) ? "true" : "false");
+ printf ("\t\t\t\t\"O-bit\": %s,\n", (opt_parity_prm->opt_reserved & PGM_PARITY_PRM_OND) ? "true" : "false");
+ printf ("\t\t\t\t\"transmissionGroupSize\": %i\n", g_ntohl(opt_parity_prm->parity_prm_tgs));
+ break;
+ }
+
+ case PGM_OPT_CURR_TGSIZE:
+ {
+ struct pgm_opt_curr_tgsize* opt_curr_tgsize = (struct pgm_opt_curr_tgsize*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"OPT_CURR_TGSIZE%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s,\n", (opt_curr_tgsize->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ printf ("\t\t\t\t\"actualTransmissionGroupSize\": %i\n", g_ntohl(opt_curr_tgsize->prm_atgsize));
+ break;
+ }
+
+ case PGM_OPT_SYN:
+ {
+ struct pgm_opt_syn* opt_syn = (struct pgm_opt_syn*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"OPT_SYN%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s\n", (opt_syn->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ break;
+ }
+
+ case PGM_OPT_FIN:
+ {
+ struct pgm_opt_fin* opt_fin = (struct pgm_opt_fin*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"OPT_FIN%s\",\n", (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s\n", (opt_fin->opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ break;
+ }
+
+ default:
+ {
+ guint8 opt_reserved = *(guint8*)(opt_header + 1);
+ printf ("\t\t\t\t\"type\": \"0x%x%s\",\n", opt_header->opt_type & PGM_OPT_MASK, (opt_header->opt_type & PGM_OPT_END) ? "|OPT_END" : "");
+ printf ("\t\t\t\t\"F-bit\": %s,\n", (opt_header->opt_reserved & PGM_OP_ENCODED) ? "true" : "false");
+ printf ("\t\t\t\t\"OPX\": \"%s\",\n", opx_text[opt_header->opt_reserved & PGM_OPX_MASK]);
+ printf ("\t\t\t\t\"U-bit\": %s\n", (opt_reserved & PGM_OP_ENCODED_NULL) ? "true" : "false");
+ break;
+ }
+ }
+ printf ("\t\t\t}");
+
+ } while (!(opt_header->opt_type & PGM_OPT_END));
+
+ printf ("\n\t\t]");
+}
+
+/* eof */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.h b/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.h
new file mode 100644
index 0000000..6fa0f9d
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/dump-json.h
@@ -0,0 +1,33 @@
+/* vim:ts=8:sts=4:sw=4:noai:noexpandtab
+ *
+ * JSON packet dump.
+ *
+ * Copyright (c) 2006-2007 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __PGM_DUMP_JSON_H__
+#define __PGM_DUMP_JSON_H__
+
+
+G_BEGIN_DECLS
+
+int monitor_packet (char*, guint);
+
+
+G_END_DECLS
+
+#endif /* __PGM_DUMP_JSON_H__ */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/heartbeat_spm.pl b/3rdparty/openpgm-svn-r1135/pgm/test/heartbeat_spm.pl
new file mode 100755
index 0000000..beb912e
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/heartbeat_spm.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+# heartbeat_spm.pl
+# 5.1.5. Heartbeat SPMs
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+
+print "mon: wait for odata ...\n";
+$mon->wait_for_odata;
+my $t0 = [gettimeofday];
+
+for (1..4) # look for four consecutive heartbeat SPMs less than 5000ms apart
+{
+ print "mon: wait for spm ...\n";
+ $mon->wait_for_spm ({ 'timeout' => 5 });
+ my $tn = [gettimeofday];
+ my $elapsed = tv_interval ( $t0, $tn );
+ $t0 = $tn;
+
+ print "mon: spm received after $elapsed seconds.\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/monitor.c b/3rdparty/openpgm-svn-r1135/pgm/test/monitor.c
new file mode 100644
index 0000000..6569a55
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/monitor.c
@@ -0,0 +1,349 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * PGM link monitor.
+ *
+ * Copyright (c) 2006-2007 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+
+#include <glib.h>
+
+#include <pgm/pgm.h>
+#include <pgm/backtrace.h>
+#include <pgm/signal.h>
+#include <pgm/log.h>
+
+#include "dump-json.h"
+
+
+/* globals */
+
+static const char* g_network = "239.192.0.1";
+static struct in_addr g_filter /* = { 0 } */;
+
+static GIOChannel* g_io_channel = NULL;
+static GIOChannel* g_stdin_channel = NULL;
+static GMainLoop* g_loop = NULL;
+
+
+static void on_signal (int, gpointer);
+static gboolean on_startup (gpointer);
+static gboolean on_mark (gpointer);
+
+static gboolean on_io_data (GIOChannel*, GIOCondition, gpointer);
+static gboolean on_io_error (GIOChannel*, GIOCondition, gpointer);
+
+static gboolean on_stdin_data (GIOChannel*, GIOCondition, gpointer);
+
+int
+main (
+ G_GNUC_UNUSED int argc,
+ G_GNUC_UNUSED char *argv[]
+ )
+{
+/* pre-initialise PGM messages module to add hook for GLib logging */
+ pgm_messages_init();
+ log_init ();
+ puts ("monitor");
+
+/* setup signal handlers */
+ signal (SIGSEGV, on_sigsegv);
+ signal (SIGHUP, SIG_IGN);
+ pgm_signal_install (SIGINT, on_signal, g_loop);
+ pgm_signal_install (SIGTERM, on_signal, g_loop);
+
+ g_filter.s_addr = 0;
+
+/* delayed startup */
+ puts ("scheduling startup.");
+ g_timeout_add(0, (GSourceFunc)on_startup, NULL);
+
+/* dispatch loop */
+ g_loop = g_main_loop_new(NULL, FALSE);
+
+ puts ("entering main event loop ... ");
+ g_main_loop_run(g_loop);
+
+ puts ("event loop terminated, cleaning up.");
+
+/* cleanup */
+ g_main_loop_unref(g_loop);
+ g_loop = NULL;
+
+ if (g_io_channel) {
+ puts ("closing socket.");
+
+ GError *err = NULL;
+ g_io_channel_shutdown (g_io_channel, FALSE, &err);
+ g_io_channel = NULL;
+ }
+
+ if (g_stdin_channel) {
+ puts ("unbinding stdin.");
+ g_io_channel_unref (g_stdin_channel);
+ g_stdin_channel = NULL;
+ }
+
+ puts ("finished.");
+ pgm_messages_shutdown();
+ return 0;
+}
+
+static void
+on_signal (
+ int signum,
+ gpointer user_data
+ )
+{
+ GMainLoop* loop = (GMainLoop*)user_data;
+ g_message ("on_signal (signum:%d user-data:%p)", signum, user_data);
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+on_startup (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ int e;
+
+ puts ("startup.");
+
+/* find PGM protocol id */
+// TODO: fix valgrind errors
+ int ipproto_pgm = IPPROTO_PGM;
+#if HAVE_GETPROTOBYNAME_R
+ char b[1024];
+ struct protoent protobuf, *proto;
+ e = getprotobyname_r("pgm", &protobuf, b, sizeof(b), &proto);
+ if (e != -1 && proto != NULL) {
+ if (proto->p_proto != ipproto_pgm) {
+ printf("Setting PGM protocol number to %i from /etc/protocols.\n");
+ ipproto_pgm = proto->p_proto;
+ }
+ }
+#else
+ struct protoent *proto = getprotobyname("pgm");
+ if (proto != NULL) {
+ if (proto->p_proto != ipproto_pgm) {
+ printf("Setting PGM protocol number to %i from /etc/protocols.\n", proto
+->p_proto);
+ ipproto_pgm = proto->p_proto;
+ }
+ }
+#endif
+
+/* open socket for snooping */
+ puts ("opening raw socket.");
+ int sock = socket(PF_INET, SOCK_RAW, ipproto_pgm);
+ if (sock < 0) {
+ int _e = errno;
+ perror("on_startup() failed");
+
+ if (_e == EPERM && 0 != getuid()) {
+ puts ("PGM protocol requires this program to run as superuser.");
+ }
+ g_main_loop_quit(g_loop);
+ return FALSE;
+ }
+
+/* drop out of setuid 0 */
+ if (0 == getuid()) {
+ puts ("dropping superuser privileges.");
+ setuid((gid_t)65534);
+ setgid((uid_t)65534);
+ }
+
+ char _t = 1;
+ e = setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &_t, sizeof(_t));
+ if (e < 0) {
+ perror("on_startup() failed");
+ close(sock);
+ g_main_loop_quit(g_loop);
+ return FALSE;
+ }
+
+/* buffers */
+ int buffer_size = 0;
+ socklen_t len = 0;
+ e = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &buffer_size, &len);
+ if (e == 0) {
+ printf ("receive buffer set at %i bytes.\n", buffer_size);
+ }
+ e = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, &len);
+ if (e == 0) {
+ printf ("send buffer set at %i bytes.\n", buffer_size);
+ }
+
+/* bind */
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ e = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
+ if (e < 0) {
+ perror("on_startup() failed");
+ close(sock);
+ g_main_loop_quit(g_loop);
+ return FALSE;
+ }
+
+/* multicast */
+ struct ip_mreq mreq;
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ printf ("listening on interface %s.\n", inet_ntoa(mreq.imr_interface));
+ mreq.imr_multiaddr.s_addr = inet_addr(g_network);
+ printf ("subscription on multicast address %s.\n", inet_ntoa(mreq.imr_multiaddr));
+ e = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+ if (e < 0) {
+ perror("on_startup() failed");
+ close(sock);
+ g_main_loop_quit(g_loop);
+ return FALSE;
+ }
+
+/* multicast loopback */
+/* multicast ttl */
+
+/* add socket to event manager */
+ g_io_channel = g_io_channel_unix_new (sock);
+ printf ("socket opened with encoding %s.\n", g_io_channel_get_encoding(g_io_channel));
+
+ /* guint event = */ g_io_add_watch (g_io_channel, G_IO_IN | G_IO_PRI, on_io_data, NULL);
+ /* guint event = */ g_io_add_watch (g_io_channel, G_IO_ERR | G_IO_HUP | G_IO_NVAL, on_io_error, NULL);
+
+/* add stdin to event manager */
+ g_stdin_channel = g_io_channel_unix_new (fileno(stdin));
+ printf ("binding stdin with encoding %s.\n", g_io_channel_get_encoding(g_stdin_channel));
+
+ g_io_add_watch (g_stdin_channel, G_IO_IN | G_IO_PRI, on_stdin_data, NULL);
+
+/* period timer to indicate some form of life */
+// TODO: Gnome 2.14: replace with g_timeout_add_seconds()
+ g_timeout_add(10 * 1000, (GSourceFunc)on_mark, NULL);
+
+ puts ("READY");
+ fflush (stdout);
+ return FALSE;
+}
+
+static gboolean
+on_mark (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ g_message ("-- MARK --");
+ return TRUE;
+}
+
+static gboolean
+on_io_data (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ char buffer[4096];
+ int fd = g_io_channel_unix_get_fd(source);
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ int len = recvfrom(fd, buffer, sizeof(buffer), MSG_DONTWAIT, (struct sockaddr*)&addr, &addr_len);
+
+ if (g_filter.s_addr && g_filter.s_addr != addr.sin_addr.s_addr) {
+ return TRUE;
+ }
+
+ printf ("%i bytes received from %s.\n", len, inet_ntoa(addr.sin_addr));
+
+ monitor_packet (buffer, len);
+ fflush (stdout);
+
+ return TRUE;
+}
+
+static gboolean
+on_io_error (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ puts ("on_error.");
+
+ GError *err;
+ g_io_channel_shutdown (source, FALSE, &err);
+
+/* remove event */
+ return FALSE;
+}
+
+/* process input commands from stdin/fd
+ */
+
+static gboolean
+on_stdin_data (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ gchar* str = NULL;
+ gsize len = 0;
+ gsize term = 0;
+ GError* err = NULL;
+
+ g_io_channel_read_line (source, &str, &len, &term, &err);
+ if (len > 0) {
+ if (term) str[term] = 0;
+
+ if (strcmp(str, "quit") == 0) {
+ g_main_loop_quit(g_loop);
+ } else if (strncmp(str, "filter ", strlen("filter ")) == 0) {
+ unsigned a, b, c, d;
+ int retval = sscanf(str, "filter %u.%u.%u.%u", &a, &b, &c, &d);
+ if (retval == 4) {
+ g_filter.s_addr = (d << 24) | (c << 16) | (b << 8) | a;
+ puts ("READY");
+ } else {
+ printf ("invalid syntax for filter command.");
+ }
+ } else {
+ printf ("unknown command: %s\n", str);
+ }
+ }
+
+ fflush (stdout);
+ g_free (str);
+ return TRUE;
+}
+
+/* eof */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/nak.pl b/3rdparty/openpgm-svn-r1135/pgm/test/nak.pl
new file mode 100755
index 0000000..bde24c0
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/nak.pl
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+# nak.pl
+# 5.3. Repairs
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao"); # to process NAK requests
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+$app->say ("send ao ichigo");
+$app->say ("send ao momo");
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..3) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+print "sim: send nak to app.\n";
+$sim->say ("net send nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 2");
+
+print "mon: wait for rdata ...\n";
+$mon->wait_for_rdata;
+print "mon: rdata received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/nak_cancellation.pl b/3rdparty/openpgm-svn-r1135/pgm/test/nak_cancellation.pl
new file mode 100755
index 0000000..9db12e9
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/nak_cancellation.pl
@@ -0,0 +1,163 @@
+#!/usr/bin/perl
+# nak_cancellation.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use IO::Handle;
+use JSON;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!";
+FROM_PARENT->autoflush(1);
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ close FROM_PARENT; close TO_CHILD;
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao NAK_BO_IVL 5000"); # increase to count for test system latency
+$app->say ("set ao NAK_RPT_IVL 10000");
+$app->say ("set ao NAK_RDATA_IVL 10000");
+$app->say ("set ao NAK_NCF_RETRIES 15");
+$app->say ("set ao NAK_DATA_RETRIES 10");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+my $t0 = [gettimeofday];
+
+if (my $pid = fork) {
+# parent
+ close FROM_PARENT;
+
+ sleep 10;
+
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data({ 'timeout' => 0 });
+ print "app: data received [$data].\n";
+
+ print TO_CHILD "die\n";
+
+ close TO_CHILD;
+ waitpid($pid,0);
+} else {
+# child
+ die "cannot fork: $!" unless defined $pid;
+ close TO_CHILD;
+
+ print "sim: loop waiting for NAKs ...\n";
+
+ my $fh = $sim->{in};
+ vec(my $rin, fileno(FROM_PARENT), 1) = 1;
+ vec($rin, fileno($fh), 1) = 1;
+ my $rout = undef;
+
+ my $b = '';
+ my $state = 0;
+ my $json = new JSON;
+ my $io = IO::Handle->new_from_fd( fileno($fh), "r" );
+ my $cnt = 0;
+ while (select($rout = $rin, undef, undef, undef))
+ {
+ last if( vec($rout, fileno(FROM_PARENT), 1) );
+ while (defined($_ = $io->getline))
+ {
+ chomp;
+ my $l = $_;
+ if ($state == 0) {
+ if ($l =~ /{$/) {
+ $state = 1;
+ } else {
+ print "sim [$l]\n";
+ last;
+ }
+ }
+
+ if ($state == 1) {
+ $b .= $l;
+
+ if ($l =~ /^}$/) {
+ $state = 0;
+
+ my $obj = $json->jsonToObj($b);
+ if ($obj->{PGM}->{type} =~ /NAK/) {
+ $cnt++;
+ my $elapsed = tv_interval ( $t0, [gettimeofday] );
+ print "sim: $cnt x NAK received in $elapsed seconds.\n";
+ $sim->say ("net send ncf ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 90002");
+ }
+
+# reset
+ $b = '';
+ last;
+ }
+ }
+ }
+ last if ($io->eof);
+ }
+
+ print "sim: loop finished.\n";
+ close FROM_PARENT;
+ exit;
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/nak_list.pl b/3rdparty/openpgm-svn-r1135/pgm/test/nak_list.pl
new file mode 100755
index 0000000..015db45
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/nak_list.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+# nak_list.pl
+# 9.3. NAK List Option - OPT_NAK_LIST
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+$app->say ("send ao ichigo");
+$app->say ("send ao momo");
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..3) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+print "sim: send nak to app.\n";
+$sim->say ("net send nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 0,1,2");
+
+my $rcnt = 0;
+for (1..3) {
+ print "mon: wait for rdata ...\n";
+ $mon->wait_for_rdata;
+ $rcnt++;
+ print "mon: received $rcnt x rdata.\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/nak_parity.pl b/3rdparty/openpgm-svn-r1135/pgm/test/nak_parity.pl
new file mode 100755
index 0000000..50f961b
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/nak_parity.pl
@@ -0,0 +1,75 @@
+#!/usr/bin/perl
+# nak_parity.pl
+# 5.3. Repairs
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("set ao FEC RS(255,4)");
+$sim->say ("bind ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao FEC RS(255,4)");
+$app->say ("bind ao");
+$app->say ("listen ao"); # to process NAK requests
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+$app->say ("send ao ichigo");
+$app->say ("send ao momo");
+$app->say ("send ao budo");
+$app->say ("send ao nashi");
+$app->say ("send ao anzu");
+$app->say ("send ao kaki");
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..7) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+print "sim: send nak to app (transmission group = 0, packet count = 1).\n";
+$sim->say ("net send parity nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 1");
+
+print "mon: wait for rdata ...\n";
+my $rdata = $mon->wait_for_rdata;
+print "mon: rdata received.\n";
+
+die "Selective RDATA received, parityPacket=false\n" unless $rdata->{PGM}->{options}->{parityPacket};
+print "Parity RDATA received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/nak_repeat.pl b/3rdparty/openpgm-svn-r1135/pgm/test/nak_repeat.pl
new file mode 100755
index 0000000..7f3d80e
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/nak_repeat.pl
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+# nak_repeat.pl
+# 5.3. Repairs
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao"); # to process NAK requests
+
+print "app: publish test data.\n";
+$app->say ("send ao " . ("ringo" x 200));
+$app->say ("send ao " . ("ichigo" x 200));
+$app->say ("send ao " . ("momo" x 200));
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..3) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+for (1..1000) {
+ my $i = $_;
+ print "sim: $i# send nak to app.\n";
+ $sim->say ("net send nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 2");
+
+ print "mon: $i# wait for rdata ...\n";
+ $mon->wait_for_rdata;
+ print "mon: $i# rdata received.\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/ncf.pl b/3rdparty/openpgm-svn-r1135/pgm/test/ncf.pl
new file mode 100755
index 0000000..128db2a
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/ncf.pl
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+# ncf.pl
+# 5.2. Negative Acknowledgment Confirmation
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+$app->say ("send ao ichigo");
+$app->say ("send ao momo");
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..3) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+print "sim: send nak to app.\n";
+$sim->say ("net send nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 2");
+
+print "mon: wait for ncf ...\n";
+$mon->wait_for_ncf;
+print "mon: ncf received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/ncf_cancellation.pl b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_cancellation.pl
new file mode 100755
index 0000000..fcfbf2b
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_cancellation.pl
@@ -0,0 +1,149 @@
+#!/usr/bin/perl
+# ncf_cancellation.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use IO::Handle;
+use JSON;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!";
+FROM_PARENT->autoflush(1);
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ close FROM_PARENT; close TO_CHILD;
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+my $t0 = [gettimeofday];
+
+if (my $pid = fork) {
+# parent
+ close FROM_PARENT;
+
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data({ 'timeout' => 0 });
+ print "app: data received [$data].\n";
+
+ print TO_CHILD "die\n";
+
+ close TO_CHILD;
+ waitpid($pid,0);
+} else {
+# child
+ die "cannot fork: $!" unless defined $pid;
+ close TO_CHILD;
+ print "sim: loop waiting for NAKs ...\n";
+
+ my $fh = $sim->{in};
+ vec(my $rin, fileno(FROM_PARENT), 1) = 1;
+ vec($rin, fileno($fh), 1) = 1;
+ my $rout = undef;
+
+ my $b = '';
+ my $state = 0;
+ my $json = new JSON;
+ my $io = IO::Handle->new_from_fd( fileno($fh), "r" );
+ my $cnt = 0;
+ while (select($rout = $rin, undef, undef, undef))
+ {
+ last if( vec($rout, fileno(FROM_PARENT), 1) );
+ last unless (defined($_ = $io->getline));
+ chomp;
+ my $l = $_;
+ if ($state == 0) {
+ if ($l =~ /{$/) {
+ $state = 1;
+ } else {
+ print "sim [$l]\n";
+ }
+ }
+
+ if ($state == 1) {
+ $b .= $l;
+
+ if ($l =~ /^}$/) {
+ $state = 0;
+
+ my $obj = $json->jsonToObj($b);
+ if ($obj->{PGM}->{type} =~ /NAK/) {
+ $cnt++;
+ my $elapsed = tv_interval ( $t0, [gettimeofday] );
+ print "sim: $cnt x NAK received in $elapsed seconds.\n";
+ }
+
+# reset
+ $b = '';
+ }
+ }
+ }
+
+ print "sim: loop finished.\n";
+ close FROM_PARENT;
+ exit;
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/ncf_list.pl b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_list.pl
new file mode 100755
index 0000000..d3082e4
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_list.pl
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+# ncf_list.pl
+# 9.3. NAK List Option - OPT_NAK_LIST
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+$app->say ("send ao ichigo");
+$app->say ("send ao momo");
+
+my $odata = undef;
+my $ocnt = 0;
+for (1..3) {
+ print "mon: wait for odata ...\n";
+ $odata = $mon->wait_for_odata;
+ $ocnt++;
+ print "mon: received $ocnt x odata.\n";
+}
+
+print "sim: send nak to app.\n";
+$sim->say ("net send nak ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 1,2,3");
+
+print "mon: wait for ncf ...\n";
+my $ncf = $mon->wait_for_ncf;
+print "mon: ncf received.\n";
+die "ncfSqn != 1\n" unless $ncf->{PGM}->{ncfSqn} == 1;
+die "NCF list incorrect\n" unless (
+ $ncf->{PGM}->{pgmOptions}[1]->{sqn}[0] == 2
+ && $ncf->{PGM}->{pgmOptions}[1]->{sqn}[1] == 3
+ );
+print "mon: ncf list correct: $ncf->{PGM}->{ncfSqn} + [$ncf->{PGM}->{pgmOptions}[1]->{sqn}[0], $ncf->{PGM}->{pgmOptions}[1]->{sqn}[1]]\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/ncf_suppression.pl b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_suppression.pl
new file mode 100755
index 0000000..fbb3de7
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/ncf_suppression.pl
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+# ncf_suppression.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+## first run through with regular NAK generation to get regular backoff interval
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+my $t0 = [gettimeofday];
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+my $normal_backoff = tv_interval ( $t0, [gettimeofday] );
+print "sim: NAK received in $normal_backoff seconds.\n";
+
+## cleanup by publishing repair data
+print "sim: publish RDATA sqn 90,002.\n";
+$sim->say ("net send odata ao 90002 90001 momo");
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+## second run with NAK suppression
+$t0 = [gettimeofday];
+print "sim: publish ODATA sqn 90,005.\n";
+$sim->say ("net send odata ao 90005 90001 anzu");
+print "sim: publish NCF sqn 90,004.\n";
+$sim->say ("net send ncf ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 90004");
+
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+my $suppressed_backoff = tv_interval ( $t0, [gettimeofday] );
+print "sim: NAK received in $suppressed_backoff seconds.\n";
+
+die "NAK suppression failed.\n" unless ($suppressed_backoff > $normal_backoff);
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata.pl
new file mode 100755
index 0000000..8dd3580
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata.pl
@@ -0,0 +1,45 @@
+#!/usr/bin/perl
+# odata.pl
+# 3.6.2.1. ODATA - Original Data
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+
+print "app: publish test data.\n";
+$app->say ("send ao ringo");
+
+print "mon: wait for odata ...\n";
+$mon->wait_for_odata;
+print "mon: received odata.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_completion.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_completion.pl
new file mode 100755
index 0000000..655aeff
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_completion.pl
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+# odata_completion.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+print "sim: publish ODATA sqn 90,002.\n";
+$sim->say ("net send odata ao 90002 90001 momo");
+
+for (1..2)
+{
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data;
+ print "app: data received [$data].\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump.pl
new file mode 100755
index 0000000..748ff23
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+# odata_jump.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump_parity.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump_parity.pl
new file mode 100755
index 0000000..99179cc
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_jump_parity.pl
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+# odata_jump_parity.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+
+print "sim: publish SPM txw_trail 32,769 txw_lead 32,768 at spm_sqn 3200, advertise on-demand parity, k=4.\n";
+$sim->say ("net send spm ao 3200 32769 32768 on-demand 4");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 32,769.\n";
+$sim->say ("net send odata ao 32769 32769 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+# force window into next transmission group
+print "sim: publish ODATA sqn 32,771.\n";
+$sim->say ("net send odata ao 32771 32769 ichigo");
+print "sim: publish ODATA sqn 32,772.\n";
+$sim->say ("net send odata ao 32772 32769 momo");
+print "sim: publish ODATA sqn 32,773.\n";
+$sim->say ("net send odata ao 32773 32769 yakitori");
+print "sim: publish ODATA sqn 32,774.\n";
+$sim->say ("net send odata ao 32774 32769 sasami");
+print "sim: publish ODATA sqn 32,775.\n";
+$sim->say ("net send odata ao 32775 32769 tebasaki");
+
+print "sim: waiting for valid NAK.\n";
+my $nak = $sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+die "Selective NAK received, parityPacket=false\n" unless $nak->{PGM}->{options}->{parityPacket};
+print "Parity NAK received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_number.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_number.pl
new file mode 100755
index 0000000..e48a7e1
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_number.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+# odata_number.pl
+# 5.1.1. Maximum Cumulative Transmit Rate
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "app: send 1000 data packets ...\n";
+# hide stdout
+open(OLDOUT, ">&STDOUT");
+open(STDOUT, ">/dev/null") or die "Can't redirect stdout: $!";
+
+for (0..999)
+{
+ my $i = $_;
+ $app->say ("send ao $i");
+ my $odata = $mon->wait_for_odata;
+
+ die "out of sequence ODATA, received $odata->{PGM}->{odSqn} expected $i\n" unless $odata->{PGM}->{odSqn} == $i;
+}
+
+# restore stdout
+close(STDOUT) or die "Can't close STDOUT: $!";
+open(STDOUT, ">&OLDOUT") or die "Can't restore stdout: $!";
+close(OLDOUT) or die "Can't close OLDOUT: $!";
+
+print "mon: received 1000 x odata.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_rate.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_rate.pl
new file mode 100755
index 0000000..0db4d80
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_rate.pl
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+# odata_number.pl
+# 5.1.1. Maximum Cumulative Transmit Rate
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao TXW_MAX_RTE 1500");
+$app->say ("bind ao");
+$app->say ("connect ao");
+
+print "app: send 50 data packets ...\n";
+my $t0 = [gettimeofday];
+
+# hide stdout
+open(OLDOUT, ">&STDOUT");
+open(STDOUT, ">/dev/null") or die "Can't redirect stdout: $!";
+
+my $payload = "ringo" x 100;
+my $bytes = 0;
+for (1..50)
+{
+ $app->say ("send ao $payload");
+ my $odata = $mon->wait_for_odata;
+ $bytes += $odata->{IP}->{length};
+}
+
+close(STDOUT) or die "Can't close STDOUT: $!";
+open(STDOUT, ">&OLDOUT") or die "Can't restore stdout: $!";
+close(OLDOUT) or die "Can't close OLDOUT: $!";
+
+my $elapsed = tv_interval ( $t0, [gettimeofday] );
+print "mon: received 50 x odata, $bytes bytes in $elapsed seconds.\n";
+
+my $rate = $bytes / $elapsed;
+$rate = $bytes if ($rate > $bytes);
+print "mon: incoming data rate $rate bps.\n";
+
+die "incoming rate exceeds set TXW_MAX_RTE\n" unless $rate < 1650;
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/odata_reception.pl b/3rdparty/openpgm-svn-r1135/pgm/test/odata_reception.pl
new file mode 100755
index 0000000..4c47dc0
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/odata_reception.pl
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+# odata_reception.pl
+# 6.1. Data Reception
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish ODATA sqn 90,000.\n";
+$sim->say ("net send odata ao 90000 90000 ringo");
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+# no NAKs should be generated.
+# TODO: test for silence in {mon}
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90000 ichigo");
+print "app: wait for data ...\n";
+$data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/on-demand_spm.pl b/3rdparty/openpgm-svn-r1135/pgm/test/on-demand_spm.pl
new file mode 100755
index 0000000..eb86cf9
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/on-demand_spm.pl
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+# on-demand_spm.pl
+# 5.1.4. Ambient SPMs with on-demand parity flag
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao FEC RS(255,64)");
+$app->say ("bind ao");
+print "app: ready.\n";
+
+print "mon: wait for spm ...\n";
+my $spm = $mon->wait_for_spm;
+print "mon: received spm.\n";
+
+die "SPM does not contain any PGM options\n" unless $spm->{PGM}->{pgmOptions};
+die "SPM does not contain a PGM_OPT_PARITY_PRM option\n" unless $spm->{PGM}->{pgmOptions}[1]->{type} =~ /OPT_PARITY_PRM/;
+print "pro-active parity " . ($spm->{PGM}->{pgmOptions}[1]->{'P-bit'} ? 'enabled' : 'disabled') . ", P-bit " . ($spm->{PGM}->{pgmOptions}[1]->{'P-bit'} ? 'true' : 'false') . "\n";
+die "on-demand parity disabled, O-bit false\n" unless $spm->{PGM}->{pgmOptions}[1]->{'O-bit'};
+print "on-demand parity enabled, O-bit true\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/outofwindow_ncf.pl b/3rdparty/openpgm-svn-r1135/pgm/test/outofwindow_ncf.pl
new file mode 100755
index 0000000..3e1eb88
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/outofwindow_ncf.pl
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+# outofwindow_ncf.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+## first run through with regular NAK generation to get regular backoff interval
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+my $t0 = [gettimeofday];
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+my $normal_backoff = tv_interval ( $t0, [gettimeofday] );
+print "sim: NAK received in $normal_backoff seconds.\n";
+
+## cleanup by publishing repair data
+print "sim: publish RDATA sqn 90,002.\n";
+$sim->say ("net send odata ao 90002 90001 momo");
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+## second run with NAK suppression
+$t0 = [gettimeofday];
+print "sim: publish ODATA sqn 90,005.\n";
+$sim->say ("net send odata ao 90005 90001 anzu");
+print "sim: publish NCF sqn 90,004.\n";
+$sim->say ("net send ncf ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort} 90002");
+
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+my $outofwindow_backoff = tv_interval ( $t0, [gettimeofday] );
+print "sim: NAK received in $outofwindow_backoff seconds.\n";
+
+# allow 100ms tolerance
+my $fabs = abs( ($outofwindow_backoff - $normal_backoff) * 1000 );
+die "Out-of-window NCF altered back-off interval by $fabs ms.\n" unless ($fabs < 100);
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion.pl b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion.pl
new file mode 100755
index 0000000..3b290ee
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion.pl
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+# rdata_completion.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,003.\n";
+$sim->say ("net send odata ao 90003 90001 ichigo");
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+print "sim: publish RDATA sqn 90,002.\n";
+$sim->say ("net send rdata ao 90002 90001 momo");
+
+for (1..2)
+{
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data;
+ print "app: data received [$data].\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity.pl b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity.pl
new file mode 100755
index 0000000..95a6f7b
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity.pl
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+# rdata_completion_parity.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao FEC RS(255,4)");
+$app->say ("bind ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("set ao FEC RS(255,4)");
+$sim->say ("bind ao");
+
+print "sim: publish SPM txw_trail 32,769 txw_lead 32,768 at spm_sqn 3200, advertise on-demand parity, k=4.\n";
+$sim->say ("net send spm ao 3200 32768 32767 on-demand 4");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 32,768.\n";
+$sim->say ("net send odata ao 32768 32768 ringo000");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 32,770.\n";
+$sim->say ("net send odata ao 32770 32768 momo0000");
+print "sim: publish ODATA sqn 32,771.\n";
+$sim->say ("net send odata ao 32771 32768 yakitori");
+print "sim: publish ODATA sqn 32,772.\n";
+$sim->say ("net send odata ao 32772 32768 sasami00");
+print "sim: publish ODATA sqn 32,773.\n";
+$sim->say ("net send odata ao 32773 32768 tebasaki");
+
+print "sim: waiting for valid parity NAK.\n";
+my $nak = $sim->wait_for_nak;
+die "Selective NAK received, parityPacket=false\n" unless $nak->{PGM}->{options}->{parityPacket};
+print "sim: Parity NAK received.\n";
+
+print "sim: publish parity RDATA, tg_sqn 32,768, pkt_cnt 1 (sqn 32,768).\n";
+$sim->say ("net send parity rdata ao 32768 32768 ringo000 ichigo00 momo0000 yakitori");
+
+for (1..5)
+{
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data;
+ print "app: data received [$data].\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity_var_pktlen.pl b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity_var_pktlen.pl
new file mode 100755
index 0000000..9011ea1
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_completion_parity_var_pktlen.pl
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+# rdata_completion_parity_var_pktlen.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("set ao FEC RS(255,4)");
+$app->say ("bind ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("set ao FEC RS(255,4)");
+$sim->say ("bind ao");
+
+print "sim: publish SPM txw_trail 32,769 txw_lead 32,768 at spm_sqn 3200, advertise on-demand parity, k=4.\n";
+$sim->say ("net send spm ao 3200 32768 32767 on-demand 4");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 32,768.\n";
+$sim->say ("net send odata ao 32768 32768 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak ({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 32,770.\n";
+$sim->say ("net send odata ao 32770 32768 momo");
+print "sim: publish ODATA sqn 32,771.\n";
+$sim->say ("net send odata ao 32771 32768 yakitori");
+print "sim: publish ODATA sqn 32,772.\n";
+$sim->say ("net send odata ao 32772 32768 sasami");
+print "sim: publish ODATA sqn 32,773.\n";
+$sim->say ("net send odata ao 32773 32768 tebasaki");
+
+print "sim: waiting for valid parity NAK.\n";
+my $nak = $sim->wait_for_nak;
+die "Selective NAK received, parityPacket=false\n" unless $nak->{PGM}->{options}->{parityPacket};
+print "sim: Parity NAK received.\n";
+
+print "sim: publish parity RDATA, tg_sqn 32,768, pkt_cnt 1 (sqn 32,768).\n";
+$sim->say ("net send parity rdata ao 32768 32768 ringo ichigo momo yakitori");
+
+for (1..5)
+{
+ print "app: wait for data ...\n";
+ my $data = $app->wait_for_data;
+ print "app: data received [$data].\n";
+}
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/rdata_jump.pl b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_jump.pl
new file mode 100755
index 0000000..dbb3e02
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_jump.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+# rdata_jump.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: data received [$data].\n";
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish RDATA sqn 90,003.\n";
+$sim->say ("net send rdata ao 90003 90001 ichigo");
+
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/rdata_reception.pl b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_reception.pl
new file mode 100755
index 0000000..3592cc7
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/rdata_reception.pl
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+# rdata_reception.pl
+# 6.1. Data Reception
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish RDATA sqn 90,000.\n";
+$sim->say ("net send rdata ao 90000 90000 ringo");
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+# no NAKs should be generated.
+# TODO: test for silence in {mon}
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90000 ichigo");
+print "app: wait for data ...\n";
+$data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/sim.c b/3rdparty/openpgm-svn-r1135/pgm/test/sim.c
new file mode 100644
index 0000000..d600fae
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/sim.c
@@ -0,0 +1,2301 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * PGM conformance endpoint simulator.
+ *
+ * Copyright (c) 2006-2008 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <errno.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <regex.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <glib.h>
+
+#include <impl/framework.h>
+#include <impl/socket.h>
+#include <impl/sqn_list.h>
+#include <impl/packet_parse.h>
+#include <pgm/pgm.h>
+#include <pgm/backtrace.h>
+#include <pgm/log.h>
+#include <pgm/signal.h>
+
+#include "dump-json.h"
+#include "async.h"
+
+
+/* typedefs */
+
+struct idle_source {
+ GSource source;
+ guint64 expiration;
+};
+
+struct sim_session {
+ char* name;
+ pgm_sock_t* sock;
+ gboolean is_transport_fake;
+ GIOChannel* recv_channel;
+ pgm_async_t* async;
+};
+
+/* globals */
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sim"
+
+#ifndef SOL_IP
+# define SOL_IP IPPROTO_IP
+#endif
+#ifndef SOL_IPV6
+# define SOL_IPV6 IPPROTO_IPV6
+#endif
+
+
+static int g_port = 7500;
+static const char* g_network = ";239.192.0.1";
+
+static int g_max_tpdu = 1500;
+static int g_sqns = 100 * 1000;
+
+static GList* g_sessions_list = NULL;
+static GHashTable* g_sessions = NULL;
+static GMainLoop* g_loop = NULL;
+static GIOChannel* g_stdin_channel = NULL;
+
+
+static void on_signal (int, gpointer);
+static gboolean on_startup (gpointer);
+static gboolean on_mark (gpointer);
+static void destroy_session (struct sim_session*);
+static int on_data (gpointer, guint, gpointer);
+static gboolean on_stdin_data (GIOChannel*, GIOCondition, gpointer);
+void generic_net_send_nak (guint8, char*, pgm_tsi_t*, struct pgm_sqn_list_t*);
+
+
+G_GNUC_NORETURN static
+void
+usage (const char* bin)
+{
+ fprintf (stderr, "Usage: %s [options]\n", bin);
+ fprintf (stderr, " -n <network> : Multicast group or unicast IP address\n");
+ fprintf (stderr, " -s <port> : IP port\n");
+ exit (1);
+}
+
+int
+main (
+ int argc,
+ char *argv[]
+ )
+{
+ pgm_error_t* pgm_err = NULL;
+
+/* pre-initialise PGM messages module to add hook for GLib logging */
+ pgm_messages_init();
+ log_init ();
+ g_message ("sim");
+
+ if (!pgm_init (&pgm_err)) {
+ g_error ("Unable to start PGM engine: %s", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ pgm_messages_shutdown();
+ return EXIT_FAILURE;
+ }
+
+/* parse program arguments */
+ const char* binary_name = strrchr (argv[0], '/');
+ int c;
+ while ((c = getopt (argc, argv, "s:n:h")) != -1)
+ {
+ switch (c) {
+ case 'n': g_network = optarg; break;
+ case 's': g_port = atoi (optarg); break;
+
+ case 'h':
+ case '?':
+ pgm_messages_shutdown();
+ usage (binary_name);
+ }
+ }
+
+ g_loop = g_main_loop_new (NULL, FALSE);
+
+/* setup signal handlers */
+ signal (SIGSEGV, on_sigsegv);
+ signal (SIGHUP, SIG_IGN);
+ pgm_signal_install (SIGINT, on_signal, g_loop);
+ pgm_signal_install (SIGTERM, on_signal, g_loop);
+
+/* delayed startup */
+ g_message ("scheduling startup.");
+ g_timeout_add (0, (GSourceFunc)on_startup, NULL);
+
+/* dispatch loop */
+ g_message ("entering main event loop ... ");
+ g_main_loop_run (g_loop);
+
+ g_message ("event loop terminated, cleaning up.");
+
+/* cleanup */
+ g_main_loop_unref(g_loop);
+ g_loop = NULL;
+
+ if (g_sessions) {
+ g_message ("destroying sessions.");
+ while (g_sessions_list) {
+ destroy_session (g_sessions_list->data);
+ g_sessions_list = g_list_delete_link (g_sessions_list, g_sessions_list);
+ }
+ g_hash_table_unref (g_sessions);
+ g_sessions = NULL;
+ }
+
+ if (g_stdin_channel) {
+ puts ("unbinding stdin.");
+ g_io_channel_unref (g_stdin_channel);
+ g_stdin_channel = NULL;
+ }
+
+ g_message ("PGM engine shutdown.");
+ pgm_shutdown();
+ g_message ("finished.");
+ pgm_messages_shutdown();
+ return EXIT_SUCCESS;
+}
+
+static
+void
+destroy_session (
+ struct sim_session* sess
+ )
+{
+ printf ("destroying socket \"%s\"\n", sess->name);
+ pgm_close (sess->sock, TRUE);
+ sess->sock = NULL;
+ g_free (sess->name);
+ sess->name = NULL;
+ g_free (sess);
+}
+
+static
+void
+on_signal (
+ int signum,
+ gpointer user_data
+ )
+{
+ GMainLoop* loop = (GMainLoop*)user_data;
+ g_message ("on_signal (signum:%d user-data:%p)", signum, user_data);
+ g_main_loop_quit (loop);
+}
+
+static
+gboolean
+on_startup (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ g_message ("startup.");
+
+ g_sessions = g_hash_table_new (g_str_hash, g_str_equal);
+
+/* add stdin to event manager */
+ g_stdin_channel = g_io_channel_unix_new (fileno(stdin));
+ printf ("binding stdin with encoding %s.\n", g_io_channel_get_encoding(g_stdin_channel));
+
+ g_io_add_watch (g_stdin_channel, G_IO_IN | G_IO_PRI, on_stdin_data, NULL);
+
+/* period timer to indicate some form of life */
+// TODO: Gnome 2.14: replace with g_timeout_add_seconds()
+ g_timeout_add(10 * 1000, (GSourceFunc)on_mark, NULL);
+
+ puts ("READY");
+ fflush (stdout);
+ return FALSE;
+}
+
+static
+bool
+fake_pgm_socket (
+ pgm_sock_t**restrict sock,
+ const sa_family_t family,
+ const int pgm_sock_type,
+ const int protocol,
+ G_GNUC_UNUSED pgm_error_t**restrict error
+ )
+{
+ pgm_sock_t* new_sock;
+
+ g_return_val_if_fail (NULL != sock, FALSE);
+ g_return_val_if_fail (AF_INET == family || AF_INET6 == family, FALSE);
+ g_return_val_if_fail (SOCK_SEQPACKET == pgm_sock_type, FALSE);
+ g_return_val_if_fail (IPPROTO_UDP == protocol || IPPROTO_PGM == protocol, FALSE);
+
+ new_sock = pgm_new0 (pgm_sock_t, 1);
+ new_sock->family = family;
+ new_sock->socket_type = pgm_sock_type;
+ new_sock->protocol = protocol;
+ new_sock->can_send_data = TRUE;
+ new_sock->can_send_nak = TRUE;
+ new_sock->can_recv_data = TRUE;
+ new_sock->dport = DEFAULT_DATA_DESTINATION_PORT;
+ new_sock->tsi.sport = DEFAULT_DATA_SOURCE_PORT;
+ new_sock->adv_mode = 0; /* advance with time */
+
+/* PGMCC */
+ new_sock->acker_nla.ss_family = family;
+
+/* open sockets to implement PGM */
+ int socket_type;
+ if (IPPROTO_UDP == new_sock->protocol) {
+ puts ("Opening UDP encapsulated sockets.");
+ socket_type = SOCK_DGRAM;
+ new_sock->udp_encap_ucast_port = DEFAULT_UDP_ENCAP_UCAST_PORT;
+ new_sock->udp_encap_mcast_port = DEFAULT_UDP_ENCAP_MCAST_PORT;
+ } else {
+ puts ("Opening raw sockets.");
+ socket_type = SOCK_RAW;
+ }
+
+ if ((new_sock->recv_sock = socket (new_sock->family,
+ socket_type,
+ new_sock->protocol)) == PGM_INVALID_SOCKET)
+ {
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Creating receive socket: %s",
+ pgm_sock_strerror (save_errno));
+#ifndef _WIN32
+ if (EPERM == save_errno) {
+ g_critical ("PGM protocol requires CAP_NET_RAW capability, e.g. sudo execcap 'cap_net_raw=ep'");
+ }
+#endif
+ goto err_destroy;
+ }
+
+ if ((new_sock->send_sock = socket (new_sock->family,
+ socket_type,
+ new_sock->protocol)) == PGM_INVALID_SOCKET)
+ {
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Creating send socket: %s",
+ pgm_sock_strerror (save_errno));
+ goto err_destroy;
+ }
+
+ if ((new_sock->send_with_router_alert_sock = socket (new_sock->family,
+ socket_type,
+ new_sock->protocol)) == PGM_INVALID_SOCKET)
+ {
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Creating IP Router Alert (RFC 2113) send socket: %s",
+ pgm_sock_strerror (save_errno));
+ goto err_destroy;
+ }
+
+ *sock = new_sock;
+
+ puts ("PGM socket successfully created.");
+ return TRUE;
+
+err_destroy:
+ if (PGM_INVALID_SOCKET != new_sock->recv_sock) {
+ if (PGM_SOCKET_ERROR == pgm_closesocket (new_sock->recv_sock)) {
+ const int save_errno = pgm_sock_errno();
+ g_warning ("Close on receive socket failed: %s",
+ pgm_sock_strerror (save_errno));
+ }
+ new_sock->recv_sock = PGM_INVALID_SOCKET;
+ }
+ if (PGM_INVALID_SOCKET != new_sock->send_sock) {
+ if (PGM_SOCKET_ERROR == pgm_closesocket (new_sock->send_sock)) {
+ const int save_errno = pgm_sock_errno();
+ g_warning ("Close on send socket failed: %s",
+ pgm_sock_strerror (save_errno));
+ }
+ new_sock->send_sock = PGM_INVALID_SOCKET;
+ }
+ if (PGM_INVALID_SOCKET != new_sock->send_with_router_alert_sock) {
+ if (PGM_SOCKET_ERROR == pgm_closesocket (new_sock->send_with_router_alert_sock)) {
+ const int save_errno = pgm_sock_errno();
+ g_warning ("Close on IP Router Alert (RFC 2113) send socket failed: %s",
+ pgm_sock_strerror (save_errno));
+ }
+ new_sock->send_with_router_alert_sock = PGM_INVALID_SOCKET;
+ }
+ pgm_free (new_sock);
+ return FALSE;
+}
+
+static
+gboolean
+on_io_data (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data
+ )
+{
+ pgm_sock_t* sock = data;
+
+ struct pgm_sk_buff_t* skb = pgm_alloc_skb (sock->max_tpdu);
+ int fd = g_io_channel_unix_get_fd(source);
+ struct sockaddr_storage src_addr;
+ socklen_t src_addr_len = sizeof(src_addr);
+ skb->len = recvfrom(fd, skb->head, sock->max_tpdu, MSG_DONTWAIT, (struct sockaddr*)&src_addr, &src_addr_len);
+
+ printf ("%i bytes received from %s.\n", skb->len, inet_ntoa(((struct sockaddr_in*)&src_addr)->sin_addr));
+
+ monitor_packet (skb->data, skb->len);
+ fflush (stdout);
+
+/* parse packet to maintain peer database */
+ if (sock->udp_encap_ucast_port) {
+ if (!pgm_parse_udp_encap (skb, NULL))
+ goto out;
+ } else {
+ struct sockaddr_storage addr;
+ if (!pgm_parse_raw (skb, (struct sockaddr*)&addr, NULL))
+ goto out;
+ }
+
+ if (PGM_IS_UPSTREAM (skb->pgm_header->pgm_type) ||
+ PGM_IS_PEER (skb->pgm_header->pgm_type))
+ goto out; /* ignore */
+
+/* downstream = source to receivers */
+ if (!PGM_IS_DOWNSTREAM (skb->pgm_header->pgm_type))
+ goto out;
+
+/* pgm packet DPORT contains our transport DPORT */
+ if (skb->pgm_header->pgm_dport != sock->dport)
+ goto out;
+
+/* search for TSI peer context or create a new one */
+ pgm_peer_t* sender = pgm_hashtable_lookup (sock->peers_hashtable, &skb->tsi);
+ if (sender == NULL)
+ {
+ printf ("new peer, tsi %s, local nla %s\n",
+ pgm_tsi_print (&skb->tsi),
+ inet_ntoa(((struct sockaddr_in*)&src_addr)->sin_addr));
+
+ pgm_peer_t* peer = g_new0 (pgm_peer_t, 1);
+ peer->sock = sock;
+ memcpy (&peer->tsi, &skb->tsi, sizeof(pgm_tsi_t));
+ ((struct sockaddr_in*)&peer->nla)->sin_addr.s_addr = INADDR_ANY;
+ memcpy (&peer->local_nla, &src_addr, src_addr_len);
+
+ pgm_hashtable_insert (sock->peers_hashtable, &peer->tsi, peer);
+ sender = peer;
+ }
+
+/* handle SPMs for advertised NLA */
+ if (skb->pgm_header->pgm_type == PGM_SPM)
+ {
+ char *pgm_data = (char*)(skb->pgm_header + 1);
+ struct pgm_spm* spm = (struct pgm_spm*)pgm_data;
+ guint32 spm_sqn = g_ntohl (spm->spm_sqn);
+
+ if ( pgm_uint32_gte (spm_sqn, sender->spm_sqn)
+ || ( ((struct sockaddr*)&sender->nla)->sa_family == 0 ) )
+ {
+ pgm_nla_to_sockaddr (&spm->spm_nla_afi, (struct sockaddr*)&sender->nla);
+ sender->spm_sqn = spm_sqn;
+ }
+ }
+
+out:
+ return TRUE;
+}
+
+static
+bool
+fake_pgm_bind3 (
+ pgm_sock_t* restrict sock,
+ const struct pgm_sockaddr_t*const restrict sockaddr,
+ const socklen_t sockaddrlen,
+ const struct pgm_interface_req_t*const send_req, /* only use gr_interface and gr_group::sin6_scope */
+ const socklen_t send_req_len,
+ const struct pgm_interface_req_t*const recv_req,
+ const socklen_t recv_req_len,
+ pgm_error_t** restrict error /* maybe NULL */
+ )
+{
+ g_return_val_if_fail (NULL != sock, FALSE);
+ g_return_val_if_fail (NULL != sockaddr, FALSE);
+ g_return_val_if_fail (0 != sockaddrlen, FALSE);
+ if (sockaddr->sa_addr.sport) pgm_return_val_if_fail (sockaddr->sa_addr.sport != sockaddr->sa_port, FALSE);
+ g_return_val_if_fail (NULL != send_req, FALSE);
+ g_return_val_if_fail (sizeof(struct pgm_interface_req_t) == send_req_len, FALSE);
+ g_return_val_if_fail (NULL != recv_req, FALSE);
+ g_return_val_if_fail (sizeof(struct pgm_interface_req_t) == recv_req_len, FALSE);
+
+ if (sock->is_bound ||
+ sock->is_destroyed)
+ {
+ pgm_return_val_if_reached (FALSE);
+ }
+
+ memcpy (&sock->tsi, &sockaddr->sa_addr, sizeof(pgm_tsi_t));
+ sock->dport = htons (sockaddr->sa_port);
+ if (sock->tsi.sport) {
+ sock->tsi.sport = htons (sock->tsi.sport);
+ } else {
+ do {
+ sock->tsi.sport = htons (pgm_random_int_range (0, UINT16_MAX));
+ } while (sock->tsi.sport == sock->dport);
+ }
+
+/* UDP encapsulation port */
+ if (sock->udp_encap_mcast_port) {
+ ((struct sockaddr_in*)&sock->send_gsr.gsr_group)->sin_port = htons (sock->udp_encap_mcast_port);
+ }
+
+/* pseudo-random number generator for back-off intervals */
+ pgm_rand_create (&sock->rand_);
+
+/* PGM Children support of POLLs requires 32-bit random node identifier RAND_NODE_ID */
+ if (sock->can_recv_data) {
+ sock->rand_node_id = pgm_rand_int (&sock->rand_);
+ }
+
+/* determine IP header size for rate regulation engine & stats */
+ sock->iphdr_len = (AF_INET == sock->family) ? sizeof(struct pgm_ip) : sizeof(struct pgm_ip6_hdr);
+ pgm_trace (PGM_LOG_ROLE_NETWORK,"Assuming IP header size of %zu bytes", sock->iphdr_len);
+
+ if (sock->udp_encap_ucast_port) {
+ const size_t udphdr_len = sizeof(struct pgm_udphdr);
+ printf ("Assuming UDP header size of %zu bytes\n", udphdr_len);
+ sock->iphdr_len += udphdr_len;
+ }
+
+ const sa_family_t pgmcc_family = sock->use_pgmcc ? sock->family : 0;
+ sock->max_tsdu = sock->max_tpdu - sock->iphdr_len - pgm_pkt_offset (FALSE, pgmcc_family);
+ sock->max_tsdu_fragment = sock->max_tpdu - sock->iphdr_len - pgm_pkt_offset (TRUE, pgmcc_family);
+ const unsigned max_fragments = sock->txw_sqns ? MIN( PGM_MAX_FRAGMENTS, sock->txw_sqns ) : PGM_MAX_FRAGMENTS;
+ sock->max_apdu = MIN( PGM_MAX_APDU, max_fragments * sock->max_tsdu_fragment );
+
+/* create peer list */
+ if (sock->can_recv_data) {
+ sock->peers_hashtable = pgm_hashtable_new (pgm_tsi_hash, pgm_tsi_equal);
+ pgm_assert (NULL != sock->peers_hashtable);
+ }
+
+/* IP/PGM only */
+ {
+ const sa_family_t recv_family = sock->family;
+ if (AF_INET == recv_family)
+ {
+/* include IP header only for incoming data, only works for IPv4 */
+ puts ("Request IP headers.");
+ if (PGM_SOCKET_ERROR == pgm_sockaddr_hdrincl (sock->recv_sock, recv_family, TRUE))
+ {
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Enabling IP header in front of user data: %s",
+ pgm_sock_strerror (save_errno));
+ return FALSE;
+ }
+ }
+ else
+ {
+ pgm_assert (AF_INET6 == recv_family);
+ puts ("Request socket packet-info.");
+ if (PGM_SOCKET_ERROR == pgm_sockaddr_pktinfo (sock->recv_sock, recv_family, TRUE))
+ {
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Enabling receipt of control message per incoming datagram: %s",
+ pgm_sock_strerror (save_errno));
+ return FALSE;
+ }
+ }
+ }
+
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in s4;
+ struct sockaddr_in6 s6;
+ struct sockaddr_storage ss;
+ } recv_addr, recv_addr2, send_addr, send_with_router_alert_addr;
+
+#ifdef CONFIG_BIND_INADDR_ANY
+/* force default interface for bind-only, source address is still valid for multicast membership.
+ * effectively same as running getaddrinfo(hints = {ai_flags = AI_PASSIVE})
+ */
+ if (AF_INET == sock->family) {
+ memset (&recv_addr.s4, 0, sizeof(struct sockaddr_in));
+ recv_addr.s4.sin_family = AF_INET;
+ recv_addr.s4.sin_addr.s_addr = INADDR_ANY;
+ } else {
+ memset (&recv_addr.s6, 0, sizeof(struct sockaddr_in6));
+ recv_addr.s6.sin6_family = AF_INET6;
+ recv_addr.s6.sin6_addr = in6addr_any;
+ }
+ puts ("Binding receive socket to INADDR_ANY.");
+#else
+ if (!pgm_if_indextoaddr (recv_req->ir_interface,
+ sock->family,
+ recv_req->ir_scope_id,
+ &recv_addr.sa,
+ error))
+ {
+ return FALSE;
+ }
+ printf ("Binding receive socket to interface index %u scope %u\n"),
+ recv_req->ir_interface,
+ recv_req->ir_scope_id);
+
+#endif /* CONFIG_BIND_INADDR_ANY */
+
+ memcpy (&recv_addr2.sa, &recv_addr.sa, pgm_sockaddr_len (&recv_addr.sa));
+ ((struct sockaddr_in*)&recv_addr)->sin_port = htons (sock->udp_encap_mcast_port);
+ if (PGM_SOCKET_ERROR == bind (sock->recv_sock,
+ &recv_addr.sa,
+ pgm_sockaddr_len (&recv_addr.sa)))
+ {
+ char addr[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&recv_addr, addr, sizeof(addr));
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Binding receive socket to address %s: %s",
+ addr,
+ pgm_sock_strerror (save_errno));
+ return FALSE;
+ }
+
+ {
+ char s[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&recv_addr, s, sizeof(s));
+ printf ("bind succeeded on recv_gsr[0] interface %s\n", s);
+ }
+
+/* keep a copy of the original address source to re-use for router alert bind */
+ memset (&send_addr, 0, sizeof(send_addr));
+
+ if (!pgm_if_indextoaddr (send_req->ir_interface,
+ sock->family,
+ send_req->ir_scope_id,
+ (struct sockaddr*)&send_addr,
+ error))
+ {
+ return FALSE;
+ }
+ else
+ {
+ printf ("Binding send socket to interface index %u scope %u\n",
+ send_req->ir_interface,
+ send_req->ir_scope_id);
+ }
+
+ memcpy (&send_with_router_alert_addr, &send_addr, pgm_sockaddr_len ((struct sockaddr*)&send_addr));
+ if (PGM_SOCKET_ERROR == bind (sock->send_sock,
+ (struct sockaddr*)&send_addr,
+ pgm_sockaddr_len ((struct sockaddr*)&send_addr)))
+ {
+ char addr[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&send_addr, addr, sizeof(addr));
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Binding send socket to address %s: %s",
+ addr,
+ pgm_sock_strerror (save_errno));
+ return FALSE;
+ }
+
+/* resolve bound address if 0.0.0.0 */
+ if (AF_INET == send_addr.ss.ss_family)
+ {
+ if ((INADDR_ANY == ((struct sockaddr_in*)&send_addr)->sin_addr.s_addr) &&
+ !pgm_if_getnodeaddr (AF_INET, (struct sockaddr*)&send_addr, sizeof(send_addr), error))
+ {
+ return FALSE;
+ }
+ }
+ else if ((memcmp (&in6addr_any, &((struct sockaddr_in6*)&send_addr)->sin6_addr, sizeof(in6addr_any)) == 0) &&
+ !pgm_if_getnodeaddr (AF_INET6, (struct sockaddr*)&send_addr, sizeof(send_addr), error))
+ {
+ return FALSE;
+ }
+
+ {
+ char s[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&send_addr, s, sizeof(s));
+ printf ("bind succeeded on send_gsr interface %s\n", s);
+ }
+
+ if (PGM_SOCKET_ERROR == bind (sock->send_with_router_alert_sock,
+ (struct sockaddr*)&send_with_router_alert_addr,
+ pgm_sockaddr_len((struct sockaddr*)&send_with_router_alert_addr)))
+ {
+ char addr[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&send_with_router_alert_addr, addr, sizeof(addr));
+ const int save_errno = pgm_sock_errno();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_SOCKET,
+ pgm_error_from_sock_errno (save_errno),
+ "Binding IP Router Alert (RFC 2113) send socket to address %s: %s",
+ addr,
+ pgm_sock_strerror (save_errno));
+ return FALSE;
+ }
+
+ {
+ char s[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&send_with_router_alert_addr, s, sizeof(s));
+ printf ("bind (router alert) succeeded on send_gsr interface %s\n", s);
+ }
+
+/* save send side address for broadcasting as source nla */
+ memcpy (&sock->send_addr, &send_addr, pgm_sockaddr_len ((struct sockaddr*)&send_addr));
+
+ sock->is_controlled_spm = FALSE;
+ sock->is_controlled_odata = FALSE;
+ sock->is_controlled_rdata = FALSE;
+
+/* allocate first incoming packet buffer */
+ sock->rx_buffer = pgm_alloc_skb (sock->max_tpdu);
+
+/* bind complete */
+ sock->is_bound = TRUE;
+
+/* cleanup */
+ puts ("PGM socket successfully bound.");
+ return TRUE;
+}
+
+static
+bool
+fake_pgm_bind (
+ pgm_sock_t* restrict sock,
+ const struct pgm_sockaddr_t*const restrict sockaddr,
+ const socklen_t sockaddrlen,
+ pgm_error_t** restrict error
+ )
+{
+ struct pgm_interface_req_t null_req;
+ memset (&null_req, 0, sizeof(null_req));
+ return fake_pgm_bind3 (sock, sockaddr, sockaddrlen, &null_req, sizeof(null_req), &null_req, sizeof(null_req), error);
+}
+
+static
+bool
+fake_pgm_connect (
+ pgm_sock_t* restrict sock,
+ G_GNUC_UNUSED pgm_error_t** restrict error /* maybe NULL */
+ )
+{
+ g_return_val_if_fail (sock != NULL, FALSE);
+ g_return_val_if_fail (sock->recv_gsr_len > 0, FALSE);
+#ifdef CONFIG_TARGET_WINE
+ g_return_val_if_fail (sock->recv_gsr_len == 1, FALSE);
+#endif
+ for (unsigned i = 0; i < sock->recv_gsr_len; i++)
+ {
+ g_return_val_if_fail (sock->recv_gsr[i].gsr_group.ss_family == sock->recv_gsr[0].gsr_group.ss_family, FALSE);
+ g_return_val_if_fail (sock->recv_gsr[i].gsr_group.ss_family == sock->recv_gsr[i].gsr_source.ss_family, FALSE);
+ }
+ g_return_val_if_fail (sock->send_gsr.gsr_group.ss_family == sock->recv_gsr[0].gsr_group.ss_family, FALSE);
+/* state */
+ if (PGM_UNLIKELY(sock->is_connected || !sock->is_bound || sock->is_destroyed)) {
+ g_return_val_if_reached (FALSE);
+ }
+
+ sock->next_poll = pgm_time_update_now() + pgm_secs( 30 );
+ sock->is_connected = TRUE;
+
+/* cleanup */
+ puts ("PGM socket successfully connected.");
+ return TRUE;
+}
+
+
+static
+bool
+fake_pgm_close (
+ pgm_sock_t* sock,
+ G_GNUC_UNUSED bool flush
+ )
+{
+ g_return_val_if_fail (sock != NULL, FALSE);
+ g_return_val_if_fail (!sock->is_destroyed, FALSE);
+/* flag existing calls */
+ sock->is_destroyed = TRUE;
+/* cancel running blocking operations */
+ if (PGM_INVALID_SOCKET != sock->recv_sock) {
+ puts ("Closing receive socket.");
+ pgm_closesocket (sock->recv_sock);
+ sock->recv_sock = PGM_INVALID_SOCKET;
+ }
+ if (PGM_INVALID_SOCKET != sock->send_sock) {
+ puts ("Closing send socket.");
+ pgm_closesocket (sock->send_sock);
+ sock->send_sock = PGM_INVALID_SOCKET;
+ }
+ if (sock->peers_hashtable) {
+ pgm_hashtable_destroy (sock->peers_hashtable);
+ sock->peers_hashtable = NULL;
+ }
+ if (sock->peers_list) {
+ do {
+ pgm_list_t* next = sock->peers_list->next;
+ pgm_peer_unref ((pgm_peer_t*)sock->peers_list->data);
+
+ sock->peers_list = next;
+ } while (sock->peers_list);
+ }
+ if (PGM_INVALID_SOCKET != sock->send_with_router_alert_sock) {
+ puts ("Closing send with router alert socket.");
+ pgm_closesocket (sock->send_with_router_alert_sock);
+ sock->send_with_router_alert_sock = PGM_INVALID_SOCKET;
+ }
+ if (sock->spm_heartbeat_interval) {
+ puts ("freeing SPM heartbeat interval data.");
+ g_free (sock->spm_heartbeat_interval);
+ sock->spm_heartbeat_interval = NULL;
+ }
+ if (sock->rx_buffer) {
+ puts ("freeing receive buffer.");
+ pgm_free_skb (sock->rx_buffer);
+ sock->rx_buffer = NULL;
+ }
+
+ g_free (sock);
+ return TRUE;
+}
+
+static
+void
+session_create (
+ char* session_name,
+ gboolean is_fake
+ )
+{
+ pgm_error_t* pgm_err = NULL;
+ gboolean status;
+
+/* check for duplicate */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess != NULL) {
+ printf ("FAILED: duplicate session name '%s'\n", session_name);
+ return;
+ }
+
+/* create new and fill in bits */
+ sess = g_new0(struct sim_session, 1);
+ sess->name = g_memdup (session_name, strlen(session_name)+1);
+
+ if (is_fake) {
+ sess->is_transport_fake = TRUE;
+ status = fake_pgm_socket (&sess->sock, AF_INET, SOCK_SEQPACKET, IPPROTO_PGM, &pgm_err);
+ } else {
+ status = pgm_socket (&sess->sock, AF_INET, SOCK_SEQPACKET, IPPROTO_PGM, &pgm_err);
+ }
+ if (!status) {
+ printf ("FAILED: pgm_socket(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ goto err_free;
+ }
+
+/* success */
+ g_hash_table_insert (g_sessions, sess->name, sess);
+ g_sessions_list = g_list_prepend (g_sessions_list, sess);
+ printf ("created new session \"%s\"\n", sess->name);
+ puts ("READY");
+ return;
+
+err_free:
+ g_free(sess->name);
+ g_free(sess);
+}
+
+static
+void
+session_set_fec (
+ char* session_name,
+ guint block_size,
+ guint group_size
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (block_size > UINT8_MAX ||
+ group_size > UINT8_MAX)
+ {
+ puts ("FAILED: value out of bounds");
+ return;
+ }
+
+ const struct pgm_fecinfo_t fecinfo = {
+ .block_size = block_size,
+ .proactive_packets = 0,
+ .group_size = group_size,
+ .ondemand_parity_enabled = TRUE,
+ .var_pktlen_enabled = TRUE
+ };
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_USE_FEC, &fecinfo, sizeof(fecinfo)))
+ printf ("FAILED: set FEC = RS(%d, %d)\n", block_size, group_size);
+ else
+ puts ("READY");
+}
+
+static
+void
+session_bind (
+ char* session_name
+ )
+{
+ pgm_error_t* pgm_err = NULL;
+
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* Use RFC 2113 tagging for PGM Router Assist */
+ const int no_router_assist = 0;
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_IP_ROUTER_ALERT, &no_router_assist, sizeof(no_router_assist)))
+ puts ("FAILED: disable IP_ROUTER_ALERT");
+
+/* set PGM parameters */
+ const int send_and_receive = 0,
+ active = 0,
+ mtu = g_max_tpdu,
+ txw_sqns = g_sqns,
+ rxw_sqns = g_sqns,
+ ambient_spm = pgm_secs (30),
+ heartbeat_spm[] = { pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (100),
+ pgm_msecs (1300),
+ pgm_secs (7),
+ pgm_secs (16),
+ pgm_secs (25),
+ pgm_secs (30) },
+ peer_expiry = pgm_secs (300),
+ spmr_expiry = pgm_msecs (250),
+ nak_bo_ivl = pgm_msecs (50),
+ nak_rpt_ivl = pgm_secs (2),
+ nak_rdata_ivl = pgm_secs (2),
+ nak_data_retries = 50,
+ nak_ncf_retries = 50;
+
+ g_assert (G_N_ELEMENTS(heartbeat_spm) > 0);
+
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SEND_ONLY, &send_and_receive, sizeof(send_and_receive)))
+ puts ("FAILED: set bi-directional transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_RECV_ONLY, &send_and_receive, sizeof(send_and_receive)))
+ puts ("FAILED: set bi-directional transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_PASSIVE, &active, sizeof(active)))
+ puts ("FAILED: set active transport");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MTU, &mtu, sizeof(mtu)))
+ printf ("FAILED: set MAX_TPDU = %d bytes\n", mtu);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_TXW_SQNS, &txw_sqns, sizeof(txw_sqns)))
+ printf ("FAILED: set TXW_SQNS = %d\n", txw_sqns);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_RXW_SQNS, &rxw_sqns, sizeof(rxw_sqns)))
+ printf ("FAILED: set RXW_SQNS = %d\n", rxw_sqns);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_AMBIENT_SPM, &ambient_spm, sizeof(ambient_spm)))
+ printf ("FAILED: set AMBIENT_SPM = %ds\n", (int)pgm_to_secs (ambient_spm));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_HEARTBEAT_SPM, &heartbeat_spm, sizeof(heartbeat_spm)))
+ {
+ char buffer[1024];
+ sprintf (buffer, "%d", heartbeat_spm[0]);
+ for (unsigned i = 1; i < G_N_ELEMENTS(heartbeat_spm); i++) {
+ char t[1024];
+ sprintf (t, ", %d", heartbeat_spm[i]);
+ strcat (buffer, t);
+ }
+ printf ("FAILED: set HEARTBEAT_SPM = { %s }\n", buffer);
+ }
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_PEER_EXPIRY, &peer_expiry, sizeof(peer_expiry)))
+ printf ("FAILED: set PEER_EXPIRY = %ds\n",(int) pgm_to_secs (peer_expiry));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SPMR_EXPIRY, &spmr_expiry, sizeof(spmr_expiry)))
+ printf ("FAILED: set SPMR_EXPIRY = %dms\n", (int)pgm_to_msecs (spmr_expiry));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_BO_IVL, &nak_bo_ivl, sizeof(nak_bo_ivl)))
+ printf ("FAILED: set NAK_BO_IVL = %dms\n", (int)pgm_to_msecs (nak_bo_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RPT_IVL, &nak_rpt_ivl, sizeof(nak_rpt_ivl)))
+ printf ("FAILED: set NAK_RPT_IVL = %dms\n", (int)pgm_to_msecs (nak_rpt_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_RDATA_IVL, &nak_rdata_ivl, sizeof(nak_rdata_ivl)))
+ printf ("FAILED: set NAK_RDATA_IVL = %dms\n", (int)pgm_to_msecs (nak_rdata_ivl));
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_DATA_RETRIES, &nak_data_retries, sizeof(nak_data_retries)))
+ printf ("FAILED: set NAK_DATA_RETRIES = %d\n", nak_data_retries);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NAK_NCF_RETRIES, &nak_ncf_retries, sizeof(nak_ncf_retries)))
+ printf ("FAILED: set NAK_NCF_RETRIES = %d\n", nak_ncf_retries);
+
+/* create global session identifier */
+ struct pgm_sockaddr_t addr;
+ memset (&addr, 0, sizeof(addr));
+ addr.sa_port = g_port;
+ addr.sa_addr.sport = 0;
+ if (!pgm_gsi_create_from_hostname (&addr.sa_addr.gsi, &pgm_err)) {
+ printf ("FAILED: pgm_gsi_create_from_hostname(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ }
+
+{
+ char buffer[1024];
+ pgm_tsi_print_r (&addr.sa_addr, buffer, sizeof(buffer));
+ printf ("pgm_bind (sock:%p addr:{port:%d tsi:%s} err:%p)\n",
+ (gpointer)sess->sock,
+ addr.sa_port, buffer,
+ (gpointer)&pgm_err);
+}
+ const bool status = sess->is_transport_fake ?
+ fake_pgm_bind (sess->sock, &addr, sizeof(addr), &pgm_err) :
+ pgm_bind (sess->sock, &addr, sizeof(addr), &pgm_err);
+ if (!status) {
+ printf ("FAILED: pgm_bind(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ } else
+ puts ("READY");
+}
+
+static
+void
+session_connect (
+ char* session_name
+ )
+{
+ struct pgm_addrinfo_t hints = {
+ .ai_family = AF_INET
+ }, *res = NULL;
+ pgm_error_t* pgm_err = NULL;
+
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ if (!pgm_getaddrinfo (g_network, &hints, &res, &pgm_err)) {
+ printf ("FAILED: pgm_getaddrinfo(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ pgm_error_free (pgm_err);
+ return;
+ }
+
+/* join IP multicast groups */
+ for (unsigned i = 0; i < res->ai_recv_addrs_len; i++)
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_JOIN_GROUP, &res->ai_recv_addrs[i], sizeof(struct group_req)))
+ {
+ char group[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&res->ai_recv_addrs[i].gsr_group, sizeof(struct sockaddr_in),
+ group, sizeof(group),
+ NULL, 0,
+ NI_NUMERICHOST);
+ printf ("FAILED: join group (#%u %s)\n", (unsigned)res->ai_recv_addrs[i].gsr_interface, group);
+ }
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_SEND_GROUP, &res->ai_send_addrs[0], sizeof(struct group_req)))
+ {
+ char group[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&res->ai_send_addrs[0].gsr_group, sizeof(struct sockaddr_in),
+ group, sizeof(group),
+ NULL, 0,
+ NI_NUMERICHOST);
+ printf ("FAILED: send group (#%u %s)\n", (unsigned)res->ai_send_addrs[0].gsr_interface, group);
+ }
+ pgm_freeaddrinfo (res);
+
+/* set IP parameters */
+ const int non_blocking = 1,
+ no_multicast_loop = 0,
+ multicast_hops = 16,
+ dscp = 0x2e << 2; /* Expedited Forwarding PHB for network elements, no ECN. */
+
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MULTICAST_LOOP, &no_multicast_loop, sizeof(no_multicast_loop)))
+ puts ("FAILED: disable multicast loop");
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_MULTICAST_HOPS, &multicast_hops, sizeof(multicast_hops)))
+ printf ("FAILED: set TTL = %d\n", multicast_hops);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_TOS, &dscp, sizeof(dscp)))
+ printf ("FAILED: set TOS = 0x%x\n", dscp);
+ if (!pgm_setsockopt (sess->sock, IPPROTO_PGM, PGM_NOBLOCK, &non_blocking, sizeof(non_blocking)))
+ puts ("FAILED: set non-blocking sockets");
+
+ const bool status = sess->is_transport_fake ?
+ fake_pgm_connect (sess->sock, &pgm_err) :
+ pgm_connect (sess->sock, &pgm_err);
+ if (!status) {
+ printf ("FAILED: pgm_connect(): %s\n", (pgm_err && pgm_err->message) ? pgm_err->message : "(null)");
+ return;
+ }
+
+ if (sess->is_transport_fake)
+ {
+/* add receive socket(s) to event manager */
+ sess->recv_channel = g_io_channel_unix_new (sess->sock->recv_sock);
+
+ GSource *source;
+ source = g_io_create_watch (sess->recv_channel, G_IO_IN);
+ g_source_set_callback (source, (GSourceFunc)on_io_data, sess->sock, NULL);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+ }
+ else
+ {
+ pgm_async_create (&sess->async, sess->sock, 0);
+ pgm_async_add_watch (sess->async, on_data, sess);
+ }
+
+ puts ("READY");
+}
+
+static inline
+gssize
+pgm_sendto_hops (
+ pgm_sock_t* sock,
+ G_GNUC_UNUSED gboolean rl,
+ gboolean ra,
+ const int hops,
+ const void* buf,
+ gsize len,
+ const struct sockaddr* to,
+ socklen_t tolen
+ )
+{
+ const int send_sock = ra ? sock->send_with_router_alert_sock : sock->send_sock;
+ pgm_mutex_lock (&sock->send_mutex);
+ const ssize_t sent = sendto (send_sock, buf, len, 0, to, tolen);
+ pgm_mutex_unlock (&sock->send_mutex);
+ return sent > 0 ? (gssize)len : (gssize)sent;
+}
+
+static
+int
+pgm_reset_heartbeat_spm (
+ pgm_sock_t* sock
+ )
+{
+ int retval = 0;
+
+ pgm_mutex_lock (&sock->timer_mutex);
+
+/* re-set spm timer */
+ sock->spm_heartbeat_state = 1;
+ sock->next_heartbeat_spm = pgm_time_update_now() + sock->spm_heartbeat_interval[sock->spm_heartbeat_state++];
+
+/* prod timer thread if sleeping */
+ if (pgm_time_after( sock->next_poll, sock->next_heartbeat_spm ))
+ sock->next_poll = sock->next_heartbeat_spm;
+
+ pgm_mutex_unlock (&sock->timer_mutex);
+
+ return retval;
+}
+
+static inline
+int
+brokn_send_apdu_unlocked (
+ pgm_sock_t* sock,
+ const gchar* buf,
+ gsize count,
+ gsize* bytes_written
+ )
+{
+ guint32 opt_sqn = pgm_txw_next_lead(sock->window);
+ guint packets = 0;
+ guint bytes_sent = 0;
+ guint data_bytes_sent = 0;
+
+ pgm_mutex_lock (&sock->source_mutex);
+
+ do {
+/* retrieve packet storage from transmit window */
+ int header_length = sizeof(struct pgm_header) + sizeof(struct pgm_data) +
+ sizeof(struct pgm_opt_length) + /* includes header */
+ sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_fragment);
+ int tsdu_length = MIN(sock->max_tpdu - sock->iphdr_len - header_length, count - data_bytes_sent);
+ int tpdu_length = header_length + tsdu_length;
+
+ struct pgm_sk_buff_t* skb = pgm_alloc_skb (tsdu_length);
+ pgm_skb_put (skb, tpdu_length);
+
+ skb->pgm_header = (struct pgm_header*)skb->data;
+ memcpy (skb->pgm_header->pgm_gsi, &sock->tsi.gsi, sizeof(pgm_gsi_t));
+ skb->pgm_header->pgm_sport = sock->tsi.sport;
+ skb->pgm_header->pgm_dport = sock->dport;
+ skb->pgm_header->pgm_type = PGM_ODATA;
+ skb->pgm_header->pgm_options = PGM_OPT_PRESENT;
+ skb->pgm_header->pgm_tsdu_length = g_htons (tsdu_length);
+
+/* ODATA */
+ skb->pgm_data = (struct pgm_data*)(skb->pgm_header + 1);
+ skb->pgm_data->data_sqn = g_htonl (pgm_txw_next_lead(sock->window));
+ skb->pgm_data->data_trail = g_htonl (pgm_txw_trail(sock->window));
+
+/* OPT_LENGTH */
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)(skb->pgm_data + 1);
+ opt_len->opt_type = PGM_OPT_LENGTH;
+ opt_len->opt_length = sizeof(struct pgm_opt_length);
+ opt_len->opt_total_length = g_htons ( sizeof(struct pgm_opt_length) +
+ sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_fragment) );
+/* OPT_FRAGMENT */
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)(opt_len + 1);
+ opt_header->opt_type = PGM_OPT_FRAGMENT | PGM_OPT_END;
+ opt_header->opt_length = sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_fragment);
+ skb->pgm_opt_fragment = (struct pgm_opt_fragment*)(opt_header + 1);
+ skb->pgm_opt_fragment->opt_reserved = 0;
+ skb->pgm_opt_fragment->opt_sqn = g_htonl (opt_sqn);
+ skb->pgm_opt_fragment->opt_frag_off = g_htonl (data_bytes_sent);
+ skb->pgm_opt_fragment->opt_frag_len = g_htonl (count);
+
+/* TODO: the assembly checksum & copy routine is faster than memcpy & pgm_cksum on >= opteron hardware */
+ skb->pgm_header->pgm_checksum = 0;
+
+ int pgm_header_len = (char*)(skb->pgm_opt_fragment + 1) - (char*)skb->pgm_header;
+ guint32 unfolded_header = pgm_csum_partial ((const void*)skb->pgm_header, pgm_header_len, 0);
+ guint32 unfolded_odata = pgm_csum_partial_copy ((const void*)(buf + data_bytes_sent), (void*)(skb->pgm_opt_fragment + 1), tsdu_length, 0);
+ skb->pgm_header->pgm_checksum = pgm_csum_fold (pgm_csum_block_add (unfolded_header, unfolded_odata, pgm_header_len));
+
+/* add to transmit window */
+ pgm_spinlock_lock (&sock->txw_spinlock);
+ pgm_txw_add (sock->window, skb);
+ pgm_spinlock_unlock (&sock->txw_spinlock);
+
+/* do not send send packet */
+ if (packets != 1)
+ pgm_sendto_hops (sock,
+ TRUE,
+ FALSE,
+ sock->hops,
+ skb->data,
+ tpdu_length,
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+
+/* save unfolded odata for retransmissions */
+ *(guint32*)&skb->cb = unfolded_odata;
+
+ packets++;
+ bytes_sent += tpdu_length + sock->iphdr_len;
+ data_bytes_sent += tsdu_length;
+
+ } while (data_bytes_sent < count);
+
+ if (data_bytes_sent > 0 && bytes_written)
+ *bytes_written = data_bytes_sent;
+
+/* release txw lock here in order to allow spms to lock mutex */
+ pgm_mutex_unlock (&sock->source_mutex);
+ pgm_reset_heartbeat_spm (sock);
+ return PGM_IO_STATUS_NORMAL;
+}
+
+static
+int
+brokn_send (
+ pgm_sock_t* sock,
+ const gchar* data,
+ gsize len,
+ gsize* bytes_written
+ )
+{
+ if ( len <= ( sock->max_tpdu - ( sizeof(struct pgm_header) +
+ sizeof(struct pgm_data) ) ) )
+ {
+ puts ("FAILED: cannot send brokn single TPDU length APDU");
+ return PGM_IO_STATUS_ERROR;
+ }
+
+ return brokn_send_apdu_unlocked (sock, data, len, bytes_written);
+}
+
+static
+void
+session_send (
+ char* session_name,
+ char* string,
+ gboolean is_brokn /* send broken apdu */
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* send message */
+ int status;
+ gsize stringlen = strlen(string) + 1;
+ int n_fds = 1;
+ struct pollfd fds[ n_fds ];
+ struct timeval tv;
+ int timeout;
+again:
+ if (is_brokn)
+ status = brokn_send (sess->sock, string, stringlen, NULL);
+ else
+ status = pgm_send (sess->sock, string, stringlen, NULL);
+ switch (status) {
+ case PGM_IO_STATUS_NORMAL:
+ puts ("READY");
+ break;
+ case PGM_IO_STATUS_TIMER_PENDING:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (sess->sock, IPPROTO_PGM, PGM_TIME_REMAIN, &tv, &optlen);
+ }
+ goto block;
+ case PGM_IO_STATUS_RATE_LIMITED:
+ {
+ socklen_t optlen = sizeof (tv);
+ pgm_getsockopt (sess->sock, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &optlen);
+ }
+/* fall through */
+ case PGM_IO_STATUS_WOULD_BLOCK:
+block:
+ timeout = PGM_IO_STATUS_WOULD_BLOCK == status ? -1 : ((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
+ memset (fds, 0, sizeof(fds));
+ pgm_poll_info (sess->sock, fds, &n_fds, POLLOUT);
+ poll (fds, n_fds, timeout /* ms */);
+ goto again;
+ default:
+ puts ("FAILED: pgm_send()");
+ break;
+ }
+}
+
+static
+void
+session_destroy (
+ char* session_name
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* remove from hash table */
+ g_hash_table_remove (g_sessions, session_name);
+
+/* close down receive side first to stop new data incoming */
+ if (sess->recv_channel) {
+ puts ("closing receive channel.");
+
+ GError *err = NULL;
+ g_io_channel_shutdown (sess->recv_channel, TRUE, &err);
+
+ if (err) {
+ g_warning ("i/o shutdown error %i %s", err->code, err->message);
+ }
+
+/* TODO: flush GLib main loop with context specific to the recv channel */
+
+ sess->recv_channel = NULL;
+ }
+
+ if (sess->is_transport_fake)
+ {
+ fake_pgm_close (sess->sock, TRUE);
+ }
+ else
+ {
+ pgm_close (sess->sock, TRUE);
+ }
+ sess->sock = NULL;
+ g_free (sess->name);
+ sess->name = NULL;
+ g_free (sess);
+
+ puts ("READY");
+}
+
+static
+void
+net_send_data (
+ char* session_name,
+ guint8 pgm_type, /* PGM_ODATA or PGM_RDATA */
+ guint32 data_sqn,
+ guint32 txw_trail,
+ char* string
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ pgm_sock_t* sock = sess->sock;
+
+/* payload is string including terminating null. */
+ int count = strlen(string) + 1;
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header) + sizeof(struct pgm_data) + count;
+
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ struct pgm_data *data = (struct pgm_data*)(header + 1);
+ memcpy (header->pgm_gsi, &sock->tsi.gsi, sizeof(pgm_gsi_t));
+ header->pgm_sport = sock->tsi.sport;
+ header->pgm_dport = sock->dport;
+ header->pgm_type = pgm_type;
+ header->pgm_options = 0;
+ header->pgm_tsdu_length = g_htons (count);
+
+/* O/RDATA */
+ data->data_sqn = g_htonl (data_sqn);
+ data->data_trail = g_htonl (txw_trail);
+
+ memcpy (data + 1, string, count);
+
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ pgm_mutex_lock (&sock->send_mutex);
+ retval = sendto (sock->send_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+ pgm_mutex_unlock (&sock->send_mutex);
+
+ puts ("READY");
+}
+
+/* differs to net_send_data in that the string parameters contains every payload
+ * for the transmission group. this is required to calculate the correct parity
+ * as the fake transport does not own a transmission window.
+ *
+ * all payloads must be the same length unless variable TSDU support is enabled.
+ */
+static
+void
+net_send_parity (
+ char* session_name,
+ guint8 pgm_type, /* PGM_ODATA or PGM_RDATA */
+ guint32 data_sqn,
+ guint32 txw_trail,
+ char* string
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ pgm_sock_t* sock = sess->sock;
+
+/* split string into individual payloads */
+ guint16 parity_length = 0;
+ gchar** src;
+ src = g_strsplit (string, " ", sock->rs_k);
+
+/* payload is string including terminating null. */
+ parity_length = strlen(*src) + 1;
+
+/* check length of payload array */
+ gboolean is_var_pktlen = FALSE;
+ guint i;
+ for (i = 0; src[i]; i++)
+ {
+ guint tsdu_length = strlen(src[i]) + 1;
+ if (tsdu_length != parity_length) {
+ is_var_pktlen = TRUE;
+
+ if (tsdu_length > parity_length)
+ parity_length = tsdu_length;
+ }
+ }
+
+ if ( i != sock->rs_k ) {
+ printf ("FAILED: payload array length %u, whilst rs_k is %u.\n", i, sock->rs_k);
+ return;
+ }
+
+/* add padding and append TSDU lengths */
+ if (is_var_pktlen)
+ {
+ for (i = 0; src[i]; i++)
+ {
+ guint tsdu_length = strlen(src[i]) + 1;
+ gchar* new_string = g_new0 (gchar, parity_length + 2);
+ strncpy (new_string, src[i], parity_length);
+ *(guint16*)(new_string + parity_length) = tsdu_length;
+ g_free (src[i]);
+ src[i] = new_string;
+ }
+ parity_length += 2;
+ }
+
+/* calculate FEC block offset */
+ guint32 tg_sqn_mask = 0xffffffff << sock->tg_sqn_shift;
+ guint rs_h = data_sqn & ~tg_sqn_mask;
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header) + sizeof(struct pgm_data) + parity_length;
+
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ struct pgm_data *data = (struct pgm_data*)(header + 1);
+ memcpy (header->pgm_gsi, &sock->tsi.gsi, sizeof(pgm_gsi_t));
+ header->pgm_sport = sock->tsi.sport;
+ header->pgm_dport = sock->dport;
+ header->pgm_type = pgm_type;
+ header->pgm_options = is_var_pktlen ? (PGM_OPT_PARITY | PGM_OPT_VAR_PKTLEN) : PGM_OPT_PARITY;
+ header->pgm_tsdu_length = g_htons (parity_length);
+
+/* O/RDATA */
+ data->data_sqn = g_htonl (data_sqn);
+ data->data_trail = g_htonl (txw_trail);
+
+ memset (data + 1, 0, parity_length);
+ pgm_rs_t rs;
+ pgm_rs_create (&rs, sock->rs_n, sock->rs_k);
+ pgm_rs_encode (&rs, (const pgm_gf8_t**)src, sock->rs_k + rs_h, (pgm_gf8_t*)(data + 1), parity_length);
+ pgm_rs_destroy (&rs);
+
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ pgm_mutex_lock (&sock->send_mutex);
+ retval = sendto (sock->send_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+ pgm_mutex_unlock (&sock->send_mutex);
+
+ g_strfreev (src);
+ src = NULL;
+
+ puts ("READY");
+}
+
+static
+void
+net_send_spm (
+ char* session_name,
+ guint32 spm_sqn,
+ guint32 txw_trail,
+ guint32 txw_lead,
+ gboolean proactive_parity,
+ gboolean ondemand_parity,
+ guint k
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ pgm_sock_t* sock = sess->sock;
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header) + sizeof(struct pgm_spm);
+
+ if (proactive_parity || ondemand_parity) {
+ tpdu_length += sizeof(struct pgm_opt_length) +
+ sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_parity_prm);
+ }
+
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ struct pgm_spm *spm = (struct pgm_spm*)(header + 1);
+ memcpy (header->pgm_gsi, &sock->tsi.gsi, sizeof(pgm_gsi_t));
+ header->pgm_sport = sock->tsi.sport;
+ header->pgm_dport = sock->dport;
+ header->pgm_type = PGM_SPM;
+ header->pgm_options = (proactive_parity || ondemand_parity) ? (PGM_OPT_PRESENT | PGM_OPT_NETWORK) : 0;
+ header->pgm_tsdu_length = 0;
+
+/* SPM */
+ spm->spm_sqn = g_htonl (spm_sqn);
+ spm->spm_trail = g_htonl (txw_trail);
+ spm->spm_lead = g_htonl (txw_lead);
+ pgm_sockaddr_to_nla ((struct sockaddr*)&sock->send_addr, (char*)&spm->spm_nla_afi);
+
+ if (proactive_parity || ondemand_parity) {
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)(spm + 1);
+ opt_len->opt_type = PGM_OPT_LENGTH;
+ opt_len->opt_length = sizeof(struct pgm_opt_length);
+ opt_len->opt_total_length = g_htons ( sizeof(struct pgm_opt_length) +
+ sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_parity_prm) );
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)(opt_len + 1);
+ opt_header->opt_type = PGM_OPT_PARITY_PRM | PGM_OPT_END;
+ opt_header->opt_length = sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_parity_prm);
+ struct pgm_opt_parity_prm* opt_parity_prm = (struct pgm_opt_parity_prm*)(opt_header + 1);
+ opt_parity_prm->opt_reserved = (proactive_parity ? PGM_PARITY_PRM_PRO : 0) |
+ (ondemand_parity ? PGM_PARITY_PRM_OND : 0);
+ opt_parity_prm->parity_prm_tgs = g_htonl (k);
+ }
+
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ retval = sendto (sock->send_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+ puts ("READY");
+}
+
+static
+void
+net_send_spmr (
+ char* session_name,
+ pgm_tsi_t* tsi
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+ pgm_sock_t* sock = sess->sock;
+
+/* check that the peer exists */
+ pgm_peer_t* peer = pgm_hashtable_lookup (sock->peers_hashtable, tsi);
+ struct sockaddr_storage peer_nla;
+ pgm_gsi_t* peer_gsi;
+ guint16 peer_sport;
+
+ if (peer == NULL) {
+/* ourself */
+ if (pgm_tsi_equal (tsi, &sock->tsi))
+ {
+ peer_gsi = &sock->tsi.gsi;
+ peer_sport = sock->tsi.sport;
+ }
+ else
+ {
+ printf ("FAILED: peer \"%s\" not found\n", pgm_tsi_print (tsi));
+ return;
+ }
+ }
+ else
+ {
+ memcpy (&peer_nla, &peer->local_nla, sizeof(struct sockaddr_storage));
+ peer_gsi = &peer->tsi.gsi;
+ peer_sport = peer->tsi.sport;
+ }
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header);
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ memcpy (header->pgm_gsi, peer_gsi, sizeof(pgm_gsi_t));
+ header->pgm_sport = sock->dport;
+ header->pgm_dport = peer_sport;
+ header->pgm_type = PGM_SPMR;
+ header->pgm_options = 0;
+ header->pgm_tsdu_length = 0;
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ pgm_mutex_lock (&sock->send_mutex);
+/* TTL 1 */
+ pgm_sockaddr_multicast_hops (sock->send_sock, sock->send_gsr.gsr_group.ss_family, 1);
+ retval = sendto (sock->send_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+/* default TTL */
+ pgm_sockaddr_multicast_hops (sock->send_sock, sock->send_gsr.gsr_group.ss_family, sock->hops);
+
+ if (!pgm_tsi_equal (tsi, &sock->tsi))
+ {
+ retval = sendto (sock->send_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&peer_nla,
+ pgm_sockaddr_len((struct sockaddr*)&peer_nla));
+ }
+
+ pgm_mutex_unlock (&sock->send_mutex);
+
+ puts ("READY");
+}
+
+/* Send a NAK on a valid transport. A fake transport would need to specify the senders NLA,
+ * we use the peer list to bypass extracting it from the monitor output.
+ */
+
+static
+void
+net_send_ncf (
+ char* session_name,
+ pgm_tsi_t* tsi,
+ struct pgm_sqn_list_t* sqn_list /* list of sequence numbers */
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* check that the peer exists */
+ pgm_sock_t* sock = sess->sock;
+ pgm_peer_t* peer = pgm_hashtable_lookup (sock->peers_hashtable, tsi);
+ if (peer == NULL) {
+ printf ("FAILED: peer \"%s\" not found\n", pgm_tsi_print (tsi));
+ return;
+ }
+
+/* check for valid nla */
+ if (((struct sockaddr*)&peer->nla)->sa_family == 0 ) {
+ puts ("FAILED: peer NLA unknown, cannot send NCF.");
+ return;
+ }
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header) + sizeof(struct pgm_nak);
+
+ if (sqn_list->len > 1) {
+ tpdu_length += sizeof(struct pgm_opt_length) + /* includes header */
+ sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_nak_list) +
+ ( (sqn_list->len-1) * sizeof(guint32) );
+ }
+
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ struct pgm_nak *ncf = (struct pgm_nak*)(header + 1);
+ memcpy (header->pgm_gsi, &sock->tsi.gsi, sizeof(pgm_gsi_t));
+
+ struct sockaddr_storage peer_nla;
+ memcpy (&peer_nla, &peer->nla, sizeof(struct sockaddr_storage));
+
+/* dport & sport swap over for a nak */
+ header->pgm_sport = sock->tsi.sport;
+ header->pgm_dport = sock->dport;
+ header->pgm_type = PGM_NCF;
+ header->pgm_options = (sqn_list->len > 1) ? (PGM_OPT_PRESENT | PGM_OPT_NETWORK) : 0;
+ header->pgm_tsdu_length = 0;
+
+/* NCF */
+ ncf->nak_sqn = g_htonl (sqn_list->sqn[0]);
+
+/* source nla */
+ pgm_sockaddr_to_nla ((struct sockaddr*)&peer_nla, (char*)&ncf->nak_src_nla_afi);
+
+/* group nla */
+ pgm_sockaddr_to_nla ((struct sockaddr*)&sock->recv_gsr[0].gsr_group, (char*)&ncf->nak_grp_nla_afi);
+
+/* OPT_NAK_LIST */
+ if (sqn_list->len > 1)
+ {
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)(ncf + 1);
+ opt_len->opt_type = PGM_OPT_LENGTH;
+ opt_len->opt_length = sizeof(struct pgm_opt_length);
+ opt_len->opt_total_length = g_htons ( sizeof(struct pgm_opt_length) +
+ sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_nak_list) +
+ ( (sqn_list->len-1) * sizeof(guint32) ) );
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)(opt_len + 1);
+ opt_header->opt_type = PGM_OPT_NAK_LIST | PGM_OPT_END;
+ opt_header->opt_length = sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_nak_list)
+ + ( (sqn_list->len-1) * sizeof(guint32) );
+ struct pgm_opt_nak_list* opt_nak_list = (struct pgm_opt_nak_list*)(opt_header + 1);
+ opt_nak_list->opt_reserved = 0;
+ for (guint i = 1; i < sqn_list->len; i++) {
+ opt_nak_list->opt_sqn[i-1] = g_htonl (sqn_list->sqn[i]);
+ }
+ }
+
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ retval = sendto (sock->send_with_router_alert_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&sock->send_gsr.gsr_group,
+ pgm_sockaddr_len((struct sockaddr*)&sock->send_gsr.gsr_group));
+
+ puts ("READY");
+}
+
+static
+void
+net_send_nak (
+ char* session_name,
+ pgm_tsi_t* tsi,
+ struct pgm_sqn_list_t* sqn_list, /* list of sequence numbers */
+ gboolean is_parity /* TRUE = parity, FALSE = selective */
+ )
+{
+/* check that session exists */
+ struct sim_session* sess = g_hash_table_lookup (g_sessions, session_name);
+ if (sess == NULL) {
+ printf ("FAILED: session '%s' not found\n", session_name);
+ return;
+ }
+
+/* check that the peer exists */
+ pgm_sock_t* sock = sess->sock;
+ pgm_peer_t* peer = pgm_hashtable_lookup (sock->peers_hashtable, tsi);
+ if (peer == NULL) {
+ printf ("FAILED: peer \"%s\" not found\n", pgm_tsi_print(tsi));
+ return;
+ }
+
+/* send */
+ int retval = 0;
+ int tpdu_length = sizeof(struct pgm_header) + sizeof(struct pgm_nak);
+
+ if (sqn_list->len > 1) {
+ tpdu_length += sizeof(struct pgm_opt_length) + /* includes header */
+ sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_nak_list) +
+ ( (sqn_list->len-1) * sizeof(guint32) );
+ }
+
+ gchar buf[ tpdu_length ];
+
+ struct pgm_header *header = (struct pgm_header*)buf;
+ struct pgm_nak *nak = (struct pgm_nak*)(header + 1);
+ memcpy (header->pgm_gsi, &peer->tsi.gsi, sizeof(pgm_gsi_t));
+
+ guint16 peer_sport = peer->tsi.sport;
+ struct sockaddr_storage peer_nla;
+ memcpy (&peer_nla, &peer->nla, sizeof(struct sockaddr_storage));
+
+/* dport & sport swap over for a nak */
+ header->pgm_sport = sock->dport;
+ header->pgm_dport = peer_sport;
+ header->pgm_type = PGM_NAK;
+ if (is_parity) {
+ header->pgm_options = (sqn_list->len > 1) ? (PGM_OPT_PRESENT | PGM_OPT_NETWORK | PGM_OPT_PARITY)
+ : PGM_OPT_PARITY;
+ } else {
+ header->pgm_options = (sqn_list->len > 1) ? (PGM_OPT_PRESENT | PGM_OPT_NETWORK) : 0;
+ }
+ header->pgm_tsdu_length = 0;
+
+/* NAK */
+ nak->nak_sqn = g_htonl (sqn_list->sqn[0]);
+
+/* source nla */
+ pgm_sockaddr_to_nla ((struct sockaddr*)&peer_nla, (char*)&nak->nak_src_nla_afi);
+
+/* group nla */
+ pgm_sockaddr_to_nla ((struct sockaddr*)&sock->recv_gsr[0].gsr_group, (char*)&nak->nak_grp_nla_afi);
+
+/* OPT_NAK_LIST */
+ if (sqn_list->len > 1)
+ {
+ struct pgm_opt_length* opt_len = (struct pgm_opt_length*)(nak + 1);
+ opt_len->opt_type = PGM_OPT_LENGTH;
+ opt_len->opt_length = sizeof(struct pgm_opt_length);
+ opt_len->opt_total_length = g_htons ( sizeof(struct pgm_opt_length) +
+ sizeof(struct pgm_opt_header) +
+ sizeof(struct pgm_opt_nak_list) +
+ ( (sqn_list->len-1) * sizeof(guint32) ) );
+ struct pgm_opt_header* opt_header = (struct pgm_opt_header*)(opt_len + 1);
+ opt_header->opt_type = PGM_OPT_NAK_LIST | PGM_OPT_END;
+ opt_header->opt_length = sizeof(struct pgm_opt_header) + sizeof(struct pgm_opt_nak_list)
+ + ( (sqn_list->len-1) * sizeof(guint32) );
+ struct pgm_opt_nak_list* opt_nak_list = (struct pgm_opt_nak_list*)(opt_header + 1);
+ opt_nak_list->opt_reserved = 0;
+ for (guint i = 1; i < sqn_list->len; i++) {
+ opt_nak_list->opt_sqn[i-1] = g_htonl (sqn_list->sqn[i]);
+ }
+ }
+
+ header->pgm_checksum = 0;
+ header->pgm_checksum = pgm_csum_fold (pgm_csum_partial ((char*)header, tpdu_length, 0));
+
+ retval = sendto (sock->send_with_router_alert_sock,
+ header,
+ tpdu_length,
+ 0, /* not expecting a reply */
+ (struct sockaddr*)&peer_nla,
+ pgm_sockaddr_len((struct sockaddr*)&peer_nla));
+
+ puts ("READY");
+}
+
+static
+int
+on_data (
+ gpointer data,
+ G_GNUC_UNUSED guint len,
+ G_GNUC_UNUSED gpointer user_data
+ )
+{
+ printf ("DATA: %s\n", (char*)data);
+ fflush (stdout);
+
+ return 0;
+}
+
+/* process input commands from stdin/fd
+ */
+
+static
+gboolean
+on_stdin_data (
+ GIOChannel* source,
+ G_GNUC_UNUSED GIOCondition condition,
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ gchar* str = NULL;
+ gsize len = 0;
+ gsize term = 0;
+ GError* err = NULL;
+
+ g_io_channel_read_line (source, &str, &len, &term, &err);
+ if (len > 0) {
+ if (term) str[term] = 0;
+
+/* quit */
+ if (strcmp(str, "quit") == 0)
+ {
+ g_main_loop_quit(g_loop);
+ goto out;
+ }
+
+ regex_t preg;
+ regmatch_t pmatch[10];
+ const char *re;
+
+/* endpoint simulator specific: */
+
+/* send odata or rdata */
+ re = "^net[[:space:]]+send[[:space:]]+([or])data[[:space:]]+"
+ "([[:alnum:]]+)[[:space:]]+" /* transport */
+ "([0-9]+)[[:space:]]+" /* sequence number */
+ "([0-9]+)[[:space:]]+" /* txw_trail */
+ "([[:alnum:]]+)$"; /* payload */
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ guint8 pgm_type = *(str + pmatch[1].rm_so) == 'o' ? PGM_ODATA : PGM_RDATA;
+
+ char *name = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ name[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ char* p = str + pmatch[3].rm_so;
+ guint32 data_sqn = strtoul (p, &p, 10);
+
+ p = str + pmatch[4].rm_so;
+ guint txw_trail = strtoul (p, &p, 10);
+
+ char *string = g_memdup (str + pmatch[5].rm_so, pmatch[5].rm_eo - pmatch[5].rm_so + 1 );
+ string[ pmatch[5].rm_eo - pmatch[5].rm_so ] = 0;
+
+ net_send_data (name, pgm_type, data_sqn, txw_trail, string);
+
+ g_free (name);
+ g_free (string);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send parity odata or rdata */
+ re = "^net[[:space:]]+send[[:space:]]+parity[[:space:]]+([or])data[[:space:]]+"
+ "([[:alnum:]]+)[[:space:]]+" /* transport */
+ "([0-9]+)[[:space:]]+" /* sequence number */
+ "([0-9]+)[[:space:]]+" /* txw_trail */
+ "([a-z0-9 ]+)$"; /* payloads */
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ guint8 pgm_type = *(str + pmatch[1].rm_so) == 'o' ? PGM_ODATA : PGM_RDATA;
+
+ char *name = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ name[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ char* p = str + pmatch[3].rm_so;
+ guint32 data_sqn = strtoul (p, &p, 10);
+
+ p = str + pmatch[4].rm_so;
+ guint txw_trail = strtoul (p, &p, 10);
+
+/* ideally confirm number of payloads matches sess->sock::rs_k ... */
+ char *string = g_memdup (str + pmatch[5].rm_so, pmatch[5].rm_eo - pmatch[5].rm_so + 1 );
+ string[ pmatch[5].rm_eo - pmatch[5].rm_so ] = 0;
+
+ net_send_parity (name, pgm_type, data_sqn, txw_trail, string);
+
+ g_free (name);
+ g_free (string);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send spm */
+ re = "^net[[:space:]]+send[[:space:]]+spm[[:space:]]+"
+ "([[:alnum:]]+)[[:space:]]+" /* transport */
+ "([0-9]+)[[:space:]]+" /* spm sequence number */
+ "([0-9]+)[[:space:]]+" /* txw_trail */
+ "([0-9]+)" /* txw_lead */
+ "([[:space:]]+pro-active)?" /* pro-active parity */
+ "([[:space:]]+on-demand)?" /* on-demand parity */
+ "([[:space:]]+[0-9]+)?$"; /* transmission group size */
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char* p = str + pmatch[2].rm_so;
+ guint32 spm_sqn = strtoul (p, &p, 10);
+
+ p = str + pmatch[3].rm_so;
+ guint txw_trail = strtoul (p, &p, 10);
+
+ p = str + pmatch[4].rm_so;
+ guint txw_lead = strtoul (p, &p, 10);
+
+ gboolean proactive_parity = pmatch[5].rm_eo > pmatch[5].rm_so;
+ gboolean ondemand_parity = pmatch[6].rm_eo > pmatch[6].rm_so;
+
+ p = str + pmatch[7].rm_so;
+ guint k = (pmatch[7].rm_eo > pmatch[7].rm_so) ? strtoul (p, &p, 10) : 0;
+
+ net_send_spm (name, spm_sqn, txw_trail, txw_lead, proactive_parity, ondemand_parity, k);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send spmr */
+ re = "^net[[:space:]]+send[[:space:]]+spmr[[:space:]]+"
+ "([[:alnum:]]+)[[:space:]]+" /* transport */
+ "([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)$"; /* TSI */
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ pgm_tsi_t tsi;
+ char *p = str + pmatch[2].rm_so;
+ tsi.gsi.identifier[0] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[1] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[2] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[3] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[4] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[5] = strtol (p, &p, 10);
+ ++p;
+ tsi.sport = g_htons ( strtol (p, NULL, 10) );
+
+ net_send_spmr (name, &tsi);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send nak/ncf */
+ re = "^net[[:space:]]+send[[:space:]](parity[[:space:]])?n(ak|cf)[[:space:]]+"
+ "([[:alnum:]]+)[[:space:]]+" /* transport */
+ "([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)[[:space:]]+" /* TSI */
+ "([0-9,]+)$"; /* sequence number or list */
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[3].rm_so, pmatch[3].rm_eo - pmatch[3].rm_so + 1 );
+ name[ pmatch[3].rm_eo - pmatch[3].rm_so ] = 0;
+
+ pgm_tsi_t tsi;
+ char *p = str + pmatch[4].rm_so;
+ tsi.gsi.identifier[0] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[1] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[2] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[3] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[4] = strtol (p, &p, 10);
+ ++p;
+ tsi.gsi.identifier[5] = strtol (p, &p, 10);
+ ++p;
+ tsi.sport = g_htons ( strtol (p, NULL, 10) );
+
+/* parse list of sequence numbers */
+ struct pgm_sqn_list_t sqn_list;
+ sqn_list.len = 0;
+ {
+ char* saveptr = NULL;
+ for (p = str + pmatch[5].rm_so; ; p = NULL) {
+ char* token = strtok_r (p, ",", &saveptr);
+ if (!token) break;
+ sqn_list.sqn[sqn_list.len++] = strtoul (token, NULL, 10);
+ }
+ }
+
+ if ( *(str + pmatch[2].rm_so) == 'a' )
+ {
+ net_send_nak (name, &tsi, &sqn_list, (pmatch[1].rm_eo > pmatch[1].rm_so));
+ }
+ else
+ {
+ net_send_ncf (name, &tsi, &sqn_list);
+ }
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/** same as test application: **/
+
+/* create transport */
+ re = "^create[[:space:]]+(fake[[:space:]]+)?([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ name[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ session_create (name, (pmatch[1].rm_eo > pmatch[1].rm_so));
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* enable Reed-Solomon Forward Error Correction */
+ re = "^set[[:space:]]+([[:alnum:]]+)[[:space:]]+FEC[[:space:]]+RS[[:space:]]*\\([[:space:]]*([0-9]+)[[:space:]]*,[[:space:]]*([0-9]+)[[:space:]]*\\)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *p = str + pmatch[2].rm_so;
+ *(str + pmatch[2].rm_eo) = 0;
+ guint n = strtol (p, &p, 10);
+ p = str + pmatch[3].rm_so;
+ *(str + pmatch[3].rm_eo) = 0;
+ guint k = strtol (p, &p, 10);
+ session_set_fec (name, n, k);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* bind socket */
+ re = "^bind[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_bind (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* connect socket */
+ re = "^connect[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_connect (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* send packet */
+ re = "^send[[:space:]]+([[:alnum:]]+)[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ char *string = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ string[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ session_send (name, string, FALSE);
+
+ g_free (name);
+ g_free (string);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+ re = "^send[[:space:]]+(brokn[[:space:]]+)?([[:alnum:]]+)[[:space:]]+([[:alnum:]]+)[[:space:]]+x[[:space:]]([0-9]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so + 1 );
+ name[ pmatch[2].rm_eo - pmatch[2].rm_so ] = 0;
+
+ char* p = str + pmatch[4].rm_so;
+ int factor = strtol (p, &p, 10);
+ int src_len = pmatch[3].rm_eo - pmatch[3].rm_so;
+ char *string = g_malloc ( (factor * src_len) + 1 );
+ for (int i = 0; i < factor; i++)
+ {
+ memcpy (string + (i * src_len), str + pmatch[3].rm_so, src_len);
+ }
+ string[ factor * src_len ] = 0;
+
+ session_send (name, string, (pmatch[1].rm_eo > pmatch[1].rm_so));
+
+ g_free (name);
+ g_free (string);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* destroy transport */
+ re = "^destroy[[:space:]]+([[:alnum:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *name = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ name[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+
+ session_destroy (name);
+
+ g_free (name);
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+/* set PGM network */
+ re = "^set[[:space:]]+network[[:space:]]+([[:print:]]*;[[:print:]]+)$";
+ regcomp (&preg, re, REG_EXTENDED);
+ if (0 == regexec (&preg, str, G_N_ELEMENTS(pmatch), pmatch, 0))
+ {
+ char *pgm_network = g_memdup (str + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1 );
+ pgm_network[ pmatch[1].rm_eo - pmatch[1].rm_so ] = 0;
+ g_network = pgm_network;
+ puts ("READY");
+
+ regfree (&preg);
+ goto out;
+ }
+ regfree (&preg);
+
+ printf ("unknown command: %s\n", str);
+ }
+
+out:
+ fflush (stdout);
+ g_free (str);
+ return TRUE;
+}
+
+/* idle log notification
+ */
+
+static
+gboolean
+on_mark (
+ G_GNUC_UNUSED gpointer data
+ )
+{
+ g_message ("-- MARK --");
+ return TRUE;
+}
+
+/* eof */
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spm.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spm.pl
new file mode 100755
index 0000000..5da52ac
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spm.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+# spm.pl
+# 5.1.4. Ambient SPMs
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+print "app: ready.\n";
+
+print "mon: wait for spm ...\n";
+$mon->wait_for_spm;
+print "mon: received spm.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump.pl
new file mode 100755
index 0000000..4660a83
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump.pl
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+# spm_jump.pl
+# 6.2. Source Path Messages
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,005 at spm_sqn 20.\n";
+$sim->say ("net send spm ao 20 90001 90005");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump2.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump2.pl
new file mode 100755
index 0000000..20d279e
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spm_jump2.pl
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+# spm_jump2.pl
+# 6.3. Data Recovery by Negative Acknowledgment
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,000 at spm_sqn 3200.\n";
+$sim->say ("net send spm ao 3200 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish SPM txw_trail 90,001 txw_lead 90,001 at spm_sqn 3201.\n";
+$sim->say ("net send spm ao 3201 90001 90001");
+
+print "sim: waiting for valid NAK.\n";
+$sim->wait_for_nak;
+print "sim: NAK received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spm_reception.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spm_reception.pl
new file mode 100755
index 0000000..5718305
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spm_reception.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+# spm_reception.pl
+# 6.1. Data Reception
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+
+print "sim: publish SPM txw_trail 90,000.\n";
+$sim->say ("net send spm ao 1 90001 90000");
+
+# no NAKs should be generated.
+print "sim: waiting 2 seconds for erroneous NAKs ...\n";
+$sim->die_on_nak({ 'timeout' => 2 });
+print "sim: no NAKs received.\n";
+
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90000 ichigo");
+print "app: wait for data ...\n";
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spmr.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spmr.pl
new file mode 100755
index 0000000..690e2a5
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spmr.pl
@@ -0,0 +1,75 @@
+#!/usr/bin/perl
+# spmr.pl
+# 13.3.1. SPM Requests
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+my $t0 = [gettimeofday];
+my $elapsed;
+
+$mon->disconnect (1);
+
+## spm hearbeats are going to clear out the data, lets wait for some quiet
+print "sim: wait for SPM interval > 5 seconds ...\n";
+do {
+ $sim->wait_for_spm;
+ $elapsed = tv_interval ( $t0, [gettimeofday] );
+ print "sim: received SPM after $elapsed seconds.\n";
+} while ($elapsed < 5);
+
+print "sim: request SPM via SPMR.\n";
+$sim->say ("net send spmr ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort}");
+$t0 = [gettimeofday];
+
+print "sim: wait for SPM ...\n";
+$sim->wait_for_spm;
+$elapsed = tv_interval ( $t0, [gettimeofday] );
+print "sim: SPM received after $elapsed seconds.\n";
+die "SPM interval too large, indicates heartbeat not SPMR induced.\n" unless ($elapsed < 5.0);
+
+print "test completed successfully.\n";
+
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spmr_after_spm.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_after_spm.pl
new file mode 100755
index 0000000..c8eb045
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_after_spm.pl
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+# spmr.pl
+# 13.3.1. SPM Requests
+
+use strict;
+use Time::HiRes qw( gettimeofday tv_interval );
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+## capture GSI of test spp
+$app->say ("send ao nashi");
+print "mon: wait for odata ...\n";
+my $odata = $mon->wait_for_odata;
+print "mon: odata received.\n";
+my $t0 = [gettimeofday];
+my $elapsed;
+
+## spm hearbeats are going to clear out the data, lets wait for some quiet
+print "mon: wait for SPM interval > 5 seconds ...\n";
+do {
+ $mon->wait_for_spm;
+ $elapsed = tv_interval ( $t0, [gettimeofday] );
+ print "mon: received SPM after $elapsed seconds.\n";
+} while ($elapsed < 5);
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: ready.\n";
+
+## app needs to send packet for sim to learn of local NLA
+$app->say ("send ao budo");
+print "sim: wait for odata ...\n";
+$odata = $sim->wait_for_odata;
+print "sim: odata received.\n";
+
+print "sim: request SPM via SPMR.\n";
+$sim->say ("net send spmr ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort}");
+$t0 = [gettimeofday];
+
+print "sim: wait for SPM ...\n";
+$sim->wait_for_spm;
+$elapsed = tv_interval ( $t0, [gettimeofday] );
+print "sim: SPM received after $elapsed seconds.\n";
+die "SPM interval too large, indicates heartbeat not SPMR induced.\n" unless ($elapsed < 5.0);
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spmr_from_odata.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_from_odata.pl
new file mode 100755
index 0000000..519518b
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_from_odata.pl
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+# spmr_from_odata.pl
+# 13.3.1. SPM Requests
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$mon->say ("filter $config{app}{ip}");
+print "mon: ready.\n";
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: publish ODATA sqn 90,001.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+print "mon: wait for SPMR ...\n";
+$mon->wait_for_spmr;
+print "mon: received SPMR.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/spmr_suppression.pl b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_suppression.pl
new file mode 100755
index 0000000..5b73c26
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/spmr_suppression.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+# spmr_suppression.pl
+# 13.3.1. SPM Requests
+
+use strict;
+use PGM::Test;
+
+BEGIN { require "test.conf.pl"; }
+
+$| = 1;
+
+my $mon = PGM::Test->new(tag => 'mon', host => $config{mon}{host}, cmd => $config{mon}{cmd});
+my $sim = PGM::Test->new(tag => 'sim', host => $config{sim}{host}, cmd => $config{sim}{cmd});
+my $app = PGM::Test->new(tag => 'app', host => $config{app}{host}, cmd => $config{app}{cmd});
+
+$mon->connect;
+$sim->connect;
+$app->connect;
+
+sub close_ssh {
+ $mon = $sim = $app = undef;
+ print "finished.\n";
+}
+
+$SIG{'INT'} = sub { print "interrupt caught.\n"; close_ssh(); };
+
+$sim->say ("create fake ao");
+$sim->say ("bind ao");
+$sim->say ("connect ao");
+print "sim: publish ODATA sqn 90,001 to monitor for GSI.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+
+## capture GSI of test sim (not app!)
+my $odata = $mon->wait_for_odata;
+$mon->say ("filter $config{app}{ip}");
+
+$app->say ("create ao");
+$app->say ("bind ao");
+$app->say ("connect ao");
+$app->say ("listen ao");
+
+print "sim: re-publish ODATA sqn 90,001 to app.\n";
+$sim->say ("net send odata ao 90001 90001 ringo");
+$sim->say ("net send spmr ao $odata->{PGM}->{gsi}.$odata->{PGM}->{sourcePort}");
+
+my $data = $app->wait_for_data;
+print "app: received data [$data].\n";
+
+print "mon: wait for erroneous SPMR ...\n";
+$mon->die_on_spmr({ 'timeout' => 2 });
+print "mon: no SPMR received.\n";
+
+print "test completed successfully.\n";
+
+$mon->disconnect (1);
+$sim->disconnect;
+$app->disconnect;
+close_ssh;
+
+# eof
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/sudoers.example b/3rdparty/openpgm-svn-r1135/pgm/test/sudoers.example
new file mode 100644
index 0000000..9212139
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/sudoers.example
@@ -0,0 +1,26 @@
+# /etc/sudoers
+#
+# This file MUST be edited with the 'visudo' command as root.
+#
+# See the man page for details on how to write a sudoers file.
+# Host alias specification
+
+# User alias specification
+User_Alias PGM_USER = steve-o
+
+# Cmnd alias specification
+Cmnd_Alias PGM_CONFORMANCE = /miru/projects/openpgm/pgm/ref/debug/test/*
+
+# Defaults
+
+Defaults !lecture,tty_tickets,!fqdn
+
+# User privilege specification
+root ALL=(ALL) ALL
+
+# Members of the admin group may gain root privileges
+%admin ALL=(ALL) ALL
+
+
+# PGM testing
+PGM_USER ALL = NOPASSWD: PGM_CONFORMANCE
diff --git a/3rdparty/openpgm-svn-r1135/pgm/test/test.conf.pl b/3rdparty/openpgm-svn-r1135/pgm/test/test.conf.pl
new file mode 100644
index 0000000..66860c6
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/test/test.conf.pl
@@ -0,0 +1,27 @@
+# test.conf.pl
+use vars qw ( %config );
+
+%config = (
+ msapp => {
+ host => 'momo',
+ ip => '10.6.28.34',
+ },
+ app => {
+ host => 'ayaka', ip => '10.6.28.31',
+ cmd => '/miru/projects/openpgm/pgm/ref/release-Linux-x86_64/test/app',
+ network => 'eth0;239.192.0.1'
+# host => 'ryoko', ip => '10.6.28.36',
+# cmd => 'LD_LIBRARY_PATH=/opt/glib-sunstudio/lib:$LD_LIBRARY_PATH /miru/projects/openpgm/pgm/ref/release-SunOS-sun4u-sunstudio/test/app',
+# network => 'eri0;239.192.0.1'
+ },
+ mon => {
+ host => 'sora',
+ cmd => '/miru/projects/openpgm/pgm/ref/release-Linux-x86_64/test/monitor',
+ network => 'eth0;239.192.0.1'
+ },
+ sim => {
+ host => 'kiku',
+ cmd => '/miru/projects/openpgm/pgm/ref/release-Linux-x86_64/test/sim',
+ network => 'eth0;239.192.0.1'
+ },
+);