use strict;
use warnings;

use Test::More tests => 63;

use Crypt::AuthEnc::SIV qw( siv_encrypt_authenticate siv_decrypt_verify );

{
  package Local::SIVStringy;
  use overload q{""} => sub { $_[0]->{value} }, fallback => 1;
  sub new { bless { value => $_[1] }, $_[0] }
}

{ ### RFC 5297 - A.1. Deterministic Authenticated Encryption Example
  my $key = pack("H*", "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
  my $ad  = pack("H*", "101112131415161718191a1b1c1d1e1f2021222324252627");
  my $pt  = pack("H*", "112233445566778899aabbccddee");

  my $ct = siv_encrypt_authenticate('AES', $key, $pt, $ad);
  is(unpack('H*', $ct), "85632d07c6e8f37f950acd320a2ecc9340c02b9690c4dc04daef7f6afe5c",
     "RFC5297 A.1 encrypt");

  my $pt2 = siv_decrypt_verify('AES', $key, $ct, $ad);
  is($pt2, $pt, "RFC5297 A.1 decrypt");

  substr($ct, 0, 1) ^= "\x01"; # tamper with the SIV tag
  my $pt3 = siv_decrypt_verify('AES', $key, $ct, $ad);
  is($pt3, undef, "RFC5297 A.1 decrypt with tampered ciphertext");
}

{ ### RFC 5297 - A.2. Nonce-Based Authenticated Encryption Example (multiple AD)
  my $key = pack("H*", "7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f");
  my $ad1 = pack("H*", "00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100");
  my $ad2 = pack("H*", "102030405060708090a0");
  my $ad3 = pack("H*", "09f911029d74e35bd84156c5635688c0");
  my $pt  = pack("H*", "7468697320697320736f6d6520706c61696e7465787420746f20656e63727970742075" .
                        "73696e67205349562d414553");

  my $ct = siv_encrypt_authenticate('AES', $key, $pt, [$ad1, $ad2, $ad3]);
  is(unpack('H*', $ct), "7bdb6e3b432667eb06f4d14bff2fbd0fcb900f2fddbe404326601965c889bf17" .
                        "dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d",
     "RFC5297 A.2 encrypt (multiple AD)");

  my $pt2 = siv_decrypt_verify('AES', $key, $ct, [$ad1, $ad2, $ad3]);
  is($pt2, $pt, "RFC5297 A.2 decrypt (multiple AD)");
}

{ ### RFC 5297 limits SIV to 126 associated-data components
  my $key = pack("H*", "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
  my $ad0 = pack("H*", "101112131415161718191a1b1c1d1e1f2021222324252627");
  my $pt  = pack("H*", "112233445566778899aabbccddee");
  my @ad  = ("\x00") x 127;

  eval { siv_encrypt_authenticate('AES', $key, $pt, \@ad) };
  like($@, qr/too many associated data components/, "encrypt rejects more than 126 AD components");

  my $ct = siv_encrypt_authenticate('AES', $key, $pt, $ad0);
  eval { siv_decrypt_verify('AES', $key, $ct, \@ad) };
  like($@, qr/too many associated data components/, "decrypt rejects more than 126 AD components");
}

{
  my $key = pack("H*", "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
  my $ad  = pack("H*", "101112131415161718191a1b1c1d1e1f2021222324252627");
  my $pt  = pack("H*", "112233445566778899aabbccddee");

  my $ct_plain = siv_encrypt_authenticate('AES', $key, $pt, $ad);
  my $ct_over  = siv_encrypt_authenticate('AES',
                    Local::SIVStringy->new($key),
                    Local::SIVStringy->new($pt),
                    Local::SIVStringy->new($ad));
  is($ct_over, $ct_plain, "overloaded key/plaintext/adata scalar matches plain values");

  my $pt_over = siv_decrypt_verify('AES',
                Local::SIVStringy->new($key),
                Local::SIVStringy->new($ct_plain),
                Local::SIVStringy->new($ad));
  is($pt_over, $pt, "overloaded key/ciphertext/adata scalar decrypts");

  my $ct_array = siv_encrypt_authenticate('AES', $key, $pt, [Local::SIVStringy->new($ad)]);
  is($ct_array, $ct_plain, "overloaded arrayref AD component matches plain scalar AD");

  eval { siv_encrypt_authenticate('AES', [], $pt, $ad) };
  like($@, qr/key must be string\/buffer scalar/, 'rejects non-string key');

  eval { siv_encrypt_authenticate('AES', $key, $pt, [[]]) };
  like($@, qr/associated data component must be string\/buffer scalar/, 'rejects invalid arrayref AD component');

  eval { siv_decrypt_verify('AES', $key, [], $ad) };
  like($@, qr/ciphertext must be string\/buffer scalar/, 'rejects non-string ciphertext');
}

{ ### zero AD components vs a single EMPTY AD component (RFC 5297 sec 2.4)
  my $key128 = pack("H*", "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
  my $key_a2 = pack("H*", "7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f");
  my $key256 = pack("C*", 0..63);
  my @pairs = (
    { name  => 'AES-128-SIV, 14-byte pt (dbl/pad path)',
      key   => $key128,
      pt    => pack("H*", "112233445566778899aabbccddee"),
      zero  => "f1c5fdeac1f15a26779c1501f9fb758827e946c669088ab06da58c5c831c",
      empty => "d1022f5b3664e5a4dfaf90f85be6f28ab66cff6b8eca0b79f083b39a0901"
    },
    { name  => 'AES-128-SIV, 47-byte pt (xorend path, RFC A.2 plaintext)',
      key   => $key_a2,
      pt    => pack("H*", "7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553"),
      zero  => "2845219b7ad669004a254fbe17dd92411e0d1c5593d4ab4d25da6dcf3b68c3936d759629322f92317acecf8254adf8d0cde09cead8117bd80da0c95f7a1caf",
      empty => "dd69885ab4f07e5e98b6bf8bc63dc8d79223b59ed79e7845c52ccfccdc2165114046bef7ad8de77b79c3e1624838a4c925b43158a5962077bf85747e6141a0"
    },
    { name  => 'AES-128-SIV, exactly 16-byte pt (xorend boundary)',
      key   => $key128,
      pt    => pack("H*", "30313233343536373839616263646566"),
      zero  => "3f5cd23c5b68fc1f8ef7557952deb21fbc7fb7c62617d12b41d9703c3c72d446",
      empty => "c6972c63cb7bcdae674a441f817e8be6b322c97182a15032d3ac5475022f72e6"
    },
    { name  => 'AES-128-SIV, empty plaintext',
      key   => $key128,
      pt    => "",
      zero  => "f2007a5beb2b8900c588a7adf599f172",
      empty => "499e3994710218de7582e0f2c0ab5ed0"
    },
    { name  => 'AES-256-SIV, 14-byte pt',
      key   => $key256,
      pt    => pack("H*", "112233445566778899aabbccddee"),
      zero  => "c561d53546fd103cd5a6e4d39112abddadc39943b2f0cf1517c3fd6efa3d",
      empty => "956c2bf56e4501728ae2079d6b73ce05df466d1acd665bfab51b4057cd83"
    },
  );
  for my $t (@pairs) {
    my ($key, $pt, $n) = ($t->{key}, $t->{pt}, $t->{name});
    my $zero_ct  = pack("H*", $t->{zero});
    my $empty_ct = pack("H*", $t->{empty});
    # --- zero AD components: every way of saying "no AD" must agree ---
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt)), $t->{zero}, "$n: encrypt with AD argument omitted == zero-AD-component KAT");
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt, undef)), $t->{zero}, "$n: encrypt with AD undef == zero-AD-component KAT");
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt, [])), $t->{zero}, "$n: encrypt with empty arrayref AD == zero-AD-component KAT");
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt, [undef])), $t->{zero}, "$n: encrypt with [undef] AD == zero-AD-component KAT");
    # --- one empty AD component is a DIFFERENT input to S2V ---
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt, "")), $t->{empty}, "$n: encrypt with \"\" AD == one-empty-AD-component KAT");
    is(unpack("H*", siv_encrypt_authenticate('AES', $key, $pt, [""])), $t->{empty}, "$n: encrypt with [\"\"] AD == one-empty-AD-component KAT");
    # --- round trips ---
    is(siv_decrypt_verify('AES', $key, $zero_ct), $pt, "$n: decrypt zero-AD ciphertext with no AD");
    is(siv_decrypt_verify('AES', $key, $empty_ct, ""), $pt, "$n: decrypt one-empty-AD ciphertext with \"\" AD");
    # --- and the two must not be interchangeable ---
    is(siv_decrypt_verify('AES', $key, $zero_ct, ""), undef, "$n: zero-AD ciphertext does NOT verify under \"\" AD");
    is(siv_decrypt_verify('AES', $key, $empty_ct), undef, "$n: one-empty-AD ciphertext does NOT verify under no AD");
  }
}
