PhysicalAddress.Parse case-insensitive and support new formats (dotnet/corefx#41696)
authorMitch Valenta <mvalenta@live.com>
Wed, 6 Nov 2019 18:14:33 +0000 (10:14 -0800)
committerStephen Toub <stoub@microsoft.com>
Wed, 6 Nov 2019 18:14:33 +0000 (13:14 -0500)
* Add parsing support for PhysicalAddresses with colon delimiter and lowercasing

* Address PR feedback

Commit migrated from https://github.com/dotnet/corefx/commit/0d2969f69c36e8d131f4620cc172b6a7c3840fc8

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

index 3a738ee..5930946 100644 (file)
@@ -105,70 +105,90 @@ namespace System.Net.NetworkInformation
 
         public static PhysicalAddress Parse(string address)
         {
-            int validCount = 0;
-            bool hasDashes = false;
-            byte[] buffer = null;
+            int validSegmentLength;
+            char? delimiter = null;
+            byte[] buffer;
 
             if (address == null)
             {
-                return PhysicalAddress.None;
+                return None;
             }
 
-            // Has dashes?
             if (address.Contains('-'))
             {
-                hasDashes = true;
-                buffer = new byte[(address.Length + 1) / 3];
-
                 if ((address.Length + 1) % 3 != 0)
                 {
-                    throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                    ThrowBadAddressException(address);
+                }
+
+                delimiter = '-';
+                buffer = new byte[(address.Length + 1) / 3]; // allow any length that's a multiple of 3
+                validSegmentLength = 2;
+            }
+            else if (address.Contains(':'))
+            {
+                delimiter = ':';
+                validSegmentLength = GetValidSegmentLength(address, ':');
+                if (validSegmentLength != 2 && validSegmentLength != 4)
+                {
+                    ThrowBadAddressException(address);
+                }
+                buffer = new byte[6];
+            }
+            else if (address.Contains('.'))
+            {
+                delimiter = '.';
+                validSegmentLength = GetValidSegmentLength(address, '.');
+                if (validSegmentLength != 4)
+                {
+                    ThrowBadAddressException(address);
                 }
+                buffer = new byte[6];
             }
             else
             {
                 if (address.Length % 2 > 0)
                 {
-                    throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                    ThrowBadAddressException(address);
                 }
 
+                validSegmentLength = address.Length;
                 buffer = new byte[address.Length / 2];
             }
 
+            int validCount = 0;
             int j = 0;
             for (int i = 0; i < address.Length; i++)
             {
-                int value = (int)address[i];
+                int value = address[i];
 
-                if (value >= 0x30 && value <= 0x39)
+                if (value >= '0' && value <= '9')
+                {
+                    value -= '0';
+                }
+                else if (value >= 'A' && value <= 'F')
                 {
-                    value -= 0x30;
+                    value -= ('A' - 10);
                 }
-                else if (value >= 0x41 && value <= 0x46)
+                else if (value >= 'a' && value <= 'f')
                 {
-                    value -= 0x37;
+                    value -= ('a' - 10);
                 }
-                else if (value == (int)'-')
+                else
                 {
-                    if (validCount == 2)
+                    if (delimiter == value && validCount == validSegmentLength)
                     {
                         validCount = 0;
                         continue;
                     }
-                    else
-                    {
-                        throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
-                    }
-                }
-                else
-                {
-                    throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+
+                    ThrowBadAddressException(address);
                 }
 
-                // we had too many characters after the last dash
-                if (hasDashes && validCount >= 2)
+                // we had too many characters after the last delimiter
+                if (validCount >= validSegmentLength)
                 {
-                    throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                    ThrowBadAddressException(address);
                 }
 
                 if (validCount % 2 == 0)
@@ -183,13 +203,46 @@ namespace System.Net.NetworkInformation
                 validCount++;
             }
 
-            // we too few characters after the last dash
-            if (validCount < 2)
+            // we had too few characters after the last delimiter
+            if (validCount < validSegmentLength)
             {
-                throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+                ThrowBadAddressException(address);
             }
 
             return new PhysicalAddress(buffer);
+
+            static void ThrowBadAddressException(string address) =>
+                throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+        }
+
+        private static int GetValidSegmentLength(string address, char delimiter)
+        {
+            int segments = 1;
+            int validSegmentLength = 0;
+            for (int i = 0; i < address.Length; i++)
+            {
+                if (address[i] == delimiter)
+                {
+                    if (validSegmentLength == 0)
+                    {
+                        validSegmentLength = i;
+                    }
+                    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++;
+                }
+            }
+
+            if (segments * validSegmentLength != 12)
+            {
+                throw new FormatException(SR.Format(SR.net_bad_mac_address, address));
+            }
+
+            return validSegmentLength;
         }
     }
 }
index f663710..c4e7c99 100644 (file)
@@ -82,8 +82,12 @@ namespace System.Net.NetworkInformation.Tests
             yield return new object[] { "47-FB-74-41-3B", new byte[] { 0x47, 0xfb, 0x74, 0x41, 0x3B } };
             yield return new object[] { "00-11-22-33-44-55", new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
             yield return new object[] { "F0-E1-D2-C3-B4-A5", new byte[] { 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5 } };
+            yield return new object[] { "f0-e1-d2-c3-b4-a5", new byte[] { 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5 } };
             yield return new object[] { "54-0C-C4-7E-66-54", new byte[] { 0x54, 0x0c, 0xc4, 0x7e, 0x66, 0x54 } };
             yield return new object[] { "001122334455", new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
+            yield return new object[] { "00:11:22:33:44:55", new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
+            yield return new object[] { "0011:2233:4455", new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
+            yield return new object[] { "0011.2233.4455", new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 } };
             yield return new object[]
             {
                 "00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F-10-11-12-13-14-15-16-17-18-19-1A-1B-1C-1D-1E-1F-" +
@@ -128,10 +132,22 @@ namespace System.Net.NetworkInformation.Tests
         [InlineData("D2-C3-")]
         [InlineData("D2-A33")]
         [InlineData("B4-A5-F01")]
-        [InlineData("f0-e1-d2-c3-b4-a5")]
-        [InlineData("47:FB:74:41:66:3B")]
-        [InlineData("de84.1251.1c9d")]
-        [InlineData("AE88.D6EC.A720")]
+        [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)
         {
             FormatException ex = Assert.Throws<FormatException>(() => PhysicalAddress.Parse(address));
@@ -150,7 +166,7 @@ namespace System.Net.NetworkInformation.Tests
         [MemberData(nameof(RoundtripParseToString_String_Bytes))]
         public void ToString_ExpectedResult(string expectedAddress, byte[] inputBytes)
         {
-            Assert.Equal(expectedAddress.Replace("-", ""), new PhysicalAddress(inputBytes).ToString());
+            Assert.Equal(expectedAddress.Replace("-", "").Replace(":", "").Replace(".", "").ToUpper(), new PhysicalAddress(inputBytes).ToString());
         }
 
         [Theory]