Browse Source

Refactor: Remake STUN attributes

Bruce Wayne 4 years ago
parent
commit
e55a340110
39 changed files with 623 additions and 650 deletions
  1. 7 5
      STUN/Client/StunClient3489.cs
  2. 1 1
      STUN/Client/StunClient5389UDP.cs
  3. 0 113
      STUN/Message/Attribute.cs
  4. 0 82
      STUN/Message/Attributes/AddressAttribute.cs
  5. 0 33
      STUN/Message/Attributes/ChangeRequestAttribute.cs
  6. 0 7
      STUN/Message/Attributes/ChangedAddressAttribute.cs
  7. 0 48
      STUN/Message/Attributes/ErrorCodeAttribute.cs
  8. 0 11
      STUN/Message/Attributes/IAttribute.cs
  9. 0 7
      STUN/Message/Attributes/MappedAddressAttribute.cs
  10. 0 7
      STUN/Message/Attributes/OtherAddressAttribute.cs
  11. 0 7
      STUN/Message/Attributes/ReflectedFromAttribute.cs
  12. 0 7
      STUN/Message/Attributes/ResponseAddressAttribute.cs
  13. 0 7
      STUN/Message/Attributes/SourceAddressAttribute.cs
  14. 0 47
      STUN/Message/Attributes/UnknownAttribute.cs
  15. 0 21
      STUN/Message/Attributes/UselessAttribute.cs
  16. 0 71
      STUN/Message/Attributes/XorMappedAddressAttribute.cs
  17. 0 100
      STUN/Message/StunMessage5389.cs
  18. 97 0
      STUN/Messages/StunAttribute.cs
  19. 80 0
      STUN/Messages/StunAttributeValues/AddressStunAttributeValue.cs
  20. 39 0
      STUN/Messages/StunAttributeValues/ChangeRequestStunAttributeValue.cs
  21. 7 0
      STUN/Messages/StunAttributeValues/ChangedAddressStunAttributeValue.cs
  22. 50 0
      STUN/Messages/StunAttributeValues/ErrorCodeStunAttributeValue.cs
  23. 11 0
      STUN/Messages/StunAttributeValues/IStunAttributeValue.cs
  24. 7 0
      STUN/Messages/StunAttributeValues/MappedAddressStunAttributeValue.cs
  25. 7 0
      STUN/Messages/StunAttributeValues/OtherAddressStunAttributeValue.cs
  26. 7 0
      STUN/Messages/StunAttributeValues/ReflectedFromStunAttributeValue.cs
  27. 7 0
      STUN/Messages/StunAttributeValues/ResponseAddressStunAttributeValue.cs
  28. 7 0
      STUN/Messages/StunAttributeValues/SourceAddressStunAttributeValue.cs
  29. 48 0
      STUN/Messages/StunAttributeValues/UnknownStunAttributeValue.cs
  30. 20 0
      STUN/Messages/StunAttributeValues/UselessStunAttributeValue.cs
  31. 73 0
      STUN/Messages/StunAttributeValues/XorMappedAddressStunAttributeValue.cs
  32. 121 0
      STUN/Messages/StunMessage5389.cs
  33. 1 1
      STUN/Proxy/IUdpProxy.cs
  34. 4 2
      STUN/Proxy/NoneUdpProxy.cs
  35. 2 2
      STUN/Proxy/Socks5UdpProxy.cs
  36. 1 1
      STUN/STUN.csproj
  37. 9 9
      STUN/Utils/AttributeExtensions.cs
  38. 0 50
      STUN/Utils/BitUtils.cs
  39. 17 11
      UnitTest/UnitTest.cs

+ 7 - 5
STUN/Client/StunClient3489.cs

@@ -1,12 +1,12 @@
 using Dns.Net.Abstractions;
 using STUN.Enums;
-using STUN.Message;
+using STUN.Messages;
 using STUN.Proxy;
 using STUN.StunResult;
 using STUN.Utils;
 using System;
+using System.Buffers;
 using System.Diagnostics;
