1
0
Эх сурвалжийг харах

style: to file-scoped namespaces

Bruce Wayne 3 жил өмнө
parent
commit
a650bba694
61 өөрчлөгдсөн 2735 нэмэгдсэн , 2797 устгасан
  1. 32 33
      NatTypeTester.Models/Config.cs
  2. 4 5
      NatTypeTester.Models/NatTypeTesterModelsModule.cs
  3. 44 45
      NatTypeTester.ViewModels/MainWindowViewModel.cs
  4. 4 5
      NatTypeTester.ViewModels/NatTypeTesterViewModelModule.cs
  5. 55 56
      NatTypeTester.ViewModels/RFC3489ViewModel.cs
  6. 52 53
      NatTypeTester.ViewModels/RFC5780ViewModel.cs
  7. 7 8
      NatTypeTester.ViewModels/SettingViewModel.cs
  8. 33 34
      NatTypeTester.ViewModels/ValueConverters/StringToIPEndpointTypeConverter.cs
  9. 4 5
      NatTypeTester.ViewModels/ViewModelBase.cs
  10. 36 37
      NatTypeTester/App.xaml.cs
  11. 5 6
      NatTypeTester/Dialogs/DisposableContentDialog.cs
  12. 23 24
      NatTypeTester/MainWindow.xaml.cs
  13. 18 19
      NatTypeTester/NatTypeTesterModule.cs
  14. 10 11
      NatTypeTester/Utils/Extensions.cs
  15. 20 21
      NatTypeTester/Views/RFC3489View.xaml.cs
  16. 22 23
      NatTypeTester/Views/RFC5780View.xaml.cs
  17. 23 24
      NatTypeTester/Views/SettingView.xaml.cs
  18. 6 7
      STUN/Client/IStunClient.cs
  19. 164 165
      STUN/Client/StunClient3489.cs
  20. 215 216
      STUN/Client/StunClient5389UDP.cs
  21. 40 41
      STUN/Enums/AttributeType.cs
  22. 7 8
      STUN/Enums/BindingTestResult.cs
  23. 7 8
      STUN/Enums/Class.cs
  24. 9 10
      STUN/Enums/FilteringBehavior.cs
  25. 8 9
      STUN/Enums/IpFamily.cs
  26. 10 11
      STUN/Enums/MappingBehavior.cs
  27. 5 6
      STUN/Enums/Method.cs
  28. 53 54
      STUN/Enums/NatType.cs
  29. 5 6
      STUN/Enums/ProxyType.cs
  30. 31 32
      STUN/Enums/StunMessageType.cs
  31. 8 10
      STUN/Enums/TransportType.cs
  32. 48 49
      STUN/HostnameEndpoint.cs
  33. 72 73
      STUN/Messages/StunAttribute.cs
  34. 54 55
      STUN/Messages/StunAttributeValues/AddressStunAttributeValue.cs
  35. 23 24
      STUN/Messages/StunAttributeValues/ChangeRequestStunAttributeValue.cs
  36. 6 7
      STUN/Messages/StunAttributeValues/ChangedAddressStunAttributeValue.cs
  37. 30 31
      STUN/Messages/StunAttributeValues/ErrorCodeStunAttributeValue.cs
  38. 5 6
      STUN/Messages/StunAttributeValues/IStunAttributeValue.cs
  39. 6 7
      STUN/Messages/StunAttributeValues/MappedAddressStunAttributeValue.cs
  40. 6 7
      STUN/Messages/StunAttributeValues/OtherAddressStunAttributeValue.cs
  41. 6 7
      STUN/Messages/StunAttributeValues/ReflectedFromStunAttributeValue.cs
  42. 6 7
      STUN/Messages/StunAttributeValues/ResponseAddressStunAttributeValue.cs
  43. 6 7
      STUN/Messages/StunAttributeValues/SourceAddressStunAttributeValue.cs
  44. 29 30
      STUN/Messages/StunAttributeValues/UnknownStunAttributeValue.cs
  45. 12 13
      STUN/Messages/StunAttributeValues/UselessStunAttributeValue.cs
  46. 48 49
      STUN/Messages/StunAttributeValues/XorMappedAddressStunAttributeValue.cs
  47. 79 80
      STUN/Messages/StunMessage5389.cs
  48. 11 12
      STUN/Messages/StunResponse.cs
  49. 8 9
      STUN/Proxy/IUdpProxy.cs
  50. 36 37
      STUN/Proxy/NoneUdpProxy.cs
  51. 17 18
      STUN/Proxy/ProxyFactory.cs
  52. 83 84
      STUN/Proxy/Socks5UdpProxy.cs
  53. 14 15
      STUN/StunResult/ClassicStunResult.cs
  54. 9 10
      STUN/StunResult/StunResult.cs
  55. 23 24
      STUN/StunResult/StunResult5389.cs
  56. 32 33
      STUN/StunServer.cs
  57. 85 86
      STUN/Utils/AttributeExtensions.cs
  58. 71 72
      UnitTest/HostnameEndpointTest.cs
  59. 580 581
      UnitTest/StunClien5389UDPTest.cs
  60. 318 319
      UnitTest/StunClient3489Test.cs
  61. 52 53
      UnitTest/XorMappedTest.cs

+ 32 - 33
NatTypeTester.Models/Config.cs

@@ -3,44 +3,43 @@ using ReactiveUI;
 using STUN.Enums;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.Models
+namespace NatTypeTester.Models;
+
+[UsedImplicitly]
+public record Config : ReactiveRecord, ISingletonDependency
 {
-	[UsedImplicitly]
-	public record Config : ReactiveRecord, ISingletonDependency
+	private string _stunServer = @"stun.syncthing.net";
+	public string StunServer
 	{
-		private string _stunServer = @"stun.syncthing.net";
-		public string StunServer
-		{
-			get => _stunServer;
-			set => this.RaiseAndSetIfChanged(ref _stunServer, value);
-		}
+		get => _stunServer;
+		set => this.RaiseAndSetIfChanged(ref _stunServer, value);
+	}
 
-		private ProxyType _proxyType = ProxyType.Plain;
-		public ProxyType ProxyType
-		{
-			get => _proxyType;
-			set => this.RaiseAndSetIfChanged(ref _proxyType, value);
-		}
+	private ProxyType _proxyType = ProxyType.Plain;
+	public ProxyType ProxyType
+	{
+		get => _proxyType;
+		set => this.RaiseAndSetIfChanged(ref _proxyType, value);
+	}
 
-		private string _proxyServer = @"127.0.0.1:1080";
-		public string ProxyServer
-		{
-			get => _proxyServer;
-			set => this.RaiseAndSetIfChanged(ref _proxyServer, value);
-		}
+	private string _proxyServer = @"127.0.0.1:1080";
+	public string ProxyServer
+	{
+		get => _proxyServer;
+		set => this.RaiseAndSetIfChanged(ref _proxyServer, value);
+	}
 
-		private string? _proxyUser;
-		public string? ProxyUser
-		{
-			get => _proxyUser;
-			set => this.RaiseAndSetIfChanged(ref _proxyUser, value);
-		}
+	private string? _proxyUser;
+	public string? ProxyUser
+	{
+		get => _proxyUser;
+		set => this.RaiseAndSetIfChanged(ref _proxyUser, value);
+	}
 
-		private string? _proxyPassword;
-		public string? ProxyPassword
-		{
-			get => _proxyPassword;
-			set => this.RaiseAndSetIfChanged(ref _proxyPassword, value);
-		}
+	private string? _proxyPassword;
+	public string? ProxyPassword
+	{
+		get => _proxyPassword;
+		set => this.RaiseAndSetIfChanged(ref _proxyPassword, value);
 	}
 }

+ 4 - 5
NatTypeTester.Models/NatTypeTesterModelsModule.cs

@@ -1,10 +1,9 @@
 using JetBrains.Annotations;
 using Volo.Abp.Modularity;
 
-namespace NatTypeTester.Models
+namespace NatTypeTester.Models;
+
+[UsedImplicitly]
+public class NatTypeTesterModelsModule : AbpModule
 {
-	[UsedImplicitly]
-	public class NatTypeTesterModelsModule : AbpModule
-	{
-	}
 }

+ 44 - 45
NatTypeTester.ViewModels/MainWindowViewModel.cs

@@ -12,65 +12,64 @@ using System.Reactive.Linq;
 using System.Threading.Tasks;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+[ExposeServices(
+	typeof(MainWindowViewModel),
+	typeof(IScreen)
+)]
+public class MainWindowViewModel : ViewModelBase, IScreen
 {
-	[ExposeServices(
-		typeof(MainWindowViewModel),
-		typeof(IScreen)
-	)]
-	public class MainWindowViewModel : ViewModelBase, IScreen
-	{
-		public RoutingState Router => LazyServiceProvider.LazyGetRequiredService<RoutingState>();
+	public RoutingState Router => LazyServiceProvider.LazyGetRequiredService<RoutingState>();
 
-		public Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
+	public Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
 
-		private readonly IEnumerable<string> _defaultServers = new HashSet<string>
-		{
-				@"stun.syncthing.net",
-				@"stun.qq.com",
-				@"stun.miwifi.com",
-				@"stun.bige0.com",
-				@"stun.stunprotocol.org"
-		};
+	private readonly IEnumerable<string> _defaultServers = new HashSet<string>
+	{
+		@"stun.syncthing.net",
+		@"stun.qq.com",
+		@"stun.miwifi.com",
+		@"stun.bige0.com",
+		@"stun.stunprotocol.org"
+	};
 
-		private SourceList<string> List { get; } = new();
-		public readonly IObservableCollection<string> StunServers = new ObservableCollectionExtended<string>();
+	private SourceList<string> List { get; } = new();
+	public readonly IObservableCollection<string> StunServers = new ObservableCollectionExtended<string>();
 
-		public MainWindowViewModel()
+	public MainWindowViewModel()
+	{
+		List.Connect()
+			.DistinctValues(x => x)
+			.ObserveOn(RxApp.MainThreadScheduler)
+			.Bind(StunServers)
+			.Subscribe();
+	}
+
+	public void LoadStunServer()
+	{
+		foreach (var server in _defaultServers)
 		{
-			List.Connect()
-				.DistinctValues(x => x)
-				.ObserveOn(RxApp.MainThreadScheduler)
-				.Bind(StunServers)
-				.Subscribe();
+			List.Add(server);
 		}
 
-		public void LoadStunServer()
+		Config.StunServer = _defaultServers.First();
+
+		Task.Run(() =>
 		{
-			foreach (var server in _defaultServers)
+			const string path = @"stun.txt";
+
+			if (!File.Exists(path))
 			{
-				List.Add(server);
+				return;
 			}
 
-			Config.StunServer = _defaultServers.First();
-
-			Task.Run(() =>
+			foreach (var line in File.ReadLines(path))
 			{
-				const string path = @"stun.txt";
-
-				if (!File.Exists(path))
-				{
-					return;
-				}
-
-				foreach (var line in File.ReadLines(path))
+				if (!string.IsNullOrWhiteSpace(line) && StunServer.TryParse(line, out var stun))
 				{
-					if (!string.IsNullOrWhiteSpace(line) && StunServer.TryParse(line, out var stun))
-					{
-						List.Add(stun.ToString());
-					}
+					List.Add(stun.ToString());
 				}
-			}).Forget();
-		}
+			}
+		}).Forget();
 	}
 }

+ 4 - 5
NatTypeTester.ViewModels/NatTypeTesterViewModelModule.cs

@@ -1,10 +1,9 @@
 using JetBrains.Annotations;
 using Volo.Abp.Modularity;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+[UsedImplicitly]
+public class NatTypeTesterViewModelModule : AbpModule
 {
-	[UsedImplicitly]
-	public class NatTypeTesterViewModelModule : AbpModule
-	{
-	}
 }

+ 55 - 56
NatTypeTester.ViewModels/RFC3489ViewModel.cs

@@ -16,80 +16,79 @@ using System.Reactive.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+[UsedImplicitly]
+public class RFC3489ViewModel : ViewModelBase, IRoutableViewModel
 {
-	[UsedImplicitly]
-	public class RFC3489ViewModel : ViewModelBase, IRoutableViewModel
-	{
-		public string UrlPathSegment => @"RFC3489";
-		public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
+	public string UrlPathSegment => @"RFC3489";
+	public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
 
-		private Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
+	private Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
 
-		private IDnsClient DnsClient => LazyServiceProvider.LazyGetRequiredService<IDnsClient>();
+	private IDnsClient DnsClient => LazyServiceProvider.LazyGetRequiredService<IDnsClient>();
 
-		public ClassicStunResult Result3489 { get; set; }
+	public ClassicStunResult Result3489 { get; set; }
 
-		public ReactiveCommand<Unit, Unit> TestClassicNatType { get; }
+	public ReactiveCommand<Unit, Unit> TestClassicNatType { get; }
 
-		private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
+	private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
 
-		public RFC3489ViewModel()
+	public RFC3489ViewModel()
+	{
+		Result3489 = new ClassicStunResult
 		{
-			Result3489 = new ClassicStunResult
-			{
-				LocalEndPoint = DefaultLocalEndpoint
-			};
-			TestClassicNatType = ReactiveCommand.CreateFromTask(TestClassicNatTypeAsync);
-		}
+			LocalEndPoint = DefaultLocalEndpoint
+		};
+		TestClassicNatType = ReactiveCommand.CreateFromTask(TestClassicNatTypeAsync);
+	}
+
+	private async Task TestClassicNatTypeAsync(CancellationToken token)
+	{
+		Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
-		private async Task TestClassicNatTypeAsync(CancellationToken token)
+		if (!HostnameEndpoint.TryParse(Config.ProxyServer, out var proxyIpe))
 		{
-			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
+			throw new NotSupportedException(@"Unknown proxy address");
+		}
 
-			if (!HostnameEndpoint.TryParse(Config.ProxyServer, out var proxyIpe))
+		var socks5Option = new Socks5CreateOption
+		{
+			Address = await DnsClient.QueryAsync(proxyIpe.Hostname, token),
+			Port = proxyIpe.Port,
+			UsernamePassword = new UsernamePassword
 			{
-				throw new NotSupportedException(@"Unknown proxy address");
+				UserName = Config.ProxyUser,
+				Password = Config.ProxyPassword
 			}
+		};
+
+		Result3489.LocalEndPoint ??= DefaultLocalEndpoint;
+		using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result3489.LocalEndPoint, socks5Option);
+
+		var ip = await DnsClient.QueryAsync(server.Hostname, token);
+		using var client = new StunClient3489(new IPEndPoint(ip, server.Port), Result3489.LocalEndPoint, proxy);
 
-			var socks5Option = new Socks5CreateOption
+		Result3489 = client.State;
+		using (Observable.Interval(TimeSpan.FromSeconds(0.1))
+			       .ObserveOn(RxApp.MainThreadScheduler)
+			       .Subscribe(_ => this.RaisePropertyChanged(nameof(Result3489))))
+		{
+			await client.ConnectProxyAsync(token);
+			try
 			{
-				Address = await DnsClient.QueryAsync(proxyIpe.Hostname, token),
-				Port = proxyIpe.Port,
-				UsernamePassword = new UsernamePassword
-				{
-					UserName = Config.ProxyUser,
-					Password = Config.ProxyPassword
-				}
-			};
-
-			Result3489.LocalEndPoint ??= DefaultLocalEndpoint;
-			using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result3489.LocalEndPoint, socks5Option);
-
-			var ip = await DnsClient.QueryAsync(server.Hostname, token);
-			using var client = new StunClient3489(new IPEndPoint(ip, server.Port), Result3489.LocalEndPoint, proxy);
-
-			Result3489 = client.State;
-			using (Observable.Interval(TimeSpan.FromSeconds(0.1))
-					.ObserveOn(RxApp.MainThreadScheduler)
-					.Subscribe(_ => this.RaisePropertyChanged(nameof(Result3489))))
+				await client.QueryAsync(token);
+				Result3489.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
+			}
+			finally
 			{
-				await client.ConnectProxyAsync(token);
-				try
-				{
-					await client.QueryAsync(token);
-					Result3489.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
-				}
-				finally
-				{
-					await client.CloseProxyAsync(token);
-				}
+				await client.CloseProxyAsync(token);
 			}
+		}
 
-			Result3489 = new ClassicStunResult();
-			Result3489.Clone(client.State);
+		Result3489 = new ClassicStunResult();
+		Result3489.Clone(client.State);
 
-			this.RaisePropertyChanged(nameof(Result3489));
-		}
+		this.RaisePropertyChanged(nameof(Result3489));
 	}
 }

+ 52 - 53
NatTypeTester.ViewModels/RFC5780ViewModel.cs

@@ -16,77 +16,76 @@ using System.Reactive.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+[UsedImplicitly]
+public class RFC5780ViewModel : ViewModelBase, IRoutableViewModel
 {
-	[UsedImplicitly]
-	public class RFC5780ViewModel : ViewModelBase, IRoutableViewModel
-	{
-		public string UrlPathSegment => @"RFC5780";
-		public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
+	public string UrlPathSegment => @"RFC5780";
+	public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
 
-		private Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
+	private Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
 
-		private IDnsClient DnsClient => LazyServiceProvider.LazyGetRequiredService<IDnsClient>();
+	private IDnsClient DnsClient => LazyServiceProvider.LazyGetRequiredService<IDnsClient>();
 
-		public StunResult5389 Result5389 { get; set; }
+	public StunResult5389 Result5389 { get; set; }
 
-		public ReactiveCommand<Unit, Unit> DiscoveryNatType { get; }
+	public ReactiveCommand<Unit, Unit> DiscoveryNatType { get; }
 
-		private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
+	private static readonly IPEndPoint DefaultLocalEndpoint = new(IPAddress.Any, 0);
+
+	public RFC5780ViewModel()
+	{
+		Result5389 = new StunResult5389 { LocalEndPoint = new IPEndPoint(IPAddress.Any, 0) };
+		DiscoveryNatType = ReactiveCommand.CreateFromTask(DiscoveryNatTypeAsync);
+	}
+
+	private async Task DiscoveryNatTypeAsync(CancellationToken token)
+	{
+		Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
 
-		public RFC5780ViewModel()
+		if (!HostnameEndpoint.TryParse(Config.ProxyServer, out var proxyIpe))
 		{
-			Result5389 = new StunResult5389 { LocalEndPoint = new IPEndPoint(IPAddress.Any, 0) };
-			DiscoveryNatType = ReactiveCommand.CreateFromTask(DiscoveryNatTypeAsync);
+			throw new NotSupportedException(@"Unknown proxy address");
 		}
 
-		private async Task DiscoveryNatTypeAsync(CancellationToken token)
+		var socks5Option = new Socks5CreateOption
 		{
-			Verify.Operation(StunServer.TryParse(Config.StunServer, out var server), @"Wrong STUN Server!");
-
-			if (!HostnameEndpoint.TryParse(Config.ProxyServer, out var proxyIpe))
+			Address = await DnsClient.QueryAsync(proxyIpe.Hostname, token),
+			Port = proxyIpe.Port,
+			UsernamePassword = new UsernamePassword
 			{
-				throw new NotSupportedException(@"Unknown proxy address");
+				UserName = Config.ProxyUser,
+				Password = Config.ProxyPassword
 			}
+		};
+
+		Result5389.LocalEndPoint ??= DefaultLocalEndpoint;
+		using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result5389.LocalEndPoint, socks5Option);
 
-			var socks5Option = new Socks5CreateOption
+		var ip = await DnsClient.QueryAsync(server.Hostname, token);
+		using var client = new StunClient5389UDP(new IPEndPoint(ip, server.Port), Result5389.LocalEndPoint, proxy);
+
+		Result5389 = client.State;
+		using (Observable.Interval(TimeSpan.FromSeconds(0.1))
+			       .ObserveOn(RxApp.MainThreadScheduler)
+			       .Subscribe(_ => this.RaisePropertyChanged(nameof(Result5389))))
+		{
+			await client.ConnectProxyAsync(token);
+			try
 			{
-				Address = await DnsClient.QueryAsync(proxyIpe.Hostname, token),
-				Port = proxyIpe.Port,
-				UsernamePassword = new UsernamePassword
-				{
-					UserName = Config.ProxyUser,
-					Password = Config.ProxyPassword
-				}
-			};
-
-			Result5389.LocalEndPoint ??= DefaultLocalEndpoint;
-			using var proxy = ProxyFactory.CreateProxy(Config.ProxyType, Result5389.LocalEndPoint, socks5Option);
-
-			var ip = await DnsClient.QueryAsync(server.Hostname, token);
-			using var client = new StunClient5389UDP(new IPEndPoint(ip, server.Port), Result5389.LocalEndPoint, proxy);
-
-			Result5389 = client.State;
-			using (Observable.Interval(TimeSpan.FromSeconds(0.1))
-					.ObserveOn(RxApp.MainThreadScheduler)
-					.Subscribe(_ => this.RaisePropertyChanged(nameof(Result5389))))
+				await client.QueryAsync(token);
+				Result5389.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
+			}
+			finally
 			{
-				await client.ConnectProxyAsync(token);
-				try
-				{
-					await client.QueryAsync(token);
-					Result5389.LocalEndPoint = new IPEndPoint(client.LocalEndPoint.AddressFamily is AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, client.LocalEndPoint.Port);
-				}
-				finally
-				{
-					await client.CloseProxyAsync(token);
-				}
+				await client.CloseProxyAsync(token);
 			}
+		}
 
-			Result5389 = new StunResult5389();
-			Result5389.Clone(client.State);
+		Result5389 = new StunResult5389();
+		Result5389.Clone(client.State);
 
-			this.RaisePropertyChanged(nameof(Result5389));
-		}
+		this.RaisePropertyChanged(nameof(Result5389));
 	}
 }

+ 7 - 8
NatTypeTester.ViewModels/SettingViewModel.cs

@@ -2,14 +2,13 @@ using JetBrains.Annotations;
 using NatTypeTester.Models;
 using ReactiveUI;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+[UsedImplicitly]
