// The MIT License (MIT)
//
// Copyright (c) 2015 xtaci
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package kcp

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/des"
	"crypto/sha1"
	"crypto/subtle"
	"sync"

	"github.com/tjfoc/gmsm/sm4"

	"golang.org/x/crypto/blowfish"
	"golang.org/x/crypto/cast5"
	"golang.org/x/crypto/pbkdf2"
	"golang.org/x/crypto/salsa20"
	"golang.org/x/crypto/tea"
	"golang.org/x/crypto/twofish"
	"golang.org/x/crypto/xtea"
)

var (
	// a defined initial vector
	// https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_.28IV.29
	// https://en.wikipedia.org/wiki/Initialization_vector
	// actually initial vector is not used in this package, we prepend a random nonce to each outgoing packets.
	// though IV is fixed, the first 8 bytes of the encrypted data is always random.
	initialVector = []byte{167, 115, 79, 156, 18, 172, 27, 1, 164, 21, 242, 193, 252, 120, 230, 107}
	saltxor       = `sH3CIVoF#rWLtJo6`
)

// BlockCrypt defines encryption/decryption methods for a given byte slice.
// Notes on implementing: the data to be encrypted contains a builtin
// nonce at the first 16 bytes
type BlockCrypt interface {
	// Encrypt encrypts the whole block in src into dst.
	// Dst and src may point at the same memory.
	Encrypt(dst, src []byte)

	// Decrypt decrypts the whole block in src into dst.
	// Dst and src may point at the same memory.
	Decrypt(dst, src []byte)
}

var _ BlockCrypt = &aeadCrypt{}

// aeadCrypt implements BlockCrypt interface using cipher.AEAD
type aeadCrypt struct {
	aead cipher.AEAD
}

func (aeadCrypt) Encrypt(_, _ []byte) {
	panic("called Encrypt on AEAD crypt")
}

func (aeadCrypt) Decrypt(_, _ []byte) {
	panic("called Decrypt on AEAD crypt")
}

func (a *aeadCrypt) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
	if dst == nil || cap(dst)-len(dst) < len(plaintext)+a.aead.Overhead() {
		panic("AEAD Seal allocated new slice, please increase MTU size")
	}

	return a.aead.Seal(dst, nonce, plaintext, additionalData)
}

func (a *aeadCrypt) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
	return a.aead.Open(dst, nonce, ciphertext, additionalData)
}

func (a *aeadCrypt) NonceSize() int {
	return a.aead.NonceSize()
}

func (a *aeadCrypt) Overhead() int {
	return a.aead.Overhead()
}

// NewAEADCrypt creates an AEAD BlockCrypt instance from an existing cipher.AEAD
func NewAEADCrypt(aead cipher.AEAD) BlockCrypt {
	if aead == nil {
		return nil
	}
	return &aeadCrypt{aead}
}

// NewAESGCMCrypt creates an AEAD BlockCrypt instance using AES-GCM
// key must be either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256.
func NewAESGCMCrypt(key []byte) (BlockCrypt, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}

	return &aeadCrypt{aesgcm}, nil
}

var _ BlockCrypt = &blockCrypt{}

// blockCrypt implements BlockCrypt interface using a cipher.Block
type blockCrypt struct {
	encMu     sync.Mutex
	decMu     sync.Mutex
	encbuf    []byte // encryption working buffer
	decbuf    []byte // decryption working buffer
	block     cipher.Block
	blockSize int // cached block size
}

//go:nosplit
func (c *blockCrypt) Encrypt(dst, src []byte) {
	c.encMu.Lock()
	encrypt(c.block, dst, src, c.encbuf)
	c.encMu.Unlock()
}

//go:nosplit
func (c *blockCrypt) Decrypt(dst, src []byte) {
	c.decMu.Lock()
	decrypt(c.block, dst, src, c.decbuf)
	c.decMu.Unlock()
}

func newBlockCrypt(block cipher.Block) BlockCrypt {
	blockSize := block.BlockSize()
	return &blockCrypt{
		block:     block,
		blockSize: blockSize,
		encbuf:    make([]byte, blockSize),
		decbuf:    make([]byte, 2*blockSize),
	}
}

type salsa20BlockCrypt struct {
	key [32]byte
}

