// Package blockmodes 使用BlockEncrypter接口封装GCM、CCM等加密模式。
// 适用于将密码机、ukey的ECB加密模式封装为GCM、CCM等加密模式。
package blockmode

import (
	"crypto/cipher"
)

// var errorCodeBase = gcl.ErrorCodeBaseMap[PACKAGE_NAME]

// EcbBlockMode 是对多组数据进行加密/解密运算。可以对密码机等的ECB加密进行封装后得到。
// GCM模式、CTR等模式将基于EcbBlockMode进行计算。
// 注：由于Cipher.NewGCM的入参是cipher.Block, 针对单组数据进行的运算，不适合密码机等，
// 否则每个分组都要调用密码机或ukey，调用消耗巨大。所以这里使用多组加密接口进行GCM、CTR、CCM
// 等封装。
type EcbBlockMode interface {
	// 每个分组长度
	BlockSize() int

	// CryptBlocks 加密多组数据。传入src保证是BlockSize()的整数倍，len(dst) >= len(src)
	// 加密完成后dst[:len(src)]为（ECB）密文。
	// 如果src非常大，BlockEncrypter负责进行分组调用密码机, 可能因为网络等原因会返回错误。
	// 本函数为阻塞函数
	EcbEncCryptBlocks(dst, src []byte) error
	EcbDecCryptBlocks(dst, src []byte) error
}

// CbcBlockMode CBC计算。BlockEncrypter对象也可以做CBC，但是，如果每次分组都去调密码卡或ukey，会非常慢。
type CbcBlockMode interface {
	// 分组长度
	BlockSize() int

	// CBCEncrypt CBC加密多组数据。传入src保证是BlockSize()的整数倍，len(dst) >= len(src)
	// iv是BlockSize()大小。 dst和src要么完全重合，要么完全不相交
	// 返回时：如果正常则填充dst[:len(src)]为密文。iv返回时不作要求，可以保持不变，也可以为最后一个分组内容。
	// 如果src非常大，CBCEncrypter负责进行分组调用密码机, 可能因为网络等原因会返回错误。
	// 本函数为阻塞函数
	CbcEncCryptBlocks(dst, iv, src []byte) error
	CbcDecCryptBlocks(dst, iv, src []byte) error
}

type EcbCbcBlockMode interface {
	EcbBlockMode
	CbcBlockMode
}

// 三段式加解密接口
type TernaryCrypter interface {
	// 三段式加密
	// Init an BlockUpdater. Set nonce as iv and additional data.
	EncryptInit(iv []byte) error

	// Encrypt 单组加密，等价于调用EncryptUpdate后EncryptFinal
	Encrypt(dst []byte, in []byte) ([]byte, error)

	// Update appends the encrypted/decrypted result to dst and return it
	EncryptUpdate(dst []byte, in []byte) ([]byte, error)
	// Final 将剩余密文和tag append到out上并返回。返回结果的最后16字节为tag.
	EncryptFinal(dst []byte) ([]byte, error)

	// 三段式解密
	DecryptInit(iv []byte) error
	Decrypt(dst []byte, in []byte) ([]byte, error)
	DecryptUpdate(dst []byte, in []byte) ([]byte, error)
	DecryptFinal(dst []byte) ([]byte, error)
}

// TernaryGCM 标准库cipher.AEAD和SDF/SKF的三段式用法。
// 在三段式调用中，若某次返回错误（比如某个大文件已经EncryptUpdate多次了，但如果某次网络原因
// EncryptUpdate返回错误），则保证内部状态不变， 可以重复该次返错的调用。
type TernaryGCM interface {
	// 保留标准库用法，AEAD.Seal和AEAD.Open前无需调用EncryptInit/DecryptInit
	cipher.AEAD

	// SpecifyADD 在三段式调用中必须在调用EncryptInit后调用
	ADDSpecifier
	TernaryCrypter
}

type ADDSpecifier interface {
	SpecifyADD(additionalData []byte)
}

type ADDAndDataLenSpecifier interface {
	SpecifyADDAndDataLen(ad []byte, dataLen int)
}

// Stream for ctr mode
type TernaryStream interface {
	cipher.Stream
	TernaryCrypter
}

// Wrapper 将一个cipher.Block接口包装为EcbCbcEncBlockMode接口
type wrapper struct {
	cipher.Block
}

func Wrap(b cipher.Block) EcbCbcBlockMode {
	// If b is also an EcbCbcBlockMode interface.
	if bm, ok := b.(EcbCbcBlockMode); ok {
		return bm
	}
	return wrapper{b}
}

func (w wrapper) BlockSize() int {
	return w.Block.BlockSize()
}

func (w wrapper) EcbEncCryptBlocks(dst, src []byte) error {
	for len(src) > 0 {
		w.Encrypt(dst, src)
		dst = dst[w.BlockSize():]
		src = src[w.BlockSize():]
	}
	return nil
}

func (w wrapper) CbcEncCryptBlocks(dst, iv, src []byte) error {
	cbc := cipher.NewCBCEncrypter(w.Block, iv)
	cbc.CryptBlocks(dst, src)
	return nil
}

func (w wrapper) EcbDecCryptBlocks(dst, src []byte) error {
	for len(src) > 0 {
		w.Decrypt(dst, src)
		dst = dst[w.BlockSize():]
		src = src[w.BlockSize():]
	}
	return nil
}

func (w wrapper) CbcDecCryptBlocks(dst, iv, src []byte) error {
	cbc := cipher.NewCBCDecrypter(w.Block, iv)
	cbc.CryptBlocks(dst, src)
	return nil
}
