예제 #1
0
        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);
            }
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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.");
        }
예제 #5
0
        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.");
        }
예제 #6
0
        /// <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);
            }
        }
예제 #7
0
        /// <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);
        }