+public class SettingViewModel : ViewModelBase, IRoutableViewModel
 {
-	[UsedImplicitly]
-	public class SettingViewModel : ViewModelBase, IRoutableViewModel
-	{
-		public string UrlPathSegment => @"Settings";
-		public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
+	public string UrlPathSegment => @"Settings";
+	public IScreen HostScreen => LazyServiceProvider.LazyGetRequiredService<IScreen>();
 
-		public Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
-	}
+	public Config Config => LazyServiceProvider.LazyGetRequiredService<Config>();
 }

+ 33 - 34
NatTypeTester.ViewModels/ValueConverters/StringToIPEndpointTypeConverter.cs

@@ -4,51 +4,50 @@ using System;
 using System.Net;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.ViewModels.ValueConverters
+namespace NatTypeTester.ViewModels.ValueConverters;
+
+[ExposeServices(typeof(IBindingTypeConverter))]
+[UsedImplicitly]
+public class StringToIPEndpointTypeConverter : IBindingTypeConverter, ISingletonDependency
 {
-	[ExposeServices(typeof(IBindingTypeConverter))]
-	[UsedImplicitly]
-	public class StringToIPEndpointTypeConverter : IBindingTypeConverter, ISingletonDependency
+	public int GetAffinityForObjects(Type fromType, Type toType)
 	{
-		public int GetAffinityForObjects(Type fromType, Type toType)
+		if (fromType == typeof(string) && toType == typeof(IPEndPoint))
 		{
-			if (fromType == typeof(string) && toType == typeof(IPEndPoint))
-			{
-				return 11;
-			}
-
-			if (fromType == typeof(IPEndPoint) && toType == typeof(string))
-			{
-				return 11;
-			}
+			return 11;
+		}
 
-			return 0;
+		if (fromType == typeof(IPEndPoint) && toType == typeof(string))
+		{
+			return 11;
 		}
 
-		public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result)
+		return 0;
+	}
+
+	public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result)
+	{
+		if (toType == typeof(IPEndPoint) && from is string str)
 		{
-			if (toType == typeof(IPEndPoint) && from is string str)
+			if (IPEndPoint.TryParse(str, out var ipe))
 			{
-				if (IPEndPoint.TryParse(str, out var ipe))
-				{
-					result = ipe;
-					return true;
-				}
-
-				result = null;
-				return false;
+				result = ipe;
+				return true;
 			}
 
-			if (from is IPEndPoint fromIPEndPoint)
-			{
-				result = fromIPEndPoint.ToString();
-			}
-			else
-			{
-				result = string.Empty;
-			}
+			result = null;
+			return false;
+		}
 
-			return true;
+		if (from is IPEndPoint fromIPEndPoint)
+		{
+			result = fromIPEndPoint.ToString();
 		}
+		else
+		{
+			result = string.Empty;
+		}
+
+		return true;
 	}
 }

+ 4 - 5
NatTypeTester.ViewModels/ViewModelBase.cs

@@ -1,10 +1,9 @@
 using ReactiveUI;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.ViewModels
+namespace NatTypeTester.ViewModels;
+
+public abstract class ViewModelBase : ReactiveObject, ISingletonDependency
 {
-	public abstract class ViewModelBase : ReactiveObject, ISingletonDependency
-	{
-		public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = null!;
-	}
+	public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = null!;
 }

+ 36 - 37
NatTypeTester/App.xaml.cs

@@ -6,52 +6,51 @@ using System.Windows;
 using Volo.Abp;
 
 #pragma warning disable VSTHRD100 // 避免使用 Async Void 方法
-namespace NatTypeTester
+namespace NatTypeTester;
+
+public partial class App
 {
-	public partial class App
+	private readonly IHost _host;
+	private readonly IAbpApplicationWithExternalServiceProvider _application;
+
+	public App()
 	{
-		private readonly IHost _host;
-		private readonly IAbpApplicationWithExternalServiceProvider _application;
+		_host = CreateHostBuilder();
+		_application = _host.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>();
+	}
 
-		public App()
+	protected override async void OnStartup(StartupEventArgs e)
+	{
+		try
 		{
-			_host = CreateHostBuilder();
-			_application = _host.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>();
+			await _host.StartAsync();
+			Initialize(_host.Services);
+			_host.Services.GetRequiredService<MainWindow>().Show();
 		}
-
-		protected override async void OnStartup(StartupEventArgs e)
+		catch (Exception ex)
 		{
-			try
-			{
-				await _host.StartAsync();
-				Initialize(_host.Services);
-				_host.Services.GetRequiredService<MainWindow>().Show();
-			}
-			catch (Exception ex)
-			{
-				MessageBox.Show(ex.Message, nameof(NatTypeTester), MessageBoxButton.OK, MessageBoxImage.Error);
-			}
+			MessageBox.Show(ex.Message, nameof(NatTypeTester), MessageBoxButton.OK, MessageBoxImage.Error);
 		}
+	}
 
-		protected override async void OnExit(ExitEventArgs e)
-		{
-			_application.Shutdown();
-			await _host.StopAsync();
-			_host.Dispose();
-		}
+	protected override async void OnExit(ExitEventArgs e)
+	{
+		_application.Shutdown();
+		await _host.StopAsync();
+		_host.Dispose();
+	}
 
-		private void Initialize(IServiceProvider serviceProvider)
-		{
-			_application.Initialize(serviceProvider);
-			serviceProvider.UseMicrosoftDependencyResolver();
-		}
+	private void Initialize(IServiceProvider serviceProvider)
+	{
+		_application.Initialize(serviceProvider);
+		serviceProvider.UseMicrosoftDependencyResolver();
+	}
 
-		private static IHost CreateHostBuilder()
-		{
-			return Host.CreateDefaultBuilder()
-					.UseAutofac()
-					.ConfigureServices((_, services) => services.AddApplication<NatTypeTesterModule>())
-					.Build();
-		}
+	private static IHost CreateHostBuilder()
+	{
+		return Host.CreateDefaultBuilder()
+			.UseAutofac()
+			.ConfigureServices((_, services) => services.AddApplication<NatTypeTesterModule>())
+			.Build();
 	}
 }

+ 5 - 6
NatTypeTester/Dialogs/DisposableContentDialog.cs

@@ -1,13 +1,12 @@
 using ModernWpf.Controls;
 using System;
 
-namespace NatTypeTester.Dialogs
+namespace NatTypeTester.Dialogs;
+
+public class DisposableContentDialog : ContentDialog, IDisposable
 {
-	public class DisposableContentDialog : ContentDialog, IDisposable
+	public void Dispose()
 	{
-		public void Dispose()
-		{
-			Hide();
-		}
+		Hide();
 	}
 }

+ 23 - 24
NatTypeTester/MainWindow.xaml.cs

@@ -8,34 +8,34 @@ using System.Linq;
 using System.Reactive.Disposables;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester
+namespace NatTypeTester;
+
+public partial class MainWindow : ISingletonDependency
 {
-	public partial class MainWindow : ISingletonDependency
+	public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider)
 	{
-		public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider)
-		{
-			InitializeComponent();
-			ViewModel = viewModel;
+		InitializeComponent();
+		ViewModel = viewModel;
 
-			this.WhenActivated(d =>
-			{
-				#region Server
+		this.WhenActivated(d =>
+		{
+			#region Server
 
-				this.Bind(ViewModel,
-						vm => vm.Config.StunServer,
-						v => v.ServersComboBox.Text
-				).DisposeWith(d);
+			this.Bind(ViewModel,
+				vm => vm.Config.StunServer,
+				v => v.ServersComboBox.Text
+			).DisposeWith(d);
 
-				this.OneWayBind(ViewModel,
-						vm => vm.StunServers,
-						v => v.ServersComboBox.ItemsSource
-				).DisposeWith(d);
+			this.OneWayBind(ViewModel,
+				vm => vm.StunServers,
+				v => v.ServersComboBox.ItemsSource
+			).DisposeWith(d);
 
-				#endregion
+			#endregion
 
-				this.Bind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router).DisposeWith(d);
+			this.Bind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router).DisposeWith(d);
 
-				NavigationView.Events().SelectionChanged
+			NavigationView.Events().SelectionChanged
 				.Subscribe(parameter =>
 				{
 					if (parameter.args.IsSettingsSelected)
@@ -63,10 +63,9 @@ namespace NatTypeTester
 						}
 					}
 				}).DisposeWith(d);
-				NavigationView.SelectedItem = NavigationView.MenuItems.OfType<NavigationViewItem>().First();
+			NavigationView.SelectedItem = NavigationView.MenuItems.OfType<NavigationViewItem>().First();
 
-				ViewModel.LoadStunServer();
-			});
-		}
+			ViewModel.LoadStunServer();
+		});
 	}
 }

+ 18 - 19
NatTypeTester/NatTypeTesterModule.cs

@@ -10,27 +10,26 @@ using Splat.Microsoft.Extensions.DependencyInjection;
 using Volo.Abp.Autofac;
 using Volo.Abp.Modularity;
 
-namespace NatTypeTester
+namespace NatTypeTester;
+
+[DependsOn(
+	typeof(AbpAutofacModule),
+	typeof(NatTypeTesterModelsModule),
+	typeof(NatTypeTesterViewModelModule)
+)]
+[UsedImplicitly]
+public class NatTypeTesterModule : AbpModule
 {
-	[DependsOn(
-		typeof(AbpAutofacModule),
-		typeof(NatTypeTesterModelsModule),
-		typeof(NatTypeTesterViewModelModule)
-		)]
-	[UsedImplicitly]
-	public class NatTypeTesterModule : AbpModule
+	public override void PreConfigureServices(ServiceConfigurationContext context)
 	{
-		public override void PreConfigureServices(ServiceConfigurationContext context)
-		{
-			context.Services.UseMicrosoftDependencyResolver();
-			Locator.CurrentMutable.InitializeSplat();
-			Locator.CurrentMutable.InitializeReactiveUI(RegistrationNamespace.Wpf);
-		}
+		context.Services.UseMicrosoftDependencyResolver();
+		Locator.CurrentMutable.InitializeSplat();
+		Locator.CurrentMutable.InitializeReactiveUI(RegistrationNamespace.Wpf);
+	}
 
-		public override void ConfigureServices(ServiceConfigurationContext context)
-		{
-			context.Services.TryAddTransient<RoutingState>();
-			context.Services.TryAddTransient<IDnsClient, DefaultDnsClient>();
-		}
+	public override void ConfigureServices(ServiceConfigurationContext context)
+	{
+		context.Services.TryAddTransient<RoutingState>();
+		context.Services.TryAddTransient<IDnsClient, DefaultDnsClient>();
 	}
 }

+ 10 - 11
NatTypeTester/Utils/Extensions.cs

@@ -2,19 +2,18 @@ using NatTypeTester.Dialogs;
 using System;
 using System.Threading.Tasks;
 
-namespace NatTypeTester.Utils
+namespace NatTypeTester.Utils;
+
+public static class Extensions
 {
-	public static class Extensions
+	public static async Task HandleExceptionWithContentDialogAsync(this Exception ex)
 	{
-		public static async Task HandleExceptionWithContentDialogAsync(this Exception ex)
+		using var dialog = new DisposableContentDialog
 		{
-			using var dialog = new DisposableContentDialog
-			{
-				Title = nameof(NatTypeTester),
-				Content = ex.Message,
-				PrimaryButtonText = @"OK"
-			};
-			await dialog.ShowAsync();
-		}
+			Title = nameof(NatTypeTester),
+			Content = ex.Message,
+			PrimaryButtonText = @"OK"
+		};
+		await dialog.ShowAsync();
 	}
 }

+ 20 - 21
NatTypeTester/Views/RFC3489View.xaml.cs

@@ -9,34 +9,33 @@ using System.Reactive.Linq;
 using System.Windows.Input;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.Views
+namespace NatTypeTester.Views;
+
+[ExposeServices(typeof(IViewFor<RFC3489ViewModel>))]
+[UsedImplicitly]
+public partial class RFC3489View : ITransientDependency
 {
-	[ExposeServices(typeof(IViewFor<RFC3489ViewModel>))]
-	[UsedImplicitly]
-	public partial class RFC3489View : ITransientDependency
+	public RFC3489View(RFC3489ViewModel viewModel)
 	{
-		public RFC3489View(RFC3489ViewModel viewModel)
-		{
-			InitializeComponent();
-			ViewModel = viewModel;
+		InitializeComponent();
+		ViewModel = viewModel;
 
-			this.WhenActivated(d =>
-			{
-				this.OneWayBind(ViewModel, vm => vm.Result3489.NatType, v => v.NatTypeTextBox.Text).DisposeWith(d);
+		this.WhenActivated(d =>
+		{
+			this.OneWayBind(ViewModel, vm => vm.Result3489.NatType, v => v.NatTypeTextBox.Text).DisposeWith(d);
 
-				this.Bind(ViewModel, vm => vm.Result3489.LocalEndPoint, v => v.LocalEndTextBox.Text).DisposeWith(d);
+			this.Bind(ViewModel, vm => vm.Result3489.LocalEndPoint, v => v.LocalEndTextBox.Text).DisposeWith(d);
 
-				this.OneWayBind(ViewModel, vm => vm.Result3489.PublicEndPoint, v => v.PublicEndTextBox.Text).DisposeWith(d);
+			this.OneWayBind(ViewModel, vm => vm.Result3489.PublicEndPoint, v => v.PublicEndTextBox.Text).DisposeWith(d);
 
-				this.BindCommand(ViewModel, vm => vm.TestClassicNatType, v => v.TestButton).DisposeWith(d);
+			this.BindCommand(ViewModel, vm => vm.TestClassicNatType, v => v.TestButton).DisposeWith(d);
 
-				this.Events().KeyDown
-						.Where(x => x.Key == Key.Enter && TestButton.Command.CanExecute(default))
-						.Subscribe(_ => TestButton.Command.Execute(default))
-						.DisposeWith(d);
+			this.Events().KeyDown
+				.Where(x => x.Key == Key.Enter && TestButton.Command.CanExecute(default))
+				.Subscribe(_ => TestButton.Command.Execute(default))
+				.DisposeWith(d);
 
-				ViewModel.TestClassicNatType.ThrownExceptions.Subscribe(ex => _ = ex.HandleExceptionWithContentDialogAsync()).DisposeWith(d);
-			});
-		}
+			ViewModel.TestClassicNatType.ThrownExceptions.Subscribe(ex => _ = ex.HandleExceptionWithContentDialogAsync()).DisposeWith(d);
+		});
 	}
 }

+ 22 - 23
NatTypeTester/Views/RFC5780View.xaml.cs

@@ -9,38 +9,37 @@ using System.Reactive.Linq;
 using System.Windows.Input;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.Views
+namespace NatTypeTester.Views;
+
+[ExposeServices(typeof(IViewFor<RFC5780ViewModel>))]
+[UsedImplicitly]
+public partial class RFC5780View : ITransientDependency
 {
-	[ExposeServices(typeof(IViewFor<RFC5780ViewModel>))]
-	[UsedImplicitly]
-	public partial class RFC5780View : ITransientDependency
+	public RFC5780View(RFC5780ViewModel viewModel)
 	{
-		public RFC5780View(RFC5780ViewModel viewModel)
-		{
-			InitializeComponent();
-			ViewModel = viewModel;
+		InitializeComponent();
+		ViewModel = viewModel;
 
-			this.WhenActivated(d =>
-			{
-				this.OneWayBind(ViewModel, vm => vm.Result5389.BindingTestResult, v => v.BindingTestTextBox.Text).DisposeWith(d);
+		this.WhenActivated(d =>
+		{
+			this.OneWayBind(ViewModel, vm => vm.Result5389.BindingTestResult, v => v.BindingTestTextBox.Text).DisposeWith(d);
 
-				this.OneWayBind(ViewModel, vm => vm.Result5389.MappingBehavior, v => v.MappingBehaviorTextBox.Text).DisposeWith(d);
+			this.OneWayBind(ViewModel, vm => vm.Result5389.MappingBehavior, v => v.MappingBehaviorTextBox.Text).DisposeWith(d);
 
-				this.OneWayBind(ViewModel, vm => vm.Result5389.FilteringBehavior, v => v.FilteringBehaviorTextBox.Text).DisposeWith(d);
+			this.OneWayBind(ViewModel, vm => vm.Result5389.FilteringBehavior, v => v.FilteringBehaviorTextBox.Text).DisposeWith(d);
 
-				this.Bind(ViewModel, vm => vm.Result5389.LocalEndPoint, v => v.LocalAddressTextBox.Text).DisposeWith(d);
+			this.Bind(ViewModel, vm => vm.Result5389.LocalEndPoint, v => v.LocalAddressTextBox.Text).DisposeWith(d);
 
-				this.OneWayBind(ViewModel, vm => vm.Result5389.PublicEndPoint, v => v.MappingAddressTextBox.Text).DisposeWith(d);
+			this.OneWayBind(ViewModel, vm => vm.Result5389.PublicEndPoint, v => v.MappingAddressTextBox.Text).DisposeWith(d);
 
-				this.BindCommand(ViewModel, vm => vm.DiscoveryNatType, v => v.DiscoveryButton).DisposeWith(d);
+			this.BindCommand(ViewModel, vm => vm.DiscoveryNatType, v => v.DiscoveryButton).DisposeWith(d);
 
-				this.Events().KeyDown
-						.Where(x => x.Key == Key.Enter && DiscoveryButton.Command.CanExecute(default))
-						.Subscribe(_ => DiscoveryButton.Command.Execute(default))
-						.DisposeWith(d);
+			this.Events().KeyDown
+				.Where(x => x.Key == Key.Enter && DiscoveryButton.Command.CanExecute(default))
+				.Subscribe(_ => DiscoveryButton.Command.Execute(default))
+				.DisposeWith(d);
 
-				ViewModel.DiscoveryNatType.ThrownExceptions.Subscribe(ex => _ = ex.HandleExceptionWithContentDialogAsync()).DisposeWith(d);
-			});
-		}
+			ViewModel.DiscoveryNatType.ThrownExceptions.Subscribe(ex => _ = ex.HandleExceptionWithContentDialogAsync()).DisposeWith(d);
+		});
 	}
 }

+ 23 - 24
NatTypeTester/Views/SettingView.xaml.cs

@@ -5,35 +5,34 @@ using STUN.Enums;
 using System.Reactive.Disposables;
 using Volo.Abp.DependencyInjection;
 
-namespace NatTypeTester.Views
+namespace NatTypeTester.Views;
+
+[ExposeServices(typeof(IViewFor<SettingViewModel>))]
+[UsedImplicitly]
+public partial class SettingView : ITransientDependency
 {
-	[ExposeServices(typeof(IViewFor<SettingViewModel>))]
-	[UsedImplicitly]
-	public partial class SettingView : ITransientDependency
+	public SettingView()
 	{
-		public SettingView()
-		{
-			InitializeComponent();
+		InitializeComponent();
 
-			this.WhenActivated(d =>
-			{
-				this.Bind(ViewModel, vm => vm.Config.ProxyServer, v => v.ProxyServerTextBox.Text).DisposeWith(d);
+		this.WhenActivated(d =>
+		{
+			this.Bind(ViewModel, vm => vm.Config.ProxyServer, v => v.ProxyServerTextBox.Text).DisposeWith(d);
 
-				this.Bind(ViewModel, vm => vm.Config.ProxyUser, v => v.ProxyUsernameTextBox.Text).DisposeWith(d);
+			this.Bind(ViewModel, vm => vm.Config.ProxyUser, v => v.ProxyUsernameTextBox.Text).DisposeWith(d);
 
-				this.Bind(ViewModel, vm => vm.Config.ProxyPassword, v => v.ProxyPasswordTextBox.Text).DisposeWith(d);
+			this.Bind(ViewModel, vm => vm.Config.ProxyPassword, v => v.ProxyPasswordTextBox.Text).DisposeWith(d);
 
-				this.Bind(ViewModel,
-					vm => vm.Config.ProxyType,
-					v => v.ProxyRadioButtons.SelectedIndex,
-					type => (int)type,
-					index =>
-					{
-						var type = (ProxyType)index;
-						ProxyConfigGrid.IsEnabled = type is not ProxyType.Plain;
-						return type;
-					}).DisposeWith(d);
-			});
-		}
+			this.Bind(ViewModel,
+				vm => vm.Config.ProxyType,
+				v => v.ProxyRadioButtons.SelectedIndex,
+				type => (int)type,
+				index =>
+				{
+					var type = (ProxyType)index;
+					ProxyConfigGrid.IsEnabled = type is not ProxyType.Plain;
+					return type;
+				}).DisposeWith(d);
+		});
 	}
 }

+ 6 - 7
STUN/Client/IStunClient.cs

@@ -2,12 +2,11 @@ using System;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Client
+namespace STUN.Client;
+
+public interface IStunClient : IDisposable
 {
-	public interface IStunClient : IDisposable
-	{
-		ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default);
-		ValueTask CloseProxyAsync(CancellationToken cancellationToken = default);
-		ValueTask QueryAsync(CancellationToken cancellationToken = default);
-	}
+	ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default);
+	ValueTask CloseProxyAsync(CancellationToken cancellationToken = default);
+	ValueTask QueryAsync(CancellationToken cancellationToken = default);
 }

+ 164 - 165
STUN/Client/StunClient3489.cs

