package crypt

import (
	"bytes"
	"encoding/base32"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/ncw/rclone/crypt/pkcs7"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewNameEncryptionMode(t *testing.T) {
	for _, test := range []struct {
		in          string
		expected    NameEncryptionMode
		expectedErr string
	}{
		{"off", NameEncryptionOff, ""},
		{"standard", NameEncryptionStandard, ""},
		{"potato", NameEncryptionMode(0), "Unknown file name encryption mode \"potato\""},
	} {
		actual, actualErr := NewNameEncryptionMode(test.in)
		assert.Equal(t, actual, test.expected)
		if test.expectedErr == "" {
			assert.NoError(t, actualErr)
		} else {
			assert.Error(t, actualErr, test.expectedErr)
		}
	}
}

func TestNewNameEncryptionModeString(t *testing.T) {
	assert.Equal(t, NameEncryptionOff.String(), "off")
	assert.Equal(t, NameEncryptionStandard.String(), "standard")
	assert.Equal(t, NameEncryptionMode(2).String(), "Unknown mode #2")
}

func TestValidString(t *testing.T) {
	for _, test := range []struct {
		in       string
		expected error
	}{
		{"", nil},
		{"\x01", ErrorBadDecryptControlChar},
		{"a\x02", ErrorBadDecryptControlChar},
		{"abc\x03", ErrorBadDecryptControlChar},
		{"abc\x04def", ErrorBadDecryptControlChar},
		{"\x05d", ErrorBadDecryptControlChar},
		{"\x06def", ErrorBadDecryptControlChar},
		{"\x07", ErrorBadDecryptControlChar},
		{"\x08", ErrorBadDecryptControlChar},
		{"\x09", ErrorBadDecryptControlChar},
		{"\x0A", ErrorBadDecryptControlChar},
		{"\x0B", ErrorBadDecryptControlChar},
		{"\x0C", ErrorBadDecryptControlChar},
		{"\x0D", ErrorBadDecryptControlChar},
		{"\x0E", ErrorBadDecryptControlChar},
		{"\x0F", ErrorBadDecryptControlChar},
		{"\x10", ErrorBadDecryptControlChar},
		{"\x11", ErrorBadDecryptControlChar},
		{"\x12", ErrorBadDecryptControlChar},
		{"\x13", ErrorBadDecryptControlChar},
		{"\x14", ErrorBadDecryptControlChar},
		{"\x15", ErrorBadDecryptControlChar},
		{"\x16", ErrorBadDecryptControlChar},
		{"\x17", ErrorBadDecryptControlChar},
		{"\x18", ErrorBadDecryptControlChar},
		{"\x19", ErrorBadDecryptControlChar},
		{"\x1A", ErrorBadDecryptControlChar},
		{"\x1B", ErrorBadDecryptControlChar},
		{"\x1C", ErrorBadDecryptControlChar},
		{"\x1D", ErrorBadDecryptControlChar},
		{"\x1E", ErrorBadDecryptControlChar},
		{"\x1F", ErrorBadDecryptControlChar},
		{"\x20", nil},
		{"\x7E", nil},
		{"\x7F", ErrorBadDecryptControlChar},
		{"£100", nil},
		{`hello? sausage/êé/Hello, 世界/ " ' @ < > & ?/z.txt`, nil},
		{"£100", nil},
		// Following tests from http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805
		{"a", nil},                                        // Valid ASCII
		{"\xc3\xb1", nil},                                 // Valid 2 Octet Sequence
		{"\xc3\x28", ErrorBadDecryptUTF8},                 // Invalid 2 Octet Sequence
		{"\xa0\xa1", ErrorBadDecryptUTF8},                 // Invalid Sequence Identifier
		{"\xe2\x82\xa1", nil},                             // Valid 3 Octet Sequence
		{"\xe2\x28\xa1", ErrorBadDecryptUTF8},             // Invalid 3 Octet Sequence (in 2nd Octet)
		{"\xe2\x82\x28", ErrorBadDecryptUTF8},             // Invalid 3 Octet Sequence (in 3rd Octet)
		{"\xf0\x90\x8c\xbc", nil},                         // Valid 4 Octet Sequence
		{"\xf0\x28\x8c\xbc", ErrorBadDecryptUTF8},         // Invalid 4 Octet Sequence (in 2nd Octet)
		{"\xf0\x90\x28\xbc", ErrorBadDecryptUTF8},         // Invalid 4 Octet Sequence (in 3rd Octet)
		{"\xf0\x28\x8c\x28", ErrorBadDecryptUTF8},         // Invalid 4 Octet Sequence (in 4th Octet)
		{"\xf8\xa1\xa1\xa1\xa1", ErrorBadDecryptUTF8},     // Valid 5 Octet Sequence (but not Unicode!)
		{"\xfc\xa1\xa1\xa1\xa1\xa1", ErrorBadDecryptUTF8}, // Valid 6 Octet Sequence (but not Unicode!)
	} {
		actual := checkValidString([]byte(test.in))
		assert.Equal(t, actual, test.expected, fmt.Sprintf("in=%q", test.in))
	}
}

