// package mac is the the implement of Message Authentication Code.
// Includes cbcmac and hmac.
//
// 一般实际应用中我们只使用两种MAC：
// 1) CBCMac, 实际上是使用全0的iv对原文进行CBC加密的最后一个分组。其中原文由调用者进行补位(padding)，输出
// 为128比特；
// 2) HMac_SM3, HMac对key和输出tag长度并没有限定 ，但一般key不能太小(< 80 bits), tag也不能小于80比特，
// 并且tag长度不超过SM3杂凑值长度，即256比特。
// 注：hmac可以使用标准库的方式：crypto/hmac.New(SM3.New, key).
// 本package只是对crypto/hmac的包装.
package mac

import (
	crypto_hmac "crypto/hmac"
	"crypto/sha256"
	"errors"
	"hash"

	"xdx.jelly/xgcl/identifier"
	"xdx.jelly/xgcl/mac/cbcmac"
	"xdx.jelly/xgcl/mac/hmac"
	"xdx.jelly/xgcl/sm/sm1"
	"xdx.jelly/xgcl/sm/sm3"
	"xdx.jelly/xgcl/sm/sm4"
)

/*
MAC is the interface for MACs (Message Authentication Codes).
This interface should be used for authentication only, and not for other purposes
(for example, it should not be used to generate pseudorandom bytes).

MAC is "easy to use, hard to mistake".

PS: The interface is from "Tink"
*/
type MAC interface {

	// ComputeMAC computes message authentication code (MAC) for code data.
	ComputeMAC(data []byte) ([]byte, error)

	// Verify returns nil if mac is a correct authentication code (MAC) for data,
	// otherwise it returns an error.
	VerifyMAC(mac, data []byte) error
}

// NewHMacSm3 is a simple wrapper of hmac.NewHMAC
// key 16- bytes
// tagSize 10-32
//
// !Deprecated. Use NewMac instead.
func NewHMacSm3(key []byte, tagSize uint32) (MAC, error) {
	return hmac.NewHMAC("SM3", key, tagSize)
}

type MacType uint32

const (
	HMAC_SM3    = MacType(identifier.SGDSM3)
	HMAC_SHA256 = MacType(identifier.SGDSHA256)
	CBCMAC_SM1  = MacType(identifier.SGDSM1Mac)
	CBCMAC_SM4  = MacType(identifier.SGDSM4Mac)
)

// NewMac returns a MAC interface, which is easy to use, hard to mistake.
func NewMac(tpe MacType, key []byte) (MAC, error) {
	switch tpe {
	case HMAC_SM3:
		return hmac.NewHMAC("SM3", key, sm3.Size)
	case HMAC_SHA256:
		return hmac.NewHMAC("SHA256", key, sha256.Size)
	case CBCMAC_SM1:
		b, err := sm1.NewCipher(key)
		if err != nil {
			return nil, err
		}
		return cbcmac.New(b)
	case CBCMAC_SM4:
		b, err := sm4.NewCipher(key)
		if err != nil {
			return nil, err
		}
		return cbcmac.New(b)
	default:
		return nil, errors.New("Unsurported mac type.")
	}
}

// Return a hash.Hash interface, for multiple messages.
func NewMacHash(tpe MacType, key []byte) (hash.Hash, error) {
	switch tpe {
	case HMAC_SM3:
		return crypto_hmac.New(sm3.New, key), nil
	case HMAC_SHA256:
		return crypto_hmac.New(sha256.New, key), nil
	// case CBCMAC_SM1:
	// 	b, err := sm1.NewCipher(key)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// 	return cbcmac.New(b)
	// case CBCMAC_SM4:
	// 	b, err := sm4.NewCipher(key)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// 	return cbcmac.New(b)
	default:
		return nil, errors.New("Unsurported mac type.")
	}
}
