#!/usr/bin/perl
use strict;
use warnings;

#use lib '../lib';

use Test::More;

#use Smart::Comments;

use Crypto::Utils::OpenSSL;
use Crypto::Utils::Noise;

use CBOR::XS qw/encode_cbor decode_cbor/;

#use Digest::SHA qw/hmac_sha256 sha256/;
#use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify);
use FindBin;
use POSIX qw/strftime/;

our $noise_conf = {
    group_name => 'prime256v1',

    #group_name             => 'secp256r1',

    hash_name => 'SHA256',
    hash_func => sub { digest( 'SHA256', @_ ) },
    hash_len  => 32,

    cipher_name => 'aes-256-gcm',
    enc_func => sub {  #encrypt: key, iv, aad, plaintext  -> ciphertext, authtag
        ### enc: scalar(@_)
        ### key, iv, aad, plain, ciphertext, tag
        my ( $key, $iv, $aad, $plain ) = @_;

        #print unpack("H*", $_), "\n" for @_;
        my $tag_len = 16;
        my $res =
          aead_encrypt( 'aes-256-gcm', $plain, $aad, $key, $iv, $tag_len );
        my $ciphertext = $res->[0];
        my $tag        = $res->[1];

        #print unpack("H*", $_), "\n" for ($ciphertext, $tag);
        return ( $ciphertext, $tag );
    },
    dec_func => sub {   #decrypt: key, iv, aad, ciphertext, authtag -> plaintext
        ### dec: scalar(@_)
        my ( $key, $iv, $aad, $ciphertext, $tag ) = @_;
        ### $key, iv, aad, ciphertext, tag, plain
        print unpack( "H*", $_ ), "\n"
          for ( $key, $iv, $aad, $ciphertext, $tag );
        my $plain =
          aead_decrypt( 'aes-256-gcm', $ciphertext, $aad, $tag, $key, $iv );

        #print unpack("H*", $_), "\n" for ($plain);
        return $plain;

        #return gcm_decrypt_verify( 'AES', @d );
    },

    check_rs_pub_func => \&check_pub_s,

    key_len     => 32,
    iv_len      => 12,
    authtag_len => 16,

    msg_pack_func   => \&encode_cbor,
    msg_unpack_func => \&decode_cbor,
};
$noise_conf->{ec_params} = get_ec_params( $noise_conf->{group_name} );
init_ciphersuite_name($noise_conf);

my @test_psk = ( [ undef, undef ], [ 'test_psk', 0 ], [ 'test_psk', 1 ] );