@@ -12,225 +12,224 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Client
+namespace STUN.Client;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-10.1
+/// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
+/// </summary>
+public class StunClient3489 : IStunClient
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-10.1
-	/// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
-	/// </summary>
-	public class StunClient3489 : IStunClient
-	{
-		public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
+	public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
 
-		public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
+	public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
 
-		private readonly IPEndPoint _remoteEndPoint;
+	private readonly IPEndPoint _remoteEndPoint;
 
-		private readonly IUdpProxy _proxy;
+	private readonly IUdpProxy _proxy;
 
-		public ClassicStunResult State { get; } = new();
+	public ClassicStunResult State { get; } = new();
 
-		public StunClient3489(IPEndPoint server, IPEndPoint local, IUdpProxy? proxy = null)
-		{
-			Requires.NotNull(server, nameof(server));
-			Requires.NotNull(local, nameof(local));
+	public StunClient3489(IPEndPoint server, IPEndPoint local, IUdpProxy? proxy = null)
+	{
+		Requires.NotNull(server, nameof(server));
+		Requires.NotNull(local, nameof(local));
 
-			_proxy = proxy ?? new NoneUdpProxy(local);
+		_proxy = proxy ?? new NoneUdpProxy(local);
 
-			_remoteEndPoint = server;
+		_remoteEndPoint = server;
 
-			State.LocalEndPoint = local;
-		}
+		State.LocalEndPoint = local;
+	}
 
-		public async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
-		{
-			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-			cts.CancelAfter(ReceiveTimeout);
+	public async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
+	{
+		using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+		cts.CancelAfter(ReceiveTimeout);
 
-			await _proxy.ConnectAsync(cts.Token);
-		}
+		await _proxy.ConnectAsync(cts.Token);
+	}
+
+	public async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
+	{
+		await _proxy.CloseAsync(cancellationToken);
+	}
 
-		public async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
+	public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
+	{
+		State.Reset();
+
+		// test I
+		var response1 = await Test1Async(cancellationToken);
+		if (response1 is null)
 		{
-			await _proxy.CloseAsync(cancellationToken);
+			State.NatType = NatType.UdpBlocked;
+			return;
 		}
 
-		public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
-		{
-			State.Reset();
+		State.LocalEndPoint = new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
 
-			// test I
-			var response1 = await Test1Async(cancellationToken);
-			if (response1 is null)
-			{
-				State.NatType = NatType.UdpBlocked;
-				return;
-			}
+		var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
+		var changedAddress = response1.Message.GetChangedAddressAttribute();
 
-			State.LocalEndPoint = new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
+		State.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
 
-			var mappedAddress1 = response1.Message.GetMappedAddressAttribute();
-			var changedAddress = response1.Message.GetChangedAddressAttribute();
+		// 某些单 IP 服务器的迷惑操作
+		if (mappedAddress1 is null || changedAddress is null
+		                           || Equals(changedAddress.Address, response1.Remote.Address)
+		                           || changedAddress.Port == response1.Remote.Port)
+		{
+			State.NatType = NatType.UnsupportedServer;
+			return;
+		}
 
-			State.PublicEndPoint = mappedAddress1; // 显示 test I 得到的映射地址
+		// test II
+		var response2 = await Test2Async(changedAddress, cancellationToken);
+		var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();
 
-			// 某些单 IP 服务器的迷惑操作
-			if (mappedAddress1 is null || changedAddress is null
-				|| Equals(changedAddress.Address, response1.Remote.Address)
-				|| changedAddress.Port == response1.Remote.Port)
+		if (response2 is not null)
+		{
+			// 有些单 IP 服务器并不能测 NAT 类型
+			if (Equals(response1.Remote.Address, response2.Remote.Address) || response1.Remote.Port == response2.Remote.Port)
 			{
 				State.NatType = NatType.UnsupportedServer;
+				State.PublicEndPoint = mappedAddress2;
 				return;
 			}
+		}
 
-			// test II
-			var response2 = await Test2Async(changedAddress, cancellationToken);
-			var mappedAddress2 = response2?.Message.GetMappedAddressAttribute();
-
-			if (response2 is not null)
-			{
-				// 有些单 IP 服务器并不能测 NAT 类型
-				if (Equals(response1.Remote.Address, response2.Remote.Address) || response1.Remote.Port == response2.Remote.Port)
-				{
-					State.NatType = NatType.UnsupportedServer;
-					State.PublicEndPoint = mappedAddress2;
-					return;
-				}
-			}
-
-			// is Public IP == link's IP?
-			if (Equals(mappedAddress1.Address, response1.LocalAddress) && mappedAddress1.Port == LocalEndPoint.Port)
+		// is Public IP == link's IP?
+		if (Equals(mappedAddress1.Address, response1.LocalAddress) && mappedAddress1.Port == LocalEndPoint.Port)
+		{
+			// No NAT
+			if (response2 is null)
 			{
-				// No NAT
-				if (response2 is null)
-				{
-					State.NatType = NatType.SymmetricUdpFirewall;
-					State.PublicEndPoint = mappedAddress1;
-				}
-				else
-				{
-					State.NatType = NatType.OpenInternet;
-					State.PublicEndPoint = mappedAddress2;
-				}
-				return;
+				State.NatType = NatType.SymmetricUdpFirewall;
+				State.PublicEndPoint = mappedAddress1;
 			}
-
-			// NAT
-			if (response2 is not null)
+			else
 			{
-				State.NatType = NatType.FullCone;
+				State.NatType = NatType.OpenInternet;
 				State.PublicEndPoint = mappedAddress2;
-				return;
 			}
+			return;
+		}
 
-			// Test I(#2)
-			var response12 = await Test1_2Async(changedAddress, cancellationToken);
-			var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();
-
-			if (mappedAddress12 is null)
-			{
-				State.NatType = NatType.Unknown;
-				return;
-			}
+		// NAT
+		if (response2 is not null)
+		{
+			State.NatType = NatType.FullCone;
+			State.PublicEndPoint = mappedAddress2;
+			return;
+		}
 
-			if (!Equals(mappedAddress12, mappedAddress1))
-			{
-				State.NatType = NatType.Symmetric;
-				State.PublicEndPoint = mappedAddress12;
-				return;
-			}
+		// Test I(#2)
+		var response12 = await Test1_2Async(changedAddress, cancellationToken);
+		var mappedAddress12 = response12?.Message.GetMappedAddressAttribute();
 
-			// Test III
-			var response3 = await Test3Async(cancellationToken);
-			if (response3 is not null)
-			{
-				var mappedAddress3 = response3.Message.GetMappedAddressAttribute();
-				if (mappedAddress3 is not null
-					&& Equals(response3.Remote.Address, response1.Remote.Address)
-					&& response3.Remote.Port != response1.Remote.Port)
-				{
-					State.NatType = NatType.RestrictedCone;
-					State.PublicEndPoint = mappedAddress3;
-					return;
-				}
-			}
+		if (mappedAddress12 is null)
+		{
+			State.NatType = NatType.Unknown;
+			return;
+		}
 
-			State.NatType = NatType.PortRestrictedCone;
+		if (!Equals(mappedAddress12, mappedAddress1))
+		{
+			State.NatType = NatType.Symmetric;
 			State.PublicEndPoint = mappedAddress12;
+			return;
 		}
 
-		private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
+		// Test III
+		var response3 = await Test3Async(cancellationToken);
+		if (response3 is not null)
 		{
-			try
+			var mappedAddress3 = response3.Message.GetMappedAddressAttribute();
+			if (mappedAddress3 is not null
+			    && Equals(response3.Remote.Address, response1.Remote.Address)
+			    && response3.Remote.Port != response1.Remote.Port)
 			{
-				using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
-				var buffer = memoryOwner.Memory;
-				var length = sendMessage.WriteTo(buffer.Span);
+				State.NatType = NatType.RestrictedCone;
+				State.PublicEndPoint = mappedAddress3;
+				return;
+			}
+		}
+
+		State.NatType = NatType.PortRestrictedCone;
+		State.PublicEndPoint = mappedAddress12;
+	}
+
+	private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
+	{
+		try
+		{
+			using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
+			var buffer = memoryOwner.Memory;
+			var length = sendMessage.WriteTo(buffer.Span);
 
-				await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
+			await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
 
-				using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-				cts.CancelAfter(ReceiveTimeout);
-				var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
+			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+			cts.CancelAfter(ReceiveTimeout);
+			var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
 
-				var message = new StunMessage5389();
-				if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
-				{
-					return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
-				}
-			}
-			catch (Exception ex)
+			var message = new StunMessage5389();
+			if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
 			{
-				Debug.WriteLine(ex);
+				return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
 			}
-			return default;
 		}
-
-		public virtual async ValueTask<StunResponse?> Test1Async(CancellationToken cancellationToken)
+		catch (Exception ex)
 		{
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				MagicCookie = 0
-			};
-			return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
+			Debug.WriteLine(ex);
 		}
+		return default;
+	}
 
-		public virtual async ValueTask<StunResponse?> Test2Async(IPEndPoint other, CancellationToken cancellationToken)
+	public virtual async ValueTask<StunResponse?> Test1Async(CancellationToken cancellationToken)
+	{
+		var message = new StunMessage5389
 		{
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				MagicCookie = 0,
-				Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
-			};
-			return await RequestAsync(message, _remoteEndPoint, other, cancellationToken);
-		}
+			StunMessageType = StunMessageType.BindingRequest,
+			MagicCookie = 0
+		};
+		return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
+	}
 
-		public virtual async ValueTask<StunResponse?> Test1_2Async(IPEndPoint other, CancellationToken cancellationToken)
+	public virtual async ValueTask<StunResponse?> Test2Async(IPEndPoint other, CancellationToken cancellationToken)
+	{
+		var message = new StunMessage5389
 		{
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				MagicCookie = 0
-			};
-			return await RequestAsync(message, other, other, cancellationToken);
-		}
+			StunMessageType = StunMessageType.BindingRequest,
+			MagicCookie = 0,
+			Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
+		};
+		return await RequestAsync(message, _remoteEndPoint, other, cancellationToken);
+	}
 
-		public virtual async ValueTask<StunResponse?> Test3Async(CancellationToken cancellationToken)
+	public virtual async ValueTask<StunResponse?> Test1_2Async(IPEndPoint other, CancellationToken cancellationToken)
+	{
+		var message = new StunMessage5389
 		{
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				MagicCookie = 0,
-				Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
-			};
-			return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
-		}
+			StunMessageType = StunMessageType.BindingRequest,
+			MagicCookie = 0
+		};
+		return await RequestAsync(message, other, other, cancellationToken);
+	}
 
-		public void Dispose()
+	public virtual async ValueTask<StunResponse?> Test3Async(CancellationToken cancellationToken)
+	{
+		var message = new StunMessage5389
 		{
-			_proxy.Dispose();
-		}
+			StunMessageType = StunMessageType.BindingRequest,
+			MagicCookie = 0,
+			Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
+		};
+		return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
+	}
+
+	public void Dispose()
+	{
+		_proxy.Dispose();
 	}
 }

+ 215 - 216
STUN/Client/StunClient5389UDP.cs

@@ -14,293 +14,292 @@ using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Client
+namespace STUN.Client;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-7.2.1
+/// https://tools.ietf.org/html/rfc5780#section-4.2
+/// </summary>
+public class StunClient5389UDP : IStunClient
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-7.2.1
-	/// https://tools.ietf.org/html/rfc5780#section-4.2
-	/// </summary>
-	public class StunClient5389UDP : IStunClient
-	{
-		public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
+	public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_proxy.Client.LocalEndPoint!;
 
-		public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
+	public TimeSpan ReceiveTimeout { get; set; } = TimeSpan.FromSeconds(3);
 
-		private readonly IPEndPoint _remoteEndPoint;
+	private readonly IPEndPoint _remoteEndPoint;
 
-		private readonly IUdpProxy _proxy;
+	private readonly IUdpProxy _proxy;
 
-		public StunResult5389 State { get; } = new();
+	public StunResult5389 State { get; } = new();
 
-		public StunClient5389UDP(IPEndPoint server, IPEndPoint local, IUdpProxy? proxy = null)
-		{
-			Requires.NotNull(server, nameof(server));
-			Requires.NotNull(local, nameof(local));
+	public StunClient5389UDP(IPEndPoint server, IPEndPoint local, IUdpProxy? proxy = null)
+	{
+		Requires.NotNull(server, nameof(server));
+		Requires.NotNull(local, nameof(local));
 
-			_proxy = proxy ?? new NoneUdpProxy(local);
+		_proxy = proxy ?? new NoneUdpProxy(local);
 
-			_remoteEndPoint = server;
+		_remoteEndPoint = server;
 
-			State.LocalEndPoint = local;
-		}
+		State.LocalEndPoint = local;
+	}
 
-		public async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
-		{
-			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-			cts.CancelAfter(ReceiveTimeout);
+	public async ValueTask ConnectProxyAsync(CancellationToken cancellationToken = default)
+	{
+		using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+		cts.CancelAfter(ReceiveTimeout);
+
+		await _proxy.ConnectAsync(cts.Token);
+	}
 
-			await _proxy.ConnectAsync(cts.Token);
+	public async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
+	{
+		await _proxy.CloseAsync(cancellationToken);
+	}
+
+	public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
+	{
+		State.Reset();
+
+		await FilteringBehaviorTestBaseAsync(cancellationToken);
+		if (State.BindingTestResult is not BindingTestResult.Success
+		    || State.FilteringBehavior == FilteringBehavior.UnsupportedServer
+		   )
+		{
+			return;
 		}
 
-		public async ValueTask CloseProxyAsync(CancellationToken cancellationToken = default)
+		if (Equals(State.PublicEndPoint, State.LocalEndPoint))
 		{
-			await _proxy.CloseAsync(cancellationToken);
+			State.MappingBehavior = MappingBehavior.Direct;
+			return;
 		}
 
-		public async ValueTask QueryAsync(CancellationToken cancellationToken = default)
+		// MappingBehaviorTest test II
+		var result2 = await MappingBehaviorTestBase2Async(cancellationToken);
+		if (State.MappingBehavior is not MappingBehavior.Unknown)
 		{
-			State.Reset();
+			return;
+		}
 
-			await FilteringBehaviorTestBaseAsync(cancellationToken);
-			if (State.BindingTestResult is not BindingTestResult.Success
-				|| State.FilteringBehavior == FilteringBehavior.UnsupportedServer
-			)
-			{
-				return;
-			}
+		// MappingBehaviorTest test III
+		await MappingBehaviorTestBase3Async(result2, cancellationToken);
+	}
 
-			if (Equals(State.PublicEndPoint, State.LocalEndPoint))
-			{
-				State.MappingBehavior = MappingBehavior.Direct;
-				return;
-			}
+	public async ValueTask<StunResult5389> BindingTestAsync(CancellationToken cancellationToken = default)
+	{
+		return await BindingTestBaseAsync(_remoteEndPoint, cancellationToken);
+	}
 
-			// MappingBehaviorTest test II
-			var result2 = await MappingBehaviorTestBase2Async(cancellationToken);
-			if (State.MappingBehavior is not MappingBehavior.Unknown)
-			{
-				return;
-			}
+	public virtual async ValueTask<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken cancellationToken = default)
+	{
+		var result = new StunResult5389();
+		var test = new StunMessage5389
+		{
+			StunMessageType = StunMessageType.BindingRequest
+		};
+		var response1 = await RequestAsync(test, remote, remote, cancellationToken);
+		var mappedAddress1 = response1?.Message.GetXorMappedAddressAttribute();
+		var otherAddress = response1?.Message.GetOtherAddressAttribute();
 
-			// MappingBehaviorTest test III
-			await MappingBehaviorTestBase3Async(result2, cancellationToken);
+		if (response1 is null)
+		{
+			result.BindingTestResult = BindingTestResult.Fail;
 		}
-
-		public async ValueTask<StunResult5389> BindingTestAsync(CancellationToken cancellationToken = default)
+		else if (mappedAddress1 is null)
 		{
-			return await BindingTestBaseAsync(_remoteEndPoint, cancellationToken);
+			result.BindingTestResult = BindingTestResult.UnsupportedServer;
 		}
-
-		public virtual async ValueTask<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, CancellationToken cancellationToken = default)
+		else
 		{
-			var result = new StunResult5389();
-			var test = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest
-			};
-			var response1 = await RequestAsync(test, remote, remote, cancellationToken);
-			var mappedAddress1 = response1?.Message.GetXorMappedAddressAttribute();
-			var otherAddress = response1?.Message.GetOtherAddressAttribute();
+			result.BindingTestResult = BindingTestResult.Success;
+		}
 
-			if (response1 is null)
-			{
-				result.BindingTestResult = BindingTestResult.Fail;
-			}
-			else if (mappedAddress1 is null)
-			{
-				result.BindingTestResult = BindingTestResult.UnsupportedServer;
-			}
-			else
-			{
-				result.BindingTestResult = BindingTestResult.Success;
-			}
+		var local = response1 is null ? null : new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
 
-			var local = response1 is null ? null : new IPEndPoint(response1.LocalAddress, LocalEndPoint.Port);
+		result.LocalEndPoint = local;
+		result.PublicEndPoint = mappedAddress1;
+		result.OtherEndPoint = otherAddress;
 
-			result.LocalEndPoint = local;
-			result.PublicEndPoint = mappedAddress1;
-			result.OtherEndPoint = otherAddress;
+		return result;
+	}
 
-			return result;
+	public async ValueTask MappingBehaviorTestAsync(CancellationToken cancellationToken = default)
+	{
+		State.Reset();
+
+		// test I
+		var bindingResult = await BindingTestAsync(cancellationToken);
+		State.Clone(bindingResult);
+		if (State.BindingTestResult is not BindingTestResult.Success)
+		{
+			return;
 		}
 
-		public async ValueTask MappingBehaviorTestAsync(CancellationToken cancellationToken = default)
+		if (!HasValidOtherAddress(State.OtherEndPoint))
 		{
-			State.Reset();
+			State.MappingBehavior = MappingBehavior.UnsupportedServer;
+			return;
+		}
 
-			// test I
-			var bindingResult = await BindingTestAsync(cancellationToken);
-			State.Clone(bindingResult);
-			if (State.BindingTestResult is not BindingTestResult.Success)
-			{
-				return;
-			}
+		if (Equals(State.PublicEndPoint, State.LocalEndPoint))
+		{
+			State.MappingBehavior = MappingBehavior.Direct; // or Endpoint-Independent
+			return;
+		}
 
-			if (!HasValidOtherAddress(State.OtherEndPoint))
-			{
-				State.MappingBehavior = MappingBehavior.UnsupportedServer;
-				return;
-			}
+		// test II
+		var result2 = await MappingBehaviorTestBase2Async(cancellationToken);
+		if (State.MappingBehavior is not MappingBehavior.Unknown)
+		{
+			return;
+		}
 
-			if (Equals(State.PublicEndPoint, State.LocalEndPoint))
-			{
-				State.MappingBehavior = MappingBehavior.Direct; // or Endpoint-Independent
-				return;
-			}
+		// test III
+		await MappingBehaviorTestBase3Async(result2, cancellationToken);
+	}
 
-			// test II
-			var result2 = await MappingBehaviorTestBase2Async(cancellationToken);
-			if (State.MappingBehavior is not MappingBehavior.Unknown)
-			{
-				return;
-			}
+	private async ValueTask<StunResult5389> MappingBehaviorTestBase2Async(CancellationToken cancellationToken)
+	{
+		Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
 
-			// test III
-			await MappingBehaviorTestBase3Async(result2, cancellationToken);
-		}
+		var result2 = await BindingTestBaseAsync(new IPEndPoint(State.OtherEndPoint.Address, _remoteEndPoint.Port), cancellationToken);
 
-		private async ValueTask<StunResult5389> MappingBehaviorTestBase2Async(CancellationToken cancellationToken)
+		if (result2.BindingTestResult is not BindingTestResult.Success)
 		{
-			Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
+			State.MappingBehavior = MappingBehavior.Fail;
+		}
+		else if (Equals(result2.PublicEndPoint, State.PublicEndPoint))
+		{
+			State.MappingBehavior = MappingBehavior.EndpointIndependent;
+		}
 
-			var result2 = await BindingTestBaseAsync(new IPEndPoint(State.OtherEndPoint.Address, _remoteEndPoint.Port), cancellationToken);
+		return result2;
+	}
 
-			if (result2.BindingTestResult is not BindingTestResult.Success)
-			{
-				State.MappingBehavior = MappingBehavior.Fail;
-			}
-			else if (Equals(result2.PublicEndPoint, State.PublicEndPoint))
-			{
-				State.MappingBehavior = MappingBehavior.EndpointIndependent;
-			}
+	private async ValueTask MappingBehaviorTestBase3Async(StunResult5389 result2, CancellationToken cancellationToken)
+	{
+		Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
 
-			return result2;
+		var result3 = await BindingTestBaseAsync(State.OtherEndPoint, cancellationToken);
+		if (result3.BindingTestResult is not BindingTestResult.Success)
+		{
+			State.MappingBehavior = MappingBehavior.Fail;
+			return;
 		}
 
-		private async ValueTask MappingBehaviorTestBase3Async(StunResult5389 result2, CancellationToken cancellationToken)
-		{
-			Verify.Operation(State.OtherEndPoint is not null, @"OTHER-ADDRESS is not returned");
+		State.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
+	}
 
-			var result3 = await BindingTestBaseAsync(State.OtherEndPoint, cancellationToken);
-			if (result3.BindingTestResult is not BindingTestResult.Success)
-			{
-				State.MappingBehavior = MappingBehavior.Fail;
-				return;
-			}
+	public async ValueTask FilteringBehaviorTestAsync(CancellationToken cancellationToken = default)
+	{
+		State.Reset();
+		await FilteringBehaviorTestBaseAsync(cancellationToken);
+	}
 
-			State.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
+	private async ValueTask FilteringBehaviorTestBaseAsync(CancellationToken cancellationToken)
+	{
+		// test I
+		var bindingResult = await BindingTestAsync(cancellationToken);
+		State.Clone(bindingResult);
+		if (State.BindingTestResult is not BindingTestResult.Success)
+		{
+			return;
 		}
 
-		public async ValueTask FilteringBehaviorTestAsync(CancellationToken cancellationToken = default)
+		if (!HasValidOtherAddress(State.OtherEndPoint))
 		{
-			State.Reset();
-			await FilteringBehaviorTestBaseAsync(cancellationToken);
+			State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
+			return;
 		}
 
-		private async ValueTask FilteringBehaviorTestBaseAsync(CancellationToken cancellationToken)
+		// test II
+		var response2 = await FilteringBehaviorTest2Async(cancellationToken);
+		if (response2 is not null)
 		{
-			// test I
-			var bindingResult = await BindingTestAsync(cancellationToken);
-			State.Clone(bindingResult);
-			if (State.BindingTestResult is not BindingTestResult.Success)
-			{
-				return;
-			}
-
-			if (!HasValidOtherAddress(State.OtherEndPoint))
-			{
-				State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
-				return;
-			}
-
-			// test II
-			var response2 = await FilteringBehaviorTest2Async(cancellationToken);
-			if (response2 is not null)
-			{
-				State.FilteringBehavior = Equals(response2.Remote, State.OtherEndPoint) ? FilteringBehavior.EndpointIndependent : FilteringBehavior.UnsupportedServer;
-				return;
-			}
-
-			// test III
-			var response3 = await FilteringBehaviorTest3Async(cancellationToken);
-			if (response3 is null)
-			{
-				State.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
-				return;
-			}
-
-			if (Equals(response3.Remote.Address, _remoteEndPoint.Address) && response3.Remote.Port != _remoteEndPoint.Port)
-			{
-				State.FilteringBehavior = FilteringBehavior.AddressDependent;
-			}
-			else
-			{
-				State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
-			}
+			State.FilteringBehavior = Equals(response2.Remote, State.OtherEndPoint) ? FilteringBehavior.EndpointIndependent : FilteringBehavior.UnsupportedServer;
+			return;
 		}
 
-		public virtual async ValueTask<StunResponse?> FilteringBehaviorTest2Async(CancellationToken cancellationToken = default)
+		// test III
+		var response3 = await FilteringBehaviorTest3Async(cancellationToken);
+		if (response3 is null)
 		{
-			Assumes.NotNull(State.OtherEndPoint);
+			State.FilteringBehavior = FilteringBehavior.AddressAndPortDependent;
+			return;
+		}
 
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
-			};
-			return await RequestAsync(message, _remoteEndPoint, State.OtherEndPoint, cancellationToken);
+		if (Equals(response3.Remote.Address, _remoteEndPoint.Address) && response3.Remote.Port != _remoteEndPoint.Port)
+		{
+			State.FilteringBehavior = FilteringBehavior.AddressDependent;
 		}
+		else
+		{
+			State.FilteringBehavior = FilteringBehavior.UnsupportedServer;
+		}
+	}
+
+	public virtual async ValueTask<StunResponse?> FilteringBehaviorTest2Async(CancellationToken cancellationToken = default)
+	{
+		Assumes.NotNull(State.OtherEndPoint);
 
-		public virtual async ValueTask<StunResponse?> FilteringBehaviorTest3Async(CancellationToken cancellationToken = default)
+		var message = new StunMessage5389
 		{
-			Assumes.NotNull(State.OtherEndPoint);
+			StunMessageType = StunMessageType.BindingRequest,
+			Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
+		};
+		return await RequestAsync(message, _remoteEndPoint, State.OtherEndPoint, cancellationToken);
+	}
 