func TestEncodeFileName(t *testing.T) {
	for _, test := range []struct {
		in       string
		expected string
	}{
		{"", ""},
		{"1", "64"},
		{"12", "64p0"},
		{"123", "64p36"},
		{"1234", "64p36d0"},
		{"12345", "64p36d1l"},
		{"123456", "64p36d1l6o"},
		{"1234567", "64p36d1l6org"},
		{"12345678", "64p36d1l6orjg"},
		{"123456789", "64p36d1l6orjge8"},
		{"1234567890", "64p36d1l6orjge9g"},
		{"12345678901", "64p36d1l6orjge9g64"},
		{"123456789012", "64p36d1l6orjge9g64p0"},
		{"1234567890123", "64p36d1l6orjge9g64p36"},
		{"12345678901234", "64p36d1l6orjge9g64p36d0"},
		{"123456789012345", "64p36d1l6orjge9g64p36d1l"},
		{"1234567890123456", "64p36d1l6orjge9g64p36d1l6o"},
	} {
		actual := encodeFileName([]byte(test.in))
		assert.Equal(t, actual, test.expected, fmt.Sprintf("in=%q", test.in))
		recovered, err := decodeFileName(test.expected)
		assert.NoError(t, err)
		assert.Equal(t, string(recovered), test.in, fmt.Sprintf("reverse=%q", test.expected))
		in := strings.ToUpper(test.expected)
		recovered, err = decodeFileName(in)
		assert.NoError(t, err)
		assert.Equal(t, string(recovered), test.in, fmt.Sprintf("reverse=%q", in))
	}
}

func TestDecodeFileName(t *testing.T) {
	// We've tested decoding the valid ones above, now concentrate on the invalid ones
	for _, test := range []struct {
		in          string
		expectedErr error
	}{
		{"64=", ErrorBadBase32Encoding},
		{"!", base32.CorruptInputError(0)},
		{"hello=hello", base32.CorruptInputError(5)},
	} {
		actual, actualErr := decodeFileName(test.in)
		assert.Equal(t, test.expectedErr, actualErr, fmt.Sprintf("in=%q got actual=%q, err = %v %T", test.in, actual, actualErr, actualErr))
	}
}

func TestEncryptSegment(t *testing.T) {
	c, _ := newCipher(NameEncryptionStandard, "", "")
	for _, test := range []struct {
		in       string
		expected string
	}{
		{"", ""},
		{"1", "p0e52nreeaj0a5ea7s64m4j72s"},
		{"12", "l42g6771hnv3an9cgc8cr2n1ng"},
		{"123", "qgm4avr35m5loi1th53ato71v0"},
		{"1234", "8ivr2e9plj3c3esisjpdisikos"},
		{"12345", "rh9vu63q3o29eqmj4bg6gg7s44"},
		{"123456", "bn717l3alepn75b2fb2ejmi4b4"},
		{"1234567", "n6bo9jmb1qe3b1ogtj5qkf19k8"},
		{"12345678", "u9t24j7uaq94dh5q53m3s4t9ok"},
		{"123456789", "37hn305g6j12d1g0kkrl7ekbs4"},
		{"1234567890", "ot8d91eplaglb62k2b1trm2qv0"},
		{"12345678901", "h168vvrgb53qnrtvvmb378qrcs"},
		{"123456789012", "s3hsdf9e29ithrqbjqu01t8q2s"},
		{"1234567890123", "cf3jimlv1q2oc553mv7s3mh3eo"},
		{"12345678901234", "moq0uqdlqrblrc5pa5u5c7hq9g"},
		{"123456789012345", "eeam3li4rnommi3a762h5n7meg"},
		{"1234567890123456", "mijbj0frqf6ms7frcr6bd9h0env53jv96pjaaoirk7forcgpt70g"},
	} {
		actual := c.encryptSegment(test.in)
		assert.Equal(t, test.expected, actual, fmt.Sprintf("Testing %q", test.in))
		recovered, err := c.decryptSegment(test.expected)
		assert.NoError(t, err, fmt.Sprintf("Testing reverse %q", test.expected))
		assert.Equal(t, test.in, recovered, fmt.Sprintf("Testing reverse %q", test.expected))
		in := strings.ToUpper(test.expected)
		recovered, err = c.decryptSegment(in)
		assert.NoError(t, err, fmt.Sprintf("Testing reverse %q", in))
		assert.Equal(t, test.in, recovered, fmt.Sprintf("Testing reverse %q", in))
	}
}

