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

package srtp

import (
	
	

	
	
)

const defaultSessionSRTCPReplayProtectionWindow = 64

// SessionSRTCP implements io.ReadWriteCloser and provides a bi-directional SRTCP session
// SRTCP 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 SessionSRTCP struct {
	session
	writeStream *WriteStreamSRTCP
}

// NewSessionSRTCP creates a SRTCP session using conn as the underlying transport.
func ( net.Conn,  *Config) (*SessionSRTCP, 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
			SRTCPReplayProtection(defaultSessionSRTCPReplayProtectionWindow),
		},
		.RemoteOptions...,
	)

	 := &SessionSRTCP{
		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 = &WriteStreamSRTCP{}

	 := .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 ( *SessionSRTCP) () (*WriteStreamSRTCP, 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 ( *SessionSRTCP) ( uint32) (*ReadStreamSRTCP, error) {
	,  := .session.getOrCreateReadStream(, , newReadStreamSRTCP)

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

	return nil, errFailedTypeAssertion
}

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

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

	return , .GetSSRC(), nil
}

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

// Private

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

	 := bufferpool.Get()
	defer bufferpool.Put()

	.session.localContextMutex.Lock()
	,  := .localContext.EncryptRTCP(.([]byte), , nil) //nolint:forcetypeassert
	.session.localContextMutex.Unlock()

	if  != nil {
		return 0, 
	}

	return .session.nextConn.Write()
}

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

// create a list of Destination SSRCs
// that's a superset of all Destinations in the slice.
func destinationSSRC( []rtcp.Packet) []uint32 {
	 := make(map[uint32]struct{})
	for ,  := range  {
		for ,  := range .DestinationSSRC() {
			[] = struct{}{}
		}
	}

	 := make([]uint32, 0, len())
	for  := range  {
		 = append(, )
	}

	return 
}

func ( *SessionSRTCP) ( []byte) error {
	,  := .remoteContext.DecryptRTCP(, , nil)
	if  != nil {
		return 
	}

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

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

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

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

	return nil
}