-			var message = new StunMessage5389
-			{
-				StunMessageType = StunMessageType.BindingRequest,
-				Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
-			};
-			return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
-		}
+	public virtual async ValueTask<StunResponse?> FilteringBehaviorTest3Async(CancellationToken cancellationToken = default)
+	{
+		Assumes.NotNull(State.OtherEndPoint);
 
-		[MethodImpl(MethodImplOptions.AggressiveInlining)]
-		private bool HasValidOtherAddress([NotNullWhen(true)] IPEndPoint? other)
+		var message = new StunMessage5389
 		{
-			return other is not null
-				   && !Equals(other.Address, _remoteEndPoint.Address)
-				   && other.Port != _remoteEndPoint.Port;
-		}
+			StunMessageType = StunMessageType.BindingRequest,
+			Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
+		};
+		return await RequestAsync(message, _remoteEndPoint, _remoteEndPoint, cancellationToken);
+	}
+
+	[MethodImpl(MethodImplOptions.AggressiveInlining)]
+	private bool HasValidOtherAddress([NotNullWhen(true)] IPEndPoint? other)
+	{
+		return other is not null
+		       && !Equals(other.Address, _remoteEndPoint.Address)
+		       && other.Port != _remoteEndPoint.Port;
+	}
 
-		private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
+	private async ValueTask<StunResponse?> RequestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive, CancellationToken cancellationToken)
+	{
+		try
 		{
-			try
-			{
-				using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
-				var buffer = memoryOwner.Memory;
-				var length = sendMessage.WriteTo(buffer.Span);
+			using var memoryOwner = MemoryPool<byte>.Shared.Rent(0x10000);
+			var buffer = memoryOwner.Memory;
+			var length = sendMessage.WriteTo(buffer.Span);
 
-				await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
+			await _proxy.SendToAsync(buffer[..length], SocketFlags.None, remote, cancellationToken);
 
-				using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-				cts.CancelAfter(ReceiveTimeout);
-				var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
+			using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+			cts.CancelAfter(ReceiveTimeout);
+			var r = await _proxy.ReceiveMessageFromAsync(buffer, SocketFlags.None, receive, cts.Token);
 
-				var message = new StunMessage5389();
-				if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
-				{
-					return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
-				}
-			}
-			catch (Exception ex)
+			var message = new StunMessage5389();
+			if (message.TryParse(buffer.Span[..r.ReceivedBytes]) && message.IsSameTransaction(sendMessage))
 			{
-				Debug.WriteLine(ex);
+				return new StunResponse(message, (IPEndPoint)r.RemoteEndPoint, r.PacketInformation.Address);
 			}
-			return default;
 		}
-
-		public void Dispose()
+		catch (Exception ex)
 		{
-			_proxy.Dispose();
+			Debug.WriteLine(ex);
 		}
+		return default;
+	}
+
+	public void Dispose()
+	{
+		_proxy.Dispose();
 	}
 }

+ 40 - 41
STUN/Enums/AttributeType.cs

@@ -1,43 +1,42 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+/// <summary>
+/// STUN Attribute Registry
+/// </summary>
+/// <remarks>
+/// https://tools.ietf.org/html/rfc3489#section-11.2
+/// https://tools.ietf.org/html/rfc5389#section-18.2
+/// https://tools.ietf.org/html/rfc5780#section-9.1
+/// https://tools.ietf.org/html/rfc8489#section-18.3
+/// </remarks>
+public enum AttributeType : ushort
 {
-	/// <summary>
-	/// STUN Attribute Registry
-	/// </summary>
-	/// <remarks>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2
-	/// https://tools.ietf.org/html/rfc5389#section-18.2
-	/// https://tools.ietf.org/html/rfc5780#section-9.1
-	/// https://tools.ietf.org/html/rfc8489#section-18.3
-	/// </remarks>
-	public enum AttributeType : ushort
-	{
-		Useless = 0x0000,
-		MappedAddress = 0x0001,
-		ResponseAddress = 0x0002,
-		ChangeRequest = 0x0003,
-		SourceAddress = 0x0004,
-		ChangedAddress = 0x0005,
-		Username = 0x0006,
-		Password = 0x0007,
-		MessageIntegrity = 0x0008,
-		ErrorCode = 0x0009,
-		UnknownAttribute = 0x000A,
-		ReflectedFrom = 0x000B,
-		Realm = 0x0014,
-		Nonce = 0x0015,
-		MessageIntegritySha256 = 0x001C,
-		PasswordAlgorithm = 0x001D,
-		UserHash = 0x001E,
-		XorMappedAddress = 0x0020,
-		Padding = 0x0026,
-		ResponsePort = 0x0027,
-		PasswordAlgorithms = 0x8002,
-		AlternateDomain = 0x8003,
-		Software = 0x8022,
-		AlternateServer = 0x8023,
-		CacheTimeout = 0x8027,
-		Fingerprint = 0x8028,
-		ResponseOrigin = 0x802B,
-		OtherAddress = 0x802C,
-	}
+	Useless = 0x0000,
+	MappedAddress = 0x0001,
+	ResponseAddress = 0x0002,
+	ChangeRequest = 0x0003,
+	SourceAddress = 0x0004,
+	ChangedAddress = 0x0005,
+	Username = 0x0006,
+	Password = 0x0007,
+	MessageIntegrity = 0x0008,
+	ErrorCode = 0x0009,
+	UnknownAttribute = 0x000A,
+	ReflectedFrom = 0x000B,
+	Realm = 0x0014,
+	Nonce = 0x0015,
+	MessageIntegritySha256 = 0x001C,
+	PasswordAlgorithm = 0x001D,
+	UserHash = 0x001E,
+	XorMappedAddress = 0x0020,
+	Padding = 0x0026,
+	ResponsePort = 0x0027,
+	PasswordAlgorithms = 0x8002,
+	AlternateDomain = 0x8003,
+	Software = 0x8022,
+	AlternateServer = 0x8023,
+	CacheTimeout = 0x8027,
+	Fingerprint = 0x8028,
+	ResponseOrigin = 0x802B,
+	OtherAddress = 0x802C,
 }

+ 7 - 8
STUN/Enums/BindingTestResult.cs

@@ -1,10 +1,9 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+public enum BindingTestResult
 {
-	public enum BindingTestResult
-	{
-		Unknown,
-		UnsupportedServer,
-		Success,
-		Fail
-	}
+	Unknown,
+	UnsupportedServer,
+	Success,
+	Fail
 }

+ 7 - 8
STUN/Enums/Class.cs

@@ -1,10 +1,9 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+internal enum Class : ushort
 {
-	internal enum Class : ushort
-	{
-		Request = 0b00000_0_000_0_0000,
-		Indication = 0b00000_0_000_1_0000,
-		SuccessResponse = 0b00000_1_000_0_0000,
-		ErrorResponse = 0b00000_1_000_1_0000,
-	}
+	Request = 0b00000_0_000_0_0000,
+	Indication = 0b00000_0_000_1_0000,
+	SuccessResponse = 0b00000_1_000_0_0000,
+	ErrorResponse = 0b00000_1_000_1_0000,
 }

+ 9 - 10
STUN/Enums/FilteringBehavior.cs

@@ -1,12 +1,11 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+public enum FilteringBehavior
 {
-	public enum FilteringBehavior
-	{
-		Unknown,
-		UnsupportedServer,
-		EndpointIndependent,
-		AddressDependent,
-		AddressAndPortDependent,
-		Fail
-	}
+	Unknown,
+	UnsupportedServer,
+	EndpointIndependent,
+	AddressDependent,
+	AddressAndPortDependent,
+	Fail
 }

+ 8 - 9
STUN/Enums/IpFamily.cs

@@ -1,11 +1,10 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.1
+/// </summary>
+public enum IpFamily : byte
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.1
-	/// </summary>
-	public enum IpFamily : byte
-	{
-		IPv4 = 0x01,
-		IPv6 = 0x02
-	}
+	IPv4 = 0x01,
+	IPv6 = 0x02
 }

+ 10 - 11
STUN/Enums/MappingBehavior.cs

@@ -1,13 +1,12 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+public enum MappingBehavior
 {
-	public enum MappingBehavior
-	{
-		Unknown,
-		UnsupportedServer,
-		Direct,
-		EndpointIndependent,
-		AddressDependent,
-		AddressAndPortDependent,
-		Fail
-	}
+	Unknown,
+	UnsupportedServer,
+	Direct,
+	EndpointIndependent,
+	AddressDependent,
+	AddressAndPortDependent,
+	Fail
 }

+ 5 - 6
STUN/Enums/Method.cs

@@ -1,8 +1,7 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+internal enum Method : ushort
 {
-	internal enum Method : ushort
-	{
-		Binding = 0b00000_0_000_0_0001,
-		SharedSecret = 0b00000_0_000_0_0010,
-	}
+	Binding = 0b00000_0_000_0_0001,
+	SharedSecret = 0b00000_0_000_0_0010,
 }

+ 53 - 54
STUN/Enums/NatType.cs

@@ -1,66 +1,65 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-5
+/// https://tools.ietf.org/html/rfc3489#section-10.1
+/// </summary>
+public enum NatType
 {
 	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-5
-	/// https://tools.ietf.org/html/rfc3489#section-10.1
+	/// Unknown
 	/// </summary>
-	public enum NatType
-	{
-		/// <summary>
-		/// Unknown
-		/// </summary>
-		Unknown,
+	Unknown,
 
-		/// <summary>
-		/// Server is not unsupported for testing NAT type
-		/// </summary>
-		UnsupportedServer,
+	/// <summary>
+	/// Server is not unsupported for testing NAT type
+	/// </summary>
+	UnsupportedServer,
 
-		/// <summary>
-		/// UDP is always blocked.
-		/// </summary>
-		UdpBlocked,
+	/// <summary>
+	/// UDP is always blocked.
+	/// </summary>
+	UdpBlocked,
 
-		/// <summary>
-		/// No NAT, public IP, no firewall.
-		/// </summary>
-		OpenInternet,
+	/// <summary>
+	/// No NAT, public IP, no firewall.
+	/// </summary>
+	OpenInternet,
 
-		/// <summary>
-		/// No NAT, public IP, but symmetric UDP firewall.
-		/// </summary>
-		SymmetricUdpFirewall,
+	/// <summary>
+	/// No NAT, public IP, but symmetric UDP firewall.
+	/// </summary>
+	SymmetricUdpFirewall,
 
-		/// <summary>
-		/// A full cone NAT is one where all requests from the same internal IP address and port are 
-		/// mapped to the same external IP address and port. Furthermore, any external host can send 
-		/// a packet to the internal host, by sending a packet to the mapped external address.
-		/// </summary>
-		FullCone,
+	/// <summary>
+	/// A full cone NAT is one where all requests from the same internal IP address and port are 
+	/// mapped to the same external IP address and port. Furthermore, any external host can send 
+	/// a packet to the internal host, by sending a packet to the mapped external address.
+	/// </summary>
+	FullCone,
 
-		/// <summary>
-		/// A restricted cone NAT is one where all requests from the same internal IP address and 
-		/// port are mapped to the same external IP address and port. Unlike a full cone NAT, an external
-		/// host (with IP address X) can send a packet to the internal host only if the internal host 
-		/// had previously sent a packet to IP address X.
-		/// </summary>
-		RestrictedCone,
+	/// <summary>
+	/// A restricted cone NAT is one where all requests from the same internal IP address and 
+	/// port are mapped to the same external IP address and port. Unlike a full cone NAT, an external
+	/// host (with IP address X) can send a packet to the internal host only if the internal host 
+	/// had previously sent a packet to IP address X.
+	/// </summary>
+	RestrictedCone,
 
-		/// <summary>
-		/// A port restricted cone NAT is like a restricted cone NAT, but the restriction 
-		/// includes port numbers. Specifically, an external host can send a packet, with source IP
-		/// address X and source port P, to the internal host only if the internal host had previously 
-		/// sent a packet to IP address X and port P.
-		/// </summary>
-		PortRestrictedCone,
+	/// <summary>
+	/// A port restricted cone NAT is like a restricted cone NAT, but the restriction 
+	/// includes port numbers. Specifically, an external host can send a packet, with source IP
+	/// address X and source port P, to the internal host only if the internal host had previously 
+	/// sent a packet to IP address X and port P.
+	/// </summary>
+	PortRestrictedCone,
 
-		/// <summary>
-		/// A symmetric NAT is one where all requests from the same internal IP address and port, 
-		/// to a specific destination IP address and port, are mapped to the same external IP address and
-		/// port.  If the same host sends a packet with the same source address and port, but to 
-		/// a different destination, a different mapping is used. Furthermore, only the external host that
-		/// receives a packet can send a UDP packet back to the internal host.
-		/// </summary>
-		Symmetric
-	}
+	/// <summary>
+	/// A symmetric NAT is one where all requests from the same internal IP address and port, 
+	/// to a specific destination IP address and port, are mapped to the same external IP address and
+	/// port.  If the same host sends a packet with the same source address and port, but to 
+	/// a different destination, a different mapping is used. Furthermore, only the external host that
+	/// receives a packet can send a UDP packet back to the internal host.
+	/// </summary>
+	Symmetric
 }

+ 5 - 6
STUN/Enums/ProxyType.cs

@@ -1,8 +1,7 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+public enum ProxyType
 {
-	public enum ProxyType
-	{
-		Plain = 0,
-		Socks5 = 1
-	}
+	Plain = 0,
+	Socks5 = 1
 }

+ 31 - 32
STUN/Enums/StunMessageType.cs

@@ -1,41 +1,40 @@
-namespace STUN.Enums
+namespace STUN.Enums;
+
+/// <summary>
+/// This enum specifies STUN message type.
+/// </summary>
+/// <returns>
+/// https://tools.ietf.org/html/rfc5389#section-6
+/// </returns>
+public enum StunMessageType : ushort
 {
 	/// <summary>
-	/// This enum specifies STUN message type.
+	/// STUN message is binding request.
 	/// </summary>
-	/// <returns>
-	/// https://tools.ietf.org/html/rfc5389#section-6
-	/// </returns>
-	public enum StunMessageType : ushort
-	{
-		/// <summary>
-		/// STUN message is binding request.
-		/// </summary>
-		BindingRequest = Class.Request | Method.Binding,
+	BindingRequest = Class.Request | Method.Binding,
 
-		/// <summary>
-		/// STUN message is binding request success response.
-		/// </summary>
-		BindingResponse = Class.SuccessResponse | Method.Binding,
+	/// <summary>
+	/// STUN message is binding request success response.
+	/// </summary>
+	BindingResponse = Class.SuccessResponse | Method.Binding,
 
-		/// <summary>
-		/// STUN message is binding request error response.
-		/// </summary>
-		BindingErrorResponse = Class.ErrorResponse | Method.Binding,
+	/// <summary>
+	/// STUN message is binding request error response.
+	/// </summary>
+	BindingErrorResponse = Class.ErrorResponse | Method.Binding,
 
-		/// <summary>
-		/// STUN message is "shared secret" request.
-		/// </summary>
-		SharedSecretRequest = Class.Request | Method.SharedSecret,
+	/// <summary>
+	/// STUN message is "shared secret" request.
+	/// </summary>
+	SharedSecretRequest = Class.Request | Method.SharedSecret,
 
-		/// <summary>
-		/// STUN message is "shared secret" request success response.
-		/// </summary>
-		SharedSecretResponse = Class.SuccessResponse | Method.SharedSecret,
+	/// <summary>
+	/// STUN message is "shared secret" request success response.
+	/// </summary>
+	SharedSecretResponse = Class.SuccessResponse | Method.SharedSecret,
 
-		/// <summary>
-		/// STUN message is "shared secret" request error response.
-		/// </summary>
-		SharedSecretErrorResponse = Class.ErrorResponse | Method.SharedSecret,
-	}
+	/// <summary>
+	/// STUN message is "shared secret" request error response.
+	/// </summary>
+	SharedSecretErrorResponse = Class.ErrorResponse | Method.SharedSecret,
 }

+ 8 - 10
STUN/Enums/TransportType.cs

@@ -1,12 +1,10 @@
-namespace STUN.Enums
-{
-	// Only UDP is supported
+namespace STUN.Enums;
+// Only UDP is supported
 
-	public enum TransportType
-	{
-		Udp,
-		Tcp,
-		Tls,
-		Dtls,
-	}
+public enum TransportType
+{
+	Udp,
+	Tcp,
+	Tls,
+	Dtls,
 }

+ 48 - 49
STUN/HostnameEndpoint.cs

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

+ 72 - 73
STUN/Messages/StunAttribute.cs

@@ -5,93 +5,92 @@ using System;
 using System.Buffers.Binary;
 using System.Security.Cryptography;
 
-namespace STUN.Messages
+namespace STUN.Messages;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15
+/// </summary>
+public class StunAttribute
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15
-	/// </summary>
-	public class StunAttribute
-	{
-		/*
-            Length 是大端
-            必须4字节对齐
-            对齐的字节可以是任意值
-             0                   1                   2                   3
-             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-            |         Type                  |            Length             |
-            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-            |                         Value (variable)                ....
-            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-         */
+	/*
+        Length 是大端
+        必须4字节对齐
+        对齐的字节可以是任意值
+         0                   1                   2                   3
+         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |         Type                  |            Length             |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |                         Value (variable)                ....
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     */
 
-		public AttributeType Type { get; set; } = AttributeType.Useless;
+	public AttributeType Type { get; set; } = AttributeType.Useless;
 
-		public ushort Length { get; set; }
+	public ushort Length { get; set; }
 
-		public ushort RealLength => (ushort)(Type == AttributeType.Useless ? 0 : 4 + Length + (4 - Length % 4) % 4);
+	public ushort RealLength => (ushort)(Type == AttributeType.Useless ? 0 : 4 + Length + (4 - Length % 4) % 4);
 
-		public IStunAttributeValue Value { get; set; } = new UselessStunAttributeValue();
+	public IStunAttributeValue Value { get; set; } = new UselessStunAttributeValue();
 
-		public int WriteTo(Span<byte> buffer)
-		{
-			var length = 4 + Length;
-			var n = (4 - length % 4) % 4; // 填充的字节数
-			var totalLength = length + n;
+	public int WriteTo(Span<byte> buffer)
+	{
+		var length = 4 + Length;
+		var n = (4 - length % 4) % 4; // 填充的字节数
+		var totalLength = length + n;
+
+		Requires.Range(buffer.Length >= totalLength, nameof(buffer));
+
+		BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)Type);
+		BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Length);
+		var valueLength = Value.WriteTo(buffer[4..]);
+
+		Assumes.True(valueLength == Length);
+
+		RandomNumberGenerator.Fill(buffer.Slice(length, n));
 
-			Requires.Range(buffer.Length >= totalLength, nameof(buffer));
+		return totalLength;
+	}
 