func TestDecryptSegment(t *testing.T) {
	// We've tested the forwards above, now concentrate on the errors
	c, _ := newCipher(NameEncryptionStandard, "", "")
	for _, test := range []struct {
		in          string
		expectedErr error
	}{
		{"64=", ErrorBadBase32Encoding},
		{"!", base32.CorruptInputError(0)},
		{encodeFileName([]byte("a")), ErrorNotAMultipleOfBlocksize},
		{encodeFileName([]byte("123456789abcdef")), ErrorNotAMultipleOfBlocksize},
		{encodeFileName([]byte("123456789abcdef0")), pkcs7.ErrorPaddingTooLong},
		{c.encryptSegment("\x01"), ErrorBadDecryptControlChar},
		{c.encryptSegment("\xc3\x28"), ErrorBadDecryptUTF8},
	} {
		actual, actualErr := c.decryptSegment(test.in)
		assert.Equal(t, test.expectedErr, actualErr, fmt.Sprintf("in=%q got actual=%q, err = %v %T", test.in, actual, actualErr, actualErr))
	}
}

func TestEncryptFileName(t *testing.T) {
	// First standard mode
	c, _ := newCipher(NameEncryptionStandard, "", "")
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptFileName("1"))
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptFileName("1/12"))
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", c.EncryptFileName("1/12/123"))
	// Now off mode
	c, _ = newCipher(NameEncryptionOff, "", "")
	assert.Equal(t, "1/12/123.bin", c.EncryptFileName("1/12/123"))
}

func TestDecryptFileName(t *testing.T) {
	for _, test := range []struct {
		mode        NameEncryptionMode
		in          string
		expected    string
		expectedErr error
	}{
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s", "1", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", "1/12", nil},
		{NameEncryptionStandard, "p0e52nreeAJ0A5EA7S64M4J72S/L42G6771HNv3an9cgc8cr2n1ng", "1/12", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1/qgm4avr35m5loi1th53ato71v0", "", ErrorNotAMultipleOfBlocksize},
		{NameEncryptionOff, "1/12/123.bin", "1/12/123", nil},
		{NameEncryptionOff, "1/12/123.bix", "", ErrorNotAnEncryptedFile},
		{NameEncryptionOff, ".bin", "", ErrorNotAnEncryptedFile},
	} {
		c, _ := newCipher(test.mode, "", "")
		actual, actualErr := c.DecryptFileName(test.in)
		what := fmt.Sprintf("Testing %q (mode=%v)", test.in, test.mode)
		assert.Equal(t, test.expected, actual, what)
		assert.Equal(t, test.expectedErr, actualErr, what)
	}
}

func TestEncryptDirName(t *testing.T) {
	// First standard mode
	c, _ := newCipher(NameEncryptionStandard, "", "")
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptDirName("1"))
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptDirName("1/12"))
	assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", c.EncryptDirName("1/12/123"))
	// Now off mode
	c, _ = newCipher(NameEncryptionOff, "", "")
	assert.Equal(t, "1/12/123", c.EncryptDirName("1/12/123"))
}

