hawkeye camera support(not tested)

This commit is contained in:
meelstorm
2025-08-19 12:45:50 +02:00
parent f80b275ad8
commit 6ef6aced8d
170 changed files with 20190 additions and 72 deletions

View File

@@ -0,0 +1,103 @@
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Describe a couple of port used to transfer video and command.
/// </summary>
public class PortCouple
{
/// <summary>
/// Gets or sets the first port number.
/// </summary>
/// <value>The first port.</value>
public int First { get; set; }
/// <summary>
/// Gets or sets the second port number.
/// </summary>
/// <remarks>If not present the value is 0</remarks>
/// <value>The second port.</value>
public int Second { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
public PortCouple()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
public PortCouple(int first)
{
First = first;
Second = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
/// <param name="second">The second port.</param>
public PortCouple(int first, int second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets a value indicating whether this instance has second port.
/// </summary>
/// <value>
/// <c>true</c> if this instance has second port; otherwise, <c>false</c>.
/// </value>
public bool IsSecondPortPresent
{
get { return Second != 0; }
}
/// <summary>
/// Parses the int values of port.
/// </summary>
/// <param name="stringValue">A string value.</param>
/// <returns>The port couple</returns>
public static PortCouple Parse(string stringValue)
{
if (stringValue == null)
throw new ArgumentNullException("stringValue");
Contract.Requires(!string.IsNullOrEmpty(stringValue));
string[] values = stringValue.Split('-');
int tempValue;
int.TryParse(values[0], out tempValue);
PortCouple result = new PortCouple(tempValue);
tempValue = 0;
if (values.Length > 1)
int.TryParse(values[1], out tempValue);
result.Second = tempValue;
return result;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
if (IsSecondPortPresent)
return First.ToString(CultureInfo.InvariantCulture) + "-" + Second.ToString(CultureInfo.InvariantCulture);
else
return First.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class wich represent each message echanged on Rtsp socket.
/// </summary>
public abstract class RtspChunk : ICloneable
{
/// <summary>
/// Logs the message to debug.
/// </summary>
public void LogMessage()
{
LogMessage(NLog.LogLevel.Debug);
}
/// <summary>
/// Logs the message.
/// </summary>
/// <param name="alevel">The log level.</param>
public abstract void LogMessage(NLog.LogLevel aLevel);
/// <summary>
/// Gets or sets the data associate with the message.
/// </summary>
/// <value>Array of byte transmit with the message.</value>
public byte[] Data
{ get; set; }
/// <summary>
/// Gets or sets the source port wich receive the message.
/// </summary>
/// <value>The source port.</value>
public RtspListener SourcePort { get; set; }
#region ICloneable Membres
/// <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 abstract object Clone();
#endregion
}
}

View File

@@ -0,0 +1,45 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Message wich represent data. ($ limited message)
/// </summary>
public class RtspData : RtspChunk
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// Logs the message to debug.
/// </summary>
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, "Data message");
if (Data == null)
_logger.Log(aLevel, "Data : null");
else
_logger.Log(aLevel, "Data length :-{0}-", Data.Length);
}
public int Channel { get; set; }
/// <summary>
/// Clones this instance.
/// <remarks>Listner is not cloned</remarks>
/// </summary>
/// <returns>a clone of this instance</returns>
public override object Clone()
{
RtspData result = new RtspData();
result.Channel = this.Channel;
if (this.Data != null)
result.Data = this.Data.Clone() as byte[];
result.SourcePort = this.SourcePort;
return result;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class containing helper constant for general use headers.
/// </summary>
public static class RtspHeaderNames
{
public const string ContentBase = "Content-Base";
public const string ContentEncoding = "Content-Encoding";
public const string ContentType = "Content-Type";
public const string Public = "Public";
public const string Session = "Session";
public const string Transport = "Transport";
public const string WWWAuthenticate = "WWW-Authenticate";
public const string Authorization = "Authorization";
}
}

View File

@@ -0,0 +1,309 @@
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;
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
using System.Diagnostics;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// An Rtsp Request
/// </summary>
public class RtspRequest : RtspMessage
{
/// <summary>
/// Request type.
/// </summary>
public enum RequestType
{
UNKNOWN,
DESCRIBE,
ANNOUNCE,
GET_PARAMETER,
OPTIONS,
PAUSE,
PLAY,
RECORD,
REDIRECT,
SETUP,
SET_PARAMETER,
TEARDOWN,
}
/// <summary>
/// Parses the request command.
/// </summary>
/// <param name="aStringRequest">A string request command.</param>
/// <returns>The typed request.</returns>
internal static RequestType ParseRequest(string aStringRequest)
{
RequestType returnValue;
if (!Enum.TryParse<RequestType>(aStringRequest, true, out returnValue))
returnValue = RequestType.UNKNOWN;
return returnValue;
}
/// <summary>
/// Gets the Rtsp request.
/// </summary>
/// <param name="aRequestParts">A request parts.</param>
/// <returns>the parsed request</returns>
internal static RtspMessage GetRtspRequest(string[] aRequestParts)
{
// <pex>
Debug.Assert(aRequestParts != (string[])null, "aRequestParts");
Debug.Assert(aRequestParts.Length != 0, "aRequestParts.Length == 0");
// </pex>
// we already know this is a Request
RtspRequest returnValue;
switch (ParseRequest(aRequestParts[0]))
{
case RequestType.OPTIONS:
returnValue = new RtspRequestOptions();
break;
case RequestType.DESCRIBE:
returnValue = new RtspRequestDescribe();
break;
case RequestType.SETUP:
returnValue = new RtspRequestSetup();
break;
case RequestType.PLAY:
returnValue = new RtspRequestPlay();
break;
case RequestType.PAUSE:
returnValue = new RtspRequestPause();
break;
case RequestType.TEARDOWN:
returnValue = new RtspRequestTeardown();
break;
case RequestType.GET_PARAMETER:
returnValue = new RtspRequestGetParameter();
break;
case RequestType.ANNOUNCE:
returnValue = new RtspRequestAnnounce();
break;
case RequestType.RECORD:
returnValue = new RtspRequestRecord();
break;
/*
case RequestType.REDIRECT:
break;
case RequestType.SET_PARAMETER:
break;
*/
case RequestType.UNKNOWN:
default:
returnValue = new RtspRequest();
break;
}
return returnValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspRequest"/> class.
/// </summary>
public RtspRequest()
{
Command = "OPTIONS * RTSP/1.0";
}
/// <summary>
/// Gets the request.
/// </summary>
/// <value>The request in string format.</value>
public string Request
{
get
{
return commandArray[0];
}
}
/// <summary>
/// Gets the request.
/// <remarks>The return value is typed with <see cref="Rtsp.RequestType"/> if the value is not
/// reconise the value is sent. The string value can be get by <see cref="Request"/></remarks>
/// </summary>
/// <value>The request.</value>
public RequestType RequestTyped
{
get
{
return ParseRequest(commandArray[0]);
}
set
{
if (Enum.IsDefined(typeof(RequestType), value))
commandArray[0] = value.ToString();
else
commandArray[0] = RequestType.UNKNOWN.ToString();
}
}
private Uri _RtspUri;
/// <summary>
/// Gets or sets the Rtsp asked URI.
/// </summary>
/// <value>The Rtsp asked URI.</value>
/// <remarks>The request with uri * is return with null URI</remarks>
public Uri RtspUri
{
get
{
if (commandArray.Length < 2 || commandArray[1]=="*")
return null;
if (_RtspUri == null)
Uri.TryCreate(commandArray[1], UriKind.Absolute, out _RtspUri);
return _RtspUri;
}
set
{
_RtspUri = value;
if (commandArray.Length < 2)
{
Array.Resize(ref commandArray, 3);
}
commandArray[1] = (value != null ? value.ToString().TrimEnd('/') : "*");
}
}
/// <summary>
/// Gets the assiociate OK response with the request.
/// </summary>
/// <returns>an Rtsp response correcponding to request.</returns>
public virtual RtspResponse CreateResponse()
{
RtspResponse returnValue = new RtspResponse();
returnValue.ReturnCode = 200;
returnValue.CSeq = this.CSeq;
if (this.Headers.ContainsKey(RtspHeaderNames.Session))
{
returnValue.Headers[RtspHeaderNames.Session] = this.Headers[RtspHeaderNames.Session];
}
return returnValue;
}
public Object ContextData { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestAnnounce : RtspRequest
{
// constructor
public RtspRequestAnnounce()
{
Command = "ANNOUNCE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestDescribe : RtspRequest
{
// constructor
public RtspRequestDescribe()
{
Command = "DESCRIBE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestGetParameter : RtspRequest
{
// Constructor
public RtspRequestGetParameter()
{
Command = "GET_PARAMETER * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestOptions : RtspRequest
{
// Constructor
public RtspRequestOptions()
{
Command = "OPTIONS * RTSP/1.0";
}
/// <summary>
/// Gets the assiociate OK response with the request.
/// </summary>
/// <returns>
/// an Rtsp response corresponding to request.
/// </returns>
public override RtspResponse CreateResponse()
{
RtspResponse response = base.CreateResponse();
// Add genric suported operations.
response.Headers.Add(RtspHeaderNames.Public, "OPTIONS,DESCRIBE,ANNOUNCE,SETUP,PLAY,PAUSE,TEARDOWN,GET_PARAMETER,SET_PARAMETER,REDIRECT");
return response;
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestPause : RtspRequest
{
// Constructor
public RtspRequestPause()
{
Command = "PAUSE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestPlay : RtspRequest
{
// Constructor
public RtspRequestPlay()
{
Command = "PLAY * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestRecord : RtspRequest
{
public RtspRequestRecord()
{
Command = "RECORD * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestSetup : RtspRequest
{
// Constructor
public RtspRequestSetup()
{
Command = "SETUP * RTSP/1.0";
}
/// <summary>
/// Gets the transports associate with the request.
/// </summary>
/// <value>The transport.</value>
public RtspTransport[] GetTransports()
{
if (!Headers.ContainsKey(RtspHeaderNames.Transport))
return new RtspTransport[] { new RtspTransport() };
string[] items = Headers[RtspHeaderNames.Transport].Split(',');
return Array.ConvertAll<string, RtspTransport>(items,
new Converter<string, RtspTransport>(RtspTransport.Parse));
}
public void AddTransport(RtspTransport newTransport)
{
string actualTransport = string.Empty;
if(Headers.ContainsKey(RtspHeaderNames.Transport))
actualTransport = Headers[RtspHeaderNames.Transport] + ",";
Headers[RtspHeaderNames.Transport] = actualTransport + newTransport.ToString();
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestTeardown : RtspRequest
{
// Constructor
public RtspRequestTeardown()
{
Command = "TEARDOWN * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Globalization;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspResponse : RtspMessage
{
public const int DEFAULT_TIMEOUT = 60;
/// <summary>
/// Gets the default error message for an error code.
/// </summary>
/// <param name="aErrorCode">An error code.</param>
/// <returns>The default error message associate</returns>
private static string GetDefaultError(int aErrorCode)
{
switch (aErrorCode)
{
case 100: return "Continue";
case 200: return "OK";
case 201: return "Created";
case 250: return "Low on Storage Space";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Moved Temporarily";
case 303: return "See Other";
case 305: return "Use Proxy";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 451: return "Invalid parameter";
case 452: return "Illegal Conference Identifier";
case 453: return "Not Enough Bandwidth";
case 454: return "Session Not Found";
case 455: return "Method Not Valid In This State";
case 456: return "Header Field Not Valid";
case 457: return "Invalid Range";
case 458: return "Parameter Is Read-Only";
case 459: return "Aggregate Operation Not Allowed";
case 460: return "Only Aggregate Operation Allowed";
case 461: return "Unsupported Transport";
case 462: return "Destination Unreachable";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "RTSP Version Not Supported";
case 551: return "Option not support";
default:
return "Return: " + aErrorCode.ToString(CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspResponse"/> class.
/// </summary>
public RtspResponse()
: base()
{
// Initialise with a default result code.
Command = "RTSP/1.0 200 OK";
}
private int _returnCode;
/// <summary>
/// Gets or sets the return code of the response.
/// </summary>
/// <value>The return code.</value>
/// <remarks>On change the error message is set to the default one associate with the code</remarks>
public int ReturnCode
{
get
{
if (_returnCode == 0 && commandArray.Length >= 2)
{
int.TryParse(commandArray[1], out _returnCode);
}
return _returnCode;
}
set
{
if (ReturnCode != value)
{
_returnCode = value;
// make sure we have the room
if (commandArray.Length < 3)
{
Array.Resize(ref commandArray, 3);
}
commandArray[1] = value.ToString(CultureInfo.InvariantCulture);
commandArray[2] = GetDefaultError(value);
}
}
}
/// <summary>
/// Gets or sets the error/return message.
/// </summary>
/// <value>The return message.</value>
public string ReturnMessage
{
get
{
if (commandArray.Length < 3)
return String.Empty;
return commandArray[2];
}
set
{
// Make sure we have the room
if (commandArray.Length < 3)
{
Array.Resize(ref commandArray, 3);
}
commandArray[2] = value;
}
}
/// <summary>
/// Gets a value indicating whether this instance correspond to an OK response.
/// </summary>
/// <value><c>true</c> if this instance is OK; otherwise, <c>false</c>.</value>
public bool IsOk
{
get
{
if (ReturnCode > 0 && ReturnCode < 400)
return true;
return false;
}
}
/// <summary>
/// Gets the timeout in second.
/// <remarks>The default timeout is 60.</remarks>
/// </summary>
/// <value>The timeout.</value>
public int Timeout
{
get
{
int returnValue = DEFAULT_TIMEOUT;
if (Headers.ContainsKey(RtspHeaderNames.Session))
{
string[] parts = Headers[RtspHeaderNames.Session].Split(';');
if (parts.Length > 1)
{
string[] subParts = parts[1].Split('=');
if (subParts.Length > 1 &&
subParts[0].ToUpperInvariant() == "TIMEOUT")
if (!int.TryParse(subParts[1], out returnValue))
returnValue = DEFAULT_TIMEOUT;
}
}
return returnValue;
}
set
{
if(Headers.ContainsKey(RtspHeaderNames.Session))
if (value != DEFAULT_TIMEOUT)
{
Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First()
+ ";timeout=" + value.ToString(CultureInfo.InvariantCulture);
}
else
{
//remove timeout part
Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First();
}
}
}
/// <summary>
/// Gets the session ID.
/// </summary>
/// <value>The session ID.</value>
public override string Session
{
get
{
if (!Headers.ContainsKey(RtspHeaderNames.Session))
return null;
return Headers[RtspHeaderNames.Session].Split(';')[0];
}
set
{
if(Timeout != DEFAULT_TIMEOUT)
{
Headers[RtspHeaderNames.Session] = value + ";timeout=" + Timeout.ToString(CultureInfo.InvariantCulture);
}
else
{
Headers[RtspHeaderNames.Session] = value;
}
}
}
/// <summary>
/// Gets or sets the original request associate with the response.
/// </summary>
/// <value>The original request.</value>
public RtspRequest OriginalRequest
{ get; set; }
}
}

View File

@@ -0,0 +1,367 @@
using System;
using System.Diagnostics.Contracts;
using System.Text;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspTransport
{
public RtspTransport()
{
// Default value is true in RFC
IsMulticast = true;
LowerTransport = LowerTransportType.UDP;
Mode = "PLAY";
}
/*
RFC
Transport = "Transport" ":"
1\#transport-spec
transport-spec = transport-protocol/profile[/lower-transport]
*parameter
transport-protocol = "RTP"
profile = "AVP"
lower-transport = "TCP" | "UDP"
parameter = ( "unicast" | "multicast" )
| ";" "destination" [ "=" address ]
| ";" "interleaved" "=" channel [ "-" channel ]
| ";" "append"
| ";" "ttl" "=" ttl
| ";" "layers" "=" 1*DIGIT
| ";" "port" "=" port [ "-" port ]
| ";" "client_port" "=" port [ "-" port ]
| ";" "server_port" "=" port [ "-" port ]
| ";" "ssrc" "=" ssrc
| ";" "mode" = <"> 1\#mode <">
ttl = 1*3(DIGIT)
port = 1*5(DIGIT)
ssrc = 8*8(HEX)
channel = 1*3(DIGIT)
address = host
mode = <"> *Method <"> | Method
*/
/// <summary>
/// List of transport
/// </summary>
[Serializable]
public enum TransportType
{
/// <summary>
/// RTP for now
/// </summary>
RTP,
}
/// <summary>
/// Profile type
/// </summary>
[Serializable]
public enum ProfileType
{
/// <summary>
/// RTP/AVP of now
/// </summary>
AVP,
}
/// <summary>
/// Transport type.
/// </summary>
[Serializable]
public enum LowerTransportType
{
/// <summary>
/// UDP transport.
/// </summary>
UDP,
/// <summary>
/// TCP transport.
/// </summary>
TCP,
}
/// <summary>
/// Gets or sets the transport.
/// </summary>
/// <value>The transport.</value>
public TransportType Transport { get; set; }
/// <summary>
/// Gets or sets the profile.
/// </summary>
/// <value>The profile.</value>
public ProfileType Profile { get; set; }
/// <summary>
/// Gets or sets the lower transport.
/// </summary>
/// <value>The lower transport.</value>
public LowerTransportType LowerTransport { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is multicast.
/// </summary>
/// <value>
/// <c>true</c> if this instance is multicast; otherwise, <c>false</c>.
/// </value>
public bool IsMulticast { get; set; }
/// <summary>
/// Gets or sets the destination.
/// </summary>
/// <value>The destination.</value>
public string Destination { get; set; }
/// <summary>
/// Gets or sets the source.
/// </summary>
/// <value>The source.</value>
public string Source { get; set; }
/// <summary>
/// Gets or sets the interleaved.
/// </summary>
/// <value>The interleaved.</value>
public PortCouple Interleaved { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is append.
/// </summary>
/// <value><c>true</c> if this instance is append; otherwise, <c>false</c>.</value>
public bool IsAppend { get; set; }
/// <summary>
/// Gets or sets the TTL.
/// </summary>
/// <value>The TTL.</value>
public int TTL { get; set; }
/// <summary>
/// Gets or sets the layers.
/// </summary>
/// <value>The layers.</value>
public int Layers { get; set; }
/// <summary>
/// Gets or sets the port.
/// </summary>
/// <value>The port.</value>
public PortCouple Port { get; set; }
/// <summary>
/// Gets or sets the client port.
/// </summary>
/// <value>The client port.</value>
public PortCouple ClientPort { get; set; }
/// <summary>
/// Gets or sets the server port.
/// </summary>
/// <value>The server port.</value>
public PortCouple ServerPort { get; set; }
/// <summary>
/// Gets or sets the S SRC.
/// </summary>
/// <value>The S SRC.</value>
public string SSrc { get; set; }
/// <summary>
/// Gets or sets the mode.
/// </summary>
/// <value>The mode.</value>
public string Mode { get; set; }
/// <summary>
/// Parses the specified transport string.
/// </summary>
/// <param name="aTransportString">A transport string.</param>
/// <returns>The transport class.</returns>
/// <exception cref="ArgumentNullException"><paramref name="aTransportString"/> is null.</exception>
public static RtspTransport Parse(string aTransportString)
{
if (aTransportString == null)
throw new ArgumentNullException("aTransportString");
Contract.EndContractBlock();
RtspTransport returnValue = new RtspTransport();
string[] transportPart = aTransportString.Split(';');
string[] transportProtocolPart = transportPart[0].Split('/');
ReadTransport(returnValue, transportProtocolPart);
ReadProfile(returnValue, transportProtocolPart);
ReadLowerTransport(returnValue, transportProtocolPart);
foreach (string part in transportPart)
{
string[] subPart = part.Split('=');
switch (subPart[0].ToUpperInvariant())
{
case "UNICAST":
returnValue.IsMulticast = false;
break;
case "MULTICAST":
returnValue.IsMulticast = true;
break;
case "DESTINATION":
if (subPart.Length == 2)
returnValue.Destination = subPart[1];
break;
case "SOURCE":
if (subPart.Length == 2)
returnValue.Source = subPart[1];
break;
case "INTERLEAVED":
returnValue.IsMulticast = false;
if (subPart.Length < 2)
throw new ArgumentException("interleaved value invalid", "aTransportString");
returnValue.Interleaved = PortCouple.Parse(subPart[1]);
break;
case "APPEND":
returnValue.IsAppend = true;
break;
case "TTL":
int ttl = 0;
if (subPart.Length < 2 || !int.TryParse(subPart[1], out ttl))
throw new ArgumentException("TTL value invalid", "aTransportString");
returnValue.TTL = ttl;
break;
case "LAYERS":
int layers = 0;
if (subPart.Length < 2 || !int.TryParse(subPart[1], out layers))
throw new ArgumentException("Layers value invalid", "aTransportString");
returnValue.TTL = layers;
break;
case "PORT":
if (subPart.Length < 2)
throw new ArgumentException("Port value invalid", "aTransportString");
returnValue.Port = PortCouple.Parse(subPart[1]);
break;
case "CLIENT_PORT":
if (subPart.Length < 2)
throw new ArgumentException("client_port value invalid", "aTransportString");
returnValue.ClientPort = PortCouple.Parse(subPart[1]);
break;
case "SERVER_PORT":
if (subPart.Length < 2)
throw new ArgumentException("server_port value invalid", "aTransportString");
returnValue.ServerPort = PortCouple.Parse(subPart[1]);
break;
case "SSRC":
if (subPart.Length < 2)
throw new ArgumentException("ssrc value invalid", "aTransportString");
returnValue.SSrc = subPart[1];
break;
case "MODE":
if (subPart.Length < 2)
throw new ArgumentException("mode value invalid", "aTransportString");
returnValue.Mode = subPart[1];
break;
default:
// TODO log invalid part
break;
}
}
return returnValue;
}
private static void ReadLowerTransport(RtspTransport returnValue, string[] transportProtocolPart)
{
if (transportProtocolPart.Length == 3)
{
LowerTransportType lowerTransport;
if (!Enum.TryParse<LowerTransportType>(transportProtocolPart[2], out lowerTransport))
throw new ArgumentException("Lower transport type invalid", "aTransportString");
returnValue.LowerTransport = lowerTransport;
}
}
private static void ReadProfile(RtspTransport returnValue, string[] transportProtocolPart)
{
ProfileType profile;
if (transportProtocolPart.Length < 2 || !Enum.TryParse<ProfileType>(transportProtocolPart[1], out profile))
throw new ArgumentException("Transport profile type invalid", "aTransportString");
returnValue.Profile = profile;
}
private static void ReadTransport(RtspTransport returnValue, string[] transportProtocolPart)
{
TransportType transport;
if (!Enum.TryParse<TransportType>(transportProtocolPart[0], out transport))
throw new ArgumentException("Transport type invalid", "aTransportString");
returnValue.Transport = transport;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
StringBuilder transportString = new StringBuilder();
transportString.Append(Transport.ToString());
transportString.Append('/');
transportString.Append(Profile.ToString());
transportString.Append('/');
transportString.Append(LowerTransport.ToString());
if (LowerTransport == LowerTransportType.TCP)
{
transportString.Append(";unicast");
}
if (LowerTransport == LowerTransportType.UDP)
{
transportString.Append(';');
transportString.Append(IsMulticast ? "multicast" : "unicast");
}
if (Destination != null)
{
transportString.Append(";destination=");
transportString.Append(Destination);
}
if (Source != null)
{
transportString.Append(";source=");
transportString.Append(Source);
}
if (Interleaved != null)
{
transportString.Append(";interleaved=");
transportString.Append(Interleaved.ToString());
}
if (IsAppend)
{
transportString.Append(";append");
}
if (TTL > 0)
{
transportString.Append(";ttl=");
transportString.Append(TTL);
}
if (Layers > 0)
{
transportString.Append(";layers=");
transportString.Append(Layers);
}
if (Port != null)
{
transportString.Append(";port=");
transportString.Append(Port.ToString());
}
if (ClientPort != null)
{
transportString.Append(";client_port=");
transportString.Append(ClientPort.ToString());
}
if (ServerPort != null)
{
transportString.Append(";server_port=");
transportString.Append(ServerPort.ToString());
}
if (SSrc != null)
{
transportString.Append(";ssrc=");
transportString.Append(SSrc);
}
if (Mode != null && Mode != "PLAY")
{
transportString.Append(";mode=");
transportString.Append(Mode);
}
return transportString.ToString();
}
}
}