// 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) {
	 := [.Header.Size():]
	 = [:.Header.Size()]
	 := .writeCBC.BlockSize()

	// Generate + Append MAC
	 := .Header

	var  error
	var  []byte
	if .ContentType == protocol.ContentTypeConnectionID {
		,  = .hmacCID(.Epoch, .SequenceNumber, .Version, , .writeMac, .h, .ConnectionID)
	} else {
		,  = .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(, ...) //nolint:makezero // todo: FIX

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

	// Update recordLayer size to include IV+MAC+Padding
	binary.BigEndian.PutUint16([.Header.Size()-2:], uint16(len()-.Header.Size())) //nolint:gosec //G115

	return , nil
}

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

	if  := .Unmarshal();  != nil {
		return nil, 
	}
	 := [.Size():]

	switch {
	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() -  - 

	 := [ : +]
	var  error
	var  []byte
	if .ContentType == protocol.ContentTypeConnectionID {
		,  = .hmacCID(
			.Epoch, .SequenceNumber, .Version, [:], .readMac, .h, .ConnectionID,
		)
	} else {
		,  = .hmac(
			.Epoch, .SequenceNumber, .ContentType, .Version, [:], .readMac, .h,
		)
	}
	// Compute Local MAC and compare
	if  != nil || !hmac.Equal(, ) {
		return nil, errInvalidMAC
	}

	return append([:.Size()], [:]...), 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())) //nolint:gosec //G115

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

	return .Sum(nil), nil
}

// hmacCID calculates a MAC according to
// https://datatracker.ietf.org/doc/html/rfc9146#section-5.1
func ( *CBC) (
	 uint16,
	 uint64,
	 protocol.Version,
	 []byte,
	 []byte,
	 func() hash.Hash,
	 []byte,
) ([]byte, error) {
	// Must unmarshal inner plaintext in orde to perform MAC.
	 := &recordlayer.InnerPlaintext{}
	if  := .Unmarshal();  != nil {
		return nil, 
	}

	 := hmac.New(, )

	var  cryptobyte.Builder

	.AddUint64(seqNumPlaceholder)
	.AddUint8(uint8(protocol.ContentTypeConnectionID))
	.AddUint8(uint8(len())) //nolint:gosec //G115
	.AddUint8(uint8(protocol.ContentTypeConnectionID))
	.AddUint8(.Major)
	.AddUint8(.Minor)
	.AddUint16()
	util.AddUint48(&, )
	.AddBytes()
	.AddUint16(uint16(len())) //nolint:gosec //G115
	.AddBytes(.Content)
	.AddUint8(uint8(.RealType))
	.AddBytes(make([]byte, .Zeros))

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

	return .Sum(nil), nil
}