func TestDecryptDirName(t *testing.T) {
	for _, test := range []struct {
		mode        NameEncryptionMode
		in          string
		expected    string
		expectedErr error
	}{
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s", "1", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", "1/12", nil},
		{NameEncryptionStandard, "p0e52nreeAJ0A5EA7S64M4J72S/L42G6771HNv3an9cgc8cr2n1ng", "1/12", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
		{NameEncryptionStandard, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1/qgm4avr35m5loi1th53ato71v0", "", ErrorNotAMultipleOfBlocksize},
		{NameEncryptionOff, "1/12/123.bin", "1/12/123.bin", nil},
		{NameEncryptionOff, "1/12/123", "1/12/123", nil},
		{NameEncryptionOff, ".bin", ".bin", nil},
	} {
		c, _ := newCipher(test.mode, "", "")
		actual, actualErr := c.DecryptDirName(test.in)
		what := fmt.Sprintf("Testing %q (mode=%v)", test.in, test.mode)
		assert.Equal(t, test.expected, actual, what)
		assert.Equal(t, test.expectedErr, actualErr, what)
	}
}

func TestEncryptedSize(t *testing.T) {
	c, _ := newCipher(NameEncryptionStandard, "", "")
	for _, test := range []struct {
		in       int64
		expected int64
	}{
		{0, 32},
		{1, 32 + 16 + 1},
		{65536, 32 + 16 + 65536},
		{65537, 32 + 16 + 65536 + 16 + 1},
		{1 << 20, 32 + 16*(16+65536)},
		{(1 << 20) + 65535, 32 + 16*(16+65536) + 16 + 65535},
		{1 << 30, 32 + 16384*(16+65536)},
		{(1 << 40) + 1, 32 + 16777216*(16+65536) + 16 + 1},
	} {
		actual := c.EncryptedSize(test.in)
		assert.Equal(t, test.expected, actual, fmt.Sprintf("Testing %d", test.in))
		recovered, err := c.DecryptedSize(test.expected)
		assert.NoError(t, err, fmt.Sprintf("Testing reverse %d", test.expected))
		assert.Equal(t, test.in, recovered, fmt.Sprintf("Testing reverse %d", test.expected))
	}
}

func TestDecryptedSize(t *testing.T) {
	// Test the errors since we tested the reverse above
	c, _ := newCipher(NameEncryptionStandard, "", "")
	for _, test := range []struct {
		in          int64
		expectedErr error
	}{
		{0, ErrorEncryptedFileTooShort},
		{0, ErrorEncryptedFileTooShort},
		{1, ErrorEncryptedFileTooShort},
		{7, ErrorEncryptedFileTooShort},
		{32 + 1, ErrorEncryptedFileBadHeader},
		{32 + 16, ErrorEncryptedFileBadHeader},
		{32 + 16 + 65536 + 1, ErrorEncryptedFileBadHeader},
		{32 + 16 + 65536 + 16, ErrorEncryptedFileBadHeader},
	} {
		_, actualErr := c.DecryptedSize(test.in)
		assert.Equal(t, test.expectedErr, actualErr, fmt.Sprintf("Testing %d", test.in))
	}
}

func TestNoncePointer(t *testing.T) {
	var x nonce
	assert.Equal(t, (*[24]byte)(&x), x.pointer())
}

func TestNonceFromReader(t *testing.T) {
	var x nonce
	buf := bytes.NewBufferString("123456789abcdefghijklmno")
	err := x.fromReader(buf)
	assert.NoError(t, err)
	assert.Equal(t, nonce{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'}, x)
	buf = bytes.NewBufferString("123456789abcdefghijklmn")
	err = x.fromReader(buf)
	assert.Error(t, err, "short read of nonce")
}

func TestNonceFromBuf(t *testing.T) {
	var x nonce
	buf := []byte("123456789abcdefghijklmnoXXXXXXXX")
	x.fromBuf(buf)
	assert.Equal(t, nonce{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'}, x)
	buf = []byte("0123456789abcdefghijklmn")
	x.fromBuf(buf)
	assert.Equal(t, nonce{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'}, x)
	buf = []byte("0123456789abcdefghijklm")
	assert.Panics(t, func() { x.fromBuf(buf) })
}

func TestNonceIncrement(t *testing.T) {
	for _, test := range []struct {
		in  nonce
		out nonce
	}{
		{
			nonce{0x00},
			nonce{0x01},
		},
		{
			nonce{0xFF},
			nonce{0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF},
			nonce{0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
		},
	} {
		x := test.in
		x.increment()
		assert.Equal(t, test.out, x)
	}
}

func TestNonceAdd(t *testing.T) {
	for _, test := range []struct {
		add uint64
		in  nonce
		out nonce
	}{
		{
			0x01,
			nonce{0x00},
			nonce{0x01},
		},
		{
			0xFF,
			nonce{0xFF},
			nonce{0xFE, 0x01},
		},
		{
			0xFFFF,
			nonce{0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0x01},
		},
		{
			0xFFFFFF,
			nonce{0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFe, 0xFF, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		},
		{
			0xFFFFFFFFFFFFFFFF,
			nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
			nonce{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
		},
	} {
		x := test.in
		x.add(test.add)
		assert.Equal(t, test.out, x)
	}
}

// randomSource can read or write a random sequence
type randomSource struct {
	counter int64
	size    int64
}

func newRandomSource(size int64) *randomSource {
	return &randomSource{
		size: size,
	}
}

func (r *randomSource) next() byte {
	r.counter++
	return byte(r.counter % 257)
}

func (r *randomSource) Read(p []byte) (n int, err error) {
	for i := range p {
		if r.counter >= r.size {
			err = io.EOF
			break
		}
		p[i] = r.next()
		n++
	}
	return n, err
}

func (r *randomSource) Write(p []byte) (n int, err error) {
	for i := range p {
		if p[i] != r.next() {
			return 0, errors.Errorf("Error in stream at %d", r.counter)
		}
	}
	return len(p), nil
}

func (r *randomSource) Close() error { return nil }

// Check interfaces
var (
	_ io.ReadCloser  = (*randomSource)(nil)
	_ io.WriteCloser = (*randomSource)(nil)
)

// Test test infrastructure first!
func TestRandomSource(t *testing.T) {
	source := newRandomSource(1E8)
	sink := newRandomSource(1E8)
	n, err := io.Copy(sink, source)
	assert.NoError(t, err)
	assert.Equal(t, int64(1E8), n)

	source = newRandomSource(1E8)
	buf := make([]byte, 16)
	_, _ = source.Read(buf)
	sink = newRandomSource(1E8)
	n, err = io.Copy(sink, source)
	assert.Error(t, err, "Error in stream")
}

type zeroes struct{}

func (z *zeroes) Read(p []byte) (n int, err error) {
	for i := range p {
		p[i] = 0
		n++
	}
	return n, nil
}

// Test encrypt decrypt with different buffer sizes
func testEncryptDecrypt(t *testing.T, bufSize int, copySize int64) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)
	c.cryptoRand = &zeroes{} // zero out the nonce
	buf := make([]byte, bufSize)
	source := newRandomSource(copySize)
	encrypted, err := c.newEncrypter(source)
	assert.NoError(t, err)
	decrypted, err := c.newDecrypter(ioutil.NopCloser(encrypted))
	assert.NoError(t, err)
	sink := newRandomSource(copySize)
	n, err := io.CopyBuffer(sink, decrypted, buf)
	assert.NoError(t, err)
	assert.Equal(t, copySize, n)
	blocks := copySize / blockSize
	if (copySize % blockSize) != 0 {
		blocks++
	}
	var expectedNonce = nonce{byte(blocks), byte(blocks >> 8), byte(blocks >> 16), byte(blocks >> 32)}
	assert.Equal(t, expectedNonce, encrypted.nonce)
	assert.Equal(t, expectedNonce, decrypted.nonce)
}

func TestEncryptDecrypt1(t *testing.T) {
	testEncryptDecrypt(t, 1, 1E7)
}

func TestEncryptDecrypt32(t *testing.T) {
	testEncryptDecrypt(t, 32, 1E8)
}

func TestEncryptDecrypt4096(t *testing.T) {
	testEncryptDecrypt(t, 4096, 1E8)
}

func TestEncryptDecrypt65536(t *testing.T) {
	testEncryptDecrypt(t, 65536, 1E8)
}

func TestEncryptDecrypt65537(t *testing.T) {
	testEncryptDecrypt(t, 65537, 1E8)
}

var (
	file0 = []byte{
		0x52, 0x43, 0x4c, 0x4f, 0x4e, 0x45, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
		0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
	}
	file1 = []byte{
		0x52, 0x43, 0x4c, 0x4f, 0x4e, 0x45, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
		0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
		0x09, 0x5b, 0x44, 0x6c, 0xd6, 0x23, 0x7b, 0xbc, 0xb0, 0x8d, 0x09, 0xfb, 0x52, 0x4c, 0xe5, 0x65,
		0xAA,
	}
	file16 = []byte{
		0x52, 0x43, 0x4c, 0x4f, 0x4e, 0x45, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
		0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
		0xb9, 0xc4, 0x55, 0x2a, 0x27, 0x10, 0x06, 0x29, 0x18, 0x96, 0x0a, 0x3e, 0x60, 0x8c, 0x29, 0xb9,
		0xaa, 0x8a, 0x5e, 0x1e, 0x16, 0x5b, 0x6d, 0x07, 0x5d, 0xe4, 0xe9, 0xbb, 0x36, 0x7f, 0xd6, 0xd4,
	}
)

func TestEncryptData(t *testing.T) {
	for _, test := range []struct {
		in       []byte
		expected []byte
	}{
		{[]byte{}, file0},
		{[]byte{1}, file1},
		{[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, file16},
	} {
		c, err := newCipher(NameEncryptionStandard, "", "")
		assert.NoError(t, err)
		c.cryptoRand = newRandomSource(1E8) // nodge the crypto rand generator

		// Check encode works
		buf := bytes.NewBuffer(test.in)
		encrypted, err := c.EncryptData(buf)
		assert.NoError(t, err)
		out, err := ioutil.ReadAll(encrypted)
		assert.NoError(t, err)
		assert.Equal(t, test.expected, out)

		// Check we can decode the data properly too...
		buf = bytes.NewBuffer(out)
		decrypted, err := c.DecryptData(ioutil.NopCloser(buf))
		assert.NoError(t, err)
		out, err = ioutil.ReadAll(decrypted)
		assert.NoError(t, err)
		assert.Equal(t, test.in, out)
	}
}

func TestNewEncrypter(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)
	c.cryptoRand = newRandomSource(1E8) // nodge the crypto rand generator

	z := &zeroes{}

	fh, err := c.newEncrypter(z)
	assert.NoError(t, err)
	assert.Equal(t, nonce{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}, fh.nonce)
	assert.Equal(t, []byte{'R', 'C', 'L', 'O', 'N', 'E', 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}, fh.buf[:32])

	// Test error path
	c.cryptoRand = bytes.NewBufferString("123456789abcdefghijklmn")
	fh, err = c.newEncrypter(z)
	assert.Nil(t, fh)
	assert.Error(t, err, "short read of nonce")

}

type errorReader struct {
	err error
}

func (er errorReader) Read(p []byte) (n int, err error) {
	return 0, er.err
}

type closeDetector struct {
	io.Reader
	closed int
}

func newCloseDetector(in io.Reader) *closeDetector {
	return &closeDetector{
		Reader: in,
	}
}

func (c *closeDetector) Close() error {
	c.closed++
	return nil
}

func TestNewDecrypter(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)
	c.cryptoRand = newRandomSource(1E8) // nodge the crypto rand generator

	cd := newCloseDetector(bytes.NewBuffer(file0))
	fh, err := c.newDecrypter(cd)
	assert.NoError(t, err)
	// check nonce is in place
	assert.Equal(t, file0[8:32], fh.nonce[:])
	assert.Equal(t, 0, cd.closed)

	// Test error paths
	for i := range file0 {
		cd := newCloseDetector(bytes.NewBuffer(file0[:i]))
		fh, err = c.newDecrypter(cd)
		assert.Nil(t, fh)
		assert.Error(t, err, ErrorEncryptedFileTooShort.Error())
		assert.Equal(t, 1, cd.closed)
	}

	er := &errorReader{errors.New("potato")}
	cd = newCloseDetector(er)
	fh, err = c.newDecrypter(cd)
	assert.Nil(t, fh)
	assert.Error(t, err, "potato")
	assert.Equal(t, 1, cd.closed)

	// bad magic
	file0copy := make([]byte, len(file0))
	copy(file0copy, file0)
	for i := range fileMagic {
		file0copy[i] ^= 0x1
		cd := newCloseDetector(bytes.NewBuffer(file0copy))
		fh, err := c.newDecrypter(cd)
		assert.Nil(t, fh)
		assert.Error(t, err, ErrorEncryptedBadMagic.Error())
		file0copy[i] ^= 0x1
		assert.Equal(t, 1, cd.closed)
	}
}

func TestNewDecrypterSeek(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)
	c.cryptoRand = &zeroes{} // nodge the crypto rand generator

	// Make random data
	const dataSize = 150000
	plaintext, err := ioutil.ReadAll(newRandomSource(dataSize))
	assert.NoError(t, err)

	// Encrypt the data
	buf := bytes.NewBuffer(plaintext)
	encrypted, err := c.EncryptData(buf)
	assert.NoError(t, err)
	ciphertext, err := ioutil.ReadAll(encrypted)
	assert.NoError(t, err)

	trials := []int{0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65,
		127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049,
		4095, 4096, 4097, 8191, 8192, 8193, 16383, 16384, 16385, 32767, 32768, 32769,
		65535, 65536, 65537, 131071, 131072, 131073, dataSize - 1, dataSize}

	// Open stream with a seek of underlyingOffset
	open := func(underlyingOffset int64) (io.ReadCloser, error) {
		return ioutil.NopCloser(bytes.NewBuffer(ciphertext[int(underlyingOffset):])), nil
	}

	inBlock := make([]byte, 1024)

	// Check the seek worked by reading a block and checking it
	// against what it should be
	check := func(rc ReadSeekCloser, offset int) {
		n, err := io.ReadFull(rc, inBlock)
		if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
			require.NoError(t, err)
		}
		seekedDecrypted := inBlock[:n]

		require.Equal(t, plaintext[offset:offset+n], seekedDecrypted)
	}

	// Now try decoding it with a open/seek
	for _, offset := range trials {
		rc, err := c.DecryptDataSeek(open, int64(offset))
		assert.NoError(t, err)

		check(rc, offset)
	}

	// Now try decoding it with a single open and lots of seeks
	rc, err := c.DecryptDataSeek(open, 0)
	for _, offset := range trials {
		_, err := rc.Seek(int64(offset), 0)
		assert.NoError(t, err)

		check(rc, offset)
	}
}

func TestDecrypterRead(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)

	// Test truncating the file at each possible point
	for i := 0; i < len(file16)-1; i++ {
		what := fmt.Sprintf("truncating to %d/%d", i, len(file16))
		cd := newCloseDetector(bytes.NewBuffer(file16[:i]))
		fh, err := c.newDecrypter(cd)
		if i < fileHeaderSize {
			assert.EqualError(t, err, ErrorEncryptedFileTooShort.Error(), what)
			continue
		}
		if err != nil {
			assert.NoError(t, err, what)
			continue
		}
		_, err = ioutil.ReadAll(fh)
		var expectedErr error
		switch {
		case i == fileHeaderSize:
			// This would normally produce an error *except* on the first block
			expectedErr = nil
		case i <= fileHeaderSize+blockHeaderSize:
			expectedErr = ErrorEncryptedFileBadHeader
		default:
			expectedErr = io.ErrUnexpectedEOF
		}
		if expectedErr != nil {
			assert.EqualError(t, err, expectedErr.Error(), what)
		} else {
			assert.NoError(t, err, what)
		}
		assert.Equal(t, 0, cd.closed, what)
	}

	// Test producing an error on the file on Read the underlying file
	in1 := bytes.NewBuffer(file1)
	in2 := &errorReader{errors.New("potato")}
	in := io.MultiReader(in1, in2)
	cd := newCloseDetector(in)
	fh, err := c.newDecrypter(cd)
	assert.NoError(t, err)
	_, err = ioutil.ReadAll(fh)
	assert.Error(t, err, "potato")
	assert.Equal(t, 0, cd.closed)

	// Test corrupting the input
	// shouldn't be able to corrupt any byte without some sort of error
	file16copy := make([]byte, len(file16))
	copy(file16copy, file16)
	for i := range file16copy {
		file16copy[i] ^= 0xFF
		fh, err := c.newDecrypter(ioutil.NopCloser(bytes.NewBuffer(file16copy)))
		if i < fileMagicSize {
			assert.Error(t, err, ErrorEncryptedBadMagic.Error())
			assert.Nil(t, fh)
		} else {
			assert.NoError(t, err)
			_, err = ioutil.ReadAll(fh)
			assert.Error(t, err, ErrorEncryptedFileBadHeader.Error())
		}
		file16copy[i] ^= 0xFF
	}
}

func TestDecrypterClose(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)

	cd := newCloseDetector(bytes.NewBuffer(file16))
	fh, err := c.newDecrypter(cd)
	assert.NoError(t, err)
	assert.Equal(t, 0, cd.closed)

	// close before reading
	assert.Equal(t, nil, fh.err)
	err = fh.Close()
	assert.Equal(t, ErrorFileClosed, fh.err)
	assert.Equal(t, 1, cd.closed)

	// double close
	err = fh.Close()
	assert.Error(t, err, ErrorFileClosed.Error())
	assert.Equal(t, 1, cd.closed)

	// try again reading the file this time
	cd = newCloseDetector(bytes.NewBuffer(file1))
	fh, err = c.newDecrypter(cd)
	assert.NoError(t, err)
	assert.Equal(t, 0, cd.closed)

	// close after reading
	out, err := ioutil.ReadAll(fh)
	assert.NoError(t, err)
	assert.Equal(t, []byte{1}, out)
	assert.Equal(t, io.EOF, fh.err)
	err = fh.Close()
	assert.Equal(t, ErrorFileClosed, fh.err)
	assert.Equal(t, 1, cd.closed)
}

func TestPutGetBlock(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)

	block := c.getBlock()
	c.putBlock(block)
	c.putBlock(block)

	assert.Panics(t, func() { c.putBlock(block[:len(block)-1]) })
}

func TestKey(t *testing.T) {
	c, err := newCipher(NameEncryptionStandard, "", "")
	assert.NoError(t, err)

	// Check zero keys OK
	assert.Equal(t, [32]byte{}, c.dataKey)
	assert.Equal(t, [32]byte{}, c.nameKey)
	assert.Equal(t, [16]byte{}, c.nameTweak)

	require.NoError(t, c.Key("potato", ""))
	assert.Equal(t, [32]byte{0x74, 0x55, 0xC7, 0x1A, 0xB1, 0x7C, 0x86, 0x5B, 0x84, 0x71, 0xF4, 0x7B, 0x79, 0xAC, 0xB0, 0x7E, 0xB3, 0x1D, 0x56, 0x78, 0xB8, 0x0C, 0x7E, 0x2E, 0xAF, 0x4F, 0xC8, 0x06, 0x6A, 0x9E, 0xE4, 0x68}, c.dataKey)
	assert.Equal(t, [32]byte{0x76, 0x5D, 0xA2, 0x7A, 0xB1, 0x5D, 0x77, 0xF9, 0x57, 0x96, 0x71, 0x1F, 0x7B, 0x93, 0xAD, 0x63, 0xBB, 0xB4, 0x84, 0x07, 0x2E, 0x71, 0x80, 0xA8, 0xD1, 0x7A, 0x9B, 0xBE, 0xC1, 0x42, 0x70, 0xD0}, c.nameKey)
	assert.Equal(t, [16]byte{0xC1, 0x8D, 0x59, 0x32, 0xF5, 0x5B, 0x28, 0x28, 0xC5, 0xE1, 0xE8, 0x72, 0x15, 0x52, 0x03, 0x10}, c.nameTweak)

	require.NoError(t, c.Key("Potato", ""))
	assert.Equal(t, [32]byte{0xAE, 0xEA, 0x6A, 0xD3, 0x47, 0xDF, 0x75, 0xB9, 0x63, 0xCE, 0x12, 0xF5, 0x76, 0x23, 0xE9, 0x46, 0xD4, 0x2E, 0xD8, 0xBF, 0x3E, 0x92, 0x8B, 0x39, 0x24, 0x37, 0x94, 0x13, 0x3E, 0x5E, 0xF7, 0x5E}, c.dataKey)
	assert.Equal(t, [32]byte{0x54, 0xF7, 0x02, 0x6E, 0x8A, 0xFC, 0x56, 0x0A, 0x86, 0x63, 0x6A, 0xAB, 0x2C, 0x9C, 0x51, 0x62, 0xE5, 0x1A, 0x12, 0x23, 0x51, 0x83, 0x6E, 0xAF, 0x50, 0x42, 0x0F, 0x98, 0x1C, 0x86, 0x0A, 0x19}, c.nameKey)
	assert.Equal(t, [16]byte{0xF8, 0xC1, 0xB6, 0x27, 0x2D, 0x52, 0x9B, 0x4A, 0x8F, 0xDA, 0xEB, 0x42, 0x4A, 0x28, 0xDD, 0xF3}, c.nameTweak)

	require.NoError(t, c.Key("potato", "sausage"))
	assert.Equal(t, [32]uint8{0x8e, 0x9b, 0x6b, 0x99, 0xf8, 0x69, 0x4, 0x67, 0xa0, 0x71, 0xf9, 0xcb, 0x92, 0xd0, 0xaa, 0x78, 0x7f, 0x8f, 0xf1, 0x78, 0xbe, 0xc9, 0x6f, 0x99, 0x9f, 0xd5, 0x20, 0x6e, 0x64, 0x4a, 0x1b, 0x50}, c.dataKey)
	assert.Equal(t, [32]uint8{0x3e, 0xa9, 0x5e, 0xf6, 0x81, 0x78, 0x2d, 0xc9, 0xd9, 0x95, 0x5d, 0x22, 0x5b, 0xfd, 0x44, 0x2c, 0x6f, 0x5d, 0x68, 0x97, 0xb0, 0x29, 0x1, 0x5c, 0x6f, 0x46, 0x2e, 0x2a, 0x9d, 0xae, 0x2c, 0xe3}, c.nameKey)
	assert.Equal(t, [16]uint8{0xf1, 0x7f, 0xd7, 0x14, 0x1d, 0x65, 0x27, 0x4f, 0x36, 0x3f, 0xc2, 0xa0, 0x4d, 0xd2, 0x14, 0x8a}, c.nameTweak)

	require.NoError(t, c.Key("potato", "Sausage"))
	assert.Equal(t, [32]uint8{0xda, 0x81, 0x8c, 0x67, 0xef, 0x11, 0xf, 0xc8, 0xd5, 0xc8, 0x62, 0x4b, 0x7f, 0xe2, 0x9e, 0x35, 0x35, 0xb0, 0x8d, 0x79, 0x84, 0x89, 0xac, 0xcb, 0xa0, 0xff, 0x2, 0x72, 0x3, 0x1a, 0x5e, 0x64}, c.dataKey)
	assert.Equal(t, [32]uint8{0x2, 0x81, 0x7e, 0x7b, 0xea, 0x99, 0x81, 0x5a, 0xd0, 0x2d, 0xb9, 0x64, 0x48, 0xb0, 0x28, 0x27, 0x7c, 0x20, 0xb4, 0xd4, 0xa4, 0x68, 0xad, 0x4e, 0x5c, 0x29, 0xf, 0x79, 0xef, 0xee, 0xdb, 0x3b}, c.nameKey)
	assert.Equal(t, [16]uint8{0x9a, 0xb5, 0xb, 0x3d, 0xcb, 0x60, 0x59, 0x55, 0xa5, 0x4d, 0xe6, 0xb6, 0x47, 0x3, 0x23, 0xe2}, c.nameTweak)

	require.NoError(t, c.Key("", ""))
	assert.Equal(t, [32]byte{}, c.dataKey)
	assert.Equal(t, [32]byte{}, c.nameKey)
	assert.Equal(t, [16]byte{}, c.nameTweak)
}
