// Generate Basic or Digest Authorization private string GenerateAuthorization(string username, string password, AUTHENTICATION authentication, string realm, string nonce, string url, string command) { if (username == null || username.Length == 0) { return(null); } if (password == null || password.Length == 0) { return(null); } if (realm == null || realm.Length == 0) { return(null); } if (authentication == AUTHENTICATION.BASIC) { String basic_authorization = "Basic " + System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password)); return(basic_authorization); } else if (authentication == AUTHENTICATION.DIGEST) { if (nonce == null || nonce.Length == 0) { return(null); } MD5 md5 = System.Security.Cryptography.MD5.Create(); String hashA1 = CalculateMD5Hash(md5, username + ":" + realm + ":" + password); String hashA2 = CalculateMD5Hash(md5, command + ":" + url); String response = CalculateMD5Hash(md5, hashA1 + ":" + nonce + ":" + hashA2); const String quote = "\""; String digest_authorization = "Digest username="******", " + "realm=" + quote + realm + quote + ", " + "nonce=" + quote + nonce + quote + ", " + "uri=" + quote + url + quote + ", " + "response=" + quote + response + quote; return(digest_authorization); } else { return(null); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); // Check if the Message has an Authenticate header and what type it is if (message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; // Parse www_authenticate // EG: WWW-Authenticate: Basic realm="xxxxxxx" // EG: WWW-Authenticate: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE string[] items = www_authenticate.Split(new char[] { ',', ' ' }); // split on Comma and Space // Process the first item if (items.Count() >= 1 && items[0].Equals("Basic")) { authentication = AUTHENTICATION.BASIC; } else if (items.Count() >= 1 && items[0].Equals("Digest")) { authentication = AUTHENTICATION.DIGEST; } // Process the remaining items for (int i = 1; i < items.Count(); i++) { string[] parts = items[i].Split(new char[] { '=' }); // Split on Equals if (parts.Count() >= 2 && parts[0].Trim().Equals("realm")) { realm = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (parts.Count() >= 2 && parts[0].Trim().Equals("nonce")) { nonce = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } } } // If we get a reply to OPTIONS and CSEQ is 1 (which was our first command), then send the DESCRIBE // If we fer a reply to OPTIONS and CSEQ is not 1, it must have been a keepalive command if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (message.CSeq == 1) { // Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds keepalive_timer = new System.Timers.Timer(); keepalive_timer.Elapsed += Timer_Elapsed; keepalive_timer.Interval = 20 * 1000; keepalive_timer.Enabled = true; // send the DESCRIBE. First time around we have no WWW-Authorise Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } else { // do nothing } } // If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // First time we send DESCRIBE we do not add any authorization (and we could not add it even if we wanted to // as we will not have the authorization Nonce value required for Digest mode // So we have to handle the Unauthorized 401 error here and send a new DESCRIBE message if (message.IsOk == false) { Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == false)) { // Error 401 - Unauthorized, but the original request did not use Authorization so try again with Authorization added if (username == null || password == null) { // we do nothave a username or password. Abort return; } // Send a new DESCRIBE with authorization Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (authentication != AUTHENTICATION.NONE) { String authorization_string = GenerateAuthorization(username, password, authentication, realm, nonce, url, "DESCRIBE"); if (authorization_string != null) { describe_message.Headers.Add("Authorization", authorization_string); } } rtsp_client.SendMessage(describe_message); return; } else if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // Authorization failed return; } else { // some other error return; } } // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP (each sub-stream) // If the attribute is for Video, then carry out a SETUP and a PLAY // Only do this for the first Video attribute in case there is more than one in the SDP for (int x = 0; x < sdp_data.Medias.Count; x++) { if (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video) { // We only want the first video sub-stream if (video_payload == -1) { video_payload = sdp_data.Medias[x].PayloadType; // search the attributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264) Rtsp.Sdp.AttributRtpMap rtpmap = null; // custom payload (>=96) details foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { String sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://")) { control = sdp_control; //absolute path } else { control = url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Rtsp.Sdp.AttributFmtp; } if (attrib.Key.Equals("rtpmap")) { rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (rtpmap != null && rtpmap.Value.Contains("H264") && fmtp != null) { video_codec = "H264"; var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count() >= 2) { byte[] sps = sps_pps[0]; byte[] pps = sps_pps[1]; Output_SPS_PPS(sps, pps); // output SPS and PPS } } RtspTransport transport = null; if (rtp_transport == RTP_TRANSPORT.TCP) { // Server interleaves the RTP packets over the RTSP connection // Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 video_data_channel = 0; // Used in DataReceived event handler video_rtcp_channel = 1; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Channel 0 for video. Channel 1 for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.UDP) { // Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages) // Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001 video_data_channel = udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = udp_pair.control_port; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(video_data_channel, video_rtcp_channel), // a Channel for video. a Channel for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.MULTICAST) { // Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages) // using Multicast Address and Ports that are in the reply to the SETUP message // Example for MULTICAST mode Transport: RTP/AVP;multicast video_data_channel = 0; // we get this information in the SETUP message reply video_rtcp_channel = 0; // we get this information in the SETUP message reply transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Send SETUP Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (authentication != AUTHENTICATION.NONE) { String authorization_string = GenerateAuthorization(username, password, authentication, realm, nonce, url, "SETUP"); if (authorization_string != null) { setup_message.Headers.Add("Authorization", authorization_string); } } rtsp_client.SendMessage(setup_message); } } } } // If we get a reply to SETUP (which was our third command), then process and then send PLAY if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP if (message.IsOk == false) { Console.WriteLine("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Setup. Session is " + message.Session); session = message.Session; // Session value used with Play, Pause, Teardown // Check the Transport header if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); // Check if Transport header includes Multicast if (transport.IsMulticast) { String multicast_address = transport.Destination; video_data_channel = transport.Port.First; video_rtcp_channel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode udp_pair = new UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); udp_pair.DataReceived += Rtp_DataReceived; udp_pair.Start(); } } // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; if (authentication != AUTHENTICATION.NONE) { String authorization_string = GenerateAuthorization(username, password, authentication, realm, nonce, url, "PLAY"); if (authorization_string != null) { play_message.Headers.Add("Authorization", authorization_string); } } rtsp_client.SendMessage(play_message); } // If we get a reply to PLAY (which was our fourth command), then we should have video being received if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY if (message.IsOk == false) { Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Play " + message.Command); } }
/// <summary> /// REST operation on a specific Uri and decodes the return string into a T Object. /// </summary> /// <typeparam name="T">Type of object to decode.</typeparam> /// <param name="uri">Uri path.</param> /// <param name="authentication">AUTHENTICATION mode.</param> /// <param name="action">REST_ACTION operation to perform.</param> /// <param name="data">Object to past to the REST operation.</param> /// <returns>T Object from the decoded return string.</returns> private async Task <T> HttpAction <T>(Uri uri, AUTHENTICATION authentication = AUTHENTICATION.PUBLIC, REST_ACTION action = REST_ACTION.GET, object data = null) { T ret = default(T); using (HttpClient hc = new HttpClient()) { switch (authentication) { // add in OAUTH token case AUTHENTICATION.AUTHENTICATED: // null check if (this.OAuthToken == null) { return(ret); } hc.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0}", this.OAuthToken)); break; } HttpResponseMessage m = null; switch (action) { case REST_ACTION.GET: m = await hc.GetAsync(uri); switch (m.StatusCode) { case HttpStatusCode.OK: // read content string content = await m.Content.ReadAsStringAsync(); ret = GetDataFromContent <T>(ref content); break; case (HttpStatusCode)422: // 422 Unprocessable Entity // TODO(duan): log this break; default: break; } break; case REST_ACTION.PUT: StringContent body = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(data)); body.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); m = await hc.PutAsync(uri, body); switch (m.StatusCode) { case HttpStatusCode.OK: string content = await m.Content.ReadAsStringAsync(); ret = GetDataFromContent <T>(ref content); break; case (HttpStatusCode)422: // 422 Unprocessable Entity // TODO(duan): log this break; } break; case REST_ACTION.POST: m = await hc.PostAsync(uri, (FormUrlEncodedContent)data); switch (m.StatusCode) { case HttpStatusCode.OK: case HttpStatusCode.NoContent: // read content string content = await m.Content.ReadAsStringAsync(); ret = GetDataFromContent <T>(ref content); break; case (HttpStatusCode)422: // 422 Unprocessable Entity // TODO(duan): log this // NOTE(duan): should pass through to the default case default: if (ret is bool) { ret = (T)Convert.ChangeType(false, typeof(bool)); } else { } break; } break; case REST_ACTION.DELETE: m = await hc.DeleteAsync(uri); switch (m.StatusCode) { case HttpStatusCode.NoContent: case HttpStatusCode.OK: // read content string content = await m.Content.ReadAsStringAsync(); ret = GetDataFromContent <T>(ref content); break; case (HttpStatusCode)422: // 422 Unprocessable Entity // TODO(duan): log this // NOTE(duan): should pass through to the default case default: if (ret is bool) { ret = (T)Convert.ChangeType(false, typeof(bool)); } break; } break; } } return(ret); }