New PhysicalAddress.TryParse methods taking span and string (#1057)
authorAlexander Nikolaev <55398552+alnikola@users.noreply.github.com>
Tue, 7 Jan 2020 13:39:46 +0000 (14:39 +0100)
committerGitHub <noreply@github.com>
Tue, 7 Jan 2020 13:39:46 +0000 (14:39 +0100)
This PR introduces two new PhysicalAddress.TryParse methods taking span and string as well as adds a PhysicalAddress.Parse overload taking span.
Fixes dotnet/corefx#29780

src/libraries/System.Net.NetworkInformation/ref/System.Net.NetworkInformation.cs
src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs
src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/PhysicalAddressTest.cs

index e40ca24..d02f767 100644 (file)
@@ -370,6 +370,9 @@ namespace System.Net.NetworkInformation
         public byte[] GetAddressBytes() { throw null; }
         public override int GetHashCode() { throw null; }
         public static System.Net.NetworkInformation.PhysicalAddress Parse(string address) { throw null; }
+        public static System.Net.NetworkInformation.PhysicalAddress Parse(ReadOnlySpan<char> address) { throw null; }
+        public static bool TryParse(string address, out System.Net.NetworkInformation.PhysicalAddress value) { throw null; }
+        public static bool TryParse(ReadOnlySpan<char> address, out System.Net.NetworkInformation.PhysicalAddress value) { throw null; }
         public override string ToString() { throw null; }
     }
     public enum PrefixOrigin
index 5930946..bf5c9f8 100644 (file)
@@ -103,22 +103,39 @@ namespace System.Net.NetworkInformation
             return (byte[])_address.Clone();
         }
 
-        public static PhysicalAddress Parse(string address)
+        public static PhysicalAddress Parse(string address) => address != null ? Parse(address.AsSpan()) : None;
+
+        public static PhysicalAddress Parse(ReadOnlySpan<char> address)
         {
-            int validSegmentLength;
-            char? delimiter = null;
-            byte[] buffer;
+            if (!TryParse(address, out PhysicalAddress value))
+                throw new FormatException(SR.Format(SR.net_bad_mac_address, new string(address)));
 
+            return value;
+        }
+
+        public static bool TryParse(string address, out PhysicalAddress value)
+        {
             if (address == null)
             {
-                return None;
+                value = None;
+                return true;
             }
 
+            return TryParse(address, out value);
+        }
+
+        public static bool TryParse(ReadOnlySpan<char> address, out PhysicalAddress value)
+        {
+            int validSegmentLength;
+            char? delimiter = null;
+            byte[] buffer;
+            value = null;
+
             if (address.Contains('-'))
             {
                 if ((address.Length + 1) % 3 != 0)
                 {
-                    ThrowBadAddressException(address);
+                    return false;
                 }
 
                 delimiter = '-';
@@ -128,20 +145,30 @@ namespace System.Net.NetworkInformation
             else if (address.Contains(':'))
             {
                 delimiter = ':';
-                validSegmentLength = GetValidSegmentLength(address, ':');
+
+                if (!TryGetValidSegmentLength(address, ':', out validSegmentLength))
+                {
+                    return false;
+                }
+
                 if (validSegmentLength != 2 && validSegmentLength != 4)
                 {
-                    ThrowBadAddressException(address);
+                    return false;
                 }
                 buffer = new byte[6];
             }
             else if (address.Contains('.'))
             {
                 delimiter = '.';
-                validSegmentLength = GetValidSegmentLength(address, '.');
+
+                if (!TryGetValidSegmentLength(address, '.', out validSegmentLength))
+                {
+                    return false;
+                }
+
                 if (validSegmentLength != 4)
                 {
-                    ThrowBadAddressException(address);
+                    return false;
                 }
                 buffer = new byte[6];
             }
@@ -149,7 +176,7 @@ namespace System.Net.NetworkInformation
             {
                 if (address.Length % 2 > 0)
                 {
-                    ThrowBadAddressException(address);
+                    return false;
                 }
 
                 validSegmentLength = address.Length;
@@ -160,44 +187,44 @@ namespace System.Net.NetworkInformation
             int j = 0;
             for (int i = 0; i < address.Length; i++)
             {
-                int value = address[i];
+                int character = address[i];
 
-                if (value >= '0' && value <= '9')
+                if (character >= '0' && character <= '9')
                 {
-                    value -= '0';
+                    character -= '0';
                 }
-                else if (value >= 'A' && value <= 'F')
+                else if (character >= 'A' && character <= 'F')
                 {
-                    value -= ('A' - 10);
+                    character -= ('A' - 10);
                 }
-                else if (value >= 'a' && value <= 'f')
+                else if (character >= 'a' && character <= 'f')
                 {
-                    value -= ('a' - 10);
+                    character -= ('a' - 10);
                 }
                 else
                 {
-                    if (delimiter == value && validCount == validSegmentLength)
+                    if (delimiter == character && validCount == validSegmentLength)
                     {
                         validCount = 0;
                         continue;
                     }
 
-                    ThrowBadAddressException(address);
+                    return false;
                 }
 
                 // we had too many characters after the last delimiter
                 if (validCount >= validSegmentLength)
                 {
-                    ThrowBadAddressException(address);
+                    return false;
                 }
 
                 if (validCount % 2 == 0)
                 {
-                    buffer[j] = (byte)(value << 4);
+                    buffer[j] = (byte)(character << 4);
                 }
                 else
                 {
-                    buffer[j++] |= (byte)value;
+                    buffer[j++] |= (byte)character;
                 }
 
                 validCount++;
@@ -206,17 +233,16 @@ namespace System.Net.NetworkInformation
             // we had too few characters after the last delimiter
             if (validCount < validSegmentLength)
             {
-                ThrowBadAddressException(address);
+                return false;
             }
 
-            return new PhysicalAddress(buffer);
-
-            static void ThrowBadAddressException(string address) =>
-                throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+            value = new PhysicalAddress(buffer);
+            return true;
         }
 
-        private static int GetValidSegmentLength(string address, char delimiter)
+        private static bool TryGetValidSegmentLength(ReadOnlySpan<char> address, char delimiter, out int value)
         {
+            value = -1;
             int segments = 1;
             int validSegmentLength = 0;
             for (int i = 0; i < address.Length; i++)
@@ -229,8 +255,8 @@ namespace System.Net.NetworkInformation
                     }
                     else if ((i - (segments - 1)) % validSegmentLength != 0)
                     {
-                        // segments - 1 = num of delimeters. Throw if new segment isn't the validSegmentLength
-                        throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                        // segments - 1 = num of delimeters. Return false if new segment isn't the validSegmentLength
+                        return false;
                     }
 
                     segments++;
@@ -239,10 +265,11 @@ namespace System.Net.NetworkInformation
 
             if (segments * validSegmentLength != 12)
             {
-                throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                return false;
             }
 
-            return validSegmentLength;
+            value = validSegmentLength;
+            return true;
         }
     }
 }
