// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package encryption

import (
	"crypto/aes"
	"crypto/cipher"

	"storj.io/common/storj"
	"storj.io/common/sync2/race2"
)

type aesgcmEncrypter struct {
	blockSize     int
	key           *storj.Key
	startingNonce *AESGCMNonce
	overhead      int
	aesgcm        cipher.AEAD
}

// NewAESGCMEncrypter returns a Transformer that encrypts the data passing
// through with key.
//
// startingNonce is treated as a big-endian encoded unsigned
// integer, and as blocks pass through, their block number and the starting
// nonce is added together to come up with that block's nonce. Encrypting
// different data with the same key and the same nonce is a huge security
// issue. It's safe to always encode new data with a random key and random
// startingNonce. The monotonically-increasing nonce (that rolls over) is to
// protect against data reordering.
//
// When in doubt, generate a new key from crypto/rand and a startingNonce
// from crypto/rand as often as possible.
func NewAESGCMEncrypter(key *storj.Key, startingNonce *AESGCMNonce, encryptedBlockSize int) (Transformer, error) {
	race2.ReadSlice(key[:])
	race2.ReadSlice(startingNonce[:])

	block, err := aes.NewCipher(key[:])
	if err != nil {
		return nil, Error.Wrap(err)
	}
	aesgcmEncrypt, err := cipher.NewGCM(block)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	if encryptedBlockSize <= aesgcmEncrypt.Overhead() {
		return nil, ErrInvalidConfig.New("encrypted block size %d too small", encryptedBlockSize)
	}
	return &aesgcmEncrypter{
		blockSize:     encryptedBlockSize - aesgcmEncrypt.Overhead(),
		key:           key,
		startingNonce: startingNonce,
		overhead:      aesgcmEncrypt.Overhead(),
		aesgcm:        aesgcmEncrypt,
	}, nil
}

func (s *aesgcmEncrypter) InBlockSize() int {
	return s.blockSize
}

func (s *aesgcmEncrypter) OutBlockSize() int {
	return s.blockSize + s.overhead
}

func calcGCMNonce(startingNonce *AESGCMNonce, blockNum int64) (rv [12]byte, err error) {
	if copy(rv[:], (*startingNonce)[:]) != len(rv) {
		return rv, Error.New("didn't copy memory?!")
	}
	_, err = incrementBytes(rv[:], blockNum)
	return rv, err
}

func (s *aesgcmEncrypter) Transform(out, in []byte, blockNum int64) ([]byte, error) {
	race2.ReadSlice(in)
	race2.WriteSlice(out)

	nonce, err := calcGCMNonce(s.startingNonce, blockNum)
	if err != nil {
		return nil, err
	}

	cipherData := s.aesgcm.Seal(out, nonce[:], in, nil)
	return cipherData, nil
}

type aesgcmDecrypter struct {
	blockSize     int
	key           *storj.Key
	startingNonce *AESGCMNonce
	overhead      int
	aesgcm        cipher.AEAD
}

// NewAESGCMDecrypter returns a Transformer that decrypts the data passing
// through with key. See the comments for NewAESGCMEncrypter about
// startingNonce.
func NewAESGCMDecrypter(key *storj.Key, startingNonce *AESGCMNonce, encryptedBlockSize int) (Transformer, error) {
	race2.ReadSlice(key[:])
	race2.ReadSlice(startingNonce[:])

	block, err := aes.NewCipher(key[:])
	if err != nil {
		return nil, Error.Wrap(err)
	}
	aesgcmDecrypt, err := cipher.NewGCM(block)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	if encryptedBlockSize <= aesgcmDecrypt.Overhead() {
		return nil, ErrInvalidConfig.New("encrypted block size %d too small", encryptedBlockSize)
	}
	return &aesgcmDecrypter{
		blockSize:     encryptedBlockSize - aesgcmDecrypt.Overhead(),
		key:           key,
		startingNonce: startingNonce,
		overhead:      aesgcmDecrypt.Overhead(),
		aesgcm:        aesgcmDecrypt,
	}, nil
}
func (s *aesgcmDecrypter) InBlockSize() int {
	return s.blockSize + s.overhead
}

func (s *aesgcmDecrypter) OutBlockSize() int {
	return s.blockSize
}

func (s *aesgcmDecrypter) Transform(out, in []byte, blockNum int64) ([]byte, error) {
	race2.ReadSlice(in)
	race2.WriteSlice(out)

	nonce, err := calcGCMNonce(s.startingNonce, blockNum)
	if err != nil {
		return nil, err
	}

	plainData, err := s.aesgcm.Open(out, nonce[:], in, nil)
	if err != nil {
		return nil, ErrDecryptFailed.Wrap(err)
	}
	return plainData, nil
}

// EncryptAESGCM encrypts byte data with a key and nonce. It returns the cipher data.
func EncryptAESGCM(data []byte, key *storj.Key, nonce *AESGCMNonce) (cipherData []byte, err error) {
	race2.ReadSlice(data)
	race2.ReadSlice(key[:])
	race2.ReadSlice(nonce[:])

	block, err := aes.NewCipher(key[:])
	if err != nil {
		return []byte{}, Error.Wrap(err)
	}
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return []byte{}, Error.Wrap(err)
	}
	cipherData = aesgcm.Seal(nil, nonce[:], data, nil)
	return cipherData, nil
}

// DecryptAESGCM decrypts byte data with a key and nonce. It returns the plain data.
func DecryptAESGCM(cipherData []byte, key *storj.Key, nonce *AESGCMNonce) (data []byte, err error) {
	if len(cipherData) == 0 {
		return []byte{}, Error.New("empty cipher data")
	}

	race2.ReadSlice(cipherData)
	race2.ReadSlice(key[:])
	race2.ReadSlice(nonce[:])

	block, err := aes.NewCipher(key[:])
	if err != nil {
		return []byte{}, Error.Wrap(err)
	}
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return []byte{}, Error.Wrap(err)
	}
	plainData, err := aesgcm.Open(nil, nonce[:], cipherData, nil)
	if err != nil {
		return []byte{}, ErrDecryptFailed.Wrap(err)
	}
	return plainData, nil
}