-			BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)Type);
-			BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Length);
-			var valueLength = Value.WriteTo(buffer[4..]);
+	/// <returns>
+	/// Parse 成功字节,0 则表示 Parse 失败
+	/// </returns>
+	public int TryParse(ReadOnlySpan<byte> buffer, ReadOnlySpan<byte> magicCookieAndTransactionId)
+	{
+		if (buffer.Length < 4)
+		{
+			return 0;
+		}
 
-			Assumes.True(valueLength == Length);
+		Type = (AttributeType)BinaryPrimitives.ReadUInt16BigEndian(buffer);
 
-			RandomNumberGenerator.Fill(buffer.Slice(length, n));
+		Length = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
 
-			return totalLength;
+		if (buffer.Length < 4 + Length)
+		{
+			return 0;
 		}
 
-		/// <returns>
-		/// Parse 成功字节,0 则表示 Parse 失败
-		/// </returns>
-		public int TryParse(ReadOnlySpan<byte> buffer, ReadOnlySpan<byte> magicCookieAndTransactionId)
+		var value = buffer.Slice(4, Length);
+
+		IStunAttributeValue t = Type switch
 		{
-			if (buffer.Length < 4)
-			{
-				return 0;
-			}
-
-			Type = (AttributeType)BinaryPrimitives.ReadUInt16BigEndian(buffer);
-
-			Length = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
-
-			if (buffer.Length < 4 + Length)
-			{
-				return 0;
-			}
-
-			var value = buffer.Slice(4, Length);
-
-			IStunAttributeValue t = Type switch
-			{
-				AttributeType.MappedAddress => new MappedAddressStunAttributeValue(),
-				AttributeType.XorMappedAddress => new XorMappedAddressStunAttributeValue(magicCookieAndTransactionId),
-				AttributeType.ResponseAddress => new ResponseAddressStunAttributeValue(),
-				AttributeType.ChangeRequest => new ChangeRequestStunAttributeValue(),
-				AttributeType.SourceAddress => new SourceAddressStunAttributeValue(),
-				AttributeType.ChangedAddress => new ChangedAddressStunAttributeValue(),
-				AttributeType.OtherAddress => new OtherAddressStunAttributeValue(),
-				AttributeType.ReflectedFrom => new ReflectedFromStunAttributeValue(),
-				AttributeType.ErrorCode => new ErrorCodeStunAttributeValue(),
-				_ => new UselessStunAttributeValue()
-			};
-			if (t.TryParse(value))
-			{
-				Value = t;
-			}
-
-			return 4 + Length + (4 - Length % 4) % 4; // 对齐
+			AttributeType.MappedAddress => new MappedAddressStunAttributeValue(),
+			AttributeType.XorMappedAddress => new XorMappedAddressStunAttributeValue(magicCookieAndTransactionId),
+			AttributeType.ResponseAddress => new ResponseAddressStunAttributeValue(),
+			AttributeType.ChangeRequest => new ChangeRequestStunAttributeValue(),
+			AttributeType.SourceAddress => new SourceAddressStunAttributeValue(),
+			AttributeType.ChangedAddress => new ChangedAddressStunAttributeValue(),
+			AttributeType.OtherAddress => new OtherAddressStunAttributeValue(),
+			AttributeType.ReflectedFrom => new ReflectedFromStunAttributeValue(),
+			AttributeType.ErrorCode => new ErrorCodeStunAttributeValue(),
+			_ => new UselessStunAttributeValue()
+		};
+		if (t.TryParse(value))
+		{
+			Value = t;
 		}
+
+		return 4 + Length + (4 - Length % 4) % 4; // 对齐
 	}
 }

+ 54 - 55
STUN/Messages/StunAttributeValues/AddressStunAttributeValue.cs

@@ -5,76 +5,75 @@ using System.Buffers.Binary;
 using System.Net;
 using System.Net.Sockets;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.1
+/// </summary>
+public abstract class AddressStunAttributeValue : IStunAttributeValue
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.1
-	/// </summary>
-	public abstract class AddressStunAttributeValue : IStunAttributeValue
-	{
-		public IpFamily Family { get; set; }
+	public IpFamily Family { get; set; }
 
-		public ushort Port { get; set; }
+	public ushort Port { get; set; }
 
-		public IPAddress? Address { get; set; }
+	public IPAddress? Address { get; set; }
 
-		public virtual int WriteTo(Span<byte> buffer)
-		{
-			Verify.Operation(Address is not null, @"You should set Address info!");
+	public virtual int WriteTo(Span<byte> buffer)
+	{
+		Verify.Operation(Address is not null, @"You should set Address info!");
 
-			Requires.Range(buffer.Length >= 4 + 4, nameof(buffer));
+		Requires.Range(buffer.Length >= 4 + 4, nameof(buffer));
 
-			buffer[0] = 0;
-			buffer[1] = (byte)Family;
-			BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Port);
-			Requires.Range(Address.TryWriteBytes(buffer[4..], out var bytesWritten), nameof(buffer));
+		buffer[0] = 0;
+		buffer[1] = (byte)Family;
+		BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Port);
+		Requires.Range(Address.TryWriteBytes(buffer[4..], out var bytesWritten), nameof(buffer));
 
-			return 4 + bytesWritten;
-		}
+		return 4 + bytesWritten;
+	}
+
+	public virtual bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		var length = 4;
 
-		public virtual bool TryParse(ReadOnlySpan<byte> buffer)
+		if (buffer.Length < length)
 		{
-			var length = 4;
+			return false;
+		}
 
-			if (buffer.Length < length)
-			{
-				return false;
-			}
-
-			Family = (IpFamily)buffer[1];
-
-			switch (Family)
-			{
-				case IpFamily.IPv4:
-					length += 4;
-					break;
-				case IpFamily.IPv6:
-					length += 16;
-					break;
-				default:
-					return false;
-			}
-
-			if (buffer.Length != length)
-			{
+		Family = (IpFamily)buffer[1];
+
+		switch (Family)
+		{
+			case IpFamily.IPv4:
+				length += 4;
+				break;
+			case IpFamily.IPv6:
+				length += 16;
+				break;
+			default:
 				return false;
-			}
+		}
+
+		if (buffer.Length != length)
+		{
+			return false;
+		}
 
-			Port = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
+		Port = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
 
-			Address = new IPAddress(buffer[4..]);
+		Address = new IPAddress(buffer[4..]);
 
-			return true;
-		}
+		return true;
+	}
 
-		public override string? ToString()
+	public override string? ToString()
+	{
+		return Address?.AddressFamily switch
 		{
-			return Address?.AddressFamily switch
-			{
-				AddressFamily.InterNetwork => $@"{Address}:{Port}",
-				AddressFamily.InterNetworkV6 => $@"[{Address}]:{Port}",
-				_ => base.ToString()
-			};
-		}
+			AddressFamily.InterNetwork => $@"{Address}:{Port}",
+			AddressFamily.InterNetworkV6 => $@"[{Address}]:{Port}",
+			_ => base.ToString()
+		};
 	}
 }

+ 23 - 24
STUN/Messages/StunAttributeValues/ChangeRequestStunAttributeValue.cs

@@ -1,39 +1,38 @@
 using Microsoft;
 using System;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5780#section-7.2
+/// </summary>
+public class ChangeRequestStunAttributeValue : IStunAttributeValue
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5780#section-7.2
-	/// </summary>
-	public class ChangeRequestStunAttributeValue : IStunAttributeValue
-	{
-		public bool ChangeIp { get; set; }
+	public bool ChangeIp { get; set; }
 
-		public bool ChangePort { get; set; }
+	public bool ChangePort { get; set; }
 
-		public int WriteTo(Span<byte> buffer)
-		{
-			Requires.Range(buffer.Length >= 4, nameof(buffer));
+	public int WriteTo(Span<byte> buffer)
+	{
+		Requires.Range(buffer.Length >= 4, nameof(buffer));
 
-			buffer[0] = buffer[1] = buffer[2] = 0;
+		buffer[0] = buffer[1] = buffer[2] = 0;
 
-			buffer[3] = (byte)(Convert.ToInt32(ChangeIp) << 2 | Convert.ToInt32(ChangePort) << 1);
+		buffer[3] = (byte)(Convert.ToInt32(ChangeIp) << 2 | Convert.ToInt32(ChangePort) << 1);
 
-			return 4;
-		}
+		return 4;
+	}
 
-		public bool TryParse(ReadOnlySpan<byte> buffer)
+	public bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		if (buffer.Length != 4)
 		{
-			if (buffer.Length != 4)
-			{
-				return false;
-			}
+			return false;
+		}
 
-			ChangeIp = Convert.ToBoolean(buffer[3] >> 2 & 1);
-			ChangePort = Convert.ToBoolean(buffer[3] >> 1 & 1);
+		ChangeIp = Convert.ToBoolean(buffer[3] >> 2 & 1);
+		ChangePort = Convert.ToBoolean(buffer[3] >> 1 & 1);
 
-			return true;
-		}
+		return true;
 	}
 }

+ 6 - 7
STUN/Messages/StunAttributeValues/ChangedAddressStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.3
-	/// </summary>
-	public class ChangedAddressStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-11.2.3
+/// </summary>
+public class ChangedAddressStunAttributeValue : AddressStunAttributeValue { }

+ 30 - 31
STUN/Messages/StunAttributeValues/ErrorCodeStunAttributeValue.cs

@@ -2,49 +2,48 @@ using Microsoft;
 using System;
 using System.Text;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.6
+/// </summary>
+public class ErrorCodeStunAttributeValue : IStunAttributeValue
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.6
-	/// </summary>
-	public class ErrorCodeStunAttributeValue : IStunAttributeValue
-	{
-		public ushort ErrorCode { get; set; }
-		public string ReasonPhrase { get; set; } = string.Empty;
+	public ushort ErrorCode { get; set; }
+	public string ReasonPhrase { get; set; } = string.Empty;
 
-		public byte Class => (byte)(ErrorCode % 1000 / 100);
-		public byte Number => (byte)(ErrorCode % 100);
+	public byte Class => (byte)(ErrorCode % 1000 / 100);
+	public byte Number => (byte)(ErrorCode % 100);
 
-		public const int MaxReasonPhraseBytesLength = 762;
+	public const int MaxReasonPhraseBytesLength = 762;
 
-		public int WriteTo(Span<byte> buffer)
-		{
-			Requires.Range(buffer.Length >= 4, nameof(buffer));
+	public int WriteTo(Span<byte> buffer)
+	{
+		Requires.Range(buffer.Length >= 4, nameof(buffer));
 
-			buffer[0] = buffer[1] = 0;
-			buffer[2] = Class;
-			buffer[3] = Number;
+		buffer[0] = buffer[1] = 0;
+		buffer[2] = Class;
+		buffer[3] = Number;
 
-			var length = Encoding.UTF8.GetBytes(ReasonPhrase, buffer[4..]);
+		var length = Encoding.UTF8.GetBytes(ReasonPhrase, buffer[4..]);
 
-			return 4 + Math.Min(length, MaxReasonPhraseBytesLength);
-		}
+		return 4 + Math.Min(length, MaxReasonPhraseBytesLength);
+	}
 
-		public bool TryParse(ReadOnlySpan<byte> buffer)
+	public bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		if (buffer.Length is < 4 or > (4 + MaxReasonPhraseBytesLength))
 		{
-			if (buffer.Length is < 4 or > (4 + MaxReasonPhraseBytesLength))
-			{
-				return false;
-			}
+			return false;
+		}
 
-			var @class = (byte)(buffer[2] & 0b111);
-			var number = Math.Min(buffer[3], (ushort)99);
+		var @class = (byte)(buffer[2] & 0b111);
+		var number = Math.Min(buffer[3], (ushort)99);
 
-			ErrorCode = (ushort)(@class * 100 + number);
+		ErrorCode = (ushort)(@class * 100 + number);
 
-			ReasonPhrase = Encoding.UTF8.GetString(buffer[4..]);
+		ReasonPhrase = Encoding.UTF8.GetString(buffer[4..]);
 
-			return true;
-		}
+		return true;
 	}
 }

+ 5 - 6
STUN/Messages/StunAttributeValues/IStunAttributeValue.cs

@@ -1,11 +1,10 @@
 using System;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+public interface IStunAttributeValue
 {
-	public interface IStunAttributeValue
-	{
-		int WriteTo(Span<byte> buffer);
+	int WriteTo(Span<byte> buffer);
 
-		bool TryParse(ReadOnlySpan<byte> buffer);
-	}
+	bool TryParse(ReadOnlySpan<byte> buffer);
 }

+ 6 - 7
STUN/Messages/StunAttributeValues/MappedAddressStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.1
-	/// </summary>
-	public class MappedAddressStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.1
+/// </summary>
+public class MappedAddressStunAttributeValue : AddressStunAttributeValue { }

+ 6 - 7
STUN/Messages/StunAttributeValues/OtherAddressStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5780#section-7.4
-	/// </summary>
-	public class OtherAddressStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5780#section-7.4
+/// </summary>
+public class OtherAddressStunAttributeValue : AddressStunAttributeValue { }

+ 6 - 7
STUN/Messages/StunAttributeValues/ReflectedFromStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.11
-	/// </summary>
-	public class ReflectedFromStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-11.2.11
+/// </summary>
+public class ReflectedFromStunAttributeValue : AddressStunAttributeValue { }

+ 6 - 7
STUN/Messages/StunAttributeValues/ResponseAddressStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.2
-	/// </summary>
-	public class ResponseAddressStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-11.2.2
+/// </summary>
+public class ResponseAddressStunAttributeValue : AddressStunAttributeValue { }

+ 6 - 7
STUN/Messages/StunAttributeValues/SourceAddressStunAttributeValue.cs

@@ -1,7 +1,6 @@
-namespace STUN.Messages.StunAttributeValues
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.5
-	/// </summary>
-	public class SourceAddressStunAttributeValue : AddressStunAttributeValue { }
-}
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc3489#section-11.2.5
+/// </summary>
+public class SourceAddressStunAttributeValue : AddressStunAttributeValue { }

+ 29 - 30
STUN/Messages/StunAttributeValues/UnknownStunAttributeValue.cs

@@ -4,45 +4,44 @@ using System;
 using System.Buffers.Binary;
 using System.Collections.Generic;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.9
+/// </summary>
+public class UnknownStunAttributeValue : IStunAttributeValue
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.9
-	/// </summary>
-	public class UnknownStunAttributeValue : IStunAttributeValue
+	public List<AttributeType> Types { get; } = new();
+
+	public int WriteTo(Span<byte> buffer)
 	{
-		public List<AttributeType> Types { get; } = new();
+		var size = Types.Count << 1;
+		Requires.Range(buffer.Length >= size, nameof(buffer));
 
-		public int WriteTo(Span<byte> buffer)
+		foreach (var attributeType in Types)
 		{
-			var size = Types.Count << 1;
-			Requires.Range(buffer.Length >= size, nameof(buffer));
+			BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)attributeType);
+			buffer = buffer[sizeof(ushort)..];
+		}
 
-			foreach (var attributeType in Types)
-			{
-				BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)attributeType);
-				buffer = buffer[sizeof(ushort)..];
-			}
+		return size;
+	}
 
-			return size;
+	public bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		if (buffer.Length < 2 || (buffer.Length & 1) == 1)
+		{
+			return false;
 		}
 
-		public bool TryParse(ReadOnlySpan<byte> buffer)
+		Types.Clear();
+		while (!buffer.IsEmpty)
 		{
-			if (buffer.Length < 2 || (buffer.Length & 1) == 1)
-			{
-				return false;
-			}
-
-			Types.Clear();
-			while (!buffer.IsEmpty)
-			{
-				var type = BinaryPrimitives.ReadUInt16BigEndian(buffer);
-				Types.Add((AttributeType)type);
-				buffer = buffer[sizeof(ushort)..];
-			}
-
-			return true;
+			var type = BinaryPrimitives.ReadUInt16BigEndian(buffer);
+			Types.Add((AttributeType)type);
+			buffer = buffer[sizeof(ushort)..];
 		}
+
+		return true;
 	}
 }

+ 12 - 13
STUN/Messages/StunAttributeValues/UselessStunAttributeValue.cs

@@ -1,20 +1,19 @@
 using System;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// 无法理解的属性
+/// </summary>
+public class UselessStunAttributeValue : IStunAttributeValue
 {
-	/// <summary>
-	/// 无法理解的属性
-	/// </summary>
-	public class UselessStunAttributeValue : IStunAttributeValue
+	public int WriteTo(Span<byte> buffer)
 	{
-		public int WriteTo(Span<byte> buffer)
-		{
-			throw new NotSupportedException();
-		}
+		throw new NotSupportedException();
+	}
 
-		public bool TryParse(ReadOnlySpan<byte> buffer)
-		{
-			return true;
-		}
+	public bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		return true;
 	}
 }

+ 48 - 49
STUN/Messages/StunAttributeValues/XorMappedAddressStunAttributeValue.cs

@@ -3,71 +3,70 @@ using System;
 using System.Buffers.Binary;
 using System.Net;
 
-namespace STUN.Messages.StunAttributeValues
+namespace STUN.Messages.StunAttributeValues;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-15.2
+/// </summary>
+public class XorMappedAddressStunAttributeValue : AddressStunAttributeValue
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.2
-	/// </summary>
-	public class XorMappedAddressStunAttributeValue : AddressStunAttributeValue
+	private readonly byte[] _magicCookieAndTransactionId;
+
+	public XorMappedAddressStunAttributeValue(ReadOnlySpan<byte> magicCookieAndTransactionId)
 	{
-		private readonly byte[] _magicCookieAndTransactionId;
+		Requires.Argument(magicCookieAndTransactionId.Length == 16, nameof(magicCookieAndTransactionId), @"Wrong Transaction ID length");
+		_magicCookieAndTransactionId = magicCookieAndTransactionId.ToArray();
+	}
 
-		public XorMappedAddressStunAttributeValue(ReadOnlySpan<byte> magicCookieAndTransactionId)
-		{
-			Requires.Argument(magicCookieAndTransactionId.Length == 16, nameof(magicCookieAndTransactionId), @"Wrong Transaction ID length");
-			_magicCookieAndTransactionId = magicCookieAndTransactionId.ToArray();
-		}
+	public override int WriteTo(Span<byte> buffer)
+	{
+		Verify.Operation(Address is not null, @"You should set Address info!");
 
-		public override int WriteTo(Span<byte> buffer)
-		{
-			Verify.Operation(Address is not null, @"You should set Address info!");
+		Requires.Range(buffer.Length >= 4 + 4, nameof(buffer));
 
-			Requires.Range(buffer.Length >= 4 + 4, nameof(buffer));
+		buffer[0] = 0;
+		buffer[1] = (byte)Family;
+		BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Xor(Port));
+		Requires.Range(Xor(Address).TryWriteBytes(buffer[4..], out var bytesWritten), nameof(buffer));
 
-			buffer[0] = 0;
-			buffer[1] = (byte)Family;
-			BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], Xor(Port));
-			Requires.Range(Xor(Address).TryWriteBytes(buffer[4..], out var bytesWritten), nameof(buffer));
+		return 4 + bytesWritten;
+	}
 
-			return 4 + bytesWritten;
+	public override bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		if (!base.TryParse(buffer))
+		{
+			return false;
 		}
 
-		public override bool TryParse(ReadOnlySpan<byte> buffer)
-		{
-			if (!base.TryParse(buffer))
-			{
-				return false;
-			}
+		Assumes.NotNull(Address);
 
-			Assumes.NotNull(Address);
+		Port = Xor(Port);
 
-			Port = Xor(Port);
+		Address = Xor(Address);
 
-			Address = Xor(Address);
+		return true;
+	}
 
-			return true;
-		}
+	private ushort Xor(ushort port)
+	{
+		Span<byte> span = stackalloc byte[2];
+		BinaryPrimitives.WriteUInt16BigEndian(span, port);
+		span[0] ^= _magicCookieAndTransactionId[0];
+		span[1] ^= _magicCookieAndTransactionId[1];
+		return BinaryPrimitives.ReadUInt16BigEndian(span);
+	}
 
-		private ushort Xor(ushort port)
-		{
-			Span<byte> span = stackalloc byte[2];
-			BinaryPrimitives.WriteUInt16BigEndian(span, port);
-			span[0] ^= _magicCookieAndTransactionId[0];
-			span[1] ^= _magicCookieAndTransactionId[1];
-			return BinaryPrimitives.ReadUInt16BigEndian(span);
-		}
+	private IPAddress Xor(IPAddress address)
+	{
+		Span<byte> b = stackalloc byte[16];
+		Assumes.True(address.TryWriteBytes(b, out var bytesWritten));
 
-		private IPAddress Xor(IPAddress address)
+		for (var i = 0; i < bytesWritten; ++i)
 		{
-			Span<byte> b = stackalloc byte[16];
-			Assumes.True(address.TryWriteBytes(b, out var bytesWritten));
-
-			for (var i = 0; i < bytesWritten; ++i)
-			{
-				b[i] ^= _magicCookieAndTransactionId[i];
-			}
-
-			return new IPAddress(b[..bytesWritten]);
+			b[i] ^= _magicCookieAndTransactionId[i];
 		}
+
+		return new IPAddress(b[..bytesWritten]);
 	}
 }

+ 79 - 80
STUN/Messages/StunMessage5389.cs

@@ -7,115 +7,114 @@ using System.Diagnostics;
 using System.Linq;
 using System.Security.Cryptography;
 
-namespace STUN.Messages
+namespace STUN.Messages;
+
+/// <summary>
+/// https://tools.ietf.org/html/rfc5389#section-6
+/// </summary>
+public class StunMessage5389
 {
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-6
-	/// </summary>
-	public class StunMessage5389
-	{
-		#region Header
+	#region Header
+
+	public StunMessageType StunMessageType { get; set; }
 
-		public StunMessageType StunMessageType { get; set; }
+	public uint MagicCookie { get; set; }
 
-		public uint MagicCookie { get; set; }
+	public byte[] TransactionId { get; }
 
-		public byte[] TransactionId { get; }
+	#endregion
 
-		#endregion
+	public IEnumerable<StunAttribute> Attributes { get; set; }
 
-		public IEnumerable<StunAttribute> Attributes { get; set; }
+	public StunMessage5389()
+	{
+		Attributes = Array.Empty<StunAttribute>();
+		StunMessageType = StunMessageType.BindingRequest;
+		MagicCookie = 0x2112A442;
+		TransactionId = new byte[12];
+		RandomNumberGenerator.Fill(TransactionId);
+	}
 
-		public StunMessage5389()
+	public int WriteTo(Span<byte> buffer)
+	{
+		var messageLength = Attributes.Aggregate<StunAttribute, ushort>(0, (current, attribute) => (ushort)(current + attribute.RealLength));
+		var length = 20 + messageLength;
+		Requires.Range(buffer.Length >= length, nameof(buffer));
+
+		BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)StunMessageType);
+		BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], messageLength);
+		BinaryPrimitives.WriteUInt32BigEndian(buffer[4..], MagicCookie);
+		TransactionId.CopyTo(buffer[8..]);
+
+		buffer = buffer[20..];
+		foreach (var attribute in Attributes)
 		{
-			Attributes = Array.Empty<StunAttribute>();
-			StunMessageType = StunMessageType.BindingRequest;
-			MagicCookie = 0x2112A442;
-			TransactionId = new byte[12];
-			RandomNumberGenerator.Fill(TransactionId);
+			var outLength = attribute.WriteTo(buffer);
+			buffer = buffer[outLength..];
 		}
 
