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

package srtp

import (
	
	
	

	
	
)

const defaultSessionSRTPReplayProtectionWindow = 64

// SessionSRTP implements io.ReadWriteCloser and provides a bi-directional SRTP session
// SRTP itself does not have a design like this, but it is common in most applications
// for local/remote to each have their own keying material. This provides those patterns
// instead of making everyone re-implement.
type SessionSRTP struct {
	session
	writeStream *WriteStreamSRTP
}

// NewSessionSRTP creates a SRTP session using conn as the underlying transport.
func ( net.Conn,  *Config) (*SessionSRTP, error) { //nolint:dupl
	if  == nil {
		return nil, errNoConfig
	} else if  == nil {
		return nil, errNoConn
	}

	 := .LoggerFactory
	if  == nil {
		 = logging.NewDefaultLoggerFactory()
	}

	 := append(
		[]ContextOption{},
		.LocalOptions...,
	)
	 := append(
		[]ContextOption{
			// Default options
			SRTPReplayProtection(defaultSessionSRTPReplayProtectionWindow),
		},
		.RemoteOptions...,
	)

	 := &SessionSRTP{
		session: session{
			nextConn:            ,
			localOptions:        ,
			remoteOptions:       ,
			readStreams:         map[uint32]readStream{},
			newStream:           make(chan readStream),
			acceptStreamTimeout: .AcceptStreamTimeout,
			started:             make(chan interface{}),
			closed:              make(chan interface{}),
			bufferFactory:       .BufferFactory,
			log:                 .NewLogger("srtp"),
		},
	}
	.writeStream = &WriteStreamSRTP{}

	 := .session.start(
		.Keys.LocalMasterKey, .Keys.LocalMasterSalt,
		.Keys.RemoteMasterKey, .Keys.RemoteMasterSalt,
		.Profile,
		,
	)
	if  != nil {
		return nil, 
	}

	return , nil
}

// OpenWriteStream returns the global write stream for the Session.
func ( *SessionSRTP) () (*WriteStreamSRTP, error) {
	return .writeStream, nil
}

// OpenReadStream opens a read stream for the given SSRC, it can be used
// if you want a certain SSRC, but don't want to wait for AcceptStream.
func ( *SessionSRTP) ( uint32) (*ReadStreamSRTP, error) {
	,  := .session.getOrCreateReadStream(, , newReadStreamSRTP)

	if ,  := .(*ReadStreamSRTP);  {
		return , nil
	}

	return nil, errFailedTypeAssertion
}

// AcceptStream returns a stream to handle RTCP for a single SSRC.
func ( *SessionSRTP) () (*ReadStreamSRTP, uint32, error) {
	,  := <-.newStream
	if ! {
		return nil, 0, errStreamAlreadyClosed
	}

	,  := .(*ReadStreamSRTP)
	if ! {
		return nil, 0, errFailedTypeAssertion
	}

	return , .GetSSRC(), nil
}

// Close ends the session.
func ( *SessionSRTP) () error {
	return .session.close()
}

func ( *SessionSRTP) ( []byte) (int, error) {
	 := &rtp.Packet{}

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

	return .writeRTP(&.Header, .Payload)
}

// bufferpool is a global pool of buffers used for encrypted packets in
// writeRTP below.  Since it's global, buffers can be shared between
// different sessions, which amortizes the cost of allocating the pool.
//
// 1472 is the maximum Ethernet UDP payload.  We give ourselves 20 bytes
// of slack for any authentication tags, which is more than enough for
// either CTR or GCM.  If the buffer is too small, no harm, it will just
// get expanded by growBuffer.
var bufferpool = sync.Pool{ // nolint:gochecknoglobals
	New: func() interface{} {
		return make([]byte, 1492)
	},
}

func ( *SessionSRTP) ( *rtp.Header,  []byte) (int, error) {
	if ,  := <-.session.started;  {
		return 0, errStartedChannelUsedIncorrectly
	}

	// encryptRTP will either return our buffer, or, if it is too
	// small, allocate a new buffer itself.  In either case, it is
	// safe to put the buffer back into the pool, but only after
	// nextConn.Write has returned.
	 := bufferpool.Get()
	defer bufferpool.Put()

	 := .([]byte)                                                      // nolint:forcetypeassert
	,  := rtp.HeaderAndPacketMarshalSize(, ) // nolint:staticcheck
	if len() < +20 {
		// The buffer is too small, so we need to allocate a new one. Add 20 bytes for auth tag like
		// for bufferpool above.
		 = make([]byte, +20)
	}
	,  := rtp.MarshalPacketTo(, , ) // nolint:staticcheck
	if  != nil {
		return 0, 
	}

	.session.localContextMutex.Lock()
	,  := .localContext.encryptRTP(, , , [:])
	.session.localContextMutex.Unlock()

	if  != nil {
		return 0, 
	}

	return .session.nextConn.Write()
}

func ( *SessionSRTP) ( time.Time) error {
	return .session.nextConn.SetWriteDeadline()
}

func ( *SessionSRTP) ( []byte) error {
	 := &rtp.Header{}
	,  := .Unmarshal()
	if  != nil {
		return 
	}

	,  := .session.getOrCreateReadStream(.SSRC, , newReadStreamSRTP)
	if  == nil {
		return nil // Session has been closed
	} else if  {
		if !.session.acceptStreamTimeout.IsZero() {
			_ = .session.nextConn.SetReadDeadline(time.Time{})
		}
		.session.newStream <-  // Notify AcceptStream
	}

	,  := .(*ReadStreamSRTP)
	if ! {
		return errFailedTypeAssertion
	}

	,  := .remoteContext.decryptRTP(, , , )
	if  != nil {
		return 
	}

	_,  = .write()
	if  != nil {
		return 
	}

	return nil
}