// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package streamingaead_test

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"testing"

	"google.golang.org/protobuf/proto"
	"github.com/tink-crypto/tink-go/v2/keyset"
	"github.com/tink-crypto/tink-go/v2/mac"
	"github.com/tink-crypto/tink-go/v2/streamingaead"
	"github.com/tink-crypto/tink-go/v2/subtle/random"
	"github.com/tink-crypto/tink-go/v2/testkeyset"
	"github.com/tink-crypto/tink-go/v2/testutil"
	"github.com/tink-crypto/tink-go/v2/tink"
	ghpb "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"
	commonpb "github.com/tink-crypto/tink-go/v2/proto/common_go_proto"
	tinkpb "github.com/tink-crypto/tink-go/v2/proto/tink_go_proto"
)

const (
	aesGCMHKDFTypeURL = "type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey"
)

func TestFactoryMultipleKeys(t *testing.T) {
	keyset := testutil.NewTestAESGCMHKDFKeyset()

	keysetHandle, err := testkeyset.NewHandle(keyset)
	if err != nil {
		t.Fatal(err)
	}

	a, err := streamingaead.New(keysetHandle)
	if err != nil {
		t.Errorf("streamingaead.New failed: %s", err)
	}

	t.Run("Encrypt with a primary RAW key and decrypt with the keyset", func(t *testing.T) {
		if err := validateFactoryCipher(a, a); err != nil {
			t.Errorf("invalid cipher: %s", err)
		}
	})

	t.Run("Encrypt with a non-primary RAW key and decrypt with the keyset", func(t *testing.T) {
		rawKey := keyset.Key[1]
		if rawKey.OutputPrefixType != tinkpb.OutputPrefixType_RAW {
			t.Errorf("expect a raw key")
		}
		keyset2 := testutil.NewKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
		keysetHandle2, err := testkeyset.NewHandle(keyset2)
		if err != nil {
			t.Fatalf("testkeyset.NewHandle(keyset2) err = %q, want nil", err)
		}
		a2, err := streamingaead.New(keysetHandle2)
		if err != nil {
			t.Errorf("streamingaead.New failed: %s", err)
		}
		if err := validateFactoryCipher(a2, a); err != nil {
			t.Errorf("invalid cipher: %s", err)
		}
	})

	t.Run("Encrypt with a random key not in the keyset, decrypt with the keyset should fail", func(t *testing.T) {
		keyset2 := testutil.NewTestAESGCMHKDFKeyset()
		keysetHandle2, err := testkeyset.NewHandle(keyset2)
		if err != nil {
			t.Fatalf("testkeyset.NewHandle(keyset2) err = %q, want nil", err)
		}
		a2, err := streamingaead.New(keysetHandle2)
		if err != nil {
			t.Errorf("streamingaead.New failed: %s", err)
		}
		err = validateFactoryCipher(a2, a)
		if err == nil || !strings.Contains(err.Error(), "decryption failed") {
			t.Errorf("expect decryption to fail with random key: %s", err)
		}
	})
}

func validateFactoryCipher(encryptCipher tink.StreamingAEAD, decryptCipher tink.StreamingAEAD) error {
	tt := []int{1, 16, 4095, 4096, 4097, 16384}

	for _, t := range tt {
		if err := encryptDecrypt(encryptCipher, decryptCipher, t, 32); err != nil {
			return fmt.Errorf("failed plaintext-size=%d: %s", t, err)
		}
	}
	return nil
}

func encryptDecrypt(encryptCipher, decryptCipher tink.StreamingAEAD, ptSize, aadSize int) error {
	pt := random.GetRandomBytes(uint32(ptSize))
	aad := random.GetRandomBytes(uint32(aadSize))

	buf := &bytes.Buffer{}
	w, err := encryptCipher.NewEncryptingWriter(buf, aad)
	if err != nil {
		return fmt.Errorf("cannot create encrypt writer: %v", err)
	}
	if _, err := w.Write(pt); err != nil {
		return fmt.Errorf("error writing data: %v", err)
	}
	if err := w.Close(); err != nil {
		return fmt.Errorf("error closing writer: %v", err)
	}

	r, err := decryptCipher.NewDecryptingReader(buf, aad)
	if err != nil {
		return fmt.Errorf("cannot create decrypt reader: %v", err)
	}
	ptGot := make([]byte, len(pt)+1)
	n, err := io.ReadFull(r, ptGot)
	if err != nil && err != io.ErrUnexpectedEOF {
		return fmt.Errorf("decryption failed: %v", err)
	}
	ptGot = ptGot[:n]
	if !bytes.Equal(pt, ptGot) {
		return fmt.Errorf("decryption failed")
	}
	return nil
}

func TestFactoryWithInvalidPrimitiveSetType(t *testing.T) {
	wrongKH, err := keyset.NewHandle(mac.HMACSHA256Tag128KeyTemplate())
	if err != nil {
		t.Fatalf("failed to build *keyset.Handle: %s", err)
	}

	_, err = streamingaead.New(wrongKH)
	if err == nil {
		t.Fatal("New() should fail with wrong *keyset.Handle")
	}
}

func TestFactoryWithValidPrimitiveSetType(t *testing.T) {
	goodKH, err := keyset.NewHandle(streamingaead.AES128GCMHKDF4KBKeyTemplate())
	if err != nil {
		t.Fatalf("failed to build *keyset.Handle: %s", err)
	}

	_, err = streamingaead.New(goodKH)
	if err != nil {
		t.Fatalf("New() failed with good *keyset.Handle: %s", err)
	}
}

func TestFactoryWithKeysetWithTinkKeys(t *testing.T) {
	key := &ghpb.AesGcmHkdfStreamingKey{
		Version:  0,
		KeyValue: []byte("0123456789abcdef"),
		Params: &ghpb.AesGcmHkdfStreamingParams{
			CiphertextSegmentSize: 512,
			DerivedKeySize:        16,
			HkdfHashType:          commonpb.HashType_SHA1,
		},
	}
	value1, err := proto.Marshal(key)
	if err != nil {
		t.Fatalf("proto.Marshal(key) err = %q, want nil", err)
	}
	key.KeyValue = []byte("ABCDEF0123456789")
	value2, err := proto.Marshal(key)
	if err != nil {
		t.Fatalf("proto.Marshal(key) err = %q, want nil", err)
	}

	keyset := &tinkpb.Keyset{
		PrimaryKeyId: 1,
		Key: []*tinkpb.Keyset_Key{{
			KeyData: &tinkpb.KeyData{
				TypeUrl:         aesGCMHKDFTypeURL,
				Value:           value1,
				KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
			},
			OutputPrefixType: tinkpb.OutputPrefixType_TINK,
			KeyId:            1,
			Status:           tinkpb.KeyStatusType_ENABLED,
		}, &tinkpb.Keyset_Key{
			KeyData: &tinkpb.KeyData{
				TypeUrl:         aesGCMHKDFTypeURL,
				Value:           value2,
				KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
			},
			OutputPrefixType: tinkpb.OutputPrefixType_RAW,
			KeyId:            2,
			Status:           tinkpb.KeyStatusType_ENABLED,
		}},
	}

	keysetHandle, err := testkeyset.NewHandle(keyset)
	if err != nil {
		t.Fatalf("testkeyset.NewHandle(keyset) err = %q, want nil", err)
	}
	a, err := streamingaead.New(keysetHandle)
	if err != nil {
		t.Errorf("streamingaead.New(keysetHandle) err = %q, want nil", err)
	}

	if err := validateFactoryCipher(a, a); err != nil {
		t.Errorf("Encryption & Decryption with TINK key should succeed")
	}
}
