public static RTSPMessage ParseRTSPMessage(byte[] buffer, IPEndPoint receivedFrom, IPEndPoint receivedOn) { string message = null; try { if (buffer == null || buffer.Length < m_minFirstLineLength) { // Ignore. return(null); } else if (buffer.Length > RTSPConstants.RTSP_MAXIMUM_LENGTH) { logger.LogError( "RTSP message received that exceeded the maximum allowed message length, ignoring."); return(null); } else if (!ByteBufferInfo.HasString(buffer, 0, buffer.Length, RTSP_MESSAGE_IDENTIFIER, m_CRLF)) { // Message does not contain "RTSP" anywhere on the first line, ignore. return(null); } else { message = Encoding.UTF8.GetString(buffer); RTSPMessage rtspMessage = ParseRTSPMessage(message, receivedFrom, receivedOn); rtspMessage.RawBuffer = buffer; return(rtspMessage); } } catch (Exception excp) { message = message.Replace("\n", "LF"); message = message.Replace("\r", "CR"); logger.LogError("Exception ParseRTSPMessage. " + excp.Message + "\nRTSP Message=" + message + "."); return(null); } }
/// <summary> /// Processes a buffer from a TCP read operation to extract the first full RTSP message. If no full RTSP /// messages are available it returns null which indicates the next read should be appended to the current /// buffer and the process re-attempted. /// </summary> /// <param name="receiveBuffer">The buffer to check for the RTSP message in.</param> /// <param name="start">The position in the buffer to start parsing for an RTSP message.</param> /// <param name="length">The position in the buffer that indicates the end of the received bytes.</param> /// <returns>A byte array holding a full RTSP message or if no full RTSP messages are avialble null.</returns> public static byte[] ProcessReceive(byte[] receiveBuffer, int start, int length, out int bytesSkipped) { // NAT keep-alives can be interspersed between RTSP messages. Treat any non-letter character // at the start of a receive as a non RTSPtransmission and skip over it. bytesSkipped = 0; bool letterCharFound = false; while (!letterCharFound && start < length) { if ((int)receiveBuffer[start] >= 65) { break; } else { start++; bytesSkipped++; } } if (start < length) { int endMessageIndex = ByteBufferInfo.GetStringPosition(receiveBuffer, start, length, m_rtspMessageDelimiter, null); if (endMessageIndex != -1) { int contentLength = GetContentLength(receiveBuffer, start, endMessageIndex); int messageLength = endMessageIndex - start + m_rtspMessageDelimiter.Length + contentLength; if (length - start >= messageLength) { byte[] rtspMsgBuffer = new byte[messageLength]; Buffer.BlockCopy(receiveBuffer, start, rtspMsgBuffer, 0, messageLength); return(rtspMsgBuffer); } } } return(null); }
public void RoundtripPictureLossIndicationReportUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); uint senderSsrc = 33; uint mediaSsrc = 44; RTCPFeedback rtcpPli = new RTCPFeedback(senderSsrc, mediaSsrc, PSFBFeedbackTypesEnum.PLI); byte[] buffer = rtcpPli.GetBytes(); logger.LogDebug($"Serialised PLI feedback report: {ByteBufferInfo.HexStr(buffer)}."); RTCPFeedback parsedPli = new RTCPFeedback(buffer); Assert.Equal(RTCPReportTypesEnum.PSFB, parsedPli.Header.PacketType); Assert.Equal(PSFBFeedbackTypesEnum.PLI, parsedPli.Header.PayloadFeedbackMessageType); Assert.Equal(senderSsrc, parsedPli.SenderSSRC); Assert.Equal(mediaSsrc, parsedPli.MediaSSRC); Assert.Equal(2, parsedPli.Header.Length); }
public void GetStringIndexUnitTest() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); string sipMsg = "REGISTER sip:Blue Face SIP/2.0\r\n" + "Via: SIP/2.0/UDP 127.0.0.1:1720;branch=z9hG4bKlgnUQcaywCOaPcXR\r\n" + "Max-Forwards: 70\r\n" + "User-Agent: PA168S\r\n" + "From: \"user\" <sip:user@Blue Face>;tag=81swjAV7dHG1yjd5\r\n" + "To: \"user\" <sip:user@Blue Face>\r\n" + "Call-ID: [email protected]\r\n" + "CSeq: 15754 REGISTER\r\n" + "Contact: <sip:[email protected]:1720>\r\n" + "Expires: 30\r\n" + "Content-Length: 0\r\n\r\n"; byte[] sample = Encoding.ASCII.GetBytes(sipMsg); int endOfMsgIndex = ByteBufferInfo.GetStringPosition(sample, 0, Int32.MaxValue, "\r\n\r\n", null); Assert.True(endOfMsgIndex == sample.Length - 4, "The string position was not correctly found in the buffer."); }
public void RTCPHeaderRoundTripTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); RTCPHeader src = new RTCPHeader(RTCPReportTypesEnum.SR, 1); byte[] headerBuffer = src.GetHeader(17, 54443); RTCPHeader dst = new RTCPHeader(headerBuffer); logger.LogDebug("Version: " + src.Version + ", " + dst.Version); logger.LogDebug("PaddingFlag: " + src.PaddingFlag + ", " + dst.PaddingFlag); logger.LogDebug("ReceptionReportCount: " + src.ReceptionReportCount + ", " + dst.ReceptionReportCount); logger.LogDebug("PacketType: " + src.PacketType + ", " + dst.PacketType); logger.LogDebug("Length: " + src.Length + ", " + dst.Length); logger.LogDebug($"Raw Header: {ByteBufferInfo.HexStr(headerBuffer.Take(headerBuffer.Length).ToArray())}."); Assert.True(src.Version == dst.Version, "Version was mismatched."); Assert.True(src.PaddingFlag == dst.PaddingFlag, "PaddingFlag was mismatched."); Assert.True(src.ReceptionReportCount == dst.ReceptionReportCount, "ReceptionReportCount was mismatched."); Assert.True(src.PacketType == dst.PacketType, "PacketType was mismatched."); Assert.True(src.Length == dst.Length, "Length was mismatched."); }
/// <summary> /// Hands the socket handle to the DTLS context and waits for the handshake to complete. /// </summary> /// <param name="webRtcSession">The WebRTC session to perform the DTLS handshake on.</param> private static bool DoDtlsHandshake(RTCPeerConnection pc, SIPSorceryMedia.DtlsHandshake dtls, bool isClient, byte[] sdpFingerprint) { logger.LogDebug("DoDtlsHandshake started."); if (!File.Exists(DTLS_CERTIFICATE_PATH)) { throw new ApplicationException($"The DTLS certificate file could not be found at {DTLS_CERTIFICATE_PATH}."); } else if (!File.Exists(DTLS_KEY_PATH)) { throw new ApplicationException($"The DTLS key file could not be found at {DTLS_KEY_PATH}."); } int res = 0; bool fingerprintMatch = false; if (isClient) { logger.LogDebug($"DTLS client handshake starting to {pc.AudioDestinationEndPoint}."); // For the DTLS handshake to work connect must be called on the socket so openssl knows where to send. var rtpSocket = pc.GetRtpChannel(SDPMediaTypesEnum.audio).RtpSocket; rtpSocket.Connect(pc.AudioDestinationEndPoint); byte[] fingerprint = null; var peerEP = pc.AudioDestinationEndPoint; res = dtls.DoHandshakeAsClient((ulong)rtpSocket.Handle, (short)peerEP.AddressFamily.GetHashCode(), peerEP.Address.GetAddressBytes(), (ushort)peerEP.Port, ref fingerprint); if (fingerprint != null) { logger.LogDebug($"DTLS server fingerprint {ByteBufferInfo.HexStr(fingerprint)}."); fingerprintMatch = sdpFingerprint.SequenceEqual(fingerprint); } } else { byte[] fingerprint = null; res = dtls.DoHandshakeAsServer((ulong)pc.GetRtpChannel(SDPMediaTypesEnum.audio).RtpSocket.Handle, ref fingerprint); if (fingerprint != null) { logger.LogDebug($"DTLS client fingerprint {ByteBufferInfo.HexStr(fingerprint)}."); fingerprintMatch = sdpFingerprint.SequenceEqual(fingerprint); } } logger.LogDebug("DtlsContext initialisation result=" + res); if (dtls.IsHandshakeComplete()) { logger.LogDebug("DTLS negotiation complete."); if (!fingerprintMatch) { logger.LogWarning("DTLS fingerprint mismatch."); return(false); } else { var srtpSendContext = new SIPSorceryMedia.Srtp(dtls, isClient); var srtpReceiveContext = new SIPSorceryMedia.Srtp(dtls, !isClient); pc.SetSecurityContext( srtpSendContext.ProtectRTP, srtpReceiveContext.UnprotectRTP, srtpSendContext.ProtectRTCP, srtpReceiveContext.UnprotectRTCP); return(true); } } else { return(false); } }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param> public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; remoteDescription = description; SDP remoteSdp = SDP.ParseSDPDescription(init.sdp); SdpType sdpType = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer; var setResult = base.SetRemoteDescription(sdpType, remoteSdp); if (setResult == SetDescriptionResultEnum.OK) { string remoteIceUser = remoteSdp.IceUfrag; string remoteIcePassword = remoteSdp.IcePwd; string dtlsFingerprint = remoteSdp.DtlsFingerprint; var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { remoteIceUser = remoteIceUser ?? audioAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint; } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null) { remoteIceUser = remoteIceUser ?? videoAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint; } } SdpSessionID = remoteSdp.SessionId; if (init.type == RTCSdpType.answer) { _rtpIceChannel.IsController = true; // Set DTLS role to be server. IceRole = IceRolesEnum.passive; } else { // Set DTLS role to be server. // As of 20 Jun 2020 the DTLS handshake logic is based on OpenSSL and the mechanism // used hands over the socket handle to the C++ class. This logic works a lot better // when acting as the server in the handshake. IceRole = IceRolesEnum.passive; } if (remoteIceUser != null && remoteIcePassword != null) { _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword); } if (!string.IsNullOrWhiteSpace(dtlsFingerprint)) { dtlsFingerprint = dtlsFingerprint.Trim().ToLower(); if (dtlsFingerprint.Length < DTLS_FINGERPRINT_DIGEST.Length + 1) { logger.LogWarning($"The DTLS fingerprint was too short."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else if (!dtlsFingerprint.StartsWith(DTLS_FINGERPRINT_DIGEST)) { logger.LogWarning($"The DTLS fingerprint was supplied with an unsupported digest function (supported one is {DTLS_FINGERPRINT_DIGEST}): {dtlsFingerprint}."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else { dtlsFingerprint = dtlsFingerprint.Substring(DTLS_FINGERPRINT_DIGEST.Length + 1).Trim().Replace(":", "").Replace(" ", ""); RemotePeerDtlsFingerprint = ByteBufferInfo.ParseHexStr(dtlsFingerprint); logger.LogDebug($"The DTLS fingerprint for the remote peer's SDP {ByteBufferInfo.HexStr(RemotePeerDtlsFingerprint)}."); } } else { logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description."); return(SetDescriptionResultEnum.DtlsFingerprintMissing); } // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } } signalingState = RTCSignalingState.have_remote_offer; onsignalingstatechange?.Invoke(); } return(setResult); }