///
/// Copyright (c) 2018 xdx. All rights reserved.
///
/// \file: encryption.go
///
/// \brief: SM9加解密
///
/// \author: xdx
///

package sm9

import (
	"io"

	"xdx.jelly/xgcl/gerrors"
	"xdx.jelly/xgcl/grand"
	"xdx.jelly/xgcl/sm/sm4"
	"xdx.jelly/xgcl/sm/sm9/errors"
)

type EncType uint32

// 加密类型
const (
	EncTypeKDF    EncType = 0
	EncTypeSM4ECB EncType = 1
	EncTypeSM4CBC EncType = 2
	EncTypeSM4CFB EncType = 4
	EncTypeSM4OFB EncType = 8
	encTypeDummy  EncType = 0xffffffff //无效类型
)

// defaultCipherLength 默认初始分配大小，并不是限定密文长度。
// 一般加密16字节SM4密钥
const defaultCipherLength = 32

// Cipher 密文结构
type Cipher struct {
	EncType EncType             // 加密类型
	IV      [sm4.BlockSize]byte // iv, 当为SM4 CBC,OFB...时有效
	C1      G1
	H       [macSize]byte // C3
	C       []byte        // C2

}

// NewCipher allocate memeory for a cipher
// TODO: NewXXX should also works when replaced by &XXX{}
func NewCipher() *Cipher {
	return &Cipher{
		EncType: encTypeDummy,
		C:       make([]byte, 0, defaultCipherLength),
	}
}

// MarshalBinary return type || X || Y || C3 || len(IV+C2) || IV || C2
func (c *Cipher) MarshalBinary() ([]byte, error) {
	dataLen := 4 + 2*Sm9RefMaxLen + macSize + 4 + len(c.C)
	if (c.EncType == EncTypeSM4CBC || c.EncType == EncTypeSM4CFB || c.EncType == EncTypeSM4OFB) && len(c.IV) == sm4.BlockSize {
		dataLen += sm4.BlockSize
	}
	data := make([]byte, 4, dataLen)
	Endian.PutUint32(data, uint32(c.EncType))
	data = append(data, c.C1.Marshal()...)
	data = append(data, c.H[:]...)
	buf := make([]byte, 4)
	Endian.PutUint32(buf, uint32(len(c.C)))
	data = append(data, buf...)
	if c.EncType == EncTypeSM4CBC && len(c.IV) == sm4.BlockSize {
		data = append(data, c.IV[:]...)
	}
	data = append(data, c.C...)
	return data, nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (c *Cipher) UnmarshalBinary(data []byte) error {
	if len(data) < 4+2*Sm9RefMaxLen+macSize+4 {
		return gerrors.WithAnnotating(errors.ErrUnmarshalFailed, "input unmarshal data too short")
	}
	encType := EncType(Endian.Uint32(data))
	data = data[4:]

	var err error
	if data, err = c.C1.Unmarshal(data); err != nil {
		return gerrors.ChainErrors(errors.ErrUnmarshalFailed, err)
	}

	c.EncType = EncType(encType)
	copy(c.H[:], data[:macSize])
	data = data[macSize:]

	// if CBC, clen include the iv
	clen := Endian.Uint32(data)
	data = data[4:]
	// clen too short even for a iv
	if encType == EncTypeSM4CBC || encType == EncTypeSM4CFB || encType == EncTypeSM4OFB {
		if clen < sm4.BlockSize {
			return gerrors.WithAnnotating(errors.ErrUnmarshalFailed, "input unmarshal data of C2 less than a block")
		}
		copy(c.IV[:], data[:sm4.BlockSize])
		data = data[sm4.BlockSize:]
	}

	if len(data) < int(clen) {
		return gerrors.WithAnnotating(errors.ErrUnmarshalFailed, "input unmarshal data too short")
	}
	c.C = append(c.C[:0], data...)
	return nil
}

// Encrypt SM9加密
//
// 输入:
//   - pube: 加密主公钥
//   - encType: 加密类型
//   - optionalIV: 当加密类型为EncTypeSM4CBC、EncTypeSM4CFB、EncTypeSM4OFB时, 输入16字节iv值。也可以输入nil, 则使用rand随机产生。
//   - plain: 加密原文，当为非KDF加密类型时，长度必须是16的倍数，内部不做padding
func Encrypt(encType EncType, pube *MastEncPublicKey, id, plain []byte, rand io.Reader, optionalIV []byte) (*Cipher, error) {
	rnd := make([]byte, Sm9RefMaxLen)
	if rand == nil {
		rand = grand.Reader
	}
	if _, err := rand.Read(rnd); err != nil {
		return nil, err
	}
	if encType != EncTypeKDF && len(plain)%sm4.BlockSize != 0 {
		return nil, gerrors.WithAnnotating(errors.ErrEncFailed, "input plaintext is not multilple of block size, padding first")
	}

	switch encType {
	case EncTypeKDF:
		return encryption(id, plain, pube, nil, rnd, len(plain)+macKeySize, xor, EncTypeKDF), nil
	case EncTypeSM4ECB:
		return encryption(id, plain, pube, nil, rnd, sm4.BlockSize+macKeySize, encEcb, EncTypeSM4ECB), nil
	case EncTypeSM4CBC:
		if len(optionalIV) < numBytes {
			optionalIV = make([]byte, sm4.BlockSize)
			if _, err := rand.Read(optionalIV); err != nil {
				return nil, err
			}
		}
		return encryption(id, plain, pube, optionalIV, rnd, sm4.BlockSize+macKeySize, encCbc, EncTypeSM4CBC), nil
	case EncTypeSM4CFB:
		if len(optionalIV) == 0 {
			optionalIV = make([]byte, sm4.BlockSize)
			if _, err := rand.Read(optionalIV); err != nil {
				return nil, err
			}
		}
		return encryption(id, plain, pube, optionalIV, rnd, sm4.BlockSize+macKeySize, encCfb, EncTypeSM4CFB), nil
	case EncTypeSM4OFB:
		if len(optionalIV) == 0 {
			optionalIV = make([]byte, sm4.BlockSize)
			if _, err := rand.Read(optionalIV); err != nil {
				return nil, err
			}
		}
		return encryption(id, plain, pube, optionalIV, rnd, sm4.BlockSize+macKeySize, encOfb, EncTypeSM4OFB), nil
	default:
		return nil, errors.ErrEncUnsupportedMode
	}
}

// Decrypt SM9解密。分组模式不对解密明文做unpadding
func Decrypt(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	keylen, f, err := DecryptParams(c)
	if err != nil {
		return nil, err
	}
	return decryption(id, c, de, keylen, f)
}

// EncryptionKDF kdf模式加密
//
// Deprecated: use Encrypt/Decrypt instead.
func EncryptionKDF(id, plain []byte, pube *MastEncPublicKey, rnd []byte) (*Cipher, error) {
	return encryption(id, plain, pube, nil, rnd, len(plain)+macKeySize, xor, EncTypeKDF), nil
}

// DecryptionKDF kdf模式加密
//
// Deprecated: use Encrypt/Decrypt instead.
func DecryptionKDF(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	return decryption(id, c, de, len(c.C)+macKeySize, xor)
}

// EncryptionSm4ECB SM4_ECB模式, 不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func EncryptionSm4ECB(id, plain []byte, pube *MastEncPublicKey, rnd []byte) (*Cipher, error) {
	if len(plain)%sm4.BlockSize != 0 {
		return nil, gerrors.WithAnnotating(errors.ErrEncFailed, "input plaintext is not multilple of block size, padding first")
	}
	return encryption(id, plain, pube, nil, rnd, sm4.BlockSize+macKeySize, encEcb, EncTypeSM4ECB), nil
}