-		public int WriteTo(Span<byte> buffer)
-		{
-			var messageLength = Attributes.Aggregate<StunAttribute, ushort>(0, (current, attribute) => (ushort)(current + attribute.RealLength));
-			var length = 20 + messageLength;
-			Requires.Range(buffer.Length >= length, nameof(buffer));
+		return length;
+	}
 
-			BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)StunMessageType);
-			BinaryPrimitives.WriteUInt16BigEndian(buffer[2..], messageLength);
-			BinaryPrimitives.WriteUInt32BigEndian(buffer[4..], MagicCookie);
-			TransactionId.CopyTo(buffer[8..]);
+	public bool TryParse(ReadOnlySpan<byte> buffer)
+	{
+		if (buffer.Length < 20)
+		{
+			return false; // Check length
+		}
 
-			buffer = buffer[20..];
-			foreach (var attribute in Attributes)
-			{
-				var outLength = attribute.WriteTo(buffer);
-				buffer = buffer[outLength..];
-			}
+		Span<byte> tempSpan = stackalloc byte[2];
 
-			return length;
-		}
+		tempSpan[0] = (byte)(buffer[0] & 0b0011_1111);
+		tempSpan[1] = buffer[1];
+		var type = (StunMessageType)BinaryPrimitives.ReadUInt16BigEndian(tempSpan);
 
-		public bool TryParse(ReadOnlySpan<byte> buffer)
+		if (!Enum.IsDefined(typeof(StunMessageType), type))
 		{
-			if (buffer.Length < 20)
-			{
-				return false; // Check length
-			}
+			return false;
+		}
 
-			Span<byte> tempSpan = stackalloc byte[2];
+		StunMessageType = type;
 
-			tempSpan[0] = (byte)(buffer[0] & 0b0011_1111);
-			tempSpan[1] = buffer[1];
-			var type = (StunMessageType)BinaryPrimitives.ReadUInt16BigEndian(tempSpan);
+		var length = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
 
-			if (!Enum.IsDefined(typeof(StunMessageType), type))
-			{
-				return false;
-			}
+		MagicCookie = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
 
-			StunMessageType = type;
+		buffer.Slice(8, 12).CopyTo(TransactionId);
 
-			var length = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
+		if (buffer.Length != length + 20)
+		{
+			return false; // Check length
+		}
 
-			MagicCookie = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
+		var list = new List<StunAttribute>();
 
-			buffer.Slice(8, 12).CopyTo(TransactionId);
+		var attributeBuffer = buffer[20..];
+		var magicCookieAndTransactionId = buffer.Slice(4, 16);
 
-			if (buffer.Length != length + 20)
+		while (attributeBuffer.Length > 0)
+		{
+			var attribute = new StunAttribute();
+			var offset = attribute.TryParse(attributeBuffer, magicCookieAndTransactionId);
+			if (offset > 0)
 			{
-				return false; // Check length
+				list.Add(attribute);
+				attributeBuffer = attributeBuffer[offset..];
 			}
-
-			var list = new List<StunAttribute>();
-
-			var attributeBuffer = buffer[20..];
-			var magicCookieAndTransactionId = buffer.Slice(4, 16);
-
-			while (attributeBuffer.Length > 0)
+			else
 			{
-				var attribute = new StunAttribute();
-				var offset = attribute.TryParse(attributeBuffer, magicCookieAndTransactionId);
-				if (offset > 0)
-				{
-					list.Add(attribute);
-					attributeBuffer = attributeBuffer[offset..];
-				}
-				else
-				{
-					Debug.WriteLine($@"[Warning] Ignore wrong attribute: {Convert.ToHexString(attributeBuffer)}");
-					break;
-				}
+				Debug.WriteLine($@"[Warning] Ignore wrong attribute: {Convert.ToHexString(attributeBuffer)}");
+				break;
 			}
+		}
 
-			Attributes = list;
+		Attributes = list;
 
-			return true;
-		}
+		return true;
+	}
 
-		public bool IsSameTransaction(StunMessage5389 other)
-		{
-			return MagicCookie == other.MagicCookie && TransactionId.AsSpan().SequenceEqual(other.TransactionId);
-		}
+	public bool IsSameTransaction(StunMessage5389 other)
+	{
+		return MagicCookie == other.MagicCookie && TransactionId.AsSpan().SequenceEqual(other.TransactionId);
 	}
 }

+ 11 - 12
STUN/Messages/StunResponse.cs

@@ -1,18 +1,17 @@
 using System.Net;
 
-namespace STUN.Messages
+namespace STUN.Messages;
+
+public class StunResponse
 {
-	public class StunResponse
-	{
-		public StunMessage5389 Message { get; set; }
-		public IPEndPoint Remote { get; set; }
-		public IPAddress LocalAddress { get; set; }
+	public StunMessage5389 Message { get; set; }
+	public IPEndPoint Remote { get; set; }
+	public IPAddress LocalAddress { get; set; }
 
-		public StunResponse(StunMessage5389 message, IPEndPoint remote, IPAddress localAddress)
-		{
-			Message = message;
-			Remote = remote;
-			LocalAddress = localAddress;
-		}
+	public StunResponse(StunMessage5389 message, IPEndPoint remote, IPAddress localAddress)
+	{
+		Message = message;
+		Remote = remote;
+		LocalAddress = localAddress;
 	}
 }

+ 8 - 9
STUN/Proxy/IUdpProxy.cs

@@ -4,14 +4,13 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Proxy
+namespace STUN.Proxy;
+
+public interface IUdpProxy : IDisposable
 {
-	public interface IUdpProxy : IDisposable
-	{
-		Socket Client { get; }
-		ValueTask ConnectAsync(CancellationToken cancellationToken = default);
-		ValueTask CloseAsync(CancellationToken cancellationToken = default);
-		ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default);
-		ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
-	}
+	Socket Client { get; }
+	ValueTask ConnectAsync(CancellationToken cancellationToken = default);
+	ValueTask CloseAsync(CancellationToken cancellationToken = default);
+	ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default);
+	ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default);
 }

+ 36 - 37
STUN/Proxy/NoneUdpProxy.cs

@@ -5,44 +5,43 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Proxy
+namespace STUN.Proxy;
+
+public class NoneUdpProxy : IUdpProxy
 {
-	public class NoneUdpProxy : IUdpProxy
+	public Socket Client { get; }
+
+	public NoneUdpProxy(IPEndPoint localEndPoint)
+	{
+		Requires.NotNull(localEndPoint, nameof(localEndPoint));
+
+		Client = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
+		Client.Bind(localEndPoint);
+	}
+
+	public ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+	{
+		return default;
+	}
+
+	public ValueTask CloseAsync(CancellationToken cancellationToken = default)
+	{
+		return default;
+	}
+
+	public ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
+	{
+		return Client.ReceiveMessageFromAsync(buffer, socketFlags, remoteEndPoint, cancellationToken);
+	}
+
+	public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
+	{
+		return Client.SendToAsync(buffer, socketFlags, remoteEP, cancellationToken);
+	}
+
+	public void Dispose()
 	{
-		public Socket Client { get; }
-
-		public NoneUdpProxy(IPEndPoint localEndPoint)
-		{
-			Requires.NotNull(localEndPoint, nameof(localEndPoint));
-
-			Client = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
-			Client.Bind(localEndPoint);
-		}
-
-		public ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-		{
-			return default;
-		}
-
-		public ValueTask CloseAsync(CancellationToken cancellationToken = default)
-		{
-			return default;
-		}
-
-		public ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
-		{
-			return Client.ReceiveMessageFromAsync(buffer, socketFlags, remoteEndPoint, cancellationToken);
-		}
-
-		public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
-		{
-			return Client.SendToAsync(buffer, socketFlags, remoteEP, cancellationToken);
-		}
-
-		public void Dispose()
-		{
-			Client.Dispose();
-			GC.SuppressFinalize(this);
-		}
+		Client.Dispose();
+		GC.SuppressFinalize(this);
 	}
 }

+ 17 - 18
STUN/Proxy/ProxyFactory.cs

@@ -3,28 +3,27 @@ using Socks5.Models;
 using STUN.Enums;
 using System.Net;
 
-namespace STUN.Proxy
+namespace STUN.Proxy;
+
+public static class ProxyFactory
 {
-	public static class ProxyFactory
+	public static IUdpProxy CreateProxy(ProxyType type, IPEndPoint local, Socks5CreateOption option)
 	{
-		public static IUdpProxy CreateProxy(ProxyType type, IPEndPoint local, Socks5CreateOption option)
+		switch (type)
 		{
-			switch (type)
+			case ProxyType.Plain:
+			{
+				return new NoneUdpProxy(local);
+			}
+			case ProxyType.Socks5:
+			{
+				Requires.NotNull(option, nameof(option));
+				Requires.Argument(option.Address is not null, nameof(option), @"Proxy server is null");
+				return new Socks5UdpProxy(local, option);
+			}
+			default:
 			{
-				case ProxyType.Plain:
-				{
-					return new NoneUdpProxy(local);
-				}
-				case ProxyType.Socks5:
-				{
-					Requires.NotNull(option, nameof(option));
-					Requires.Argument(option.Address is not null, nameof(option), @"Proxy server is null");
-					return new Socks5UdpProxy(local, option);
-				}
-				default:
-				{
-					throw Assumes.NotReachable();
-				}
+				throw Assumes.NotReachable();
 			}
 		}
 	}

+ 83 - 84
STUN/Proxy/Socks5UdpProxy.cs

@@ -12,119 +12,118 @@ using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace STUN.Proxy
+namespace STUN.Proxy;
+
+public class Socks5UdpProxy : IUdpProxy
 {
-	public class Socks5UdpProxy : IUdpProxy
+	public Socket Client
 	{
-		public Socket Client
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		get
 		{
-			[MethodImpl(MethodImplOptions.AggressiveInlining)]
-			get
-			{
-				Verify.Operation(_socks5Client?.UdpClient is not null, @"Socks5 is not established.");
-				return _socks5Client.UdpClient;
-			}
+			Verify.Operation(_socks5Client?.UdpClient is not null, @"Socks5 is not established.");
+			return _socks5Client.UdpClient;
 		}
+	}
 
-		private readonly Socks5CreateOption _socks5Options;
-		private readonly IPEndPoint _localEndPoint;
+	private readonly Socks5CreateOption _socks5Options;
+	private readonly IPEndPoint _localEndPoint;
 
-		private Socks5Client? _socks5Client;
-		private ServerBound _udpServerBound;
+	private Socks5Client? _socks5Client;
+	private ServerBound _udpServerBound;
 
-		public Socks5UdpProxy(IPEndPoint localEndPoint, Socks5CreateOption socks5Options)
-		{
-			_socks5Options = socks5Options;
-			Requires.NotNull(localEndPoint, nameof(localEndPoint));
-			Requires.NotNull(socks5Options, nameof(socks5Options));
-			Requires.Argument(socks5Options.Address is not null, nameof(socks5Options), @"SOCKS5 address is null");
+	public Socks5UdpProxy(IPEndPoint localEndPoint, Socks5CreateOption socks5Options)
+	{
+		_socks5Options = socks5Options;
+		Requires.NotNull(localEndPoint, nameof(localEndPoint));
+		Requires.NotNull(socks5Options, nameof(socks5Options));
+		Requires.Argument(socks5Options.Address is not null, nameof(socks5Options), @"SOCKS5 address is null");
 
-			_localEndPoint = localEndPoint;
-			_socks5Options = socks5Options;
-		}
+		_localEndPoint = localEndPoint;
+		_socks5Options = socks5Options;
+	}
 
-		public async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-		{
-			Verify.Operation(_socks5Client?.Status is not Status.Established, @"SOCKS5 client has been connected");
-			_socks5Client?.Dispose();
+	public async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+	{
+		Verify.Operation(_socks5Client?.Status is not Status.Established, @"SOCKS5 client has been connected");
+		_socks5Client?.Dispose();
 
-			_socks5Client = new Socks5Client(_socks5Options);
-			_udpServerBound = await _socks5Client.UdpAssociateAsync(_localEndPoint.Address, (ushort)_localEndPoint.Port, cancellationToken);
-		}
+		_socks5Client = new Socks5Client(_socks5Options);
+		_udpServerBound = await _socks5Client.UdpAssociateAsync(_localEndPoint.Address, (ushort)_localEndPoint.Port, cancellationToken);
+	}
 
-		public ValueTask CloseAsync(CancellationToken cancellationToken = default)
+	public ValueTask CloseAsync(CancellationToken cancellationToken = default)
+	{
+		if (_socks5Client is not null)
 		{
-			if (_socks5Client is not null)
-			{
-				_socks5Client.Dispose();
-				_socks5Client = null;
-			}
-			return default;
+			_socks5Client.Dispose();
+			_socks5Client = null;
 		}
+		return default;
+	}
 
-		public async ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
-		{
-			Verify.Operation(_socks5Client?.Status is Status.Established && _socks5Client.UdpClient is not null, @"Socks5 is not established.");
+	public async ValueTask<SocketReceiveMessageFromResult> ReceiveMessageFromAsync(Memory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken = default)
+	{
+		Verify.Operation(_socks5Client?.Status is Status.Established && _socks5Client.UdpClient is not null, @"Socks5 is not established.");
 
-			var t = ArrayPool<byte>.Shared.Rent(buffer.Length);
-			try
+		var t = ArrayPool<byte>.Shared.Rent(buffer.Length);
+		try
+		{
+			if (_udpServerBound.Type is AddressType.Domain)
 			{
-				if (_udpServerBound.Type is AddressType.Domain)
-				{
-					ThrowErrorAddressType();
-				}
-
-				var remote = new IPEndPoint(_udpServerBound.Address!, _udpServerBound.Port);
-				var r = await _socks5Client.UdpClient.ReceiveMessageFromAsync(t, socketFlags, remote, cancellationToken);
-				var u = Unpack.Udp(t.AsMemory(0, r.ReceivedBytes));
-
-				u.Data.CopyTo(buffer);
-
-				if (u.Type is AddressType.Domain)
-				{
-					ThrowErrorAddressType();
-				}
-
-				return new SocketReceiveMessageFromResult
-				{
-					ReceivedBytes = u.Data.Length,
-					SocketFlags = r.SocketFlags,
-					RemoteEndPoint = new IPEndPoint(u.Address!, u.Port),
-					PacketInformation = r.PacketInformation
-				};
+				ThrowErrorAddressType();
 			}
-			finally
+
+			var remote = new IPEndPoint(_udpServerBound.Address!, _udpServerBound.Port);
+			var r = await _socks5Client.UdpClient.ReceiveMessageFromAsync(t, socketFlags, remote, cancellationToken);
+			var u = Unpack.Udp(t.AsMemory(0, r.ReceivedBytes));
+
+			u.Data.CopyTo(buffer);
+
+			if (u.Type is AddressType.Domain)
 			{
-				ArrayPool<byte>.Shared.Return(t);
+				ThrowErrorAddressType();
 			}
 
-			static void ThrowErrorAddressType()
+			return new SocketReceiveMessageFromResult
 			{
-				throw new InvalidDataException(@"Received error AddressType");
-			}
+				ReceivedBytes = u.Data.Length,
+				SocketFlags = r.SocketFlags,
+				RemoteEndPoint = new IPEndPoint(u.Address!, u.Port),
+				PacketInformation = r.PacketInformation
+			};
 		}
-
-		public async ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
+		finally
 		{
-			Verify.Operation(_socks5Client is not null, @"SOCKS5 client is not connected");
+			ArrayPool<byte>.Shared.Return(t);
+		}
 
-			if (remoteEP is not IPEndPoint remote)
-			{
-				ThrowNotSupportedException();
-			}
+		static void ThrowErrorAddressType()
+		{
+			throw new InvalidDataException(@"Received error AddressType");
+		}
+	}
 
-			return await _socks5Client.SendUdpAsync(buffer, remote.Address, (ushort)remote.Port, cancellationToken);
+	public async ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP, CancellationToken cancellationToken = default)
+	{
+		Verify.Operation(_socks5Client is not null, @"SOCKS5 client is not connected");
 
-			static void ThrowNotSupportedException()
-			{
-				throw new NotSupportedException();
-			}
+		if (remoteEP is not IPEndPoint remote)
+		{
+			ThrowNotSupportedException();
 		}
 
-		public void Dispose()
+		return await _socks5Client.SendUdpAsync(buffer, remote.Address, (ushort)remote.Port, cancellationToken);
+
+		static void ThrowNotSupportedException()
 		{
-			_socks5Client?.Dispose();
-			GC.SuppressFinalize(this);
+			throw new NotSupportedException();
 		}
 	}
+
+	public void Dispose()
+	{
+		_socks5Client?.Dispose();
+		GC.SuppressFinalize(this);
+	}
 }

+ 14 - 15
STUN/StunResult/ClassicStunResult.cs

@@ -1,22 +1,21 @@
 using STUN.Enums;
 
-namespace STUN.StunResult
+namespace STUN.StunResult;
+
+public class ClassicStunResult : StunResult
 {
-	public class ClassicStunResult : StunResult
-	{
-		public NatType NatType { get; set; } = NatType.Unknown;
+	public NatType NatType { get; set; } = NatType.Unknown;
 
-		public void Clone(ClassicStunResult result)
-		{
-			PublicEndPoint = result.PublicEndPoint;
-			LocalEndPoint = result.LocalEndPoint;
-			NatType = result.NatType;
-		}
+	public void Clone(ClassicStunResult result)
+	{
+		PublicEndPoint = result.PublicEndPoint;
+		LocalEndPoint = result.LocalEndPoint;
+		NatType = result.NatType;
+	}
 
-		public override void Reset()
-		{
-			base.Reset();
-			NatType = NatType.Unknown;
-		}
+	public override void Reset()
+	{
+		base.Reset();
+		NatType = NatType.Unknown;
 	}
 }

+ 9 - 10
STUN/StunResult/StunResult.cs

@@ -1,16 +1,15 @@
 using System.Net;
 
-namespace STUN.StunResult
+namespace STUN.StunResult;
+
+public abstract class StunResult
 {
-	public abstract class StunResult
-	{
-		public IPEndPoint? PublicEndPoint { get; set; }
-		public IPEndPoint? LocalEndPoint { get; set; }
+	public IPEndPoint? PublicEndPoint { get; set; }
+	public IPEndPoint? LocalEndPoint { get; set; }
 
-		public virtual void Reset()
-		{
-			PublicEndPoint = default;
-			LocalEndPoint = default;
-		}
+	public virtual void Reset()
+	{
+		PublicEndPoint = default;
+		LocalEndPoint = default;
 	}
 }

+ 23 - 24
STUN/StunResult/StunResult5389.cs

@@ -1,35 +1,34 @@
 using STUN.Enums;
 using System.Net;
 
-namespace STUN.StunResult
+namespace STUN.StunResult;
+
+public class StunResult5389 : StunResult
 {
-	public class StunResult5389 : StunResult
-	{
-		public IPEndPoint? OtherEndPoint { get; set; }
+	public IPEndPoint? OtherEndPoint { get; set; }
 
-		public BindingTestResult BindingTestResult { get; set; } = BindingTestResult.Unknown;
+	public BindingTestResult BindingTestResult { get; set; } = BindingTestResult.Unknown;
 
-		public MappingBehavior MappingBehavior { get; set; } = MappingBehavior.Unknown;
+	public MappingBehavior MappingBehavior { get; set; } = MappingBehavior.Unknown;
 
-		public FilteringBehavior FilteringBehavior { get; set; } = FilteringBehavior.Unknown;
+	public FilteringBehavior FilteringBehavior { get; set; } = FilteringBehavior.Unknown;
 
-		public void Clone(StunResult5389 result)
-		{
-			PublicEndPoint = result.PublicEndPoint;
-			LocalEndPoint = result.LocalEndPoint;
-			OtherEndPoint = result.OtherEndPoint;
-			BindingTestResult = result.BindingTestResult;
-			MappingBehavior = result.MappingBehavior;
-			FilteringBehavior = result.FilteringBehavior;
-		}
+	public void Clone(StunResult5389 result)
+	{
+		PublicEndPoint = result.PublicEndPoint;
+		LocalEndPoint = result.LocalEndPoint;
+		OtherEndPoint = result.OtherEndPoint;
+		BindingTestResult = result.BindingTestResult;
+		MappingBehavior = result.MappingBehavior;
+		FilteringBehavior = result.FilteringBehavior;
+	}
 
-		public override void Reset()
-		{
-			base.Reset();
-			OtherEndPoint = default;
-			BindingTestResult = BindingTestResult.Unknown;
-			MappingBehavior = MappingBehavior.Unknown;
-			FilteringBehavior = FilteringBehavior.Unknown;
-		}
+	public override void Reset()
+	{
+		base.Reset();
+		OtherEndPoint = default;
+		BindingTestResult = BindingTestResult.Unknown;
+		MappingBehavior = MappingBehavior.Unknown;
+		FilteringBehavior = FilteringBehavior.Unknown;
 	}
 }

+ 32 - 33
STUN/StunServer.cs

@@ -2,50 +2,49 @@ using System.Diagnostics.CodeAnalysis;
 using System.Net;
 using System.Net.Sockets;
 
-namespace STUN
+namespace STUN;
+
+public class StunServer
 {
-	public class StunServer
+	public string Hostname { get; }
+	public ushort Port { get; }
+
+	private const ushort DefaultPort = 3478;
+
+	public StunServer()
 	{
-		public string Hostname { get; }
-		public ushort Port { get; }
+		Hostname = @"stun.syncthing.net";
+		Port = DefaultPort;
+	}
 
-		private const ushort DefaultPort = 3478;
+	private StunServer(string hostname, ushort port)
+	{
+		Hostname = hostname;
+		Port = port;
+	}
 
-		public StunServer()
+	public static bool TryParse(string s, [NotNullWhen(true)] out StunServer? result)
+	{
+		if (!HostnameEndpoint.TryParse(s, out var host, DefaultPort))
 		{
-			Hostname = @"stun.syncthing.net";
-			Port = DefaultPort;
+			result = null;
+			return false;
 		}
 
-		private StunServer(string hostname, ushort port)
-		{
-			Hostname = hostname;
-			Port = port;
-		}
+		result = new StunServer(host.Hostname, host.Port);
+		return true;
+	}
 
-		public static bool TryParse(string s, [NotNullWhen(true)] out StunServer? result)
+	public override string ToString()
+	{
+		if (Port is DefaultPort)
 		{
-			if (!HostnameEndpoint.TryParse(s, out var host, DefaultPort))
-			{
-				result = null;
-				return false;
-			}
-
-			result = new StunServer(host.Hostname, host.Port);
-			return true;
+			return Hostname;
 		}
-
-		public override string ToString()
+		if (IPAddress.TryParse(Hostname, out var ip) && ip.AddressFamily is AddressFamily.InterNetworkV6)
 		{
-			if (Port is DefaultPort)
-			{
-				return Hostname;
-			}
-			if (IPAddress.TryParse(Hostname, out var ip) && ip.AddressFamily is AddressFamily.InterNetworkV6)
-			{
-				return $@"[{ip}]:{Port}";
-			}
-			return $@"{Hostname}:{Port}";
+			return $@"[{ip}]:{Port}";
 		}
+		return $@"{Hostname}:{Port}";
 	}
 }

+ 85 - 86
STUN/Utils/AttributeExtensions.cs

@@ -5,116 +5,115 @@ using System;
 using System.Linq;
 using System.Net;
 
-namespace STUN.Utils
+namespace STUN.Utils;
+
+public static class AttributeExtensions
 {
-	public static class AttributeExtensions
+	public static StunAttribute BuildChangeRequest(bool changeIp, bool changePort)
 	{
-		public static StunAttribute BuildChangeRequest(bool changeIp, bool changePort)
+		return new StunAttribute
 		{
-			return new StunAttribute
-			{
-				Type = AttributeType.ChangeRequest,
-				Length = 4,
-				Value = new ChangeRequestStunAttributeValue { ChangeIp = changeIp, ChangePort = changePort }
-			};
-		}
+			Type = AttributeType.ChangeRequest,
+			Length = 4,
+			Value = new ChangeRequestStunAttributeValue { ChangeIp = changeIp, ChangePort = changePort }
+		};
+	}
 
-		public static StunAttribute BuildMapping(IpFamily family, IPAddress ip, ushort port)
+	public static StunAttribute BuildMapping(IpFamily family, IPAddress ip, ushort port)
+	{
+		var length = family switch
 		{
-			var length = family switch
-			{
-				IpFamily.IPv4 => 4,
-				IpFamily.IPv6 => 16,
-				_ => throw new ArgumentOutOfRangeException(nameof(family), family, null)
-			};
-			return new StunAttribute
-			{
-				Type = AttributeType.MappedAddress,
-				Length = (ushort)(4 + length),
-				Value = new MappedAddressStunAttributeValue
-				{
-					Family = family,
-					Address = ip,
-					Port = port
-				}
-			};
-		}
-
-		public static StunAttribute BuildChangeAddress(IpFamily family, IPAddress ip, ushort port)
+			IpFamily.IPv4 => 4,
+			IpFamily.IPv6 => 16,
+			_ => throw new ArgumentOutOfRangeException(nameof(family), family, null)
+		};
+		return new StunAttribute
 		{
-			var length = family switch
-			{
-				IpFamily.IPv4 => 4,
-				IpFamily.IPv6 => 16,
-				_ => throw new ArgumentOutOfRangeException(nameof(family), family, null)
-			};
-			return new StunAttribute
+			Type = AttributeType.MappedAddress,
+			Length = (ushort)(4 + length),
+			Value = new MappedAddressStunAttributeValue
 			{
-				Type = AttributeType.ChangedAddress,
-				Length = (ushort)(4 + length),
-				Value = new ChangedAddressStunAttributeValue
-				{
-					Family = family,
-					Address = ip,
-					Port = port
-				}
-			};
-		}
+				Family = family,
+				Address = ip,
+				Port = port
+			}
+		};
+	}
 