-using System.Linq;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
@@ -184,7 +184,9 @@ namespace STUN.Client
 		{
 			try
 			{
-				var b1 = sendMessage.Bytes.ToArray();
+				using var memoryOwner = MemoryPool<byte>.Shared.Rent(ushort.MaxValue);
+				var sendBuffer = memoryOwner.Memory;
+				var length = sendMessage.WriteTo(sendBuffer.Span);
 				//var t = DateTime.Now;
 
 				// Simple retransmissions
@@ -193,11 +195,11 @@ namespace STUN.Client
 				{
 					try
 					{
-						var (receive1, ipe, local) = await Proxy.ReceiveAsync(b1, remote, receive, token);
+						var (receive1, ipe, local) = await Proxy.ReceiveAsync(sendBuffer[..length], remote, receive, token);
 
 						var message = new StunMessage5389();
 						if (message.TryParse(receive1) &&
-							message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
+							message.IsSameTransaction(sendMessage))
 						{
 							return (message, ipe, local);
 						}

+ 1 - 1
STUN/Client/StunClient5389UDP.cs

@@ -1,6 +1,6 @@
 using Dns.Net.Abstractions;
 using STUN.Enums;
-using STUN.Message;
+using STUN.Messages;
 using STUN.Proxy;
 using STUN.StunResult;
 using STUN.Utils;

+ 0 - 113
STUN/Message/Attribute.cs

@@ -1,113 +0,0 @@
-using STUN.Enums;
-using STUN.Message.Attributes;
-using STUN.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace STUN.Message
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15
-	/// </summary>
-	public class Attribute
-	{
-		/*
-            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 ushort Length { get; set; }
-
-		public int RealLength => Type == AttributeType.Useless ? 0 : 4 + Length + (4 - Length % 4) % 4;
-
-		public IAttribute Value { get; set; } = new UselessAttribute();
-
-		private readonly byte[] _magicCookie;
-		private readonly byte[] _transactionId;
-
-		public Attribute()
-		{
-			_magicCookie = new byte[4];
-			_transactionId = new byte[12];
-		}
-
-		public Attribute(byte[] magicCookie, byte[] transactionId)
-		{
-			if (magicCookie.Length != 4 || transactionId.Length != 12)
-			{
-				throw new ArgumentException(@"Wrong Transaction ID length");
-			}
-
-			_magicCookie = magicCookie;
-
-			_transactionId = transactionId;
-		}
-
-		public IEnumerable<byte> ToBytes()
-		{
-			var res = new List<byte>();
-
-			res.AddRange(Convert.ToUInt16(Type).ToBe());
-			res.AddRange(Length.ToBe());
-			res.AddRange(Value.Bytes);
-
-			var n = (4 - res.Count % 4) % 4; // 填充的字节数
-			res.AddRange(BitUtils.GetRandomBytes(n));
-
-			return res;
-		}
-
-		/// <returns>
-		/// Parse 成功字节,0 则表示 Parse 失败
-		/// </returns>
-		public int TryParse(byte[] bytes)
-		{
-			if (bytes.Length < 4)
-			{
-				return 0;
-			}
-
-			Type = (AttributeType)BitUtils.FromBe(bytes[0], bytes[1]);
-
-			Length = BitUtils.FromBe(bytes[2], bytes[3]);
-
-			if (bytes.Length < 4 + Length)
-			{
-				return 0;
-			}
-
-			var value = bytes.Skip(4).Take(Length).ToArray();
-
-			IAttribute t = Type switch
-			{
-				AttributeType.MappedAddress => new MappedAddressAttribute(),
-				AttributeType.XorMappedAddress => new XorMappedAddressAttribute(_magicCookie, _transactionId),
-				AttributeType.ResponseAddress => new ResponseAddressAttribute(),
-				AttributeType.ChangeRequest => new ChangeRequestAttribute(),
-				AttributeType.SourceAddress => new SourceAddressAttribute(),
-				AttributeType.ChangedAddress => new ChangedAddressAttribute(),
-				AttributeType.OtherAddress => new OtherAddressAttribute(),
-				AttributeType.ReflectedFrom => new ReflectedFromAttribute(),
-				AttributeType.ErrorCode => new ErrorCodeAttribute(),
-				_ => new UselessAttribute()
-			};
-			if (t.TryParse(value))
-			{
-				Value = t;
-			}
-
-			return 4 + Length + (4 - Length % 4) % 4; // 对齐
-		}
-	}
-}

+ 0 - 82
STUN/Message/Attributes/AddressAttribute.cs

@@ -1,82 +0,0 @@
-using STUN.Enums;
-using STUN.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.1
-	/// </summary>
-	public abstract class AddressAttribute : IAttribute
-	{
-		public virtual IEnumerable<byte> Bytes
-		{
-			get
-			{
-				if (Address is null)
-				{
-					return Array.Empty<byte>();
-				}
-				var res = new List<byte> { 0, (byte)Family };
-				res.AddRange(Port.ToBe());
-				res.AddRange(Address.GetAddressBytes());
-				return res;
-			}
-		}
-
-		public IpFamily Family { get; set; }
-
-		public ushort Port { get; set; }
-
-		public IPAddress? Address { get; set; }
-
-		public virtual bool TryParse(byte[] bytes)
-		{
-			var length = 4;
-
-			if (bytes.Length < length)
-			{
-				return false;
-			}
-
-			Family = (IpFamily)bytes[1];
-
-			switch (Family)
-			{
-				case IpFamily.IPv4:
-					length += 4;
-					break;
-				case IpFamily.IPv6:
-					length += 16;
-					break;
-				default:
-					return false;
-			}
-
-			if (bytes.Length != length)
-			{
-				return false;
-			}
-
-			Port = BitUtils.FromBe(bytes[2], bytes[3]);
-
-			Address = new IPAddress(bytes.Skip(4).ToArray());
-
-			return true;
-		}
-
-		public override string? ToString()
-		{
-			return Address?.AddressFamily switch
-			{
-				AddressFamily.InterNetwork => $@"{Address}:{Port}",
-				AddressFamily.InterNetworkV6 => $@"[{Address}]:{Port}",
-				_ => base.ToString()
-			};
-		}
-	}
-}

+ 0 - 33
STUN/Message/Attributes/ChangeRequestAttribute.cs

@@ -1,33 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5780#section-7.2
-	/// </summary>
-	public class ChangeRequestAttribute : IAttribute
-	{
-		public IEnumerable<byte> Bytes => new byte[] { 0, 0, 0, (byte)(Convert.ToInt32(ChangeIp) << 2 | Convert.ToInt32(ChangePort) << 1) };
-
-		public bool ChangeIp { get; set; }
-
-		public bool ChangePort { get; set; }
-
-		public bool TryParse(byte[] bytes)
-		{
-			if (bytes.Length != 4)
-			{
-				return false;
-			}
-
-			var bits = new BitArray(bytes);
-
-			ChangeIp = bits[29];
-			ChangePort = bits[30];
-
-			return true;
-		}
-	}
-}

+ 0 - 7
STUN/Message/Attributes/ChangedAddressAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.3
-	/// </summary>
-	public class ChangedAddressAttribute : AddressAttribute { }
-}

+ 0 - 48
STUN/Message/Attributes/ErrorCodeAttribute.cs

@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.6
-	/// </summary>
-	public class ErrorCodeAttribute : IAttribute
-	{
-		public IEnumerable<byte> Bytes
-		{
-			get
-			{
-				var res = new List<byte> { 0, 0, Class, Number };
-				res.AddRange(Encoding.UTF8.GetBytes(ReasonPhrase ?? string.Empty).Take(MaxReasonPhraseBytesLength));
-				return res;
-			}
-		}
-
-		public ushort ErrorCode { get; set; }
-		public string? ReasonPhrase { get; set; }
-
-		public byte Class => (byte)(ErrorCode % 1000 / 100);
-		public byte Number => (byte)(ErrorCode % 100);
-
-		public const int MaxReasonPhraseBytesLength = 762;
-
-		public bool TryParse(byte[] bytes)
-		{
-			if (bytes.Length is < 4 or > (4 + MaxReasonPhraseBytesLength))
-			{
-				return false;
-			}
-
-			var @class = (byte)(bytes[2] & 0b111);
-			var number = Math.Min(bytes[3], (ushort)99);
-
-			ErrorCode = (ushort)(@class * 100 + number);
-
-			ReasonPhrase = bytes.Length > 4 ? Encoding.UTF8.GetString(bytes, 4, bytes.Length - 4) : string.Empty;
-
-			return true;
-		}
-	}
-}

+ 0 - 11
STUN/Message/Attributes/IAttribute.cs

@@ -1,11 +0,0 @@
-using System.Collections.Generic;
-
-namespace STUN.Message.Attributes
-{
-	public interface IAttribute
-	{
-		public IEnumerable<byte> Bytes { get; }
-
-		public bool TryParse(byte[] bytes);
-	}
-}

+ 0 - 7
STUN/Message/Attributes/MappedAddressAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.1
-	/// </summary>
-	public class MappedAddressAttribute : AddressAttribute { }
-}

+ 0 - 7
STUN/Message/Attributes/OtherAddressAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5780#section-7.4
-	/// </summary>
-	public class OtherAddressAttribute : AddressAttribute { }
-}

+ 0 - 7
STUN/Message/Attributes/ReflectedFromAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.11
-	/// </summary>
-	public class ReflectedFromAttribute : AddressAttribute { }
-}

+ 0 - 7
STUN/Message/Attributes/ResponseAddressAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.2
-	/// </summary>
-	public class ResponseAddressAttribute : AddressAttribute { }
-}

+ 0 - 7
STUN/Message/Attributes/SourceAddressAttribute.cs

@@ -1,7 +0,0 @@
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc3489#section-11.2.5
-	/// </summary>
-	public class SourceAddressAttribute : AddressAttribute { }
-}

+ 0 - 47
STUN/Message/Attributes/UnknownAttribute.cs

@@ -1,47 +0,0 @@
-using STUN.Enums;
-using STUN.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.9
-	/// </summary>
-	public class UnknownAttribute : IAttribute
-	{
-		public IEnumerable<byte> Bytes
-		{
-			get
-			{
-				var res = new List<byte>();
-				foreach (var type in Types)
-				{
-					res.AddRange(Convert.ToUInt16(type).ToBe());
-				}
-				return res;
-			}
-		}
-
-		public IEnumerable<AttributeType> Types { get; set; } = Array.Empty<AttributeType>();
-
-		public bool TryParse(byte[] bytes)
-		{
-			if (bytes.Length < 2 || (bytes.Length & 1) == 1)
-			{
-				return false;
-			}
-
-			var list = new List<AttributeType>();
-			for (var i = 0; i < bytes.Length >> 1; ++i)
-			{
-				var b = bytes.Skip(i << 1).Take(2);
-				list.Add((AttributeType)BitUtils.FromBe(b));
-			}
-			Types = list;
-
-			return true;
-		}
-	}
-}

+ 0 - 21
STUN/Message/Attributes/UselessAttribute.cs

@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// 无法理解的属性
-	/// </summary>
-	public class UselessAttribute : IAttribute
-	{
-		public IEnumerable<byte> Bytes => _bytes;
-
-		private byte[] _bytes = Array.Empty<byte>();
-
-		public bool TryParse(byte[] bytes)
-		{
-			_bytes = bytes;
-			return true;
-		}
-	}
-}

+ 0 - 71
STUN/Message/Attributes/XorMappedAddressAttribute.cs

@@ -1,71 +0,0 @@
-using STUN.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-
-namespace STUN.Message.Attributes
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-15.2
-	/// </summary>
-	public class XorMappedAddressAttribute : AddressAttribute
-	{
-		private readonly byte[] _magicCookie;
-		private readonly byte[] _transactionId;
-
-		public XorMappedAddressAttribute(byte[] magicCookie, byte[] transactionId)
-		{
-			_magicCookie = magicCookie;
-			_transactionId = transactionId;
-		}
-
-		public override IEnumerable<byte> Bytes
-		{
-			get
-			{
-				if (Address is null)
-				{
-					return Array.Empty<byte>();
-				}
-
-				var res = new List<byte> { 0, (byte)Family };
-				res.AddRange(Xor(Port).ToBe());
-				res.AddRange(Xor(Address).GetAddressBytes());
-				return res;
-			}
-		}
-
-		public override bool TryParse(byte[] bytes)
-		{
-			if (!base.TryParse(bytes))
-			{
-				return false;
-			}
-
-			Port = Xor(Port);
-
-			Address = Xor(Address!);
-
-			return true;
-		}
-
-		private ushort Xor(ushort port)
-		{
-			var b = port.ToBe().ToArray();
-			var xPort = BitUtils.FromBe((byte)(_magicCookie[0] ^ b[0]), (byte)(_magicCookie[1] ^ b[1]));
-			return xPort;
-		}
-
-		private IPAddress Xor(IPAddress address)
-		{
-			var b = address.GetAddressBytes();
-			var xor = _magicCookie.Concat(_transactionId).ToArray();
-			for (var i = 0; i < b.Length; ++i)
-			{
-				b[i] ^= xor[i];
-			}
-			return new IPAddress(b);
-		}
-	}
-}

+ 0 - 100
STUN/Message/StunMessage5389.cs

@@ -1,100 +0,0 @@
-using STUN.Enums;
-using STUN.Utils;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-
-namespace STUN.Message
-{
-	/// <summary>
-	/// https://tools.ietf.org/html/rfc5389#section-6
-	/// </summary>
-	public class StunMessage5389
-	{
-		public IEnumerable<byte> Header =>
-				Convert.ToUInt16(StunMessageType).ToBe().Concat(MessageLengthBytes)
-						.Concat(MagicCookieBytes).Concat(TransactionId);
-
-		public IEnumerable<Attribute> Attributes { get; set; }
-
-		#region Header
-
-		public StunMessageType StunMessageType { get; set; }
-
-		public ushort MessageLength => Attributes.Aggregate<Attribute, ushort>(0, (current, attribute) => (ushort)(current + Convert.ToUInt16(attribute.RealLength)));
-
-		public IEnumerable<byte> MessageLengthBytes => MessageLength.ToBe();
-
-		public int MagicCookie { get; set; }
-
-		public IEnumerable<byte> MagicCookieBytes => MagicCookie.ToBe();
-
-		public byte[] TransactionId { get; private set; }
-
-		public IEnumerable<byte> ClassicTransactionId => MagicCookieBytes.Concat(TransactionId);
-
-		#endregion
-
-		public IEnumerable<byte> Bytes =>
-				Attributes.Aggregate(Header, (current, attribute) => current.Concat(attribute.ToBytes()));
-
-		public StunMessage5389()
-		{
-			Attributes = Array.Empty<Attribute>();
-			StunMessageType = StunMessageType.BindingRequest;
-			MagicCookie = 0x2112A442;
-			TransactionId = BitUtils.GetRandomBytes(12).ToArray();
-		}
-
-		public bool TryParse(byte[] bytes)
-		{
-			if (bytes.Length < 20)
-			{
-				return false; // Check length
-			}
-
-			StunMessageType = (StunMessageType)BitUtils.FromBe((byte)(bytes[0] & 0b0011_1111), bytes[1]);
-
-			if (!Enum.IsDefined(typeof(StunMessageType), StunMessageType))
-			{
-				return false;
-			}
-
-			var length = BitUtils.FromBe(bytes[2], bytes[3]);
-
-			MagicCookie = BitUtils.FromBeToInt(bytes.Skip(4).Take(4));
-
-			TransactionId = bytes.Skip(8).Take(12).ToArray();
-
-			if (bytes.Length != length + 20)
-			{
-				return false; // Check length
-			}
-
-			var list = new List<Attribute>();
-
-			var b = bytes.Skip(20).ToArray();
-
-			while (b.Length > 0)
-			{
-				var attribute = new Attribute(MagicCookieBytes.ToArray(), TransactionId);
-				var offset = attribute.TryParse(b);
-				if (offset > 0)
-				{
-					list.Add(attribute);
-					b = b.Skip(offset).ToArray();
-				}
-				else
-				{
-					Debug.WriteLine($@"[Warning] Ignore wrong attribute: {BitConverter.ToString(b)}");
-					break;
-				}
-			}
-
-			Attributes = list;
-
-			return true;
-		}
-	}
-}

+ 97 - 0
STUN/Messages/StunAttribute.cs

@@ -0,0 +1,97 @@
+using Microsoft;
+using STUN.Enums;
+using STUN.Messages.StunAttributeValues;
+using System;
+using System.Buffers.Binary;
+using System.Security.Cryptography;
+
+namespace STUN.Messages
+{
+	/// <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)                ....
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         */
+
+		public AttributeType Type { get; set; } = AttributeType.Useless;
+
+		public ushort Length { get; set; }
+
+		public ushort RealLength => (ushort)(Type == AttributeType.Useless ? 0 : 4 + Length + (4 - Length % 4) % 4);
+
+		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;
+
+			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));
+
+			return totalLength;
+		}
+
+		/// <returns>
+		/// Parse 成功字节,0 则表示 Parse 失败
+		/// </returns>
+		public int TryParse(ReadOnlySpan<byte> buffer, ReadOnlySpan<byte> magicCookieAndTransactionId)
+		{
+			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; // 对齐
+		}
+	}
+}

