// Copyright (C) 2014 Space Monkey, Inc.
//
// 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.

// +build !darwin

package openssl

import (
	"bytes"
	"fmt"
	"strings"
	"testing"
)

func expectError(t *testing.T, err error, msg string) {
	if err == nil {
		t.Fatalf("Expected error containing %#v, but got none", msg)
	}
	if !strings.Contains(err.Error(), msg) {
		t.Fatalf("Expected error containing %#v, but got %s", msg, err)
	}
}

func TestBadInputs(t *testing.T) {
	_, err := NewGCMEncryptionCipherCtx(256, nil,
		[]byte("abcdefghijklmnopqrstuvwxyz"), nil)
	expectError(t, err, "bad key size")
	_, err = NewGCMEncryptionCipherCtx(128, nil,
		[]byte("abcdefghijklmnopqrstuvwxyz"), nil)
	expectError(t, err, "bad key size")
	_, err = NewGCMEncryptionCipherCtx(200, nil,
		[]byte("abcdefghijklmnopqrstuvwxy"), nil)
	expectError(t, err, "unknown block size")
	c, err := GetCipherByName("AES-128-CBC")
	if err != nil {
		t.Fatal("Could not look up AES-128-CBC")
	}
	_, err = NewEncryptionCipherCtx(c, nil, []byte("abcdefghijklmnop"),
		[]byte("abc"))
	expectError(t, err, "bad IV size")
}

func doEncryption(key, iv, aad, plaintext []byte, blocksize, bufsize int) (
	ciphertext, tag []byte, err error) {
	ectx, err := NewGCMEncryptionCipherCtx(blocksize, nil, key, iv)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed making GCM encryption ctx: %s", err)
	}
	err = ectx.ExtraData(aad)
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to add authenticated data: %s",
			err)
	}
	plainb := bytes.NewBuffer(plaintext)
	cipherb := new(bytes.Buffer)
	for plainb.Len() > 0 {
		moar, err := ectx.EncryptUpdate(plainb.Next(bufsize))
		if err != nil {
			return nil, nil, fmt.Errorf("Failed to perform an encryption: %s",
				err)
		}
		cipherb.Write(moar)
	}
	moar, err := ectx.EncryptFinal()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to finalize encryption: %s", err)
	}
	cipherb.Write(moar)
	tag, err = ectx.GetTag()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to get GCM tag: %s", err)
	}
	return cipherb.Bytes(), tag, nil
}

func doDecryption(key, iv, aad, ciphertext, tag []byte, blocksize,
	bufsize int) (plaintext []byte, err error) {
	dctx, err := NewGCMDecryptionCipherCtx(blocksize, nil, key, iv)
	if err != nil {
		return nil, fmt.Errorf("Failed making GCM decryption ctx: %s", err)
	}
	aadbuf := bytes.NewBuffer(aad)
	for aadbuf.Len() > 0 {
		err = dctx.ExtraData(aadbuf.Next(bufsize))
		if err != nil {
			return nil, fmt.Errorf("Failed to add authenticated data: %s", err)
		}
	}
	plainb := new(bytes.Buffer)
	cipherb := bytes.NewBuffer(ciphertext)
	for cipherb.Len() > 0 {
		moar, err := dctx.DecryptUpdate(cipherb.Next(bufsize))
		if err != nil {
			return nil, fmt.Errorf("Failed to perform a decryption: %s", err)
		}
		plainb.Write(moar)
	}
	err = dctx.SetTag(tag)
	if err != nil {
		return nil, fmt.Errorf("Failed to set expected GCM tag: %s", err)
	}
	moar, err := dctx.DecryptFinal()
	if err != nil {
		return nil, fmt.Errorf("Failed to finalize decryption: %s", err)
	}
	plainb.Write(moar)
	return plainb.Bytes(), nil
}

func checkEqual(t *testing.T, output []byte, original string) {
	output_s := string(output)
	if output_s != original {
		t.Fatalf("output != original! %#v != %#v", output_s, original)
	}
}

func TestGCM(t *testing.T) {
	aad := []byte("foo bar baz")
	key := []byte("nobody can guess this i'm sure..") // len=32
	iv := []byte("just a bunch of bytes")
	plaintext := "Long long ago, in a land far away..."

	blocksizes_to_test := []int{256, 192, 128}

	// best for this to have no common factors with blocksize, so that the
	// buffering layer inside the CIPHER_CTX gets exercised
	bufsize := 33

	if len(plaintext)%8 == 0 {
		plaintext += "!" // make sure padding is exercised
	}

	for _, bsize := range blocksizes_to_test {
		subkey := key[:bsize/8]
		ciphertext, tag, err := doEncryption(subkey, iv, aad, []byte(plaintext),
			bsize, bufsize)
		if err != nil {
			t.Fatalf("Encryption with b=%d: %s", bsize, err)
		}
		plaintext_out, err := doDecryption(subkey, iv, aad, ciphertext, tag,
			bsize, bufsize)
		if err != nil {
			t.Fatalf("Decryption with b=%d: %s", bsize, err)
		}
		checkEqual(t, plaintext_out, plaintext)
	}
}

func TestGCMWithNoAAD(t *testing.T) {
	key := []byte("0000111122223333")
	iv := []byte("9999")
	plaintext := "ABORT ABORT ABORT DANGAR"

	ciphertext, tag, err := doEncryption(key, iv, nil, []byte(plaintext),
		128, 32)
	if err != nil {
		t.Fatal("Encryption failure:", err)
	}
	plaintext_out, err := doDecryption(key, iv, nil, ciphertext, tag, 128, 129)
	if err != nil {
		t.Fatal("Decryption failure:", err)
	}
	checkEqual(t, plaintext_out, plaintext)
}