-		public static IPEndPoint? GetMappedAddressAttribute(this StunMessage5389 response)
+	public static StunAttribute BuildChangeAddress(IpFamily family, IPAddress ip, ushort port)
+	{
+		var length = family switch
 		{
-			var mappedAddressAttribute = response.Attributes.FirstOrDefault(t => t.Type == AttributeType.MappedAddress);
-
-			if (mappedAddressAttribute is null)
+			IpFamily.IPv4 => 4,
+			IpFamily.IPv6 => 16,
+			_ => throw new ArgumentOutOfRangeException(nameof(family), family, null)
+		};
+		return new StunAttribute
+		{
+			Type = AttributeType.ChangedAddress,
+			Length = (ushort)(4 + length),
+			Value = new ChangedAddressStunAttributeValue
 			{
-				return null;
+				Family = family,
+				Address = ip,
+				Port = port
 			}
+		};
+	}
 
-			var mapped = (MappedAddressStunAttributeValue)mappedAddressAttribute.Value;
-			return new IPEndPoint(mapped.Address!, mapped.Port);
-		}
+	public static IPEndPoint? GetMappedAddressAttribute(this StunMessage5389 response)
+	{
+		var mappedAddressAttribute = response.Attributes.FirstOrDefault(t => t.Type == AttributeType.MappedAddress);
 
-		public static IPEndPoint? GetChangedAddressAttribute(this StunMessage5389 response)
+		if (mappedAddressAttribute is null)
 		{
-			var changedAddressAttribute = response.Attributes.FirstOrDefault(t => t.Type == AttributeType.ChangedAddress);
+			return null;
+		}
 
-			if (changedAddressAttribute is null)
-			{
-				return null;
-			}
+		var mapped = (MappedAddressStunAttributeValue)mappedAddressAttribute.Value;
+		return new IPEndPoint(mapped.Address!, mapped.Port);
+	}
 
-			var address = (ChangedAddressStunAttributeValue)changedAddressAttribute.Value;
-			return new IPEndPoint(address.Address!, address.Port);
-		}
+	public static IPEndPoint? GetChangedAddressAttribute(this StunMessage5389 response)
+	{
+		var changedAddressAttribute = response.Attributes.FirstOrDefault(t => t.Type == AttributeType.ChangedAddress);
 
-		public static IPEndPoint? GetXorMappedAddressAttribute(this StunMessage5389 response)
+		if (changedAddressAttribute is null)
 		{
-			var mappedAddressAttribute =
-				response.Attributes.FirstOrDefault(t => t.Type == AttributeType.XorMappedAddress) ??
-				response.Attributes.FirstOrDefault(t => t.Type == AttributeType.MappedAddress);
+			return null;
+		}
 
-			if (mappedAddressAttribute is null)
-			{
-				return null;
-			}
+		var address = (ChangedAddressStunAttributeValue)changedAddressAttribute.Value;
+		return new IPEndPoint(address.Address!, address.Port);
+	}
 
-			var mapped = (AddressStunAttributeValue)mappedAddressAttribute.Value;
-			return new IPEndPoint(mapped.Address!, mapped.Port);
-		}
+	public static IPEndPoint? GetXorMappedAddressAttribute(this StunMessage5389 response)
+	{
+		var mappedAddressAttribute =
+			response.Attributes.FirstOrDefault(t => t.Type == AttributeType.XorMappedAddress) ??
+			response.Attributes.FirstOrDefault(t => t.Type == AttributeType.MappedAddress);
 
-		public static IPEndPoint? GetOtherAddressAttribute(this StunMessage5389 response)
+		if (mappedAddressAttribute is null)
 		{
-			var addressAttribute =
-				response.Attributes.FirstOrDefault(t => t.Type == AttributeType.OtherAddress) ??
-				response.Attributes.FirstOrDefault(t => t.Type == AttributeType.ChangedAddress);
+			return null;
+		}
 
-			if (addressAttribute is null)
-			{
-				return null;
-			}
+		var mapped = (AddressStunAttributeValue)mappedAddressAttribute.Value;
+		return new IPEndPoint(mapped.Address!, mapped.Port);
+	}
 
-			var address = (AddressStunAttributeValue)addressAttribute.Value;
-			return new IPEndPoint(address.Address!, address.Port);
+	public static IPEndPoint? GetOtherAddressAttribute(this StunMessage5389 response)
+	{
+		var addressAttribute =
+			response.Attributes.FirstOrDefault(t => t.Type == AttributeType.OtherAddress) ??
+			response.Attributes.FirstOrDefault(t => t.Type == AttributeType.ChangedAddress);
+
+		if (addressAttribute is null)
+		{
+			return null;
 		}
+
+		var address = (AddressStunAttributeValue)addressAttribute.Value;
+		return new IPEndPoint(address.Address!, address.Port);
 	}
 }

+ 71 - 72
UnitTest/HostnameEndpointTest.cs

@@ -1,84 +1,83 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using STUN;
 
-namespace UnitTest
+namespace UnitTest;
+
+[TestClass]
+public class HostnameEndpointTest
 {
-	[TestClass]
-	public class HostnameEndpointTest
+	[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)
 	{
-		[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());
-		}
+		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(@"")]
+	[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(@"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]
+	[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);
-		}
+	[TestMethod]
+	public void DefaultServer()
+	{
+		var server = new StunServer();
+		Assert.AreEqual(@"stun.syncthing.net", 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:3478")]
-		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]", @"[2001:db8:1234:5678:11:2233:4455:6677]:0")]
-		[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]:3478", @"[2001:db8:1234:5678:11:2233:4455:6677]:3478")]
-		[DataRow(@"1.1.1.1:3478", @"1.1.1.1:3478")]
-		[DataRow(@"1.1.1.1:1919", @"1.1.1.1:1919")]
-		public void HostnameEndpointToString(string str, string expected)
-		{
-			Assert.IsTrue(HostnameEndpoint.TryParse(str, out var server));
-			Assert.IsNotNull(server);
-			Assert.AreEqual(expected, server.ToString());
-		}
+	[TestMethod]
+	[DataRow(@"stun.syncthing.net:114", @"stun.syncthing.net:114")]
+	[DataRow(@"stun.syncthing.net:3478", @"stun.syncthing.net:3478")]
+	[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]", @"[2001:db8:1234:5678:11:2233:4455:6677]:0")]
+	[DataRow(@"[2001:db8:1234:5678:11:2233:4455:6677]:3478", @"[2001:db8:1234:5678:11:2233:4455:6677]:3478")]
+	[DataRow(@"1.1.1.1:3478", @"1.1.1.1:3478")]
+	[DataRow(@"1.1.1.1:1919", @"1.1.1.1:1919")]
+	public void HostnameEndpointToString(string str, string expected)
+	{
+		Assert.IsTrue(HostnameEndpoint.TryParse(str, out var server));
+		Assert.IsNotNull(server);
+		Assert.AreEqual(expected, server.ToString());
 	}
 }

+ 580 - 581
UnitTest/StunClien5389UDPTest.cs

@@ -10,437 +10,383 @@ using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace UnitTest
+namespace UnitTest;
+
+[TestClass]
+public class StunClien5389UDPTest
 {
-	[TestClass]
-	public class StunClien5389UDPTest
-	{
-		private readonly IDnsClient _dnsClient = new DefaultDnsClient();
+	private readonly IDnsClient _dnsClient = new DefaultDnsClient();
 
-		private const string Server = @"stun.syncthing.net";
-		private const ushort Port = 3478;
+	private const string Server = @"stun.syncthing.net";
+	private const ushort Port = 3478;
 
-		private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
-		private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
-		private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
-		private static readonly IPEndPoint MappedAddress2 = IPEndPoint.Parse(@"1.1.1.1:514");
-		private static readonly IPEndPoint ServerAddress = IPEndPoint.Parse(@"2.2.2.2:1919");
-		private static readonly IPEndPoint ChangedAddress1 = IPEndPoint.Parse(@"3.3.3.3:23333");
-		private static readonly IPEndPoint ChangedAddress2 = IPEndPoint.Parse(@"2.2.2.2:810");
-		private static readonly IPEndPoint ChangedAddress3 = IPEndPoint.Parse(@"3.3.3.3:1919");
+	private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
+	private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
+	private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
+	private static readonly IPEndPoint MappedAddress2 = IPEndPoint.Parse(@"1.1.1.1:514");
+	private static readonly IPEndPoint ServerAddress = IPEndPoint.Parse(@"2.2.2.2:1919");
+	private static readonly IPEndPoint ChangedAddress1 = IPEndPoint.Parse(@"3.3.3.3:23333");
+	private static readonly IPEndPoint ChangedAddress2 = IPEndPoint.Parse(@"2.2.2.2:810");
+	private static readonly IPEndPoint ChangedAddress3 = IPEndPoint.Parse(@"3.3.3.3:1919");
 
-		private static readonly StunMessage5389 DefaultStunMessage = new();
+	private static readonly StunMessage5389 DefaultStunMessage = new();
 
-		[TestMethod]
-		public async Task BindingTestSuccessAsync()
-		{
-			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient5389UDP(new IPEndPoint(ip, Port), Any);
+	[TestMethod]
+	public async Task BindingTestSuccessAsync()
+	{
+		var ip = await _dnsClient.QueryAsync(Server);
+		using var client = new StunClient5389UDP(new IPEndPoint(ip, Port), Any);
 
-			var response = await client.BindingTestAsync();
+		var response = await client.BindingTestAsync();
 
-			Assert.AreEqual(BindingTestResult.Success, response.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, response.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, response.FilteringBehavior);
-			Assert.IsNotNull(response.PublicEndPoint);
-			Assert.IsNotNull(response.LocalEndPoint);
-			Assert.IsNotNull(response.OtherEndPoint);
-		}
+		Assert.AreEqual(BindingTestResult.Success, response.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, response.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, response.FilteringBehavior);
+		Assert.IsNotNull(response.PublicEndPoint);
+		Assert.IsNotNull(response.LocalEndPoint);
+		Assert.IsNotNull(response.OtherEndPoint);
+	}
 
-		[TestMethod]
-		public async Task BindingTestFailAsync()
-		{
-			var ip = IPAddress.Parse(@"1.1.1.1");
-			using var client = new StunClient5389UDP(new IPEndPoint(ip, Port), Any);
+	[TestMethod]
+	public async Task BindingTestFailAsync()
+	{
+		var ip = IPAddress.Parse(@"1.1.1.1");
+		using var client = new StunClient5389UDP(new IPEndPoint(ip, Port), Any);
 
-			var response = await client.BindingTestAsync();
+		var response = await client.BindingTestAsync();
 
-			Assert.AreEqual(BindingTestResult.Fail, response.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, response.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, response.FilteringBehavior);
-			Assert.IsNull(response.PublicEndPoint);
-			Assert.IsNull(response.LocalEndPoint);
-			Assert.IsNull(response.OtherEndPoint);
-		}
+		Assert.AreEqual(BindingTestResult.Fail, response.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, response.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, response.FilteringBehavior);
+		Assert.IsNull(response.PublicEndPoint);
+		Assert.IsNull(response.LocalEndPoint);
+		Assert.IsNull(response.OtherEndPoint);
+	}
 
-		[TestMethod]
-		public async Task MappingBehaviorTestFailAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+	[TestMethod]
+	public async Task MappingBehaviorTestFailAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
+		var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
 
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
 
-			await client.MappingBehaviorTestAsync();
+		await client.MappingBehaviorTestAsync();
 
-			Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNull(client.State.PublicEndPoint);
-			Assert.IsNull(client.State.LocalEndPoint);
-			Assert.IsNull(client.State.OtherEndPoint);
-		}
+		Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNull(client.State.PublicEndPoint);
+		Assert.IsNull(client.State.LocalEndPoint);
+		Assert.IsNull(client.State.OtherEndPoint);
+	}
 
-		[TestMethod]
-		public async Task MappingBehaviorTestUnsupportedServerAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			await TestAsync();
-
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress2
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			await TestAsync();
-
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress3
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-			await TestAsync();
-
-			async Task TestAsync()
-			{
-				await client.MappingBehaviorTestAsync();
-
-				Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-				Assert.AreEqual(MappingBehavior.UnsupportedServer, client.State.MappingBehavior);
-				Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-				Assert.IsNotNull(client.State.PublicEndPoint);
-				Assert.IsNotNull(client.State.LocalEndPoint);
-			}
-		}
+	[TestMethod]
+	public async Task MappingBehaviorTestUnsupportedServerAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task MappingBehaviorTestDirectAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var response = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = MappedAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
-
-			await client.MappingBehaviorTestAsync();
-
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Direct, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
-
-		[TestMethod]
-		public async Task MappingBehaviorTestEndpointIndependentAsync()
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		await TestAsync();
+
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress2
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		await TestAsync();
+
+		var r3 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress3
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		await TestAsync();
+
+		async Task TestAsync()
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			await client.MappingBehaviorTestAsync();
-
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.EndpointIndependent, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
-
-		[TestMethod]
-		public async Task MappingBehaviorTest2FailAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Fail,
-			};
-
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
 			await client.MappingBehaviorTestAsync();
 
 			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
+			Assert.AreEqual(MappingBehavior.UnsupportedServer, client.State.MappingBehavior);
 			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
 			Assert.IsNotNull(client.State.PublicEndPoint);
 			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
 		}
+	}
 
-		[TestMethod]
-		public async Task MappingBehaviorTestAddressDependentAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress2,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress2,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+	[TestMethod]
+	public async Task MappingBehaviorTestDirectAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			await client.MappingBehaviorTestAsync();
+		var response = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = MappedAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
+
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Direct, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.AddressDependent, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task MappingBehaviorTestEndpointIndependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task MappingBehaviorTestAddressAndPortDependentAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress2,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.EndpointIndependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			await client.MappingBehaviorTestAsync();
+	[TestMethod]
+	public async Task MappingBehaviorTest2FailAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.AddressAndPortDependent, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Fail,
+		};
+
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-		[TestMethod]
-		public async Task MappingBehaviorTest3FailAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress2,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Fail
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+	[TestMethod]
+	public async Task MappingBehaviorTestAddressDependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			await client.MappingBehaviorTestAsync();
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.AddressDependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task MappingBehaviorTestAddressAndPortDependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task FilteringBehaviorTestFailAsync()
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.AddressAndPortDependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
+	[TestMethod]
+	public async Task MappingBehaviorTest3FailAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Fail
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+
+		await client.MappingBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Fail, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			await client.FilteringBehaviorTestAsync();
+	[TestMethod]
+	public async Task FilteringBehaviorTestFailAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNull(client.State.PublicEndPoint);
-			Assert.IsNull(client.State.LocalEndPoint);
-			Assert.IsNull(client.State.OtherEndPoint);
-		}
+		var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
 
-		[TestMethod]
-		public async Task FilteringBehaviorTestUnsupportedServerAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			await TestAsync();
-
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress2
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			await TestAsync();
-
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress3
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-			await TestAsync();
-
-			async Task TestAsync()
-			{
-				await client.FilteringBehaviorTestAsync();
-
-				Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-				Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-				Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
-				Assert.IsNotNull(client.State.PublicEndPoint);
-				Assert.IsNotNull(client.State.LocalEndPoint);
-			}
-		}
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
 
-		[TestMethod]
-		public async Task FilteringBehaviorTestEndpointIndependentAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+		await client.FilteringBehaviorTestAsync();
 
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResponse(DefaultStunMessage, ChangedAddress1, LocalAddress1.Address);
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-
-			await client.FilteringBehaviorTestAsync();
+		Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNull(client.State.PublicEndPoint);
+		Assert.IsNull(client.State.LocalEndPoint);
+		Assert.IsNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.EndpointIndependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task FilteringBehaviorTestUnsupportedServerAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task FilteringBehaviorTest2UnsupportedServerAsync()
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		await TestAsync();
+
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress2
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		await TestAsync();
+
+		var r3 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress3
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		await TestAsync();
+
+		async Task TestAsync()
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResponse(DefaultStunMessage, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-
 			await client.FilteringBehaviorTestAsync();
 
 			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
@@ -448,231 +394,284 @@ namespace UnitTest
 			Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
 			Assert.IsNotNull(client.State.PublicEndPoint);
 			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
 		}
+	}
 