// NewSalsa20BlockCrypt https://en.wikipedia.org/wiki/Salsa20
func NewSalsa20BlockCrypt(key []byte) (BlockCrypt, error) {
	c := new(salsa20BlockCrypt)
	copy(c.key[:], key)
	return c, nil
}

//go:nosplit
func (c *salsa20BlockCrypt) Encrypt(dst, src []byte) {
	if len(src) < 8 {
		return
	}
	salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
	if &dst[0] != &src[0] {
		copy(dst[:8], src[:8])
	}
}

//go:nosplit
func (c *salsa20BlockCrypt) Decrypt(dst, src []byte) {
	if len(src) < 8 {
		return
	}
	salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
	if &dst[0] != &src[0] {
		copy(dst[:8], src[:8])
	}
}

// NewSM4BlockCrypt https://github.com/tjfoc/gmsm/tree/master/sm4
func NewSM4BlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := sm4.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewTwofishBlockCrypt https://en.wikipedia.org/wiki/Twofish
func NewTwofishBlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := twofish.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewTripleDESBlockCrypt https://en.wikipedia.org/wiki/Triple_DES
func NewTripleDESBlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := des.NewTripleDESCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewCast5BlockCrypt https://en.wikipedia.org/wiki/CAST-128
func NewCast5BlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := cast5.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewBlowfishBlockCrypt https://en.wikipedia.org/wiki/Blowfish_(cipher)
func NewBlowfishBlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := blowfish.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewAESBlockCrypt https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
func NewAESBlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewTEABlockCrypt https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
func NewTEABlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := tea.NewCipherWithRounds(key, 16)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

// NewXTEABlockCrypt https://en.wikipedia.org/wiki/XTEA
func NewXTEABlockCrypt(key []byte) (BlockCrypt, error) {
	block, err := xtea.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return newBlockCrypt(block), nil
}

type simpleXORBlockCrypt struct {
	xortbl []byte
}

// NewSimpleXORBlockCrypt simple xor with key expanding
func NewSimpleXORBlockCrypt(key []byte) (BlockCrypt, error) {
	c := new(simpleXORBlockCrypt)
	c.xortbl = pbkdf2.Key(key, []byte(saltxor), 32, mtuLimit, sha1.New)
	return c, nil
}

func (c *simpleXORBlockCrypt) Encrypt(dst, src []byte) {
	if len(src) == 0 {
		return
	}
	subtle.XORBytes(dst, src, c.xortbl)
}
func (c *simpleXORBlockCrypt) Decrypt(dst, src []byte) {
	if len(src) == 0 {
		return
	}
	subtle.XORBytes(dst, src, c.xortbl)
}

type noneBlockCrypt struct{}

// NewNoneBlockCrypt does nothing but copying
func NewNoneBlockCrypt(key []byte) (BlockCrypt, error) {
	return new(noneBlockCrypt), nil
}

//go:nosplit
func (c *noneBlockCrypt) Encrypt(dst, src []byte) {
	if len(src) == 0 {
		return
	}
	if &dst[0] != &src[0] {
		copy(dst, src)
	}
}

//go:nosplit
func (c *noneBlockCrypt) Decrypt(dst, src []byte) {
	if len(src) == 0 {
		return
	}
	if &dst[0] != &src[0] {
		copy(dst, src)
	}
}

// -----------------------------------------------------------------------
// CFB-mode encryption/decryption
// -----------------------------------------------------------------------
//
// For block ciphers (non-AEAD), packets are encrypted using a local variant
// of CFB (Cipher Feedback) mode. The encrypt/decrypt functions below are
// hand-optimized with 8x loop unrolling to reduce data dependencies
// between consecutive block cipher calls.
//
// Two block sizes are supported:
//   - 8-byte blocks  (e.g. Blowfish, CAST5, DES, TEA, XTEA)
//   - 16-byte blocks (e.g. AES, Twofish, SM4)

// encrypt dispatches to the appropriate block-size-specific CFB encryptor.
func encrypt(block cipher.Block, dst, src, buf []byte) {
	switch block.BlockSize() {
	case 8:
		encrypt8(block, dst, src, buf)
	case 16:
		encrypt16(block, dst, src, buf)
	default:
		panic("unsupported cipher block size")
	}
}

