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 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); }