+ 80 - 0
STUN/Messages/StunAttributeValues/AddressStunAttributeValue.cs

@@ -0,0 +1,80 @@
+using Microsoft;
+using STUN.Enums;
+using System;
+using System.Buffers.Binary;
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN.Messages.StunAttributeValues
+{
+	/// <summary>
+	/// https://tools.ietf.org/html/rfc5389#section-15.1
+	/// </summary>
+	public abstract class AddressStunAttributeValue : IStunAttributeValue
+	{
+		public IpFamily Family { get; set; }
+
+		public ushort Port { 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!");
+
+			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));
+
+			return 4 + bytesWritten;
+		}
+
+		public virtual bool TryParse(ReadOnlySpan<byte> buffer)
+		{
+			var length = 4;
+
+			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)
+			{
+				return false;
+			}
+
+			Port = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
+
+			Address = new IPAddress(buffer[4..]);
+
+			return true;
+		}
+
+		public override string? ToString()
+		{
+			return Address?.AddressFamily switch
+			{
+				AddressFamily.InterNetwork => $@"{Address}:{Port}",
+				AddressFamily.InterNetworkV6 => $@"[{Address}]:{Port}",
+				_ => base.ToString()
+			};
+		}
+	}
+}

+ 39 - 0
STUN/Messages/StunAttributeValues/ChangeRequestStunAttributeValue.cs

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

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

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

