// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 siv_test

import (
	"crypto/rand"
	"testing"

	"github.com/jacobsa/crypto/siv"
	aes_testing "github.com/jacobsa/crypto/testing"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestEncrypt(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type EncryptTest struct{}

func init() { RegisterTestSuite(&EncryptTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *EncryptTest) NilKey() {
	key := []byte(nil)
	plaintext := []byte{}

	_, err := siv.Encrypt(nil, key, plaintext, nil)
	ExpectThat(err, Error(HasSubstr("-byte")))
}

func (t *EncryptTest) ShortKey() {
	key := make([]byte, 31)
	plaintext := []byte{}

	_, err := siv.Encrypt(nil, key, plaintext, nil)
	ExpectThat(err, Error(HasSubstr("-byte")))
}

func (t *EncryptTest) LongKey() {
	key := make([]byte, 65)
	plaintext := []byte{}

	_, err := siv.Encrypt(nil, key, plaintext, nil)
	ExpectThat(err, Error(HasSubstr("-byte")))
}

func (t *EncryptTest) TooMuchAssociatedData() {
	key := make([]byte, 64)
	plaintext := []byte{}
	associated := make([][]byte, 127)

	_, err := siv.Encrypt(nil, key, plaintext, associated)
	ExpectThat(err, Error(HasSubstr("associated")))
	ExpectThat(err, Error(HasSubstr("126")))
}

func (t *EncryptTest) JustLittleEnoughAssociatedData() {
	key := make([]byte, 64)
	plaintext := []byte{}
	associated := make([][]byte, 126)

	_, err := siv.Encrypt(nil, key, plaintext, associated)
	ExpectEq(nil, err)
}

func (t *EncryptTest) DoesntClobberAssociatedSlice() {
	key := make([]byte, 32)
	plaintext := []byte{}

	associated0 := aes_testing.FromRfcHex("deadbeef")
	associated1 := aes_testing.FromRfcHex("feedface")
	associated2 := aes_testing.FromRfcHex("ba5eba11")
	associated := [][]byte{associated0, associated1, associated2}

	// Call with a slice of associated missing the last element. The last element
	// shouldn't be clobbered.
	_, err := siv.Encrypt(nil, key, plaintext, associated[:2])
	AssertEq(nil, err)

	ExpectThat(
		associated,
		ElementsAre(
			DeepEquals(associated0),
			DeepEquals(associated1),
			DeepEquals(associated2),
		))
}

func (t *EncryptTest) OutputIsDeterministic() {
	cases := aes_testing.EncryptCases()
	AssertGt(len(cases), 37)
	c := cases[37]

	output0, err := siv.Encrypt(nil, c.Key, c.Plaintext, c.Associated)
	AssertEq(nil, err)

	output1, err := siv.Encrypt(nil, c.Key, c.Plaintext, c.Associated)
	AssertEq(nil, err)

	output2, err := siv.Encrypt(nil, c.Key, c.Plaintext, c.Associated)
	AssertEq(nil, err)

	AssertGt(len(output0), 0)
	ExpectThat(output0, DeepEquals(output1))
	ExpectThat(output0, DeepEquals(output2))
}

func (t *EncryptTest) Rfc5297TestCaseA1() {
	key := aes_testing.FromRfcHex(
		"fffefdfc fbfaf9f8 f7f6f5f4 f3f2f1f0" +
			"f0f1f2f3 f4f5f6f7 f8f9fafb fcfdfeff")

	plaintext := aes_testing.FromRfcHex(
		"11223344 55667788 99aabbcc ddee")

	associated := [][]byte{
		aes_testing.FromRfcHex(
			"10111213 14151617 18191a1b 1c1d1e1f" +
				"20212223 24252627"),
	}

	expected := aes_testing.FromRfcHex(
		"85632d07 c6e8f37f 950acd32 0a2ecc93" +
			"40c02b96 90c4dc04 daef7f6a fe5c")

	output, err := siv.Encrypt(nil, key, plaintext, associated)
	AssertEq(nil, err)
	ExpectThat(output, DeepEquals(expected))
}

func (t *EncryptTest) Rfc5297TestCaseA2() {
	key := aes_testing.FromRfcHex(
		"7f7e7d7c 7b7a7978 77767574 73727170" +
			"40414243 44454647 48494a4b 4c4d4e4f")

	plaintext := aes_testing.FromRfcHex(
		"74686973 20697320 736f6d65 20706c61" +
			"696e7465 78742074 6f20656e 63727970" +
			"74207573 696e6720 5349562d 414553")

	associated := [][]byte{
		aes_testing.FromRfcHex(
			"00112233 44556677 8899aabb ccddeeff" +
				"deaddada deaddada ffeeddcc bbaa9988" +
				"77665544 33221100"),
		aes_testing.FromRfcHex(
			"10203040 50607080 90a0"),
		aes_testing.FromRfcHex(
			"09f91102 9d74e35b d84156c5 635688c0"),
	}

	expected := aes_testing.FromRfcHex(
		"7bdb6e3b 432667eb 06f4d14b ff2fbd0f" +
			"cb900f2f ddbe4043 26601965 c889bf17" +
			"dba77ceb 094fa663 b7a3f748 ba8af829" +
			"ea64ad54 4a272e9c 485b62a3 fd5c0d")

	output, err := siv.Encrypt(nil, key, plaintext, associated)
	AssertEq(nil, err)
	ExpectThat(output, DeepEquals(expected))
}

func (t *EncryptTest) GeneratedTestCases() {
	cases := aes_testing.EncryptCases()
	AssertGe(len(cases), 100)

	for i, c := range cases {
		output, err := siv.Encrypt(nil, c.Key, c.Plaintext, c.Associated)
		AssertEq(nil, err, "Case %d: %v", i, c)
		ExpectThat(output, DeepEquals(c.Output), "Case %d: %v", i, c)
	}
}

////////////////////////////////////////////////////////////////////////
// Benchmarks
////////////////////////////////////////////////////////////////////////

func benchmarkEncrypt(
	b *testing.B,
	size int) {
	var err error

	// Generate the appropriate amount of random data.
	plaintext := make([]byte, size)
	_, err = rand.Read(plaintext)
	if err != nil {
		b.Fatalf("rand.Read: %v", err)
	}

	// Create a random key.
	const keyLen = 32
	key := make([]byte, keyLen)

	_, err = rand.Read(key)
	if err != nil {
		b.Fatalf("rand.Read: %v", err)
	}

	// Repeatedly encrypt.
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err = siv.Encrypt(nil, key, plaintext, nil)
		if err != nil {
			b.Fatalf("Encrypt: %v", err)
		}
	}

	b.SetBytes(int64(size))
}

func BenchmarkEncrypt1KiB(b *testing.B) {
	const size = 1 << 10
	benchmarkEncrypt(b, size)
}

func BenchmarkEncrypt1MiB(b *testing.B) {
	const size = 1 << 20
	benchmarkEncrypt(b, size)
}

func BenchmarkEncrypt16MiB(b *testing.B) {
	const size = 1 << 24
	benchmarkEncrypt(b, size)
}
