<commit_before>package rtspserver

import (
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/djwackey/dorsvr/livemedia"
)

type RTSPClientSession struct {
	isMulticast          bool
	isTimerRunning       bool
	streamAfterSETUP     bool
	numStreamStates      int
	TCPStreamIDCount     uint
	sessionID            string
	streamStates         *StreamServerState
	connection           *RTSPClientConnection
	serverMediaSession   *livemedia.ServerMediaSession
	livenessTimeoutTimer *time.Timer
}

func newRTSPClientSession(connection *RTSPClientConnection, sessionID string) *RTSPClientSession {
	s := &RTSPClientSession{
		sessionID:  sessionID,
		connection: connection,
	}
	s.noteLiveness()
	return s
}

func (s *RTSPClientSession) server() *RTSPServer {
	return s.connection.server
}

func (s *RTSPClientSession) destroy() {
	// turn off any liveness check:
	s.livenessTimeoutTimer.Stop()

	s.server().removeClientSession(s.sessionID)

	if s.serverMediaSession != nil {
		streamName := s.serverMediaSession.StreamName()
		s.server().removeServerMediaSession(streamName)
	}
}

func (s *RTSPClientSession) handleCommandSetup(urlPreSuffix, urlSuffix, reqStr string) {
	streamName, trackID := urlPreSuffix, urlSuffix

	sms := s.server().lookupServerMediaSession(streamName)
	if sms == nil {
		if s.serverMediaSession == nil {
			s.connection.handleCommandNotFound()
		} else {
			s.connection.handleCommandBad()
		}
		return
	}

	if s.serverMediaSession == nil {
		s.serverMediaSession = sms
	} else if sms != s.serverMediaSession {
		s.connection.handleCommandBad()
		return
	}

	if s.streamStates == nil {
		s.numStreamStates = s.serverMediaSession.SubsessionCounter

		s.streamStates = new(StreamServerState)
		for i := 0; i < s.numStreamStates; i++ {
			s.streamStates.subsession = s.serverMediaSession.Subsessions[i]
		}
	}

	// Look up information for the specified subsession (track):
	var subsession livemedia.IServerMediaSubsession
	if trackID != "" {
		for streamNum := 0; streamNum < s.numStreamStates; streamNum++ {
			subsession = s.streamStates.subsession
			if subsession != nil && strings.EqualFold(trackID, subsession.TrackID()) {
				break
			}
		}
	} else {
		if s.numStreamStates != 1 && s.streamStates == nil {
			s.connection.handleCommandBad()
			return
		}
		subsession = s.streamStates.subsession
	}

	// Look for a "Transport:" header in the request string, to extract client parameters:
	transportHeader := livemedia.ParseTransportHeader(reqStr)
	rtpChannelID := transportHeader.RTPChannelID
	rtcpChannelID := transportHeader.RTCPChannelID
	streamingMode := transportHeader.StreamingMode
	clientRTPPort := transportHeader.ClientRTPPortNum
	clientRTCPPort := transportHeader.ClientRTCPPortNum
	streamingModeStr := transportHeader.StreamingModeStr

	if streamingMode == livemedia.RTP_TCP && rtpChannelID == 0xFF {
		rtpChannelID = s.TCPStreamIDCount
		rtcpChannelID = s.TCPStreamIDCount + 1
	}
	if streamingMode == livemedia.RTP_TCP {
		rtcpChannelID = s.TCPStreamIDCount + 2
	}

	_, sawRangeHeader := livemedia.ParseRangeHeader(reqStr)
	if sawRangeHeader {
		s.streamAfterSETUP = true
	} else if livemedia.ParsePlayNowHeader(reqStr) {
		s.streamAfterSETUP = true
	} else {
		s.streamAfterSETUP = false
	}

	sourceAddrStr := s.connection.localAddr
	destAddrStr := s.connection.remoteAddr

	var tcpSocketNum net.Conn
	if streamingMode == livemedia.RTP_TCP {
		tcpSocketNum = s.connection.socket
	}

	streamParameter := subsession.GetStreamParameters(tcpSocketNum,
		destAddrStr,
		s.sessionID,
		clientRTPPort,
		clientRTCPPort,
		rtpChannelID,
		rtcpChannelID)
	serverRTPPort := streamParameter.ServerRTPPort
	serverRTCPPort := streamParameter.ServerRTCPPort

	s.streamStates.streamToken = streamParameter.StreamToken

	if s.isMulticast {
		switch streamingMode {
		case livemedia.RTP_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,
				sourceAddrStr,
				serverRTPPort,
				serverRTCPPort,
				transportHeader.DestinationTTL,
				s.sessionID)
		case livemedia.RTP_TCP:
			// multicast streams can't be sent via TCP
			s.connection.handleCommandUnsupportedTransport()
		case livemedia.RAW_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,
				sourceAddrStr,
				serverRTPPort,
				serverRTCPPort,
				transportHeader.DestinationTTL,
				s.sessionID)
		default:
		}
	} else {
		switch streamingMode {
		case livemedia.RTP_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,<commit_msg>fix printf verb %s of wrong type<commit_after>package rtspserver

import (
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/djwackey/dorsvr/livemedia"
)

type RTSPClientSession struct {
	isMulticast          bool
	isTimerRunning       bool
	streamAfterSETUP     bool
	numStreamStates      int
	TCPStreamIDCount     uint
	sessionID            string
	streamStates         *StreamServerState
	connection           *RTSPClientConnection
	serverMediaSession   *livemedia.ServerMediaSession
	livenessTimeoutTimer *time.Timer
}

func newRTSPClientSession(connection *RTSPClientConnection, sessionID string) *RTSPClientSession {
	s := &RTSPClientSession{
		sessionID:  sessionID,
		connection: connection,
	}
	s.noteLiveness()
	return s
}

func (s *RTSPClientSession) server() *RTSPServer {
	return s.connection.server
}

func (s *RTSPClientSession) destroy() {
	// turn off any liveness check:
	s.livenessTimeoutTimer.Stop()

	s.server().removeClientSession(s.sessionID)

	if s.serverMediaSession != nil {
		streamName := s.serverMediaSession.StreamName()
		s.server().removeServerMediaSession(streamName)
	}
}

func (s *RTSPClientSession) handleCommandSetup(urlPreSuffix, urlSuffix, reqStr string) {
	streamName, trackID := urlPreSuffix, urlSuffix

	sms := s.server().lookupServerMediaSession(streamName)
	if sms == nil {
		if s.serverMediaSession == nil {
			s.connection.handleCommandNotFound()
		} else {
			s.connection.handleCommandBad()
		}
		return
	}

	if s.serverMediaSession == nil {
		s.serverMediaSession = sms
	} else if sms != s.serverMediaSession {
		s.connection.handleCommandBad()
		return
	}

	if s.streamStates == nil {
		s.numStreamStates = s.serverMediaSession.SubsessionCounter

		s.streamStates = new(StreamServerState)
		for i := 0; i < s.numStreamStates; i++ {
			s.streamStates.subsession = s.serverMediaSession.Subsessions[i]
		}
	}

	// Look up information for the specified subsession (track):
	var subsession livemedia.IServerMediaSubsession
	if trackID != "" {
		for streamNum := 0; streamNum < s.numStreamStates; streamNum++ {
			subsession = s.streamStates.subsession
			if subsession != nil && strings.EqualFold(trackID, subsession.TrackID()) {
				break
			}
		}
	} else {
		if s.numStreamStates != 1 && s.streamStates == nil {
			s.connection.handleCommandBad()
			return
		}
		subsession = s.streamStates.subsession
	}

	// Look for a "Transport:" header in the request string, to extract client parameters:
	transportHeader := livemedia.ParseTransportHeader(reqStr)
	rtpChannelID := transportHeader.RTPChannelID
	rtcpChannelID := transportHeader.RTCPChannelID
	streamingMode := transportHeader.StreamingMode
	clientRTPPort := transportHeader.ClientRTPPortNum
	clientRTCPPort := transportHeader.ClientRTCPPortNum
	streamingModeStr := transportHeader.StreamingModeStr

	if streamingMode == livemedia.RTP_TCP && rtpChannelID == 0xFF {
		rtpChannelID = s.TCPStreamIDCount
		rtcpChannelID = s.TCPStreamIDCount + 1
	}
	if streamingMode == livemedia.RTP_TCP {
		rtcpChannelID = s.TCPStreamIDCount + 2
	}

	_, sawRangeHeader := livemedia.ParseRangeHeader(reqStr)
	if sawRangeHeader {
		s.streamAfterSETUP = true
	} else if livemedia.ParsePlayNowHeader(reqStr) {
		s.streamAfterSETUP = true
	} else {
		s.streamAfterSETUP = false
	}

	sourceAddrStr := s.connection.localAddr
	destAddrStr := s.connection.remoteAddr

	var tcpSocketNum net.Conn
	if streamingMode == livemedia.RTP_TCP {
		tcpSocketNum = s.connection.socket
	}

	streamParameter := subsession.GetStreamParameters(tcpSocketNum,
		destAddrStr,
		s.sessionID,
		clientRTPPort,
		clientRTCPPort,
		rtpChannelID,
		rtcpChannelID)
	serverRTPPort := streamParameter.ServerRTPPort
	serverRTCPPort := streamParameter.ServerRTCPPort

	s.streamStates.streamToken = streamParameter.StreamToken

	if s.isMulticast {
		switch streamingMode {
		case livemedia.RTP_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,
				sourceAddrStr,
				serverRTPPort,
				serverRTCPPort,
				transportHeader.DestinationTTL,
				s.sessionID)
		case livemedia.RTP_TCP:
			// multicast streams can't be sent via TCP
			s.connection.handleCommandUnsupportedTransport()
		case livemedia.RAW_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: %s;multicast;destination=%s;source=%d;port=%d;ttl=%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,
				sourceAddrStr,
				serverRTPPort,
				serverRTCPPort,
				transportHeader.DestinationTTL,
				s.sessionID)
		default:
		}
	} else {
		switch streamingMode {
		case livemedia.RTP_UDP:
			s.connection.responseBuffer = fmt.Sprintf("RTSP/1.0 200 OK\r\n"+
				"CSeq: %s\r\n"+
				"%s"+
				"Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"+
				"Session: %s\r\n\r\n", s.connection.currentCSeq,
				livemedia.DateHeader(),
				destAddrStr,
{'fraction_non_alphanumeric': 0.11158840183539978, 'fraction_numerical': 0.004100361222298155, 'mean_word_length': 4.197361745306951, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6053158', 'n_tokens_mistral': 3846, 'n_tokens_neox': 3483, 'n_words': 635}