// DecryptionSm4ECB SM4_ECB模式, 不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func DecryptionSm4ECB(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	return decryption(id, c, de, sm4.BlockSize+macKeySize, decEcb)
}

// EncryptionSm4CBC SM4_CBC模式，不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func EncryptionSm4CBC(id, plain []byte, pube *MastEncPublicKey, iv, rnd []byte) (*Cipher, error) {
	if len(plain)%sm4.BlockSize != 0 {
		return nil, gerrors.WithAnnotating(errors.ErrEncFailed, "input plaintext is not multilple of block size, padding first")
	}
	return encryption(id, plain, pube, iv, rnd, sm4.BlockSize+macKeySize, encCbc, EncTypeSM4CBC), nil
}

// DecryptionSm4CBC SM4_ECB模式, 不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func DecryptionSm4CBC(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	return decryption(id, c, de, sm4.BlockSize+macKeySize, decCbc)
}

// EncryptionSm4CFB SM4_CFB模式，不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func EncryptionSm4CFB(id, plain []byte, pube *MastEncPublicKey, iv, rnd []byte) (*Cipher, error) {
	if len(plain)%sm4.BlockSize != 0 {
		return nil, gerrors.WithAnnotating(errors.ErrEncFailed, "input plaintext is not multilple of block size, padding first")
	}
	return encryption(id, plain, pube, iv, rnd, sm4.BlockSize+macKeySize, encCfb, EncTypeSM4CFB), nil
}

// DecryptionSm4CFB SM4_CFB模式, 不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func DecryptionSm4CFB(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	return decryption(id, c, de, sm4.BlockSize+macKeySize, decCfb)
}

// EncryptionSm4OFB SM4_OFB模式，不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func EncryptionSm4OFB(id, plain []byte, pube *MastEncPublicKey, iv, rnd []byte) (*Cipher, error) {
	if len(plain)%sm4.BlockSize != 0 {
		return nil, gerrors.WithAnnotating(errors.ErrEncFailed, "input plaintext is not multilple of block size, padding first")
	}
	return encryption(id, plain, pube, iv, rnd, sm4.BlockSize+macKeySize, encOfb, EncTypeSM4OFB), nil
}

// DecryptionSm4OFB SM4_OFB模式, 不做padding和unpadding
//
// Deprecated: use Encrypt/Decrypt instead.
func DecryptionSm4OFB(id []byte, c *Cipher, de *UserEncKey) ([]byte, error) {
	return decryption(id, c, de, sm4.BlockSize+macKeySize, decOfb)
}