+ 50 - 0
STUN/Messages/StunAttributeValues/ErrorCodeStunAttributeValue.cs

@@ -0,0 +1,50 @@
+using Microsoft;
+using System;
+using System.Text;
+
+namespace STUN.Messages.StunAttributeValues
+{
+	/// <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 byte Class => (byte)(ErrorCode % 1000 / 100);
+		public byte Number => (byte)(ErrorCode % 100);
+
+		public const int MaxReasonPhraseBytesLength = 762;
+
+		public int WriteTo(Span<byte> buffer)
+		{
+			Requires.Range(buffer.Length >= 4, nameof(buffer));
+
+			buffer[0] = buffer[1] = 0;
+			buffer[2] = Class;
+			buffer[3] = Number;
+
+			var length = Encoding.UTF8.GetBytes(ReasonPhrase, buffer[4..]);
+
+			return 4 + Math.Min(length, MaxReasonPhraseBytesLength);
+		}
+
+		public bool TryParse(ReadOnlySpan<byte> buffer)
+		{
+			if (buffer.Length is < 4 or > (4 + MaxReasonPhraseBytesLength))
+			{
+				return false;
+			}
+
+			var @class = (byte)(buffer[2] & 0b111);
+			var number = Math.Min(buffer[3], (ushort)99);
+
+			ErrorCode = (ushort)(@class * 100 + number);
+
+			ReasonPhrase = Encoding.UTF8.GetString(buffer[4..]);
+
+			return true;
+		}
+	}
+}

