use strict;
use warnings;
use Test::More;

use Crypt::Sodium::XS::Util ":all";

my $nonce = scalar("\xff" x 6) . scalar("\xfe" x (24 - 6));
$nonce = sodium_increment($nonce);
is(unpack("H*", $nonce), "000000000000fffefefefefefefefefefefefefefefefefe",
   "sodium_increment() (xFF x 6)");

$nonce = scalar("\xff" x 10) . scalar("\xfe" x (24 - 10));
$nonce = sodium_increment($nonce);
is(unpack("H*", $nonce), "00000000000000000000fffefefefefefefefefefefefefe",
   "sodium_increment() (xFF x 10)");


$nonce = scalar("\xff" x 22) . scalar("\xfe" x (24 - 22));
$nonce = sodium_increment($nonce);
is(unpack("H*", $nonce), "00000000000000000000000000000000000000000000fffe",
   "sodium_increment() (xFF x 22)");

my $str = "foobarbaz";
my $str2 = "more";
sodium_memzero($str);
is($str, scalar("\0" x 9), "sodium_memzero nulls string");
$str = "foobarbaz";
sodium_memzero($str, $str2);
is($str . $str2, scalar("\0" x 13), "sodium_memzero multiple args");

my $x = "\x01";
$x = sodium_add($x, "\x01");
is(unpack("H*", $x), "02", "sodium_add right answer");
$x = sodium_sub($x, "\x01");
is(unpack("H*", $x), "01", "sodium_sub right answer");
$x = "\xbd\xbd\xbd\xbd";
$x = sodium_add($x, "\x01\x02");
is(unpack("H*", $x), "bebfbdbd", "sodium_add with rhs shorter");
$x = sodium_sub($x, "\x01\x02");
is(unpack("H*", $x), "bdbdbdbd", "sodium_sub with rhs shorter");
$x = sodium_add("\x01\x02", $x);
is(unpack("H*", $x), "bebfbdbd", "sodium_add with lhs shorter, lhs constant");
$x = "\xff\xff";
$x = sodium_add($x, "\x01\x02");
is(unpack("H*", $x), "0002", "sodium_add with overflow");
$x = sodium_sub($x, "\x01\x02");
is(unpack("H*", $x), "ffff", "sodium_sub with overflow");
$x = "\xff\xff\xff";
$x = sodium_add($x, "\x01");
is(unpack("H*", $x), "000000", "sodium_add with overflow, lhs shorter");
$x = sodium_sub($x, "\x01");
is(unpack("H*", $x), "ffffff", "sodium_sub with overflow, lhs shorter");
$x = sodium_add("\x03", $x);
is(unpack("H*", $x), "020000", "sodium_add with overflow, lhs shorter, lhs constant");
$x = sodium_sub("\x01", $x);
is(unpack("H*", $x), "ffffff", "sodium_sub with overflow, lhs shorter, lhs constant");

$x = "\xbd\xbd\xbd\xbd";
my $y = "\xbd\xbd\xbd\xbd";
ok(sodium_memcmp($x, $y), "memcmp: equivalent arguments returns true");
ok(!sodium_compare($x, $y), "compare: equivalent arguments returns false");
$y = "\xbd\xbd\xbd\xff";
ok(!sodium_memcmp($x, $y), "memcmp: different arguments returns true");
ok(sodium_compare($x, $y) == -1, "compare: less than returns -1");
$y = "\xbd\xbd\xbd\x11";
ok(sodium_compare($x, $y) == 1, "compare: greater than returns 1");
ok(sodium_memcmp($x, $y, 2), "memcmp: equivalent arguments (length) returns true");
ok(!sodium_compare($x, $y, 2), "compare: equivalent arguments (length) returns false");
eval { sodium_memcmp($x, "\1\2") };
like($@, qr/Length of operands must be equal/, "memcmp: invalid lengths");
eval { sodium_compare($x, "\1\2") };
like($@, qr/Length of operands must be equal/, "compare: invalid lengths");

ok(sodium_is_zero("\0\0\0\0\0"), "sodium_is_zero detects zero");
ok(!sodium_is_zero("\0\0\0\0\0\1\0"), "sodium_is_zero detects not zero");

is(sodium_bin2hex("foobar"), "666f6f626172", "sodium_hex2bin correct");
is(sodium_hex2bin("666f6f626172"), "foobar", "sodium_bin2hex correct");
is(sodium_hex2bin("666f6f626172:6666"), "foobar", "sodium_bin2hex stops parsing at invalid hex");
is(sodium_hex2bin("666f6f6261726"), "", "sodium_bin2hex returns empty when unparsable");

$x = sodium_pad("foobar", 16);
is(unpack("H*", $x), "666f6f62617280000000000000000000", "sodium_pad foobar blocksize 16");
$y = sodium_pad("foobar", 15);
is(unpack("H*", $y), "666f6f626172800000000000000000", "sodium_pad foobar blocksize 15");
my $z = sodium_pad("fooba", 3);
is(unpack("H*", $z), "666f6f626180", "sodium_pad fooba blocksize 3");
$z = sodium_pad("foobarz", 3);
is(unpack("H*", $z), "666f6f6261727a8000", "sodium_pad foobarz blocksize 3");
$z = sodium_pad("foobar", 3);
is(unpack("H*", $z), "666f6f626172800000", "sodium_pad foobar blocksize 3");
is(unpack("H*", sodium_unpad($x, 16)), "666f6f626172", "sodium_unpad foobar blocksize 16");
is(unpack("H*", sodium_unpad($y, 15)), "666f6f626172", "sodium_unpad foobar blocksize 15");
is(unpack("H*", sodium_unpad($z, 3)), "666f6f626172", "sodium_unpad foobar blocksize 3");

if (sodium_has_ip_codecs()) {
  my $ip = "10.9.8.7";
  $x = sodium_ip2bin($ip);
  is(unpack("H*", $x), "00000000000000000000ffff0a090807", "sodium_ip2bin v4 $ip");
  $ip = "fe80::dead:beef:c0ff:eeee";
  $x = sodium_ip2bin($ip);
  is(unpack("H*", $x), "fe80000000000000deadbeefc0ffeeee", "sodium_ip2bin v6 $ip");
  $ip = "10.9.8.256";
  eval { $x = sodium_ip2bin($ip) };
  like($@, qr/(?i)invalid ip/, "sodium_ip2bin invalid v4 $ip");

  $ip = "00000000000000000000ffff0a090807";
  $x = sodium_bin2ip(pack("H*", $ip));
  is($x, "10.9.8.7", "sodium_bin2ip v4 $ip");
  $ip = "fe80000000000000deadbeefc0ffeeee";
  $x = sodium_bin2ip(pack("H*", $ip));
  is($x, "fe80::dead:beef:c0ff:eeee", "sodium_bin2ip v6 $ip");
}

done_testing();
