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

package srtp

import (
	
	
	
	
	

	
	
)

type streamSession interface {
	Close() error
	write([]byte) (int, error)
	decrypt([]byte) error
}

type session struct {
	localContextMutex           sync.Mutex
	localContext, remoteContext *Context
	localOptions, remoteOptions []ContextOption

	newStream           chan readStream
	acceptStreamTimeout time.Time

	started chan interface{}
	closed  chan interface{}

	readStreamsClosed bool
	readStreams       map[uint32]readStream
	readStreamsLock   sync.Mutex

	log           logging.LeveledLogger
	bufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser

	nextConn net.Conn
}

// Config is used to configure a session.
// You can provide either a KeyingMaterialExporter to export keys
// or directly pass the keys themselves.
// After a Config is passed to a session it must not be modified.
type Config struct {
	Keys                SessionKeys
	Profile             ProtectionProfile
	BufferFactory       func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser
	LoggerFactory       logging.LoggerFactory
	AcceptStreamTimeout time.Time

	// List of local/remote context options.
	// ReplayProtection is enabled on remote context by default.
	// Default replay protection window size is 64.
	LocalOptions, RemoteOptions []ContextOption
}

// SessionKeys bundles the keys required to setup an SRTP session.
type SessionKeys struct {
	LocalMasterKey   []byte
	LocalMasterSalt  []byte
	RemoteMasterKey  []byte
	RemoteMasterSalt []byte
}

func ( *session) ( uint32,  streamSession,  func() readStream) (readStream, bool) {
	.readStreamsLock.Lock()
	defer .readStreamsLock.Unlock()

	if .readStreamsClosed {
		return nil, false
	}

	,  := .readStreams[]
	if  {
		return , false
	}

	// Create the readStream.
	 = ()

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

	.readStreams[] = 

	return , true
}

func ( *session) ( uint32) {
	.readStreamsLock.Lock()
	defer .readStreamsLock.Unlock()

	if .readStreamsClosed {
		return
	}

	delete(.readStreams, )
}

func ( *session) () error {
	if .nextConn == nil {
		return nil
	} else if  := .nextConn.Close();  != nil {
		return 
	}

	<-.closed

	return nil
}

func ( *session) (
	, , ,  []byte,
	 ProtectionProfile,
	 streamSession,
) error {
	var  error
	.localContext,  = CreateContext(, , , .localOptions...)
	if  != nil {
		return 
	}

	.remoteContext,  = CreateContext(, , , .remoteOptions...)
	if  != nil {
		return 
	}

	if  = .nextConn.SetReadDeadline(.acceptStreamTimeout);  != nil {
		return 
	}

	go func() {
		defer func() {
			close(.newStream)

			.readStreamsLock.Lock()
			.readStreamsClosed = true
			.readStreamsLock.Unlock()
			close(.closed)
		}()

		 := make([]byte, 8192)
		for {
			var  int
			,  = .nextConn.Read()
			if  != nil {
				if !errors.Is(, io.EOF) {
					.log.Error(.Error())
				}

				return
			}

			if  = .decrypt([:]);  != nil {
				.log.Info(.Error())
			}
		}
	}()

	close(.started)

	return nil
}