func TestBadTag(t *testing.T) {
	key := []byte("abcdefghijklmnop")
	iv := []byte("v7239qjfv3qr793fuaj")
	plaintext := "The red rooster has flown the coop I REPEAT" +
		"the red rooster has flown the coop!!1!"

	ciphertext, tag, err := doEncryption(key, iv, nil, []byte(plaintext),
		128, 32)
	if err != nil {
		t.Fatal("Encryption failure:", err)
	}
	// flip the last bit
	tag[len(tag)-1] ^= 1
	plaintext_out, err := doDecryption(key, iv, nil, ciphertext, tag, 128, 129)
	if err == nil {
		t.Fatal("Expected error for bad tag, but got none")
	}
	// flip it back, try again just to make sure
	tag[len(tag)-1] ^= 1
	plaintext_out, err = doDecryption(key, iv, nil, ciphertext, tag, 128, 129)
	if err != nil {
		t.Fatal("Decryption failure:", err)
	}
	checkEqual(t, plaintext_out, plaintext)
}

func TestBadCiphertext(t *testing.T) {
	key := []byte("hard boiled eggs & bacon")
	iv := []byte("x") // it's not a very /good/ IV, is it
	aad := []byte("mu")
	plaintext := "Roger roger bingo charlie, we have a niner fourteen tango"

	ciphertext, tag, err := doEncryption(key, iv, aad, []byte(plaintext),
		192, 1)
	if err != nil {
		t.Fatal("Encryption failure:", err)
	}
	// flip the last bit
	ciphertext[len(ciphertext)-1] ^= 1
	plaintext_out, err := doDecryption(key, iv, aad, ciphertext, tag, 192, 192)
	if err == nil {
		t.Fatal("Expected error for bad ciphertext, but got none")
	}
	// flip it back, try again just to make sure
	ciphertext[len(ciphertext)-1] ^= 1
	plaintext_out, err = doDecryption(key, iv, aad, ciphertext, tag, 192, 192)
	if err != nil {
		t.Fatal("Decryption failure:", err)
	}
	checkEqual(t, plaintext_out, plaintext)
}

func TestBadAAD(t *testing.T) {
	key := []byte("Ive got a lovely buncha coconuts")
	iv := []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab")
	aad := []byte("Hi i am a plain")
	plaintext := "Whatever."

	ciphertext, tag, err := doEncryption(key, iv, aad, []byte(plaintext),
		256, 256)
	if err != nil {
		t.Fatal("Encryption failure:", err)
	}
	// flip the last bit
	aad[len(aad)-1] ^= 1
	plaintext_out, err := doDecryption(key, iv, aad, ciphertext, tag, 256, 256)
	if err == nil {
		t.Fatal("Expected error for bad AAD, but got none")
	}
	// flip it back, try again just to make sure
	aad[len(aad)-1] ^= 1
	plaintext_out, err = doDecryption(key, iv, aad, ciphertext, tag, 256, 256)
	if err != nil {
		t.Fatal("Decryption failure:", err)
	}
	checkEqual(t, plaintext_out, plaintext)
}

func TestNonAuthenticatedEncryption(t *testing.T) {
	key := []byte("never gonna give you up, never g")
	iv := []byte("onna let you dow")
	plaintext1 := "n, never gonna run around"
	plaintext2 := " and desert you"

	cipher, err := GetCipherByName("aes-256-cbc")
	if err != nil {
		t.Fatal("Could not get cipher: ", err)
	}

	eCtx, err := NewEncryptionCipherCtx(cipher, nil, key, iv)
	if err != nil {
		t.Fatal("Could not create encryption context: ", err)
	}
	cipherbytes, err := eCtx.EncryptUpdate([]byte(plaintext1))
	if err != nil {
		t.Fatal("EncryptUpdate(plaintext1) failure: ", err)
	}
	ciphertext := string(cipherbytes)
	cipherbytes, err = eCtx.EncryptUpdate([]byte(plaintext2))
	if err != nil {
		t.Fatal("EncryptUpdate(plaintext2) failure: ", err)
	}
	ciphertext += string(cipherbytes)
	cipherbytes, err = eCtx.EncryptFinal()
	if err != nil {
		t.Fatal("EncryptFinal() failure: ", err)
	}
	ciphertext += string(cipherbytes)

	dCtx, err := NewDecryptionCipherCtx(cipher, nil, key, iv)
	if err != nil {
		t.Fatal("Could not create decryption context: ", err)
	}
	plainbytes, err := dCtx.DecryptUpdate([]byte(ciphertext[:15]))
	if err != nil {
		t.Fatal("DecryptUpdate(ciphertext part 1) failure: ", err)
	}
	plainOutput := string(plainbytes)
	plainbytes, err = dCtx.DecryptUpdate([]byte(ciphertext[15:]))
	if err != nil {
		t.Fatal("DecryptUpdate(ciphertext part 2) failure: ", err)
	}
	plainOutput += string(plainbytes)
	plainbytes, err = dCtx.DecryptFinal()
	if err != nil {
		t.Fatal("DecryptFinal() failure: ", err)
	}
	plainOutput += string(plainbytes)

	checkEqual(t, []byte(plainOutput), plaintext1+plaintext2)
}