-		[TestMethod]
-		public async Task FilteringBehaviorTestAddressAndPortDependentAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-
-			await client.FilteringBehaviorTestAsync();
-
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task FilteringBehaviorTestEndpointIndependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task FilteringBehaviorTestAddressDependentAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResponse(DefaultStunMessage, ChangedAddress2, LocalAddress1.Address);
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-
-			await client.FilteringBehaviorTestAsync();
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResponse(DefaultStunMessage, ChangedAddress1, LocalAddress1.Address);
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+
+		await client.FilteringBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.EndpointIndependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.AddressDependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task FilteringBehaviorTest2UnsupportedServerAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task FilteringBehaviorTest3UnsupportedServerAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResponse(DefaultStunMessage, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+
+		await client.FilteringBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResponse(DefaultStunMessage, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+	[TestMethod]
+	public async Task FilteringBehaviorTestAddressAndPortDependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			await client.FilteringBehaviorTestAsync();
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		await client.FilteringBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task FilteringBehaviorTestAddressDependentAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task QueryFailTestAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResponse(DefaultStunMessage, ChangedAddress2, LocalAddress1.Address);
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+
+		await client.FilteringBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.AddressDependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
+	[TestMethod]
+	public async Task FilteringBehaviorTest3UnsupportedServerAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResponse(DefaultStunMessage, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+
+		await client.FilteringBehaviorTestAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			await client.QueryAsync();
+	[TestMethod]
+	public async Task QueryFailTestAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
-			Assert.IsNull(client.State.PublicEndPoint);
-			Assert.IsNull(client.State.LocalEndPoint);
-			Assert.IsNull(client.State.OtherEndPoint);
-		}
+		var fail = new StunResult5389 { BindingTestResult = BindingTestResult.Fail };
 
-		[TestMethod]
-		public async Task QueryUnsupportedServerTestAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fail);
 
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ServerAddress
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		await client.QueryAsync();
 
-			await client.QueryAsync();
+		Assert.AreEqual(BindingTestResult.Fail, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.Unknown, client.State.FilteringBehavior);
+		Assert.IsNull(client.State.PublicEndPoint);
+		Assert.IsNull(client.State.LocalEndPoint);
+		Assert.IsNull(client.State.OtherEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-		}
+	[TestMethod]
+	public async Task QueryUnsupportedServerTestAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task QueryMappingBehaviorDirectTestAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = MappedAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-
-			await client.QueryAsync();
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ServerAddress
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Unknown, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.UnsupportedServer, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+	}
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.Direct, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+	[TestMethod]
+	public async Task QueryMappingBehaviorDirectTestAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-		[TestMethod]
-		public async Task QueryMappingBehaviorEndpointIndependentTestAsync()
+		var r1 = new StunResult5389
 		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = MappedAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.Direct, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-			await client.QueryAsync();
+	[TestMethod]
+	public async Task QueryMappingBehaviorEndpointIndependentTestAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.EndpointIndependent, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.EndpointIndependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
+	}
 
-		[TestMethod]
-		public async Task QueryMappingBehaviorAddressAndPortDependentTestAsync()
-		{
-			var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
-			var client = mock.Object;
-
-			var r1 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r2 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress2,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			var r3 = new StunResult5389
-			{
-				BindingTestResult = BindingTestResult.Success,
-				PublicEndPoint = MappedAddress1,
-				LocalEndPoint = LocalAddress1,
-				OtherEndPoint = ChangedAddress1
-			};
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-			mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-
-			await client.QueryAsync();
+	[TestMethod]
+	public async Task QueryMappingBehaviorAddressAndPortDependentTestAsync()
+	{
+		var mock = new Mock<StunClient5389UDP>(ServerAddress, Any, default);
+		var client = mock.Object;
 
-			Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
-			Assert.AreEqual(MappingBehavior.AddressAndPortDependent, client.State.MappingBehavior);
-			Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
-			Assert.IsNotNull(client.State.PublicEndPoint);
-			Assert.IsNotNull(client.State.LocalEndPoint);
-			Assert.IsNotNull(client.State.OtherEndPoint);
-		}
+		var r1 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r2 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress2,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		var r3 = new StunResult5389
+		{
+			BindingTestResult = BindingTestResult.Success,
+			PublicEndPoint = MappedAddress1,
+			LocalEndPoint = LocalAddress1,
+			OtherEndPoint = ChangedAddress1
+		};
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ServerAddress)), It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress3)), It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		mock.Setup(x => x.BindingTestBaseAsync(It.Is<IPEndPoint>(p => Equals(p, ChangedAddress1)), It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		mock.Setup(x => x.FilteringBehaviorTest2Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.FilteringBehaviorTest3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		await client.QueryAsync();
+
+		Assert.AreEqual(BindingTestResult.Success, client.State.BindingTestResult);
+		Assert.AreEqual(MappingBehavior.AddressAndPortDependent, client.State.MappingBehavior);
+		Assert.AreEqual(FilteringBehavior.AddressAndPortDependent, client.State.FilteringBehavior);
+		Assert.IsNotNull(client.State.PublicEndPoint);
+		Assert.IsNotNull(client.State.LocalEndPoint);
+		Assert.IsNotNull(client.State.OtherEndPoint);
 	}
 }

+ 318 - 319
UnitTest/StunClient3489Test.cs

@@ -12,395 +12,394 @@ using System.Threading;
 using System.Threading.Tasks;
 using static STUN.Utils.AttributeExtensions;
 
-namespace UnitTest
+namespace UnitTest;
+
+[TestClass]
+public class StunClient3489Test
 {
-	[TestClass]
-	public class StunClient3489Test
+	private readonly IDnsClient _dnsClient = new DefaultDnsClient();
+
+	private const string Server = @"stun.syncthing.net";
+	private const ushort Port = 3478;
+
+	private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
+	private static readonly IPEndPoint IPv6Any = new(IPAddress.IPv6Any, 0);
+	private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
+	private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
+	private static readonly IPEndPoint MappedAddress2 = IPEndPoint.Parse(@"1.1.1.1:514");
+	private static readonly IPEndPoint ServerAddress = IPEndPoint.Parse(@"2.2.2.2:1919");
+	private static readonly IPEndPoint ChangedAddress1 = IPEndPoint.Parse(@"3.3.3.3:23333");
+	private static readonly IPEndPoint ChangedAddress2 = IPEndPoint.Parse(@"2.2.2.2:810");
+
+	private static readonly StunMessage5389 DefaultStunMessage = new();
+
+	[TestMethod]
+	public async Task UdpBlockedTestAsync()
 	{
-		private readonly IDnsClient _dnsClient = new DefaultDnsClient();
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
 
-		private const string Server = @"stun.syncthing.net";
-		private const ushort Port = 3478;
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
 
-		private static readonly IPEndPoint Any = new(IPAddress.Any, 0);
-		private static readonly IPEndPoint IPv6Any = new(IPAddress.IPv6Any, 0);
-		private static readonly IPEndPoint LocalAddress1 = IPEndPoint.Parse(@"127.0.0.1:114");
-		private static readonly IPEndPoint MappedAddress1 = IPEndPoint.Parse(@"1.1.1.1:114");
-		private static readonly IPEndPoint MappedAddress2 = IPEndPoint.Parse(@"1.1.1.1:514");
-		private static readonly IPEndPoint ServerAddress = IPEndPoint.Parse(@"2.2.2.2:1919");
-		private static readonly IPEndPoint ChangedAddress1 = IPEndPoint.Parse(@"3.3.3.3:23333");
-		private static readonly IPEndPoint ChangedAddress2 = IPEndPoint.Parse(@"2.2.2.2:810");
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.UdpBlocked, client.State.NatType);
+	}
+
+	[TestMethod]
+	public async Task UnsupportedServerTestAsync()
+	{
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
 
-		private static readonly StunMessage5389 DefaultStunMessage = new();
+		mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
+		var unknownResponse = new StunResponse(DefaultStunMessage, Any, LocalAddress1.Address);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(unknownResponse);
+		await TestAsync();
 
-		[TestMethod]
-		public async Task UdpBlockedTestAsync()
+		var r1 = new StunResponse(new StunMessage5389
 		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
+			Attributes = new[]
+			{
+				BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
+			}
+		}, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r1);
+		await TestAsync();
 
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		var r2 = new StunResponse(new StunMessage5389
+		{
+			Attributes = new[]
+			{
+				BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+			}
+		}, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
+		await TestAsync();
 
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.UdpBlocked, client.State.NatType);
-		}
+		var r3 = new StunResponse(new StunMessage5389
+		{
+			Attributes = new[]
+			{
+				BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+				BuildChangeAddress(IpFamily.IPv4, ServerAddress.Address, (ushort)ChangedAddress1.Port)
+			}
+		}, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
+		await TestAsync();
 
-		[TestMethod]
-		public async Task UnsupportedServerTestAsync()
+		var r4 = new StunResponse(new StunMessage5389
 		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
+			Attributes = new[]
+			{
+				BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+				BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ServerAddress.Port)
+			}
+		}, ServerAddress, LocalAddress1.Address);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r4);
+		await TestAsync();
 
-			mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
-			var unknownResponse = new StunResponse(DefaultStunMessage, Any, LocalAddress1.Address);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(unknownResponse);
-			await TestAsync();
+		async Task TestAsync()
+		{
+			await client.QueryAsync();
+			Assert.AreEqual(NatType.UnsupportedServer, client.State.NatType);
+		}
+	}
 
-			var r1 = new StunResponse(new StunMessage5389
+	[TestMethod]
+	public async Task NoNatTestAsync()
+	{
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
+
+		var openInternetTest1Response = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
+				{
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ServerAddress,
+			MappedAddress1.Address
+		);
+		var test2Response = new StunResponse(
+			new StunMessage5389
 			{
 				Attributes = new[]
 				{
 					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
 				}
-			}, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r1);
-			await TestAsync();
+			},
+			ChangedAddress1,
+			MappedAddress1.Address
+		);
 
-			var r2 = new StunResponse(new StunMessage5389
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(openInternetTest1Response);
+		mock.Setup(x => x.LocalEndPoint).Returns(MappedAddress1);
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test2Response);
+
+		Assert.AreEqual(NatType.Unknown, client.State.NatType);
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.OpenInternet, client.State.NatType);
+
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.SymmetricUdpFirewall, client.State.NatType);
+	}
+
+	[TestMethod]
+	public async Task FullConeTestAsync()
+	{
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
+
+		var test1Response = new StunResponse(
+			new StunMessage5389
 			{
 				Attributes = new[]
 				{
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
 					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
 				}
-			}, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r2);
-			await TestAsync();
-
-			var r3 = new StunResponse(new StunMessage5389
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		var fullConeResponse = new StunResponse(
+			new StunMessage5389
 			{
 				Attributes = new[]
 				{
-					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-					BuildChangeAddress(IpFamily.IPv4, ServerAddress.Address, (ushort)ChangedAddress1.Port)
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
 				}
-			}, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r3);
-			await TestAsync();
-
-			var r4 = new StunResponse(new StunMessage5389
+			},
+			ChangedAddress1,
+			LocalAddress1.Address
+		);
+		var unsupportedResponse1 = new StunResponse(
+			new StunMessage5389
 			{
 				Attributes = new[]
 				{
-					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ServerAddress.Port)
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
 				}
-			}, ServerAddress, LocalAddress1.Address);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(r4);
-			await TestAsync();
-
-			async Task TestAsync()
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		var unsupportedResponse2 = new StunResponse(
+			new StunMessage5389
 			{
-				await client.QueryAsync();
-				Assert.AreEqual(NatType.UnsupportedServer, client.State.NatType);
-			}
-		}
-
-		[TestMethod]
-		public async Task NoNatTestAsync()
-		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
-
-			var openInternetTest1Response = new StunResponse(
-				new StunMessage5389
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				MappedAddress1.Address
-			);
-			var test2Response = new StunResponse(
-				new StunMessage5389
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
+				}
+			},
+			new IPEndPoint(ServerAddress.Address, ChangedAddress1.Port),
+			LocalAddress1.Address
+		);
+		var unsupportedResponse3 = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
-					}
-				},
-				ChangedAddress1,
-				MappedAddress1.Address
-			);
-
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(openInternetTest1Response);
-			mock.Setup(x => x.LocalEndPoint).Returns(MappedAddress1);
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test2Response);
-
-			Assert.AreEqual(NatType.Unknown, client.State.NatType);
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.OpenInternet, client.State.NatType);
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
+				}
+			},
+			new IPEndPoint(ChangedAddress1.Address, ServerAddress.Port),
+			LocalAddress1.Address
+		);
 
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
+		mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fullConeResponse);
 
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.SymmetricUdpFirewall, client.State.NatType);
-		}
+		Assert.AreEqual(NatType.Unknown, client.State.NatType);
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.FullCone, client.State.NatType);
 
-		[TestMethod]
-		public async Task FullConeTestAsync()
-		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse1);
+		await TestUnsupportedServerAsync();
 
-			var test1Response = new StunResponse(
-				new StunMessage5389
-				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			var fullConeResponse = new StunResponse(
-				new StunMessage5389
-				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
-					}
-				},
-				ChangedAddress1,
-				LocalAddress1.Address
-			);
-			var unsupportedResponse1 = new StunResponse(
-				new StunMessage5389
-				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			var unsupportedResponse2 = new StunResponse(
-				new StunMessage5389
-				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
-					}
-				},
-				new IPEndPoint(ServerAddress.Address, ChangedAddress1.Port),
-				LocalAddress1.Address
-			);
-			var unsupportedResponse3 = new StunResponse(
-				new StunMessage5389
-				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port)
-					}
-				},
-				new IPEndPoint(ChangedAddress1.Address, ServerAddress.Port),
-				LocalAddress1.Address
-			);
-
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
-			mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(fullConeResponse);
-
-			Assert.AreEqual(NatType.Unknown, client.State.NatType);
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.FullCone, client.State.NatType);
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse2);
+		await TestUnsupportedServerAsync();
 
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse1);
-			await TestUnsupportedServerAsync();
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse3);
+		await TestUnsupportedServerAsync();
 
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse2);
-			await TestUnsupportedServerAsync();
-
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(unsupportedResponse3);
-			await TestUnsupportedServerAsync();
-
-			async Task TestUnsupportedServerAsync()
-			{
-				await client.QueryAsync();
-				Assert.AreEqual(NatType.UnsupportedServer, client.State.NatType);
-			}
+		async Task TestUnsupportedServerAsync()
+		{
+			await client.QueryAsync();
+			Assert.AreEqual(NatType.UnsupportedServer, client.State.NatType);
 		}
+	}
 
-		[TestMethod]
-		public async Task SymmetricTestAsync()
-		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
+	[TestMethod]
+	public async Task SymmetricTestAsync()
+	{
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
 
-			var test1Response = new StunResponse(
-				new StunMessage5389
+		var test1Response = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			var test12Response = new StunResponse(
-				new StunMessage5389
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		var test12Response = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress2.Address, (ushort)MappedAddress2.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
-			mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-
-			Assert.AreEqual(NatType.Unknown, client.State.NatType);
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.Unknown, client.State.NatType);
-
-			mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test12Response);
-
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.Symmetric, client.State.NatType);
-		}
+					BuildMapping(IpFamily.IPv4, MappedAddress2.Address, (ushort)MappedAddress2.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
+		mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+
+		Assert.AreEqual(NatType.Unknown, client.State.NatType);
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.Unknown, client.State.NatType);
+
+		mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test12Response);
+
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.Symmetric, client.State.NatType);
+	}
 
-		[TestMethod]
-		public async Task RestrictedConeTestAsync()
-		{
-			var mock = new Mock<StunClient3489>(Any, Any, default);
-			var client = mock.Object;
+	[TestMethod]
+	public async Task RestrictedConeTestAsync()
+	{
+		var mock = new Mock<StunClient3489>(Any, Any, default);
+		var client = mock.Object;
 
-			var test1Response = new StunResponse(
-				new StunMessage5389
+		var test1Response = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			var test3Response = new StunResponse(
-				new StunMessage5389
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		var test3Response = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ChangedAddress2,
-				LocalAddress1.Address
-			);
-			var test3ErrorResponse = new StunResponse(
-				new StunMessage5389
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ChangedAddress2,
+			LocalAddress1.Address
+		);
+		var test3ErrorResponse = new StunResponse(
+			new StunMessage5389
+			{
+				Attributes = new[]
 				{
-					Attributes = new[]
-					{
-						BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
-						BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
-					}
-				},
-				ServerAddress,
-				LocalAddress1.Address
-			);
-			mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
-			mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
-			mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
-
-			mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(test3Response);
-			Assert.AreEqual(NatType.Unknown, client.State.NatType);
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.RestrictedCone, client.State.NatType);
-
-			mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(test3ErrorResponse);
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.PortRestrictedCone, client.State.NatType);
-
-			mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
-			await client.QueryAsync();
-			Assert.AreEqual(NatType.PortRestrictedCone, client.State.NatType);
-		}
+					BuildMapping(IpFamily.IPv4, MappedAddress1.Address, (ushort)MappedAddress1.Port),
+					BuildChangeAddress(IpFamily.IPv4, ChangedAddress1.Address, (ushort)ChangedAddress1.Port)
+				}
+			},
+			ServerAddress,
+			LocalAddress1.Address
+		);
+		mock.Setup(x => x.Test1Async(It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
+		mock.Setup(x => x.LocalEndPoint).Returns(LocalAddress1);
+		mock.Setup(x => x.Test2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		mock.Setup(x => x.Test1_2Async(It.IsAny<IPEndPoint>(), It.IsAny<CancellationToken>())).ReturnsAsync(test1Response);
+
+		mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(test3Response);
+		Assert.AreEqual(NatType.Unknown, client.State.NatType);
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.RestrictedCone, client.State.NatType);
+
+		mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(test3ErrorResponse);
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.PortRestrictedCone, client.State.NatType);
+
+		mock.Setup(x => x.Test3Async(It.IsAny<CancellationToken>())).ReturnsAsync(default(StunResponse?));
+		await client.QueryAsync();
+		Assert.AreEqual(NatType.PortRestrictedCone, client.State.NatType);
+	}
 
-		[TestMethod]
-		public async Task Test1Async()
-		{
-			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
+	[TestMethod]
+	public async Task Test1Async()
+	{
+		var ip = await _dnsClient.QueryAsync(Server);
+		using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
 
-			// test I
-			var response1 = await client.Test1Async(default);
+		// test I
+		var response1 = await client.Test1Async(default);
 
-			Assert.IsNotNull(response1);
-			Assert.AreEqual(ip, response1.Remote.Address);
-			Assert.AreEqual(Port, response1.Remote.Port);
-			Assert.AreNotEqual(Any, client.LocalEndPoint);
+		Assert.IsNotNull(response1);
+		Assert.AreEqual(ip, response1.Remote.Address);
+		Assert.AreEqual(Port, response1.Remote.Port);
+		Assert.AreNotEqual(Any, client.LocalEndPoint);
 
-			var mappedAddress = response1.Message.GetMappedAddressAttribute();
-			var changedAddress = response1.Message.GetChangedAddressAttribute();
+		var mappedAddress = response1.Message.GetMappedAddressAttribute();
+		var changedAddress = response1.Message.GetChangedAddressAttribute();
 
-			Assert.IsNotNull(mappedAddress);
-			Assert.IsNotNull(changedAddress);
+		Assert.IsNotNull(mappedAddress);
+		Assert.IsNotNull(changedAddress);
 
-			Assert.AreNotEqual(ip, changedAddress.Address);
-			Assert.AreNotEqual(Port, changedAddress.Port);
+		Assert.AreNotEqual(ip, changedAddress.Address);
+		Assert.AreNotEqual(Port, changedAddress.Port);
 
-			// Test I(#2)
-			var response12 = await client.Test1_2Async(changedAddress, default);
+		// Test I(#2)
+		var response12 = await client.Test1_2Async(changedAddress, default);
 
-			Assert.IsNotNull(response12);
-			Assert.AreEqual(changedAddress.Address, response12.Remote.Address);
-			Assert.AreEqual(changedAddress.Port, response12.Remote.Port);
-		}
+		Assert.IsNotNull(response12);
+		Assert.AreEqual(changedAddress.Address, response12.Remote.Address);
+		Assert.AreEqual(changedAddress.Port, response12.Remote.Port);
+	}
 
 #if FullCone
 		[TestMethod]
 #endif
-		public async Task Test2Async()
-		{
-			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
-			var response2 = await client.Test2Async(ip.AddressFamily is AddressFamily.InterNetworkV6 ? IPv6Any : Any, default);
+	public async Task Test2Async()
+	{
+		var ip = await _dnsClient.QueryAsync(Server);
+		using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
+		var response2 = await client.Test2Async(ip.AddressFamily is AddressFamily.InterNetworkV6 ? IPv6Any : Any, default);
 
-			Assert.IsNotNull(response2);
+		Assert.IsNotNull(response2);
 
-			Assert.AreNotEqual(ip, response2.Remote.Address);
-			Assert.AreNotEqual(Port, response2.Remote.Port);
-		}
+		Assert.AreNotEqual(ip, response2.Remote.Address);
+		Assert.AreNotEqual(Port, response2.Remote.Port);
+	}
 
 #if FullCone
 		[TestMethod]
 #endif
-		public async Task Test3Async()
-		{
-			var ip = await _dnsClient.QueryAsync(Server);
-			using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
-			var response = await client.Test3Async(default);
+	public async Task Test3Async()
+	{
+		var ip = await _dnsClient.QueryAsync(Server);
+		using var client = new StunClient3489(new IPEndPoint(ip, Port), Any);
+		var response = await client.Test3Async(default);
 
-			Assert.IsNotNull(response);
+		Assert.IsNotNull(response);
 
-			Assert.AreEqual(ip, response.Remote.Address);
-			Assert.AreNotEqual(Port, response.Remote.Port);
-		}
+		Assert.AreEqual(ip, response.Remote.Address);
+		Assert.AreNotEqual(Port, response.Remote.Port);
 	}
 }

+ 52 - 53
UnitTest/XorMappedTest.cs

@@ -5,69 +5,68 @@ using System;
 using System.Linq;
 using System.Net;
 
-namespace UnitTest
+namespace UnitTest;
+
+[TestClass]
+public class XorMappedTest
 {
-	[TestClass]
-	public class XorMappedTest
+	private static ReadOnlySpan<byte> MagicCookieAndTransactionId => new byte[]
 	{
-		private static ReadOnlySpan<byte> MagicCookieAndTransactionId => new byte[]
-		{
-			0x21, 0x12, 0xa4, 0x42,
-			0xb7, 0xe7, 0xa7, 0x01,
-			0xbc, 0x34, 0xd6, 0x86,
-			0xfa, 0x87, 0xdf, 0xae
-		};
+		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 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 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();
+	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()
+	/// <summary>
+	/// https://tools.ietf.org/html/rfc5769.html
+	/// </summary>
+	[TestMethod]
+	public void TestXorMapped()
+	{
+		var t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId)
 		{
-			var t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId)
-			{
-				Port = Port,
-				Family = IpFamily.IPv4,
-				Address = IPv4
-			};
-			Span<byte> temp = stackalloc byte[ushort.MaxValue];
+			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));
+		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(_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);
+		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));
-		}
+		var length6 = t.WriteTo(temp);
+		Assert.AreNotEqual(0, length6);
+		Assert.IsTrue(temp[..length6].SequenceEqual(_ipv6Response));
 	}
 }