// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package ciphersuite

import ( //nolint:gci
	
	
	
	
	
	

	
	
	
	
)

// block ciphers using cipher block chaining.
type cbcMode interface {
	cipher.BlockMode
	SetIV([]byte)
}

// CBC Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
type CBC struct {
	writeCBC, readCBC cbcMode
	writeMac, readMac []byte
	h                 prf.HashFunc
}

// NewCBC creates a DTLS CBC Cipher
func (, , , , ,  []byte,  prf.HashFunc) (*CBC, error) {
	,  := aes.NewCipher()
	if  != nil {
		return nil, 
	}

	,  := aes.NewCipher()
	if  != nil {
		return nil, 
	}

	,  := cipher.NewCBCEncrypter(, ).(cbcMode)
	if ! {
		return nil, errFailedToCast
	}

	,  := cipher.NewCBCDecrypter(, ).(cbcMode)
	if ! {
		return nil, errFailedToCast
	}

	return &CBC{
		writeCBC: ,
		writeMac: ,

		readCBC: ,
		readMac: ,
		h:       ,
	}, nil
}

// Encrypt encrypt a DTLS RecordLayer message
func ( *CBC) ( *recordlayer.RecordLayer,  []byte) ([]byte, error) {
	 := [recordlayer.HeaderSize:]
	 = [:recordlayer.HeaderSize]
	 := .writeCBC.BlockSize()

	// Generate + Append MAC
	 := .Header

	,  := .hmac(.Epoch, .SequenceNumber, .ContentType, .Version, , .writeMac, .h)
	if  != nil {
		return nil, 
	}
	 = append(, ...)

	// Generate + Append padding
	 := make([]byte, -len()%)
	 := len()
	for  := 0;  < ; ++ {
		[] = byte( - 1)
	}
	 = append(, ...)

	// Generate IV
	 := make([]byte, )
	if ,  := rand.Read();  != nil {
		return nil, 
	}

	// Set IV + Encrypt + Prepend IV
	.writeCBC.SetIV()
	.writeCBC.CryptBlocks(, )
	 = append(, ...)

	// Prepend unencrypte header with encrypted payload
	 = append(, ...)

	// Update recordLayer size to include IV+MAC+Padding
	binary.BigEndian.PutUint16([recordlayer.HeaderSize-2:], uint16(len()-recordlayer.HeaderSize))

	return , nil
}

// Decrypt decrypts a DTLS RecordLayer message
func ( *CBC) ( []byte) ([]byte, error) {
	 := [recordlayer.HeaderSize:]
	 := .readCBC.BlockSize()
	 := .h()

	var  recordlayer.Header
	 := .Unmarshal()
	switch {
	case  != nil:
		return nil, 
	case .ContentType == protocol.ContentTypeChangeCipherSpec:
		// Nothing to encrypt with ChangeCipherSpec
		return , nil
	case len()% != 0 || len() < +util.Max(.Size()+1, ):
		return nil, errNotEnoughRoomForNonce
	}

	// Set + remove per record IV
	.readCBC.SetIV([:])
	 = [:]

	// Decrypt
	.readCBC.CryptBlocks(, )

	// Padding+MAC needs to be checked in constant time
	// Otherwise we reveal information about the level of correctness
	,  := examinePadding()
	if  != 255 {
		return nil, errInvalidMAC
	}

	 := .Size()
	if len() <  {
		return nil, errInvalidMAC
	}

	 := len() -  - 

	 := [ : +]
	,  := .hmac(.Epoch, .SequenceNumber, .ContentType, .Version, [:], .readMac, .h)

	// Compute Local MAC and compare
	if  != nil || !hmac.Equal(, ) {
		return nil, errInvalidMAC
	}

	return append([:recordlayer.HeaderSize], [:]...), nil
}

func ( *CBC) ( uint16,  uint64,  protocol.ContentType,  protocol.Version,  []byte,  []byte,  func() hash.Hash) ([]byte, error) {
	 := hmac.New(, )

	 := make([]byte, 13)

	binary.BigEndian.PutUint16(, )
	util.PutBigEndianUint48([2:], )
	[8] = byte()
	[9] = .Major
	[10] = .Minor
	binary.BigEndian.PutUint16([11:], uint16(len()))

	if ,  := .Write();  != nil {
		return nil, 
	} else if ,  := .Write();  != nil {
		return nil, 
	}

	return .Sum(nil), nil
}