// encrypt8 performs CFB encryption for 8-byte block ciphers.
// Uses 8x loop unrolling for throughput optimization.
func encrypt8(block cipher.Block, dst, src, buf []byte) {
	tbl := buf[:8]
	block.Encrypt(tbl, initialVector)
	n := len(src) >> 3 // number of full 8-byte blocks
	base := 0
	repeat := n >> 3 // number of 8-block groups (64 bytes each)
	left := n & 7    // remaining blocks after groups

	for range repeat {
		s := src[base:][0:64]
		d := dst[base:][0:64]
		// 1
		subtle.XORBytes(d[0:8], s[0:8], tbl)
		block.Encrypt(tbl, d[0:8])
		// 2
		subtle.XORBytes(d[8:16], s[8:16], tbl)
		block.Encrypt(tbl, d[8:16])
		// 3
		subtle.XORBytes(d[16:24], s[16:24], tbl)
		block.Encrypt(tbl, d[16:24])
		// 4
		subtle.XORBytes(d[24:32], s[24:32], tbl)
		block.Encrypt(tbl, d[24:32])
		// 5
		subtle.XORBytes(d[32:40], s[32:40], tbl)
		block.Encrypt(tbl, d[32:40])
		// 6
		subtle.XORBytes(d[40:48], s[40:48], tbl)
		block.Encrypt(tbl, d[40:48])
		// 7
		subtle.XORBytes(d[48:56], s[48:56], tbl)
		block.Encrypt(tbl, d[48:56])
		// 8
		subtle.XORBytes(d[56:64], s[56:64], tbl)
		block.Encrypt(tbl, d[56:64])
		base += 64
	}

	switch left {
	case 7:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 6:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 5:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 4:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 3:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 2:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 1:
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		block.Encrypt(tbl, dst[base:base+8])
		base += 8
		fallthrough
	case 0:
		subtle.XORBytes(dst[base:], src[base:], tbl)
	}
}

// encrypt16 performs CFB encryption for 16-byte block ciphers.
// Uses 8x loop unrolling for throughput optimization.
func encrypt16(block cipher.Block, dst, src, buf []byte) {
	tbl := buf[:16]
	block.Encrypt(tbl, initialVector)
	n := len(src) >> 4 // number of full 16-byte blocks
	base := 0
	repeat := n >> 3 // number of 8-block groups (128 bytes each)
	left := n & 7    // remaining blocks after groups
	for range repeat {
		s := src[base:][0:128]
		d := dst[base:][0:128]
		// 1
		subtle.XORBytes(d[0:16], s[0:16], tbl)
		block.Encrypt(tbl, d[0:16])
		// 2
		subtle.XORBytes(d[16:32], s[16:32], tbl)
		block.Encrypt(tbl, d[16:32])
		// 3
		subtle.XORBytes(d[32:48], s[32:48], tbl)
		block.Encrypt(tbl, d[32:48])
		// 4
		subtle.XORBytes(d[48:64], s[48:64], tbl)
		block.Encrypt(tbl, d[48:64])
		// 5
		subtle.XORBytes(d[64:80], s[64:80], tbl)
		block.Encrypt(tbl, d[64:80])
		// 6
		subtle.XORBytes(d[80:96], s[80:96], tbl)
		block.Encrypt(tbl, d[80:96])
		// 7
		subtle.XORBytes(d[96:112], s[96:112], tbl)
		block.Encrypt(tbl, d[96:112])
		// 8
		subtle.XORBytes(d[112:128], s[112:128], tbl)
		block.Encrypt(tbl, d[112:128])
		base += 128
	}

	switch left {
	case 7:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 6:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 5:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 4:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 3:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 2:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 1:
		subtle.XORBytes(dst[base:], src[base:], tbl)
		block.Encrypt(tbl, dst[base:])
		base += 16
		fallthrough
	case 0:
		subtle.XORBytes(dst[base:], src[base:], tbl)
	}
}

// decrypt dispatches to the appropriate block-size-specific CFB decryptor.
func decrypt(block cipher.Block, dst, src, buf []byte) {
	switch block.BlockSize() {
	case 8:
		decrypt8(block, dst, src, buf)
	case 16:
		decrypt16(block, dst, src, buf)
	default:
		panic("unsupported cipher block size")
	}
}

