diff options
author | Sebastien Braun | 2010-10-03 16:14:44 +0200 |
---|---|---|
committer | Sebastien Braun | 2010-10-03 16:14:44 +0200 |
commit | ffee0868ef1341cfb7622821431cb73c52590962 (patch) | |
tree | bc96be65e0346ea25a8effb2118de59b08d54466 /3rdparty/openpgm-svn-r1135/pgm/test | |
parent | Add patch for OpenPGM to fix switch() fallthrough (diff) | |
download | pvs-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')
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' + }, +); |