# Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
# Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com

use Test::More tests => 15;
use strict;
use warnings;

use lib '@amperldir@';
use Installcheck;
use Amanda::IPC::Binary;
use IO::Handle;
use Amanda::Debug;
use Data::Dumper;
use Carp;
use POSIX;

##
# Define a test protocol

package TestProtocol;
use base "Amanda::IPC::Binary";
use Amanda::IPC::Binary;

# cmd_id's
use constant SIMPLE => 1;
use constant FOO => 2;
use constant BAR => 3;

# arg_id's
use constant NAME => 1;
use constant NICKNAME => 2;
use constant MANDATORY => 3;
use constant OPTIONAL => 4;

magic(0x1234);

command(SIMPLE);

command(FOO,
    NAME, $IPC_BINARY_STRING,
    NICKNAME, $IPC_BINARY_STRING);

command(BAR,
    MANDATORY, 0,
    OPTIONAL, $IPC_BINARY_OPTIONAL);

package main;

# set up debugging so debug output doesn't interfere with test results
Amanda::Debug::dbopen("installcheck");
Installcheck::log_test_output();

# and disable Debug's die() and warn() overrides
Amanda::Debug::disable_die_override();

sub to_bytes {
    my @result;
    for my $byte (@_) {
	if (length($byte) == 1) {
	    push @result, $byte;
	} else {
	    push @result, chr(hex($byte));
	}
    }
    return join('', @result);
}

sub to_hex {
    my ($bytes) = @_;
    my @result;
    for my $byte (split //, $bytes) {
	if ((ord($byte) >= ord('a') and ord($byte) <= ord('z')) || $byte eq '-') {
	    push @result, $byte;
	} else {
	    push @result, sprintf("%02x", ord($byte));
	}
    }

    return join(" ", @result);
}

# first, try reading a pre-defined sequence of bytes; this lets us make sure
# byte-ordering is right on all platforms.
my $fh;
my $tmpfile = "$Installcheck::TMP/ipc-binary-test";
my ($chan, $msg);

open($fh, ">", $tmpfile);
print $fh to_bytes(
    qw(12 34), # magic
    qw(00 01), # cmd_id = SIMPLE
    qw(00 00 00 0A), # length
    qw(00 00), # count

    qw(12 34), # magic
    qw(00 02), # cmd_id = FOO
    qw(00 00 00 22), # length
    qw(00 02), # n_args
    qw(00 00 00 07), # length
    qw(00 01), # arg_id = NAME
    qw(n i k o l a s), # data
    qw(00 00 00 05), # length
    qw(00 02), # arg_id = NICKNAME
    qw(a t r u s), # data

    qw(12 34), # magic
    qw(00 03), # cmd_id = BAR
    qw(00 00 00 1f), # length
    qw(00 01), # n_args
    qw(00 00 00 0f), # length
    qw(00 03), # arg_id = MANDATORY
    qw(v e r b o d e n - v r u c h t), # data

    qw(12 34), # magic
    qw(00 03), # cmd_id = BAR
    qw(00 00 00 29), # length
    qw(00 02), # n_args
    qw(00 00 00 0a), # length
    qw(00 03), # arg_id = MANDATORY
    qw(o u d e - g e u z e), # data
    qw(00 00 00 09), # length
    qw(00 04), # arg_id = OPTIONAL
    qw(r o d e n b a c h), # data
);
close($fh);

open($fh, "<", $tmpfile);

$chan = TestProtocol->new();

$msg = $chan->read_message($fh);
is($msg->{'cmd_id'}, TestProtocol::SIMPLE,
    "got SIMPLE");

$msg = $chan->read_message($fh);
is($msg->{'cmd_id'}, TestProtocol::FOO,
    "got FOO");
is($msg->{'args'}[TestProtocol::NAME], "nikolas",
    "got NAME arg");
is($msg->{'args'}[TestProtocol::NICKNAME], "atrus",
    "got NICKNAME arg");

$msg = $chan->read_message($fh);
is($msg->{'cmd_id'}, TestProtocol::BAR,
    "got BAR");
is($msg->{'args'}[TestProtocol::MANDATORY], "verboden-vrucht",
    "got MANDATORY arg");
is($msg->{'args'}[TestProtocol::OPTIONAL], undef,
    "got no OPTIONAL arg");

$msg = $chan->read_message($fh);
is($msg->{'cmd_id'}, TestProtocol::BAR,
    "got BAR");
is($msg->{'args'}[TestProtocol::MANDATORY], "oude-geuze",
    "got MANDATORY arg");
is($msg->{'args'}[TestProtocol::OPTIONAL], "rodenbach",
    "got OPTIONAL arg");

$msg = $chan->read_message($fh);
is($msg, undef, "no more messages");

close($fh);

# now try writing a set of messages, and check that the result is what it should be
open($fh, ">", $tmpfile);
$chan = TestProtocol->new();

ok($chan->write_message($fh, $chan->message(
	TestProtocol::FOO,
	TestProtocol::NAME, "james",
	TestProtocol::NICKNAME, "jimmy")),
    "wrote FOO message");

ok($chan->write_message($fh, $chan->message(
	TestProtocol::BAR,
	TestProtocol::MANDATORY, "absolutely",
	TestProtocol::OPTIONAL, "maybe")),
    "wrote BAR message with optional arg");

ok($chan->write_message($fh, $chan->message(
	TestProtocol::BAR,
	TestProtocol::MANDATORY, "yessir")),
    "wrote BAR message without optional arg");

$chan->close();
close($fh);

my $bytes_expected = to_bytes(
    qw(12 34), # magic
    qw(00 02), # cmd_id = FOO
    qw(00 00 00 20), # length
    qw(00 02), # n_args
    qw(00 00 00 05), # length
    qw(00 01), # arg_id = NAME
    qw(j a m e s), # data
    qw(00 00 00 05), # length
    qw(00 02), # arg_id = NICKNAME
    qw(j i m m y), # data

    qw(12 34), # magic
    qw(00 03), # cmd_id = BAR
    qw(00 00 00 25), # length
    qw(00 02), # n_args
    qw(00 00 00 0a), # length
    qw(00 03), # arg_id = MANDATORY
    qw(a b s o l u t e l y), # data
    qw(00 00 00 05), # length
    qw(00 04), # arg_id = OPTIONAL
    qw(m a y b e), # data

    qw(12 34), # magic
    qw(00 03), # cmd_id = BAR
    qw(00 00 00 16), # length
    qw(00 01), # n_args
    qw(00 00 00 06), # length
    qw(00 03), # arg_id = MANDATORY
    qw(y e s s i r), # data
);

# slurp the contents of the temp file and see if it matches
open($fh, "<", $tmpfile);
my $bytes_written = do { local $/; <$fh> };
close($fh);

is(to_hex($bytes_written),
   to_hex($bytes_expected),
    "got the expected bytes");