+ 11 - 0
STUN/Messages/StunAttributeValues/IStunAttributeValue.cs

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

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

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

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

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

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

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

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

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

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

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

+ 48 - 0
STUN/Messages/StunAttributeValues/UnknownStunAttributeValue.cs

@@ -0,0 +1,48 @@
+using Microsoft;
+using STUN.Enums;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+
+namespace STUN.Messages.StunAttributeValues
+{
+	/// <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)
+		{
+			var size = Types.Count << 1;
+			Requires.Range(buffer.Length >= size, nameof(buffer));
+
+			foreach (var attributeType in Types)
+			{
+				BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)attributeType);
+				buffer = buffer[sizeof(ushort)..];
+			}
+
+			return size;
+		}
+
+		public bool TryParse(ReadOnlySpan<byte> buffer)
+		{
+			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;
+		}
+	}
+}

+ 20 - 0
STUN/Messages/StunAttributeValues/UselessStunAttributeValue.cs

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

+ 73 - 0
STUN/Messages/StunAttributeValues/XorMappedAddressStunAttributeValue.cs

@@ -0,0 +1,73 @@
+using Microsoft;
+using System;
+using System.Buffers.Binary;
+using System.Net;
+
+namespace STUN.Messages.StunAttributeValues
+{
+	/// <summary>
+	/// https://tools.ietf.org/html/rfc5389#section-15.2
+	/// </summary>
+	public class XorMappedAddressStunAttributeValue : AddressStunAttributeValue
+	{
+		private readonly byte[] _magicCookieAndTransactionId;
+
+		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!");
+
+			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));
+
+			return 4 + bytesWritten;
+		}
+
+		public override bool TryParse(ReadOnlySpan<byte> buffer)
+		{
+			if (!base.TryParse(buffer))
+			{
+				return false;
+			}
+
+			Assumes.NotNull(Address);
+
+			Port = Xor(Port);
+
+			Address = Xor(Address);
+
+			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 IPAddress Xor(IPAddress address)
+		{
+			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]);
+		}
+	}
+}

