소스 검색

refactor: Remake StunServer

Bruce Wayne 4 년 전
부모
커밋
49fdbd9c6d

+ 2 - 3
NatTypeTester.ViewModels/MainWindowViewModel.cs

@@ -2,7 +2,7 @@ using DynamicData;
 using DynamicData.Binding;
 using NatTypeTester.Models;
 using ReactiveUI;
-using STUN.Utils;
+using STUN;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -62,10 +62,9 @@ namespace NatTypeTester.ViewModels
 					return;
 				}
 
-				var stun = new StunServer();
 				foreach (var line in File.ReadLines(path))
 				{
-					if (!string.IsNullOrWhiteSpace(line) && stun.Parse(line))
+					if (!string.IsNullOrWhiteSpace(line) && StunServer.TryParse(line, out var stun))
 					{
 						List.Add(stun.ToString());
 					}

+ 3 - 6
NatTypeTester.ViewModels/RFC3489ViewModel.cs

@@ -1,11 +1,12 @@
 using Dns.Net.Abstractions;
 using JetBrains.Annotations;
+using Microsoft;
 using NatTypeTester.Models;
 using ReactiveUI;
+using STUN;
 using STUN.Client;
 using STUN.Proxy;
 using STUN.StunResult;
-using STUN.Utils;
 using System;
 using System.Net;
 using System.Reactive;
@@ -37,11 +38,7 @@ namespace NatTypeTester.ViewModels
 
 		private async Task TestClassicNatTypeImpl(CancellationToken token)
 		{
-			var server = new StunServer();
-			if (!server.Parse(Config.StunServer))
-			{
-				throw new Exception(@"Wrong STUN Server!");
-			}
+			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
 			using var proxy = ProxyFactory.CreateProxy(
 					Config.ProxyType,

+ 3 - 6
NatTypeTester.ViewModels/RFC5780ViewModel.cs

@@ -1,11 +1,12 @@
 using Dns.Net.Abstractions;
 using JetBrains.Annotations;
+using Microsoft;
 using NatTypeTester.Models;
 using ReactiveUI;
+using STUN;
 using STUN.Client;
 using STUN.Proxy;
 using STUN.StunResult;
-using STUN.Utils;
 using System;
 using System.Net;
 using System.Reactive;
@@ -37,11 +38,7 @@ namespace NatTypeTester.ViewModels
 
 		private async Task DiscoveryNatTypeImpl(CancellationToken token)
 		{
-			var server = new StunServer();
-			if (!server.Parse(Config.StunServer))
-			{
-				throw new Exception(@"Wrong STUN Server!");
-			}
+			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
 			using var proxy = ProxyFactory.CreateProxy(
 					Config.ProxyType,

+ 94 - 0
STUN/StunServer.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN
+{
+	public class StunServer
+	{
+		public string Hostname { get; }
+		public ushort Port { get; }
+
+		private const ushort DefaultPort = 3478;
+
+		public StunServer()
+		{
+			Hostname = @"stun.syncthing.net";
+			Port = DefaultPort;
+		}
+
+		private StunServer(string host, ushort port)
+		{
+			Hostname = host;
+			Port = port;
+		}
+
+		public static bool TryParse(string str, [NotNullWhen(true)] out StunServer? server)
+		{
+			server = null;
+			if (string.IsNullOrEmpty(str))
+			{
+				return false;
+			}
+
+			var hostLength = str.Length;
+			var pos = str.LastIndexOf(':');
+
+			if (pos > 0)
+			{
+				if (str[pos - 1] is ']')
+				{
+					hostLength = pos;
+				}
+				else if (str.AsSpan(0, pos).LastIndexOf(':') is -1)
+				{
+					hostLength = pos;
+				}
+			}
+
+			var host = str[..hostLength];
+			var type = Uri.CheckHostName(host);
+			switch (type)
+			{
+				case UriHostNameType.Dns:
+				case UriHostNameType.IPv4:
+				case UriHostNameType.IPv6:
+				{
+					break;
+				}
+				default:
+				{
+					return false;
+				}
+			}
+
+			if (hostLength == str.Length)
+			{
+				server = new StunServer(host, DefaultPort);
+				return true;
+			}
+
+			if (ushort.TryParse(str.AsSpan(hostLength + 1), out var port))
+			{
+				server = new StunServer(host, port);
+				return true;
+			}
+
+			return false;
+		}
+
+		public override string ToString()
+		{
+			if (Port is DefaultPort)
+			{
+				return Hostname;
+			}
+			if (IPAddress.TryParse(Hostname, out var ip) && ip.AddressFamily is AddressFamily.InterNetworkV6)
+			{
+				return $@"[{ip}]:{Port}";
+			}
+			return $@"{Hostname}:{Port}";
+		}
+	}
+}

+ 0 - 97
STUN/Utils/StunServer.cs

@@ -1,97 +0,0 @@
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-
-namespace STUN.Utils
-{
-	public class StunServer
-	{
-		public string Hostname { get; set; }
-		public ushort Port { get; set; }
-
-		public StunServer()
-		{
-			Hostname = @"stun.syncthing.net";
-			Port = 3478;
-		}
-
-		public bool Parse(string str)
-		{
-			var ipPort = str.Trim().Split(':', ':');
-			switch (ipPort.Length)
-			{
-				case 0:
-					return false;
-				case 1:
-				{
-					var host = ipPort[0].Trim();
-					if (Uri.CheckHostName(host) != UriHostNameType.Dns && !IPAddress.TryParse(host, out _))
-					{
-						return false;
-					}
-					Hostname = host;
-					Port = 3478;
-					return true;
-				}
-				case 2:
-				{
-					var host = ipPort[0].Trim();
-					if (Uri.CheckHostName(host) != UriHostNameType.Dns && !IPAddress.TryParse(host, out _))
-					{
-						return false;
-					}
-					if (ushort.TryParse(ipPort[1], out var port))
-					{
-						Hostname = host;
-						Port = port;
-						return true;
-					}
-					break;
-				}
-				default:
-				{
-					if (IPAddress.TryParse(str.Trim(), out var ipv6))
-					{
-						Hostname = $@"{ipv6}";
-						Port = ushort.TryParse(ipPort.Last(), out var portV6) ? portV6 : (ushort)3478;
-						return true;
-					}
-
-					var ipStr = string.Join(@":", ipPort, 0, ipPort.Length - 1);
-					if (!ipStr.StartsWith(@"[") || !ipStr.EndsWith(@"]") || !IPAddress.TryParse(ipStr, out _))
-					{
-						return false;
-					}
-
-					if (ushort.TryParse(ipPort.Last(), out var port))
-					{
-						Port = port;
-						return true;
-					}
-
-					break;
-				}
-			}
-
-			return false;
-		}
-
-		public override string ToString()
-		{
-			if (string.IsNullOrEmpty(Hostname))
-			{
-				return string.Empty;
-			}
-			if (Port == 3478)
-			{
-				return Hostname;
-			}
-			if (IPAddress.TryParse(Hostname, out var ip) && ip.AddressFamily != AddressFamily.InterNetwork)
-			{
-				return $@"[{Hostname}]:{Port}";
-			}
-			return $@"{Hostname}:{Port}";
-		}
-	}
-}

+ 70 - 0
UnitTest/StunServerTest.cs

@@ -0,0 +1,70 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using STUN;
+
+namespace UnitTest
+{
+	[TestClass]
+	public class StunServerTest
+	{
+		[TestMethod]
+		[DataRow(@"www.google.com", ushort.MinValue)]
+		[DataRow(@"1.1.1.1", (ushort)1)]
+		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]", (ushort)1919)]
+		public void IsTrue(string host, ushort port)
+		{
+			var str = $@"{host}:{port}";
+			Assert.IsTrue(StunServer.TryParse(str, out var server));
+			Assert.IsNotNull(server);
+			Assert.AreEqual(host, server.Hostname);
+			Assert.AreEqual(port, server.Port);
+			Assert.AreEqual(str, server.ToString());
+		}
+
+		[TestMethod]
+		[DataRow(@"")]
+		[DataRow(@"www.google.com:114514")]
+		[DataRow(@"/dw.[/[:114")]
+		[DataRow(@"2001:db8:1234:5678:11:2233:4455:6677:65535")]
+		public void IsFalse(string str)
+		{
+			Assert.IsFalse(StunServer.TryParse(str, out var server));
+			Assert.IsNull(server);
+		}
+
+		[TestMethod]
+		[DataRow(@"www.google.com")]
+		[DataRow(@"1.1.1.1")]
+		[DataRow(@"2001:db8:1234:5678:11:2233:4455:6677")]
+		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]")]
+		[DataRow(@"2001:db8:1234:5678:11:2233:4455:db8")]
+		public void TestDefaultPort(string str)
+		{
+			Assert.IsTrue(StunServer.TryParse(str, out var server));
+			Assert.IsNotNull(server);
+			Assert.AreEqual(str, server.Hostname);
+			Assert.AreEqual(3478, server.Port);
+		}
+
+		[TestMethod]
+		[DataRow(@"stun.syncthing.net:114", @"stun.syncthing.net:114")]
+		[DataRow(@"stun.syncthing.net:3478", @"stun.syncthing.net")]
+		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]", @"[2001:db8:1234:5678:11:2233:4455:6677]")]
+		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]:3478", @"[2001:db8:1234:5678:11:2233:4455:6677]")]
+		[DataRow(@"1.1.1.1:3478", @"1.1.1.1")]
+		[DataRow(@"1.1.1.1:1919", @"1.1.1.1:1919")]
+		public void ToString(string str, string expected)
+		{
+			Assert.IsTrue(StunServer.TryParse(str, out var server));
+			Assert.IsNotNull(server);
+			Assert.AreEqual(expected, server.ToString());
+		}
+
+		[TestMethod]
+		public void DefaultServer()
+		{
+			var server = new StunServer();
+			Assert.AreEqual(@"stun.syncthing.net", server.Hostname);
+			Assert.AreEqual(3478, server.Port);
+		}
+	}
+}

+ 0 - 62
UnitTest/UnitTest.cs

@@ -2,10 +2,8 @@ using Dns.Net.Clients;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using STUN.Client;
 using STUN.Enums;
-using STUN.Messages.StunAttributeValues;
 using STUN.Proxy;
 using System;
-using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
 
@@ -14,66 +12,6 @@ namespace UnitTest
 	[TestClass]
 	public class UnitTest
 	{
-		private static ReadOnlySpan<byte> MagicCookieAndTransactionId => new byte[]
-		{
-			0x21, 0x12, 0xa4, 0x42,
-			0xb7, 0xe7, 0xa7, 0x01,
-			0xbc, 0x34, 0xd6, 0x86,
-			0xfa, 0x87, 0xdf, 0xae
-		};
-
-		private static readonly byte[] XorPort = { 0xa1, 0x47 };
-		private static readonly byte[] XorIPv4 = { 0xe1, 0x12, 0xa6, 0x43 };
-		private static readonly byte[] XorIPv6 =
-		{
-				0x01, 0x13, 0xa9, 0xfa,
-				0xa5, 0xd3, 0xf1, 0x79,
-				0xbc, 0x25, 0xf4, 0xb5,
-				0xbe, 0xd2, 0xb9, 0xd9
-		};
-
-		private const ushort Port = 32853;
-		private readonly IPAddress IPv4 = IPAddress.Parse(@"192.0.2.1");
-		private readonly IPAddress IPv6 = IPAddress.Parse(@"2001:db8:1234:5678:11:2233:4455:6677");
-
-		private readonly byte[] _ipv4Response = new byte[] { 0x00, (byte)IpFamily.IPv4 }.Concat(XorPort).Concat(XorIPv4).ToArray();
-		private readonly byte[] _ipv6Response = new byte[] { 0x00, (byte)IpFamily.IPv6 }.Concat(XorPort).Concat(XorIPv6).ToArray();
-
-		/// <summary>
-		/// https://tools.ietf.org/html/rfc5769.html
-		/// </summary>
-		[TestMethod]
-		public void TestXorMapped()
-		{
-			var t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId)
-			{
-				Port = Port,
-				Family = IpFamily.IPv4,
-				Address = IPv4
-			};
-			Span<byte> temp = stackalloc byte[ushort.MaxValue];
-
-			var length4 = t.WriteTo(temp);
-			Assert.AreNotEqual(0, length4);
-			Assert.IsTrue(temp[..length4].SequenceEqual(_ipv4Response));
-
-			t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId);
-			Assert.IsTrue(t.TryParse(_ipv4Response));
-			Assert.AreEqual(t.Port, Port);
-			Assert.AreEqual(t.Family, IpFamily.IPv4);
-			Assert.AreEqual(t.Address, IPv4);
-
-			t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId);
-			Assert.IsTrue(t.TryParse(_ipv6Response));
-			Assert.AreEqual(t.Port, Port);
-			Assert.AreEqual(t.Family, IpFamily.IPv6);
-			Assert.AreEqual(t.Address, IPv6);
-
-			var length6 = t.WriteTo(temp);
-			Assert.AreNotEqual(0, length6);
-			Assert.IsTrue(temp[..length6].SequenceEqual(_ipv6Response));
-		}
-
 		[TestMethod]
 		public async Task BindingTest()
 		{

+ 73 - 0
UnitTest/XorMappedTest.cs

@@ -0,0 +1,73 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using STUN.Enums;
+using STUN.Messages.StunAttributeValues;
+using System;
+using System.Linq;
+using System.Net;
+
+namespace UnitTest
+{
+	[TestClass]
+	public class XorMappedTest
+	{
+		private static ReadOnlySpan<byte> MagicCookieAndTransactionId => new byte[]
+		{
+			0x21, 0x12, 0xa4, 0x42,
+			0xb7, 0xe7, 0xa7, 0x01,
+			0xbc, 0x34, 0xd6, 0x86,
+			0xfa, 0x87, 0xdf, 0xae
+		};
+
+		private static readonly byte[] XorPort = { 0xa1, 0x47 };
+		private static readonly byte[] XorIPv4 = { 0xe1, 0x12, 0xa6, 0x43 };
+		private static readonly byte[] XorIPv6 =
+		{
+			0x01, 0x13, 0xa9, 0xfa,
+			0xa5, 0xd3, 0xf1, 0x79,
+			0xbc, 0x25, 0xf4, 0xb5,
+			0xbe, 0xd2, 0xb9, 0xd9
+		};
+
+		private const ushort Port = 32853;
+		private readonly IPAddress IPv4 = IPAddress.Parse(@"192.0.2.1");
+		private readonly IPAddress IPv6 = IPAddress.Parse(@"2001:db8:1234:5678:11:2233:4455:6677");
+
+		private readonly byte[] _ipv4Response = new byte[] { 0x00, (byte)IpFamily.IPv4 }.Concat(XorPort).Concat(XorIPv4).ToArray();
+		private readonly byte[] _ipv6Response = new byte[] { 0x00, (byte)IpFamily.IPv6 }.Concat(XorPort).Concat(XorIPv6).ToArray();
+
+		/// <summary>
+		/// https://tools.ietf.org/html/rfc5769.html
+		/// </summary>
+		[TestMethod]
+		public void TestXorMapped()
+		{
+			var t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId)
+			{
+				Port = Port,
+				Family = IpFamily.IPv4,
+				Address = IPv4
+			};
+			Span<byte> temp = stackalloc byte[ushort.MaxValue];
+
+			var length4 = t.WriteTo(temp);
+			Assert.AreNotEqual(0, length4);
+			Assert.IsTrue(temp[..length4].SequenceEqual(_ipv4Response));
+
+			t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId);
+			Assert.IsTrue(t.TryParse(_ipv4Response));
+			Assert.AreEqual(t.Port, Port);
+			Assert.AreEqual(t.Family, IpFamily.IPv4);
+			Assert.AreEqual(t.Address, IPv4);
+
+			t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId);
+			Assert.IsTrue(t.TryParse(_ipv6Response));
+			Assert.AreEqual(t.Port, Port);
+			Assert.AreEqual(t.Family, IpFamily.IPv6);
+			Assert.AreEqual(t.Address, IPv6);
+
+			var length6 = t.WriteTo(temp);
+			Assert.AreNotEqual(0, length6);
+			Assert.IsTrue(temp[..length6].SequenceEqual(_ipv6Response));
+		}
+	}
+}