/SpitfireUtils/IceParser.cs

https://github.com/RainwayApp/spitfire · C# · 204 lines · 150 code · 23 blank · 31 comment · 6 complexity · e1f63714366ae1b23703bc3b27e59816 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Text.RegularExpressions;
  5. namespace SpitfireUtils
  6. {
  7. public enum IceType
  8. {
  9. /// <summary>
  10. /// The candidate is a host candidate, whose IP address as specified in the RTCIceCandidate.ip property is in fact the true address of the remote peer.
  11. /// </summary>
  12. Host,
  13. /// <summary>
  14. /// The candidate is a server reflexive candidate; the ip indicates an intermediary address assigned by the STUN server to represent the candidate's peer anonymously.
  15. /// </summary>
  16. Srflx,
  17. /// <summary>
  18. /// The candidate is a peer reflexive candidate; the ip is an intermediary address assigned by the STUN server to represent the candidate's peer anonymously.
  19. /// </summary>
  20. Prflx,
  21. /// <summary>
  22. /// The candidate is a relay candidate, obtained from a TURN server. The relay candidate's IP address is an address the TURN server uses to forward the media between the two peers.
  23. /// </summary>
  24. Relay
  25. }
  26. public enum IceTransport
  27. {
  28. Tcp,
  29. Udp
  30. }
  31. /// <summary>
  32. /// Contains information on a parse ICE candidate.
  33. /// </summary>
  34. public class IceCandidate
  35. {
  36. public string Raw { get; set; }
  37. public ulong Foundation { get; set; }
  38. public uint ComponentId { get; set; }
  39. public IceTransport Transport { get; set; }
  40. public ulong Priority { get; set; }
  41. public string LocalIp { get; set; }
  42. public ushort LocalPort { get; set; }
  43. public IceType Type { get; set; }
  44. public string RemoteIp { get; set; }
  45. public ushort RemotePort { get; set; }
  46. public uint Generation { get; set; }
  47. public string UFrag { get; set; }
  48. public int NetworkCost { get; set; }
  49. public int NetworkId { get; set; }
  50. }
  51. public static class IceParser
  52. {
  53. #region static members
  54. private static readonly Dictionary<string, IceType> IceTypeMap = new Dictionary<string, IceType>
  55. {
  56. {"host", IceType.Host},
  57. {"srflx", IceType.Srflx},
  58. {"prflx", IceType.Prflx},
  59. {"relay", IceType.Relay}
  60. };
  61. private static readonly Dictionary<string, IceTransport> IceTransportMap =
  62. new Dictionary<string, IceTransport>
  63. {
  64. {"udp", IceTransport.Udp},
  65. {"tcp", IceTransport.Tcp}
  66. };
  67. private static readonly Regex TokenRegex = new Regex("[0-9a-zA-Z\\-\\.!\\%\\*_\\+\\`\\\'\\~]+");
  68. private static readonly Regex IceRegex = new Regex("[a-zA-Z0-9\\+\\/]+");
  69. private static readonly Regex ComponentIdRegex = new Regex("[0-9]{1,5}");
  70. private static readonly Regex PriorityRegex = new Regex("[0-9]{1,10}");
  71. private static readonly Regex Ipv4AddressRegex = new Regex("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}");
  72. private static readonly Regex Ipv6AdresssRegex = new Regex(":?(?:[0-9a-fA-F]{0,4}:?)+");
  73. private static readonly Regex DomainRegex = new Regex(@"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]");
  74. private static readonly Regex ConnectAddressRegex =
  75. new Regex($"(?:{Ipv4AddressRegex})|(?:{Ipv6AdresssRegex})|(?:{DomainRegex})");
  76. private static readonly Regex PortRegex = new Regex("[0-9]{1,5}");
  77. private static readonly Regex MasterRegex =
  78. new Regex(
  79. $"(?:a=)?candidate:({IceRegex})\\s({ComponentIdRegex})\\s({TokenRegex})\\s" +
  80. $"({PriorityRegex})\\s({ConnectAddressRegex})\\s({PortRegex})" +
  81. $"\\styp\\s({TokenRegex})(?:\\sraddr\\s({ConnectAddressRegex})\\srport\\s({PortRegex}))?(?:\\sgeneration\\s(\\d+))?(?:\\sufrag\\s({IceRegex}))?(?:\\snetwork-id\\s({PriorityRegex}))?(?:\\snetwork-cost\\s({PriorityRegex}))?")
  82. ;
  83. #endregion
  84. /// <summary>
  85. /// Checks the transport type Dictionary for the connection type.
  86. /// </summary>
  87. /// <param name="transport"></param>
  88. /// <returns>The connection type</returns>
  89. private static IceTransport GetTransportType(string transport)
  90. {
  91. transport = transport.Trim().ToLower();
  92. return IceTransportMap.ContainsKey(transport) ? IceTransportMap[transport] : throw new IndexOutOfRangeException($"Unaware of transport type {transport}");
  93. }
  94. /// <summary>
  95. /// Parse an ice candidate string and extract information on the connection
  96. /// </summary>
  97. /// <param name="candidate"></param>
  98. /// <returns>A parse ice candidate</returns>
  99. public static IceCandidate Parse(string candidate)
  100. {
  101. var iceCandidate = new IceCandidate();
  102. var parsed = MasterRegex.Match(candidate);
  103. //we should only ever have 11 results, even if generation is missing.
  104. if (!parsed.Success || parsed.Groups.Count != 14)
  105. {
  106. throw new DataException($"Failed to parse ICE candidate {candidate}");
  107. }
  108. for (var i = 0; i < parsed.Groups.Count; i++)
  109. {
  110. try
  111. {
  112. var value = parsed.Groups[i].Value;
  113. if (string.IsNullOrWhiteSpace(value))
  114. {
  115. continue;
  116. }
  117. switch (i)
  118. {
  119. case 0:
  120. iceCandidate.Raw = value;
  121. break;
  122. case 1:
  123. iceCandidate.Foundation = ulong.Parse(value);
  124. break;
  125. case 2:
  126. iceCandidate.ComponentId = uint.Parse(value);
  127. break;
  128. case 3:
  129. iceCandidate.Transport = GetTransportType(value);
  130. break;
  131. case 4:
  132. iceCandidate.Priority = ulong.Parse(value);
  133. break;
  134. case 5:
  135. iceCandidate.LocalIp = value;
  136. break;
  137. case 6:
  138. iceCandidate.LocalPort = ushort.Parse(value);
  139. break;
  140. case 7:
  141. iceCandidate.Type = GetIceType(value);
  142. break;
  143. case 8:
  144. iceCandidate.RemoteIp = value;
  145. break;
  146. case 9:
  147. iceCandidate.RemotePort = ushort.Parse(value);
  148. break;
  149. case 10:
  150. iceCandidate.Generation = uint.Parse(value);
  151. break;
  152. case 11:
  153. iceCandidate.UFrag = value;
  154. break;
  155. case 12:
  156. iceCandidate.NetworkId = int.Parse(value);
  157. break;
  158. case 13:
  159. iceCandidate.NetworkCost = int.Parse(value);
  160. break;
  161. }
  162. }
  163. catch (System.Exception ex)
  164. {
  165. throw new DataException(ex.Message);
  166. }
  167. }
  168. return iceCandidate;
  169. }
  170. /// <summary>
  171. /// Checks the ice type against the type Dictionary.
  172. /// </summary>
  173. /// <param name="value"></param>
  174. /// <returns>The ice type</returns>
  175. private static IceType GetIceType(string value)
  176. {
  177. value = value.ToLower();
  178. return IceTypeMap.ContainsKey(value) ? IceTypeMap[value] : throw new IndexOutOfRangeException($"Unaware of candidate type {value}");
  179. }
  180. }
  181. }