+ 121 - 0
STUN/Messages/StunMessage5389.cs

@@ -0,0 +1,121 @@
+using Microsoft;
+using STUN.Enums;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Security.Cryptography;
+
+namespace STUN.Messages
+{
+	/// <summary>
+	/// https://tools.ietf.org/html/rfc5389#section-6
+	/// </summary>
+	public class StunMessage5389
+	{
+		#region Header
+
+		public StunMessageType StunMessageType { get; set; }
+
+		public uint MagicCookie { get; set; }
+
+		public byte[] TransactionId { get; }
+
+		#endregion
+
+		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 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)
+			{
+				var outLength = attribute.WriteTo(buffer);
+				buffer = buffer[outLength..];
+			}
+
+			return length;
+		}
+
+		public bool TryParse(ReadOnlySpan<byte> buffer)
+		{
+			if (buffer.Length < 20)
+			{
+				return false; // Check length
+			}
+
+			Span<byte> tempSpan = stackalloc byte[2];
+
+			tempSpan[0] = (byte)(buffer[0] & 0b0011_1111);
+			tempSpan[1] = buffer[1];
+			var type = (StunMessageType)BinaryPrimitives.ReadUInt16BigEndian(tempSpan);
+
+			if (!Enum.IsDefined(typeof(StunMessageType), type))
+			{
+				return false;
+			}
+
+			StunMessageType = type;
+
+			var length = BinaryPrimitives.ReadUInt16BigEndian(buffer[2..]);
+
+			MagicCookie = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
+
+			buffer.Slice(8, 12).CopyTo(TransactionId);
+
+			if (buffer.Length != length + 20)
+			{
+				return false; // Check length
+			}
+
+			var list = new List<StunAttribute>();
+
+			var attributeBuffer = buffer[20..];
+			var magicCookieAndTransactionId = buffer.Slice(4, 16);
+
+			while (attributeBuffer.Length > 0)
+			{
+				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;
+				}
+			}
+
+			Attributes = list;
+
+			return true;
+		}
+
+		public bool IsSameTransaction(StunMessage5389 other)
+		{
+			return MagicCookie == other.MagicCookie && TransactionId.AsSpan().SequenceEqual(other.TransactionId);
+		}
+	}
+}