index c4e7c99..db10cb3 100644 (file)
@@ -116,44 +116,145 @@ namespace System.Net.NetworkInformation.Tests
             };
         }
 
+        public static IEnumerable<object[]> InvalidAddressStrings()
+        {
+            yield return new object[] { "F" };
+            yield return new object[] { "M0" };
+            yield return new object[] { "33-3" };
+            yield return new object[] { "D2-C3-" };
+            yield return new object[] { "D2-A33" };
+            yield return new object[] { "B4-A5-F01" };
+            yield return new object[] { "00:111:22:33:44:55" };
+            yield return new object[] { "0:1:22:33:44:55" };
+            yield return new object[] { "0:0:1:1:22:33:44:55" };
+            yield return new object[] { ":::00112233444" };
+            yield return new object[] { "0011:2233" };
+            yield return new object[] { "0011:2233:4455:6677" };
+            yield return new object[] { "0011:2233:4455:6677:8899:0011" };
+            yield return new object[] { "001:12233:4455" };
+            yield return new object[] { "0011:2233:" };
+            yield return new object[] { "0011:2233:44555" };
+            yield return new object[] { "001.12233.4455" };
+            yield return new object[] { "0011.2233." };
+            yield return new object[] { "0011.2233.44555" };
+            yield return new object[] { "00.111.22.33.44.55" };
+            yield return new object[] { "001.122.334.455" };
+        }
+
         [Theory]
         [MemberData(nameof(RoundtripParseToString_String_Bytes))]