// decrypt8 performs CFB decryption for 8-byte block ciphers.
// Uses double-buffering (tbl/next) with 8x loop unrolling
// to break the data dependency chain between consecutive blocks.
func decrypt8(block cipher.Block, dst, src, buf []byte) {
	tbl := buf[0:8]
	next := buf[8:16]
	block.Encrypt(tbl, initialVector)
	n := len(src) >> 3 // number of full 8-byte blocks
	base := 0
	repeat := n >> 3 // number of 8-block groups (64 bytes each)
	left := n & 7    // remaining blocks after groups

	// 8x loop unrolling: alternates tbl/next to relieve data dependency
	for range repeat {
		s := src[base:][0:64]
		d := dst[base:][0:64]
		// 1
		block.Encrypt(next, s[0:8])
		subtle.XORBytes(d[0:8], s[0:8], tbl)
		// 2
		block.Encrypt(tbl, s[8:16])
		subtle.XORBytes(d[8:16], s[8:16], next)
		// 3
		block.Encrypt(next, s[16:24])
		subtle.XORBytes(d[16:24], s[16:24], tbl)
		// 4
		block.Encrypt(tbl, s[24:32])
		subtle.XORBytes(d[24:32], s[24:32], next)
		// 5
		block.Encrypt(next, s[32:40])
		subtle.XORBytes(d[32:40], s[32:40], tbl)
		// 6
		block.Encrypt(tbl, s[40:48])
		subtle.XORBytes(d[40:48], s[40:48], next)
		// 7
		block.Encrypt(next, s[48:56])
		subtle.XORBytes(d[48:56], s[48:56], tbl)
		// 8
		block.Encrypt(tbl, s[56:64])
		subtle.XORBytes(d[56:64], s[56:64], next)
		base += 64
	}

	switch left {
	case 7:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 6:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 5:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 4:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 3:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 2:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 1:
		block.Encrypt(next, src[base:base+8])
		subtle.XORBytes(dst[base:base+8], src[base:base+8], tbl)
		tbl, next = next, tbl
		base += 8
		fallthrough
	case 0:
		subtle.XORBytes(dst[base:], src[base:], tbl)
	}
}

// decrypt16 performs CFB decryption for 16-byte block ciphers.
// Uses double-buffering (tbl/next) with 8x loop unrolling.
func decrypt16(block cipher.Block, dst, src, buf []byte) {
	tbl := buf[0:16]
	next := buf[16:32]
	block.Encrypt(tbl, initialVector)
	n := len(src) >> 4 // number of full 16-byte blocks
	base := 0
	repeat := n >> 3 // number of 8-block groups (128 bytes each)
	left := n & 7    // remaining blocks after groups

	// 8x loop unrolling: alternates tbl/next to relieve data dependency
	for range repeat {
		s := src[base:][0:128]
		d := dst[base:][0:128]
		// 1
		block.Encrypt(next, s[0:16])
		subtle.XORBytes(d[0:16], s[0:16], tbl)
		// 2
		block.Encrypt(tbl, s[16:32])
		subtle.XORBytes(d[16:32], s[16:32], next)
		// 3
		block.Encrypt(next, s[32:48])
		subtle.XORBytes(d[32:48], s[32:48], tbl)
		// 4
		block.Encrypt(tbl, s[48:64])
		subtle.XORBytes(d[48:64], s[48:64], next)
		// 5
		block.Encrypt(next, s[64:80])
		subtle.XORBytes(d[64:80], s[64:80], tbl)
		// 6
		block.Encrypt(tbl, s[80:96])
		subtle.XORBytes(d[80:96], s[80:96], next)
		// 7
		block.Encrypt(next, s[96:112])
		subtle.XORBytes(d[96:112], s[96:112], tbl)
		// 8
		block.Encrypt(tbl, s[112:128])
		subtle.XORBytes(d[112:128], s[112:128], next)
		base += 128
	}

	switch left {
	case 7:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 6:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 5:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 4:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 3:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 2:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 1:
		block.Encrypt(next, src[base:])
		subtle.XORBytes(dst[base:], src[base:], tbl)
		tbl, next = next, tbl
		base += 16
		fallthrough
	case 0:
		subtle.XORBytes(dst[base:], src[base:], tbl)
	}
}