+ 1 - 1
STUN/Proxy/IUdpProxy.cs

@@ -10,7 +10,7 @@ namespace STUN.Proxy
 		TimeSpan Timeout { get; set; }
 		IPEndPoint LocalEndPoint { get; }
 		Task ConnectAsync(CancellationToken token = default);
-		Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default);
+		Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default);
 		Task DisconnectAsync();
 	}
 }

+ 4 - 2
STUN/Proxy/NoneUdpProxy.cs

@@ -36,11 +36,13 @@ namespace STUN.Proxy
 			return Task.CompletedTask;
 		}
 
-		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
 		{
 			Debug.WriteLine($@"{LocalEndPoint} => {remote} {bytes.Length} 字节");
 
-			await _udpClient.SendAsync(bytes, bytes.Length, remote);
+			//TODO .NET6.0
+			var buffer = bytes.ToArray();
+			await _udpClient.SendAsync(buffer, buffer.Length, remote);
 
 			var res = new byte[ushort.MaxValue];
 

+ 2 - 2
STUN/Proxy/Socks5UdpProxy.cs

@@ -183,7 +183,7 @@ namespace STUN.Proxy
 			}
 		}
 
-		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+		public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(ReadOnlyMemory<byte> bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
 		{
 			var state = _assoc.GetState();
 			if (state != TcpState.Established)
@@ -194,7 +194,7 @@ namespace STUN.Proxy
 			var remoteBytes = GetEndPointByte(remote);
 			var proxyBytes = new byte[bytes.Length + remoteBytes.Length + 3];
 			Array.Copy(remoteBytes, 0, proxyBytes, 3, remoteBytes.Length);
-			Array.Copy(bytes, 0, proxyBytes, remoteBytes.Length + 3, bytes.Length);
+			bytes.Span.CopyTo(proxyBytes.AsSpan(remoteBytes.Length + 3));
 
 			await _udpClient.SendAsync(proxyBytes, proxyBytes.Length, _assocEndPoint);
 			var res = new byte[ushort.MaxValue];

+ 1 - 1
STUN/STUN.csproj

@@ -6,7 +6,6 @@
     <NoWarn>CS1591</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
-    <Authors>HMBSbige</Authors>
     <Copyright>Copyright © 2018 - 2021 HMBSbige</Copyright>
     <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <PackageProjectUrl>https://github.com/HMBSbige/NatTypeTester</PackageProjectUrl>
@@ -18,6 +17,7 @@
 
   <ItemGroup>
     <PackageReference Include="Dns.Net.Abstractions" Version="0.1.0" />
+    <PackageReference Include="Microsoft.VisualStudio.Validation" Version="16.10.34" />
   </ItemGroup>
 
 </Project>

+ 9 - 9
STUN/Utils/AttributeExtensions.cs

@@ -1,6 +1,6 @@
 using STUN.Enums;
-using STUN.Message;
-using STUN.Message.Attributes;
+using STUN.Messages;
+using STUN.Messages.StunAttributeValues;
 using System.Linq;
 using System.Net;
 
@@ -8,13 +8,13 @@ namespace STUN.Utils
 {
 	public static class AttributeExtensions
 	{
-		public static Attribute BuildChangeRequest(bool changeIp, bool changePort)
+		public static StunAttribute BuildChangeRequest(bool changeIp, bool changePort)
 		{
-			return new()
+			return new StunAttribute
 			{
 				Type = AttributeType.ChangeRequest,
 				Length = 4,
-				Value = new ChangeRequestAttribute { ChangeIp = changeIp, ChangePort = changePort }
+				Value = new ChangeRequestStunAttributeValue { ChangeIp = changeIp, ChangePort = changePort }
 			};
 		}
 
@@ -27,7 +27,7 @@ namespace STUN.Utils
 				return null;
 			}
 
-			var mapped = (MappedAddressAttribute)mappedAddressAttribute.Value;
+			var mapped = (MappedAddressStunAttributeValue)mappedAddressAttribute.Value;
 			return new IPEndPoint(mapped.Address!, mapped.Port);
 		}
 
@@ -40,7 +40,7 @@ namespace STUN.Utils
 				return null;
 			}
 
-			var address = (ChangedAddressAttribute)changedAddressAttribute.Value;
+			var address = (ChangedAddressStunAttributeValue)changedAddressAttribute.Value;
 			return new IPEndPoint(address.Address!, address.Port);
 		}
 
