310 lines
9.9 KiB
C#
310 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Inspectron.HawkEye.RTSP.Messages
|
|
{
|
|
public class RtspMessage : RtspChunk
|
|
{
|
|
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
|
|
|
|
/// <summary>
|
|
/// The regex to validate the Rtsp message.
|
|
/// </summary>
|
|
private static readonly Regex _rtspVersionTest = new Regex(@"^RTSP/\d\.\d", RegexOptions.Compiled);
|
|
/// <summary>
|
|
/// Create the good type of Rtsp Message from the header.
|
|
/// </summary>
|
|
/// <param name="aRequestLine">A request line.</param>
|
|
/// <returns>An Rtsp message</returns>
|
|
public static RtspMessage GetRtspMessage(string aRequestLine)
|
|
{
|
|
// We can't determine the message
|
|
if (string.IsNullOrEmpty(aRequestLine))
|
|
return new RtspMessage();
|
|
string[] requestParts = aRequestLine.Split(new char[] { ' ' }, 3);
|
|
RtspMessage returnValue;
|
|
if (requestParts.Length == 3)
|
|
{
|
|
// A request is : Method SP Request-URI SP RTSP-Version
|
|
// A response is : RTSP-Version SP Status-Code SP Reason-Phrase
|
|
// RTSP-Version = "RTSP" "/" 1*DIGIT "." 1*DIGIT
|
|
if (_rtspVersionTest.IsMatch(requestParts[2]))
|
|
returnValue = RtspRequest.GetRtspRequest(requestParts);
|
|
else if (_rtspVersionTest.IsMatch(requestParts[0]))
|
|
returnValue = new RtspResponse();
|
|
else
|
|
{
|
|
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
|
|
returnValue = new RtspMessage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
|
|
returnValue = new RtspMessage();
|
|
}
|
|
returnValue.Command = aRequestLine;
|
|
return returnValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="RtspMessage"/> class.
|
|
/// </summary>
|
|
public RtspMessage()
|
|
{
|
|
Data = new byte[0];
|
|
Creation = DateTime.Now;
|
|
}
|
|
|
|
private Dictionary<string, string> _headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
internal protected string[] commandArray;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the creation time.
|
|
/// </summary>
|
|
/// <value>The creation time.</value>
|
|
public DateTime Creation { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the command of the message (first line).
|
|
/// </summary>
|
|
/// <value>The command.</value>
|
|
public string Command
|
|
{
|
|
get
|
|
{
|
|
if (commandArray == null)
|
|
return string.Empty;
|
|
return string.Join(" ", commandArray);
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
commandArray = new string[] { String.Empty };
|
|
else
|
|
commandArray = value.Split(new char[] {' '}, 3);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the Method of the message (eg OPTIONS, DESCRIBE, SETUP, PLAY).
|
|
/// </summary>
|
|
/// <value>The Method</value>
|
|
public string Method
|
|
{
|
|
get
|
|
{
|
|
if (commandArray == null)
|
|
return string.Empty;
|
|
return commandArray[0];
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the headers of the message.
|
|
/// </summary>
|
|
/// <value>The headers.</value>
|
|
public Dictionary<string, string> Headers
|
|
{
|
|
get
|
|
{
|
|
return _headers;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds one header from a string.
|
|
/// </summary>
|
|
/// <param name="line">The string containing header of format Header: Value.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="line"/> is null</exception>
|
|
public void AddHeader(string line)
|
|
{
|
|
if (line == (string)null)
|
|
throw new ArgumentNullException("line");
|
|
|
|
//spliter
|
|
string[] elements = line.Split(new char[] { ':' }, 2);
|
|
if (elements.Length == 2)
|
|
{
|
|
_headers[elements[0].Trim()] = elements[1].TrimStart();
|
|
}
|
|
else
|
|
{
|
|
_logger.Warn(CultureInfo.InvariantCulture, "Invalid Header received : -{0}-", line);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Ccommande Seqquence number.
|
|
/// <remarks>If the header is not define or not a valid number it return 0</remarks>
|
|
/// </summary>
|
|
/// <value>The sequence number.</value>
|
|
public int CSeq
|
|
{
|
|
get
|
|
{
|
|
string returnStringValue;
|
|
int returnValue;
|
|
if (!(_headers.TryGetValue("CSeq", out returnStringValue) &&
|
|
int.TryParse(returnStringValue, out returnValue)))
|
|
returnValue = 0;
|
|
|
|
return returnValue;
|
|
}
|
|
set
|
|
{
|
|
_headers["CSeq"] = value.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the session ID.
|
|
/// </summary>
|
|
/// <value>The session ID.</value>
|
|
public virtual string Session
|
|
{
|
|
get
|
|
{
|
|
if (!_headers.ContainsKey("Session"))
|
|
return null;
|
|
|
|
return _headers["Session"];
|
|
}
|
|
set
|
|
{
|
|
_headers["Session"] = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialises the length of the data byte array from content lenth header.
|
|
/// </summary>
|
|
public void InitialiseDataFromContentLength()
|
|
{
|
|
int dataLength;
|
|
if (!(_headers.ContainsKey("Content-Length")
|
|
&& int.TryParse(_headers["Content-Length"], out dataLength)))
|
|
{
|
|
dataLength = 0;
|
|
}
|
|
this.Data = new byte[dataLength];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjusts the content length header.
|
|
/// </summary>
|
|
public void AdjustContentLength()
|
|
{
|
|
if (Data.Length > 0)
|
|
{
|
|
_headers["Content-Length"] = Data.Length.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
else
|
|
{
|
|
_headers.Remove("Content-Length");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends to the message to a stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is empty</exception>
|
|
/// <exception cref="ArgumentException"><paramref name="stream"/> can't be written.</exception>
|
|
public void SendTo(Stream stream)
|
|
{
|
|
// <pex>
|
|
if (stream == null)
|
|
throw new ArgumentNullException("stream");
|
|
if (!stream.CanWrite)
|
|
throw
|
|
new ArgumentException("Stream CanWrite == false, can't send message to it", "stream");
|
|
// </pex>
|
|
Contract.EndContractBlock();
|
|
|
|
Encoding encoder = ASCIIEncoding.UTF8;
|
|
StringBuilder outputString = new StringBuilder();
|
|
|
|
AdjustContentLength();
|
|
|
|
// output header
|
|
outputString.Append(Command);
|
|
outputString.Append("\r\n");
|
|
foreach (KeyValuePair<string, string> item in _headers)
|
|
{
|
|
outputString.AppendFormat("{0}: {1}\r\n", item.Key, item.Value);
|
|
}
|
|
outputString.Append("\r\n");
|
|
byte[] buffer = encoder.GetBytes(outputString.ToString());
|
|
lock(stream) {
|
|
stream.Write(buffer, 0, buffer.Length);
|
|
|
|
// Output data
|
|
if (Data.Length > 0)
|
|
stream.Write(Data, 0, Data.Length);
|
|
|
|
}
|
|
stream.Flush();
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Logs the message.
|
|
/// </summary>
|
|
/// <param name="aLevel">A log level.</param>
|
|
public override void LogMessage(NLog.LogLevel aLevel)
|
|
{
|
|
// Default value to debug
|
|
if (aLevel == null)
|
|
aLevel = NLog.LogLevel.Debug;
|
|
// if the level is not logged directly return
|
|
if (!_logger.IsEnabled(aLevel))
|
|
return;
|
|
|
|
_logger.Log(aLevel, "Commande : {0}", Command);
|
|
foreach (KeyValuePair<string, string> item in _headers)
|
|
{
|
|
_logger.Log(aLevel, "Header : {0}: {1}", item.Key, item.Value);
|
|
}
|
|
|
|
if (Data.Length > 0)
|
|
{
|
|
_logger.Log(aLevel, "Data :-{0}-", ASCIIEncoding.ASCII.GetString(Data));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Crée un nouvel objet qui est une copie de l'instance en cours.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Nouvel objet qui est une copie de cette instance.
|
|
/// </returns>
|
|
public override object Clone()
|
|
{
|
|
RtspMessage returnValue = GetRtspMessage(this.Command);
|
|
|
|
foreach (var item in this.Headers)
|
|
{
|
|
if (item.Value == null)
|
|
returnValue.Headers.Add(item.Key.Clone() as string, null);
|
|
else
|
|
returnValue.Headers.Add(item.Key.Clone() as string, item.Value.Clone() as string);
|
|
}
|
|
returnValue.Data = this.Data.Clone() as byte[];
|
|
returnValue.SourcePort = this.SourcePort;
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
}
|
|
}
|