-        public void Parse_Valid_Success(string address, byte[] expectedBytes)
+        public void ParseString_Valid_Success(string address, byte[] expectedBytes)
         {
             PhysicalAddress parsedAddress = PhysicalAddress.Parse(address);
             byte[] addressBytes = parsedAddress.GetAddressBytes();
-            Assert.Equal(addressBytes, expectedBytes);
+            Assert.Equal(expectedBytes, addressBytes);
+        }
+
+        [Fact]
+        public void ParseString_Null_Success()
+        {
+            PhysicalAddress parsedAddress = PhysicalAddress.Parse(null);
+            Assert.Equal(PhysicalAddress.None, parsedAddress);
+        }
+
+        [Fact]
+        public void ParseString_Empty_Success()
+        {
+            PhysicalAddress parsedAddress = PhysicalAddress.Parse(string.Empty);
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Empty(addressBytes);
+        }
+
+        [Theory]
+        [MemberData(nameof(RoundtripParseToString_String_Bytes))]
+        public void TryParseString_Valid_Success(string address, byte[] expectedBytes)
+        {
+            Assert.True(PhysicalAddress.TryParse(address, out PhysicalAddress parsedAddress));
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Equal(expectedBytes, addressBytes);
+        }
+
+        [Fact]
+        public void TryParseString_Null_Success()
+        {
+            Assert.True(PhysicalAddress.TryParse(null, out PhysicalAddress parsedAddress));
+            Assert.Equal(PhysicalAddress.None, parsedAddress);
+        }
+
+        [Fact]
+        public void TryParseString_Empty_Success()
+        {
+            Assert.True(PhysicalAddress.TryParse(string.Empty, out PhysicalAddress parsedAddress));
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Empty(addressBytes);
+        }
+
+        [Theory]
+        [MemberData(nameof(RoundtripParseToString_String_Bytes))]
+        public void ParseSpan_Valid_Success(string address, byte[] expectedBytes)
+        {
+            PhysicalAddress parsedAddress = PhysicalAddress.Parse(address.AsSpan());
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Equal(expectedBytes, addressBytes);
+        }
+
+        [Fact]
+        public void ParseSpan_Empty_Success()
+        {
+            PhysicalAddress parsedAddress = PhysicalAddress.Parse(new ReadOnlySpan<char>());
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Empty(addressBytes);
         }
 
         [Theory]
-        [InlineData("F")]
-        [InlineData("M0")]
-        [InlineData("33-3")]
-        [InlineData("D2-C3-")]
-        [InlineData("D2-A33")]
-        [InlineData("B4-A5-F01")]
-        [InlineData("00:111:22:33:44:55")]
-        [InlineData("0:1:22:33:44:55")]
-        [InlineData("0:0:1:1:22:33:44:55")]
-        [InlineData(":::00112233444")]
-        [InlineData("0011:2233")]
-        [InlineData("0011:2233:4455:6677")]
-        [InlineData("0011:2233:4455:6677:8899:0011")]
-        [InlineData("001:12233:4455")]
-        [InlineData("0011:2233:")]
-        [InlineData("0011:2233:44555")]
-        [InlineData("001.12233.4455")]
-        [InlineData("0011.2233.")]
-        [InlineData("0011.2233.44555")]
-        [InlineData("00.111.22.33.44.55")]
-        [InlineData("001.122.334.455")]
-
-        public void Parse_Invalid_ThrowsFormatException(string address)
+        [MemberData(nameof(RoundtripParseToString_String_Bytes))]
+        public void TryParseSpan_Valid_Success(string address, byte[] expectedBytes)
+        {
+            Assert.True(PhysicalAddress.TryParse(address.AsSpan(), out PhysicalAddress parsedAddress));
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Equal(expectedBytes, addressBytes);
+        }
+
+        [Fact]
+        public void TryParseSpan_Empty_Success()
+        {
+            Assert.True(PhysicalAddress.TryParse(new ReadOnlySpan<char>(), out PhysicalAddress parsedAddress));
+            byte[] addressBytes = parsedAddress.GetAddressBytes();
+            Assert.Empty(addressBytes);
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidAddressStrings))]
+        public void ParseString_Invalid_ThrowsFormatException(string address)
         {
             FormatException ex = Assert.Throws<FormatException>(() => PhysicalAddress.Parse(address));
             Assert.Contains(address, ex.Message);
         }
 
+        [Theory]
+        [MemberData(nameof(InvalidAddressStrings))]
+        public void TryParseString_Invalid_ReturnsFalse(string address)
+        {
+            Assert.False(PhysicalAddress.TryParse(address, out PhysicalAddress value));
+            Assert.Null(value);
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidAddressStrings))]
+        public void ParseSpan_Invalid_ThrowsFormatException(string address)
+        {
+            FormatException ex = Assert.Throws<FormatException>(() => PhysicalAddress.Parse(address.AsSpan()));
+            Assert.Contains(address, ex.Message);
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidAddressStrings))]
+        public void TryParseSpan_Invalid_ReturnsFalse(string address)
+        {
+            Assert.False(PhysicalAddress.TryParse(address.AsSpan(), out PhysicalAddress value));
+            Assert.Null(value);
+        }
+
         [Fact]
         public void ToString_NullAddress_NullReferenceException()
         {