@@ -55,7 +55,7 @@ namespace STUN.Utils
 				return null;
 			}
 
-			var mapped = (AddressAttribute)mappedAddressAttribute.Value;
+			var mapped = (AddressStunAttributeValue)mappedAddressAttribute.Value;
 			return new IPEndPoint(mapped.Address!, mapped.Port);
 		}
 
@@ -70,7 +70,7 @@ namespace STUN.Utils
 				return null;
 			}
 
-			var address = (AddressAttribute)addressAttribute.Value;
+			var address = (AddressStunAttributeValue)addressAttribute.Value;
 			return new IPEndPoint(address.Address!, address.Port);
 		}
 	}

+ 0 - 50
STUN/Utils/BitUtils.cs

@@ -1,50 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Cryptography;
-
-namespace STUN.Utils
-{
-	public static class BitUtils
-	{
-		public static IEnumerable<byte> ToBe(this int num)
-		{
-			var res = BitConverter.GetBytes(num);
-			return BitConverter.IsLittleEndian ? res.Reverse() : res;
-		}
-
-		public static IEnumerable<byte> ToBe(this ushort num)
-		{
-			var res = BitConverter.GetBytes(num);
-			return BitConverter.IsLittleEndian ? res.Reverse() : res;
-		}
-
-		public static ushort FromBe(byte b1, byte b2)
-		{
-			return BitConverter.ToUInt16(BitConverter.IsLittleEndian ? new[] { b2, b1 } : new[] { b1, b2 }, 0);
-		}
-
-		public static ushort FromBe(IEnumerable<byte> b)
-		{
-			return BitConverter.ToUInt16(BitConverter.IsLittleEndian ? b.Reverse().ToArray() : b.ToArray(), 0);
-		}
-
-		public static int FromBeToInt(IEnumerable<byte> b)
-		{
-			return BitConverter.ToInt32(BitConverter.IsLittleEndian ? b.Reverse().ToArray() : b.ToArray(), 0);
-		}
-
-		public static IEnumerable<byte> GetRandomBytes(int n)
-		{
-			var temp = new byte[n];
-			using var rng = new RNGCryptoServiceProvider();
-			rng.GetBytes(temp);
-			return temp;
-		}
-
-		public static bool IsEqual(this IEnumerable<byte>? a, IEnumerable<byte>? b)
-		{
-			return a != null && b != null && a.SequenceEqual(b);
-		}
-	}
-}

+ 17 - 11
UnitTest/UnitTest.cs

@@ -2,7 +2,7 @@ using Dns.Net.Clients;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using STUN.Client;
 using STUN.Enums;
-using STUN.Message.Attributes;
+using STUN.Messages.StunAttributeValues;
 using STUN.Proxy;
 using STUN.Utils;
 using System;
@@ -15,12 +15,12 @@ namespace UnitTest
 	[TestClass]
 	public class UnitTest
 	{
-		private readonly byte[] _magicCookie = { 0x21, 0x12, 0xa4, 0x42 };
-		private readonly byte[] _transactionId =
+		private static ReadOnlySpan<byte> MagicCookieAndTransactionId => new byte[]
 		{
-				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 };
@@ -46,27 +46,33 @@ namespace UnitTest
 		[TestMethod]
 		public void TestXorMapped()
 		{
-			var t = new XorMappedAddressAttribute(_magicCookie, _transactionId)
+			var t = new XorMappedAddressStunAttributeValue(MagicCookieAndTransactionId)
 			{
 				Port = Port,
 				Family = IpFamily.IPv4,
 				Address = IPv4
 			};
-			Assert.IsTrue(_ipv4Response.SequenceEqual(t.Bytes));
+			Span<byte> temp = stackalloc byte[ushort.MaxValue];
 
-			t = new XorMappedAddressAttribute(_magicCookie, _transactionId);
+			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 XorMappedAddressAttribute(_magicCookie, _transactionId);
+			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);
 
-			Assert.IsTrue(_ipv6Response.SequenceEqual(t.Bytes));
+			var length6 = t.WriteTo(temp);
+			Assert.AreNotEqual(0, length6);
+			Assert.IsTrue(temp[..length6].SequenceEqual(_ipv6Response));
 		}
 
 		[TestMethod]