Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Tests for Net::RNDC::Session + Net::RNDC::Session is_server support.

 * Net::RNDC::Session can be used as both a client and a server
 * Minor bug fixes to length check
 * Full test of client/server interaction
  • Loading branch information...
commit e8910fdb33623b1b7a6681b619e46f55ce5d9a8a 1 parent 6ebac4e
@wolfsage authored
Showing with 287 additions and 27 deletions.
  1. +2 −2 lib/Net/RNDC/Packet.pm
  2. +81 −25 lib/Net/RNDC/Session.pm
  3. +204 −0 t/02_session.t
View
4 lib/Net/RNDC/Packet.pm
@@ -345,7 +345,7 @@ sub _value_towire {
sub _cklen {
my ($self, $buff, $len) = @_;
- unless (length $buff >= $len) {
+ unless ((length($buff) || 0) >= $len) {
$self->_set_error(Net::RNDC::Exception->new(
"Unexpected end of data reading buffer. (Expected $len more bytes at least)"
));
@@ -355,7 +355,7 @@ sub _cklen {
sub _cklen_d {
my ($buff, $len) = @_;
- unless (length $buff >= $len) {
+ unless ((length($buff) || 0) >= $len) {
die Net::RNDC::Exception->new(
"Unexpected end of data reading buffer. (Expected $len more bytes at least)"
);
View
106 lib/Net/RNDC/Session.pm
@@ -46,6 +46,10 @@ sub new {
}
}
+ unless (exists $args{is_client} || exists $args{is_server}) {
+ croak("Argument 'is_client' or 'is_server' must be defined");
+ }
+
for my $r (@required_subs, @optional_subs) {
next unless exists $args{$r};
@@ -54,10 +58,6 @@ sub new {
}
}
- unless (exists $args{is_client} || exists $args{is_server}) {
- croak("Argument 'is_client' or 'is_server' must be defined");
- }
-
if (exists $args{is_client} && exists $args{is_server}) {
croak("Argument 'is_client' cannot be mixed with 'is_server'");
}
@@ -72,11 +72,6 @@ sub new {
$obj{is_server} = 1;
}
- # Soon?
- if ($obj{is_server}) {
- croak("Argument 'is_server' not yet supported");
- }
-
my $obj = bless \%obj, $class;
# Base state
@@ -158,6 +153,7 @@ sub _got_start {
return $self->_run_want('want_write', $packet->data, $packet);
} else {
+ # Server step 1: expect a packet with no data section
$self->_state('want_read');
return $self->_run_want('want_read');
@@ -170,16 +166,16 @@ sub _got_read {
if ($self->_is_client) {
my $packet = Net::RNDC::Packet->new(key => $self->_key);
+ if (!$packet->parse($data)) {
+ $self->_state('want_error');
+
+ return $self->_run_want('want_error', $packet->error);
+ }
+
if (! $self->_nonce) {
# Client step 2: Parse response, get nonce
$self->{nonce} = 1;
- if (!$packet->parse($data)) {
- $self->_state('want_error');
-
- return $self->_run_want('want_error', $packet->error);
- }
-
my $nonce = $packet->{data}->{_ctrl}{_nonce};
# Client step 3: Send request with nonce/data section
@@ -191,22 +187,68 @@ sub _got_read {
$self->_state('want_write');
- return $self->_run_want('want_write', $packet2->data);
+ return $self->_run_want('want_write', $packet2->data, $packet2);
} else {
# Client step 4: Read response to command
- if (!$packet->parse($data)) {
+ my $response = $packet->{data}{_data}{text} || 'command success';
+
+ $self->_state('want_finish');
+
+ return $self->_run_want('want_finish', $response);
+ }
+ } else {
+ my $packet = Net::RNDC::Packet->new(key => $self->_key);
+
+ if (!$packet->parse($data)) {
+ $self->_state('want_error');
+
+ return $self->_run_want('want_error', $packet->error);
+ }
+
+ if (! $self->_nonce) {
+ $self->{nonce} = 1;
+
+ my $nonce = int(rand(2**32));
+
+ $self->{_nonce_data} = $nonce;
+
+ my $challenge = Net::RNDC::Packet->new(
+ key => $self->_key,
+ nonce => $nonce,
+ );
+
+ $self->_state('want_write');
+
+ return $self->_run_want('want_write', $challenge->data, $challenge);
+ } else {
+ my $nonce = $self->{_nonce_data};
+
+ unless ($packet->{data}->{_ctrl}{_nonce}) {
$self->_state('want_error');
- return $self->_run_want('want_error', $packet->error);
+ return $self->_run_want('want_error', "Client nonce not set");
}
- my $response = $packet->{data}{_data}{text} || 'command success';
+ unless ($packet->{data}->{_ctrl}{_nonce} == $nonce) {
+ $self->_state('want_error');
+
+ return $self->_run_want('want_error', "Client nonce does not match");
+ }
+
+ my $response = Net::RNDC::Packet->new(
+ key => $self->_key,
+ data => {text => $self->_command},
+ );
+
+ $self->_state('want_write');
+
+ $self->_run_want('want_write', $response->data, $response);
$self->_state('want_finish');
- return $self->_run_want('want_finish', $response);
+ $self->_run_want('want_finish');
}
- }
+ }
}
sub _got_write {
@@ -217,6 +259,10 @@ sub _got_write {
$self->_state('want_read');
return $self->_run_want('want_read');
+ } elsif ($self->_is_server) {
+ $self->_state('want_read');
+
+ return $self->_run_want('want_read');
}
}
@@ -238,7 +284,7 @@ Net::RNDC::Session - Helper package to manage the RNDC 4-packet session
=head1 SYNOPSIS
-To use synchronously:
+To use synchronously as a client:
use IO::Socket::INET;
use Net::RNDC::Session;
@@ -270,11 +316,21 @@ To use asynchronously (for example, with IO::Async):
TBD
+To use as a server:
+
+TBD
+
+To use asynchronously as a server:
+
+TBD
+
=head1 DESCRIPTION
-This package is intended to provide the logic for a RNDC session which is used
-to run a single command against a remote server and get a response. See
-L<SESSION> below for a description of the RNDC client session logic.
+This package is intended to provide the logic for an RNDC client session which
+can used to run a single command against a remote server and get a response.
+See L<SESSION> below for a description of the RNDC client session logic.
+
+This package also supports running sessions as an RNDC server.
For simple use of the RNDC protocol, see L<Net::RNDC>.
View
204 t/02_session.t
@@ -0,0 +1,204 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+
+use Net::RNDC::Session;
+
+## new
+throws_ok { Net::RNDC::Session->new(); }
+ qr/Missing required argument 'want_read'/,
+ "new() without want_read fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ ) }
+ qr/Missing required argument 'want_write'/,
+ "new() without want_write fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ ) }
+ qr/Missing required argument 'want_finish'/,
+ "new() without want_finish fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ ) }
+ qr/Missing required argument 'want_error'/,
+ "new() without want_finish fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => sub {},
+ ) }
+ qr/Missing required argument 'key'/,
+ "new() without key fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => sub {},
+ key => 'aabc',
+ ) }
+ qr/Missing required argument 'command'/,
+ "new() without command fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => sub {},
+ key => 'aabc',
+ command => 'status',
+ ) }
+ qr/Argument 'is_client' or 'is_server' must be defined/,
+ "new() without is_client or is_server fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => 'cat',
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => sub {},
+ key => 'aabc',
+ command => 'status',
+ is_client => 1,
+ ) }
+ qr/Argument 'want_read' is not a code ref/,
+ "new() with bad want_read fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => 'cat',
+ want_finish => sub {},
+ want_error => sub {},
+ key => 'aabc',
+ command => 'status',
+ is_client => 1,
+ ) }
+ qr/Argument 'want_write' is not a code ref/,
+ "new() with bad want_write fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => 'cat',
+ want_error => sub {},
+ key => 'aabc',
+ command => 'status',
+ is_client => 1,
+ ) }
+ qr/Argument 'want_finish' is not a code ref/,
+ "new() with bad want_finish fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => 'cat',
+ key => 'aabc',
+ command => 'status',
+ is_client => 1,
+ ) }
+ qr/Argument 'want_error' is not a code ref/,
+ "new() with bad want_error fails";
+
+throws_ok { Net::RNDC::Session->new(
+ want_read => sub {},
+ want_write => sub {},
+ want_finish => sub {},
+ want_error => sub {},
+ key => 'aabc',
+ command => 'status',
+ is_client => 1,
+ is_server => 1,
+ ) }
+ qr/Argument 'is_client' cannot be mixed with 'is_server'/,
+ "new() with is_client and is_server fails";
+
+{
+
+# Test both client/session (which also tests parsing/generation)
+
+# Response from server
+my $sresp;
+
+# Client error, if any
+my $cerror;
+
+# Server error, if any
+my $serror;
+
+my ($client, $server);
+
+$client = Net::RNDC::Session->new(
+ want_read => sub {}, # $server->want_write() handles this
+ want_write => sub {
+ my $c = shift;
+
+ $c->next;
+
+ $server->next(shift);
+ },
+ want_finish => sub {
+ my $c = shift;
+
+ $sresp = shift;
+ },
+ want_error => sub {
+ my $c = shift;
+
+ $cerror = shift;
+ },
+ key => 'abcd',
+ is_client => 1,
+ command => 'status',
+);
+
+$server = Net::RNDC::Session->new(
+ want_read => sub {}, # $client->want_write() handles this
+ want_write => sub {
+ my $s = shift;
+
+ $s->next;
+
+ $client->next(shift);
+ },
+ want_finish => sub {
+ my $s = shift;
+
+ return;
+ },
+ want_error => sub {
+ my $s = shift;
+
+ $serror = shift;
+ },
+ key => 'abcd',
+ is_server => 1,
+ command => 'hahahaha',
+);
+
+# Calls $server->want_read which does nothing
+$server->start;
+
+# Calls $client->want_write which kicks off the flow
+$client->start;
+
+is($cerror, undef, 'No client error reported');
+is($serror, undef, 'No server error reported');
+
+is($sresp, 'hahahaha', 'Client/Server communicated');
+
+}
+
+done_testing;
Please sign in to comment.
Something went wrong with that request. Please try again.