/// <summary> /// Sends a Rtsp Response on the given client session /// </summary> /// <param name="message">The RtspResponse to send</param> If this was byte[] then it could handle http /// <param name="ci">The session to send the response on</param> internal void ProcessSendRtspMessage(RtspMessage message, ClientSession session, bool sendResponse = true) { //Todo have a SupportedFeatures and RequiredFeatures HashSet. //Check Require Header // And /* Add Unsupported Header if needed Require: play.basic, con.persistent (basic play, TCP is supported) setup.playing means that setup and teardown can be used in the play state. */ //If we have a session if (session == null || session.IsDisposed) return; try { //if there is a message to send if (message != null && false == message.IsDisposed) { //AddServerHeaders()-> if (false == message.ContainsHeader(RtspHeaders.Server)) message.SetHeader(RtspHeaders.Server, ServerName); if (false == message.ContainsHeader(RtspHeaders.Date)) message.SetHeader(RtspHeaders.Date, DateTime.UtcNow.ToString("r")); #region RFC2326 12.38 Timestamp / Delay /* 12.38 Timestamp The timestamp general header describes when the client sent the request to the server. The value of the timestamp is of significance only to the client and may use any timescale. The server MUST echo the exact same value and MAY, if it has accurate information about this, add a floating point number indicating the number of seconds that has elapsed since it has received the request. The timestamp is used by the client to compute the round-trip time to the server so that it can adjust the timeout value for retransmissions. Timestamp = "Timestamp" ":" *(DIGIT) [ "." *(DIGIT) ] [ delay ] delay = *(DIGIT) [ "." *(DIGIT) ] */ if (false == message.ContainsHeader(RtspHeaders.Timestamp) && session.LastRequest != null && session.LastRequest.ContainsHeader(RtspHeaders.Timestamp)) { ////Apparently not joined with ; ////message.SetHeader(RtspHeaders.Timestamp, session.LastRequest[RtspHeaders.Timestamp] + "delay=" + (DateTime.UtcNow - session.LastRequest.Created).TotalSeconds); //Set the value of the Timestamp header as given message.AppendOrSetHeader(RtspHeaders.Timestamp, session.LastRequest[RtspHeaders.Timestamp]); //Add a delay datum message.AppendOrSetHeader(RtspHeaders.Timestamp, "delay=" + (DateTime.UtcNow - session.LastRequest.Created).TotalSeconds); } #endregion string sess = message.GetHeader(RtspHeaders.Session); //Check for a session header if (false == string.IsNullOrWhiteSpace(sess)) { //Add the timeout header if there was a session header. if (RtspClientInactivityTimeout > TimeSpan.Zero && false == sess.Contains("timeout")) message.AppendOrSetHeader(RtspHeaders.Session, "timeout=" + (int)(RtspClientInactivityTimeout.TotalSeconds / 2)); } //Oops //if (session.m_RtspSocket.ProtocolType == ProtocolType.Tcp && session.Attached.Count > 0) response.SetHeader("Ignore", "$0\09\r\n$\0:\0"); //Dispose the last response if (session.LastResponse != null) session.LastResponse.Dispose(); //Todo //Content-Encoding should be the same as the request's if possible.. //If sending a response if (sendResponse) { //Log response if (Logger != null) Logger.LogResponse(message, session); session.SendRtspData((session.LastResponse = message).ToBytes()); }//Otherwise just update the property else session.LastResponse = message; //Indicate the session is not disconnected session.IsDisconnected = false; //Indicate the last response was sent now. session.LastResponse.Transferred = DateTime.UtcNow; } else { //Test the connectivity and start another receive session.SendRtspData(Media.Common.MemorySegment.EmptyBytes); } } catch (Exception ex) { if (Logger != null) Logger.LogException(ex); //if a socket exception occured then handle it. if (session != null && ex is SocketException) HandleClientSocketException((SocketException)ex, session); } }
/// <summary> /// Creates a RtspResponse based on the SequenceNumber contained in the given RtspRequest /// </summary> /// <param name="request">The request to utilize the SequenceNumber from, if null the current SequenceNumber is used</param> /// <param name="statusCode">The StatusCode of the generated response</param> /// <returns>The RtspResponse created</returns> internal RtspMessage CreateRtspResponse(RtspMessage request = null, RtspStatusCode statusCode = RtspStatusCode.OK, string body = null) { RtspMessage response = new RtspMessage(RtspMessageType.Response); response.StatusCode = statusCode; /* 12.4.1 400 Bad Request The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications [H10.4.1]. If the request does not have a CSeq header, the server MUST NOT include a CSeq in the response. */ if (request != null) { if (request.ContainsHeader(RtspHeaders.Session)) response.SetHeader(RtspHeaders.Session, request.GetHeader(RtspHeaders.Session)); if (statusCode != RtspStatusCode.BadRequest) response.CSeq = request.CSeq; } else if (statusCode != RtspStatusCode.BadRequest && LastRequest.CSeq >= 0) response.CSeq = LastRequest.CSeq; //Include any body. if (false == string.IsNullOrWhiteSpace(body)) response.Body = body; return response; }
/// <summary> /// Processes a RtspRequest based on the contents /// </summary> /// <param name="request">The rtsp Request</param> /// <param name="session">The client information</param> internal void ProcessRtspRequest(RtspMessage request, ClientSession session, bool sendResponse = true) { try { //Log the reqeust now if (Logger != null) Logger.LogRequest(request, session); //Ensure we have a session and request and that the server is still running and that the session is still contained in this server if (request == null || request.IsDisposed || session == null || session.IsDisposed) { return; }//Ensure the session is added to the connected clients if it has not been already. else if (session.m_Contained == null) TryAddSession(session); //Check for session moved from another process. //else if (session.m_Contained != null && session.m_Contained != this) return; //Turtle power! if (request.MethodString == "REGISTER") { //Send back turtle food. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, session.RtspSocket.ProtocolType == ProtocolType.Tcp ? "FU_WILL_NEVER_SUPPORT_THIS$00\a" : string.Empty, sendResponse); return; } //All requests need the CSeq if (false == request.ContainsHeader(RtspHeaders.CSeq)) { //Send back a BadRequest. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, null, sendResponse); return; } /* 12.37 Session This request and response header field identifies an RTSP session started by the media server in a SETUP response and concluded by TEARDOWN on the presentation URL. The session identifier is chosen by the media server (see Section 3.4). Once a client receives a Session identifier, it MUST return it for any request related to that session. A server does not have to set up a session identifier if it has other means of identifying a session, such as dynamically generated URLs. */ //Determine if the request is specific to a session if (request.ContainsHeader(RtspHeaders.Session)) { //Determine what session is being acted on. string requestedSessionId = request.GetHeader(RtspHeaders.Session); //If there is a null or empty session id this request is invalid. if (string.IsNullOrWhiteSpace(requestedSessionId)) { //Send back a BadRequest. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, null, sendResponse); return; } else requestedSessionId = requestedSessionId.Trim(); //If the given session does not have a sessionId or does not match the sessionId requested. if (session.SessionId != requestedSessionId) { //Find any session which has the given id. IEnumerable<ClientSession> matches = GetSessions(requestedSessionId); //Atttempt to get the correct session ClientSession correctSession = matches.FirstOrDefault();//(c => false == c.IsDisconnected); //If no session could be found by the given sessionId if (correctSession == null) { //Indicate the session requested could not be found. ProcessInvalidRtspRequest(session, RtspStatusCode.SessionNotFound, null, sendResponse); return; } else //There was a session found by the given Id { //Indicate the last request of this session was as given session.LastRequest = request; //The LastResponse is updated to be the value of whatever the correctSessions LastResponse is // session.LastResponse = correctSession.LastResponse; //Process the request from the correctSession but do not send a response. ProcessRtspRequest(request, correctSession, false); session.LastResponse = correctSession.LastResponse; //Take the created response and sent it to the new session using it as the last response. ProcessSendRtspMessage(session.LastResponse, session, sendResponse); return; } } } //Check for out of order or duplicate requests. if (session.LastRequest != null) { //Out of order if (request.CSeq < session.LastRequest.CSeq) { //Send back a BadRequest. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, null, sendResponse); return; } } //Dispose any last request. if (session.LastRequest != null && false == session.LastRequest.IsDisposed) session.LastRequest.Dispose(); //Synchronize the server and client since this is not a duplicate session.LastRequest = request; //Determine if we support what the client requests in `Require` Header if (request.ContainsHeader(RtspHeaders.Require)) { //Certain features are requried... tcp etc. //Todo ProcessRequired( } //If there is a body and no content-length if (false == string.IsNullOrWhiteSpace(request.Body) && false == request.ContainsHeader(RtspHeaders.ContentLength)) { //Send back a BadRequest. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, null, sendResponse); return; } //Optional Checks //UserAgent if (RequireUserAgent && false == request.ContainsHeader(RtspHeaders.UserAgent)) { //Send back a BadRequest. ProcessInvalidRtspRequest(session, Rtsp.RtspStatusCode.BadRequest, null, sendResponse); return; } //Minor version reflects changes made to the protocol but not the 'general message parsing' `algorithm` //Thus, RTSP/2.4 is a lower version than RTSP/2.13, which in turn is lower than RTSP/12.3. //Leading zeros SHALL NOT be sent and MUST be ignored by recipients. //Version - Should check request.Version != Version and that earlier versions are supprted. if (request.Version > Version) { //ConvertToMessage ProcessInvalidRtspRequest(session, RtspStatusCode.RtspVersionNotSupported, null, sendResponse); return; } //4.2. RTSP IRI and URI, Draft requires 501 response for rtspu iri but not for udp sockets using a rtsp location.... //Should check request.Location.Scheme to not be rtspu but it was allowed previously... //If any custom handlers were registered. if (m_RequestHandlers.Count > 0) { //Determine if there is a custom handler for the mthod RtspRequestHandler custom; //If there is if (m_RequestHandlers.TryGetValue(request.Method, out custom)) { //Then create the response RtspMessage response; //By invoking the handler, if true is returned if (custom(request, out response)) { //Use the response created by the custom handler ProcessSendRtspMessage(response, session, sendResponse); //Return because the custom handler has handled the request. return; } } } //From this point if Authrorization is required and the stream exists //The server will responsd with AuthorizationRequired when it should NOT have probably respoded with that at this point. //The problem is that RequiredCredentails uses a Uri format by ID. //We could get a stream and then respond accordingly but that is how it currently works and it allows probing of streams which is what not desirable in some cases //Thus we have to use the location of the request and see if RequiredCredentials has anything which matches root. //This would force everything to have some type of authentication which would also be applicable to all lower level streams in the uri in the credential cache. //I could also change up the semantic and make everything Uri based rather then locations //Then it would also be easier to make /audio only passwords etc. //When stopping only handle teardown and keep alives if (m_StopRequested && (request.Method != RtspMethod.TEARDOWN && request.Method != RtspMethod.GET_PARAMETER && request.Method != RtspMethod.OPTIONS)) { ProcessInvalidRtspRequest(session, RtspStatusCode.BadRequest, null, sendResponse); return; } //Determine the handler for the request and process it switch (request.Method) { case RtspMethod.ANNOUNCE: { ProcessInvalidRtspRequest(session, RtspStatusCode.MethodNotAllowed, null, sendResponse); break; } case RtspMethod.OPTIONS: { ProcessRtspOptions(request, session, sendResponse); break; } case RtspMethod.DESCRIBE: { ProcessRtspDescribe(request, session, sendResponse); break; } case RtspMethod.SETUP: { ProcessRtspSetup(request, session, sendResponse); break; } case RtspMethod.PLAY: { ProcessRtspPlay(request, session, sendResponse); break; } case RtspMethod.RECORD: { ProcessRtspRecord(request, session, sendResponse); break; } case RtspMethod.PAUSE: { ProcessRtspPause(request, session, sendResponse); break; } case RtspMethod.TEARDOWN: { ProcessRtspTeardown(request, session, sendResponse); break; } case RtspMethod.GET_PARAMETER: { ProcessGetParameter(request, session, sendResponse); break; } case RtspMethod.SET_PARAMETER: { ProcessSetParameter(request, session, sendResponse); break; } case RtspMethod.REDIRECT: //Client can't redirect a server { ProcessInvalidRtspRequest(session, RtspStatusCode.BadRequest, null, sendResponse); break; } case RtspMethod.UNKNOWN: default: { //Per 2.0 Draft ProcessInvalidRtspRequest(session, RtspStatusCode.NotImplemented, null, sendResponse); break; } } } catch (Exception ex) { //Log it if (Logger != null) Logger.LogException(ex); } }