for my $pattern_name (qw/N K X/) {

    #for my $pattern_name ( qw/N / ) {

    #my $pattern_cnf = noise_pattern($pattern);
    for my $psk_r (@test_psk) {

        my ( $psk, $psk_id ) = @$psk_r;

        ### -----------start a_hs --------: $pattern_name, $psk, $psk_id
        my $a_hs = new_handshake_state(
            $noise_conf,
            {
                who          => 'a',
                pattern_name => $pattern_name,
                initiator    => 1,
                prologue     => 'some_info',

                psk    => $psk,
                psk_id => $psk_id,

                s_priv =>
                  read_key_from_pem( $FindBin::Bin . '/noise-a_s_priv.pem' ),
                s_pub =>
                  read_pubkey_from_pem( $FindBin::Bin . '/noise-a_s_pub.pem' ),
                rs_pub =>
                  read_pubkey_from_pem( $FindBin::Bin . '/noise-b_s_pub.pem' ),

                s_pub_type => 'raw',
                s_pub_bin  => pack(
                    "H*",
                    read_pubkey(
                        read_pubkey_from_pem(
                            $FindBin::Bin . '/noise-a_s_pub.pem'
                        )
                    )
                ),
                rs_pub_type => 'raw',
                rs_pub_bin  => pack(
                    "H*",
                    read_pubkey(
                        read_pubkey_from_pem(
                            $FindBin::Bin . '/noise-a_rs_pub.pem'
                        )
                    )
                ),
            },
        );

        ### -----------start bs --------: $pattern_name, $psk, $psk_id
        my $b_hs = new_handshake_state(
            $noise_conf,
            {
                who => 'b',

                pattern_name => $pattern_name,

                initiator => 0,
                prologue  => 'some_info',

                psk    => $psk,
                psk_id => $psk_id,

                s_priv =>
                  read_key_from_pem( $FindBin::Bin . '/noise-b_s_priv.pem' ),
                s_pub =>
                  read_pubkey_from_pem( $FindBin::Bin . '/noise-b_s_pub.pem' ),
                rs_pub =>
                  read_pubkey_from_pem( $FindBin::Bin . '/noise-a_s_pub.pem' ),

                s_pub_type => 'raw',
                s_pub_bin  => pack(
                    "H*",
                    read_pubkey(
                        read_pubkey_from_pem(
                            $FindBin::Bin . '/noise-b_s_pub.pem'
                        )
                    )
                ),
                rs_pub_type => 'raw',
                rs_pub_bin  => pack(
                    "H*",
                    read_pubkey(
                        read_pubkey_from_pem(
                            $FindBin::Bin . '/noise-b_rs_pub.pem'
                        )
                    )
                ),
            }
        );

        ### a write message to b
        ### init a_hs
        ### $a_hs
        ### a send msg to b
        my $a_msg_src = "init.syn";
        ### $a_msg_src write_message
        my ( $a_msg, $a_c1, $a_c2 ) =
          write_message( $noise_conf, $a_hs, [], $a_msg_src );
        ### a_msg: unpack( "H*", $a_msg )
        ### $a_hs
        ### $a_c1
        ### $a_c2
        ### end a write message to b

        ### b read message from a
        ### init b_hs
        ###   $b_hs
        ### b recv msg from a
        my ( $b_recv_a_msg_r, $b_c1, $b_c2 ) =
          read_message( $noise_conf, $b_hs, [], $a_msg );
        ### b_recv_a_msg: $b_recv_a_msg_r->[0]
        ###   $b_hs
        ### $b_c1
        ### $b_c2
        ### end b read message from a

        # a -> b : plain_a  -> trans_cipherinfo_a
        ### a send comm msg to b
        my $plain_a = 'fujian quanzhou 666';
        my ( $a_c1_key, $a_c1_iv ) =
          derive_session_key_iv( $noise_conf, $a_c1->{k}, '' );
        my $a_trans_cipherinfo_b =
          session_encrypt( $noise_conf, $a_c1_key, $a_c1_iv, $a_hs->{ss}{h},
            $plain_a );

        ### b recv comm msg from a
        my ( $b_c1_key, $b_c1_iv ) =
          derive_session_key_iv( $noise_conf, $b_c1->{k}, '' );
        my $b_recv_plaintext_a =
          session_decrypt( $noise_conf, $b_c1_key, $b_c1_iv, $b_hs->{ss}{h},
            $a_trans_cipherinfo_b );
        ### $b_recv_plaintext_a

        # b -> a : plain_b -> trans_cipherinfo_b
        ### b send comm msg to a
        my $plain_b = 'anhui hefei 888';
        my ( $b_c2_key, $b_c2_iv ) =
          derive_session_key_iv( $noise_conf, $b_c2->{k}, '' );
        my $b_trans_cipherinfo_a =
          session_encrypt( $noise_conf, $b_c2_key, $b_c2_iv, $b_hs->{ss}{h},
            $plain_b );

        ### a recv comm msg from b
        my ( $a_c2_key, $a_c2_iv ) =
          derive_session_key_iv( $noise_conf, $a_c2->{k}, '' );
        my $a_recv_plaintext_b =
          session_decrypt( $noise_conf, $a_c2_key, $a_c2_iv, $a_hs->{ss}{h},
            $b_trans_cipherinfo_a );
        ### $a_recv_plaintext_b

        is( $plain_a, $b_recv_plaintext_a, "$pattern_name: plain_a" );
        is( $plain_b, $a_recv_plaintext_b, "$pattern_name: plain_b" );

        ### -----------end test --------: $pattern_name, $psk, $psk_id
    } ## end for my $psk_r ( @test_psk)
} ## end for my $pattern_name ( ...)

done_testing();

sub check_pub_s {
    my ( $type, $value ) = @_;
    ### check pub s: $type, unpack("H*", $value)

    if ( $type eq 'raw' ) {

        #check the value is in the TOFU (trust on first use) record or not
        return $value;    #pub raw
    }

    if ( $type eq 'id' ) {

        #check the value is in the TOFU (trust on first use) record or not
        #map value to the pub raw
    }

    if ( $type eq 'sn' ) {

        #check the value is in the TOFU (trust on first use) record or not
        #map value to the cert, extract the pub raw from cert
    }

    if ( $type eq 'cert' ) {

        #check the value is in the TOFU (trust on first use) record or not
        #if not, check_cert_avail
        #extract the pub raw from cert
    }
} ## end sub check_pub_s
