File: System\Net\IPAddress.cs
Web Access
Project: src\src\libraries\System.Net.Primitives\src\System.Net.Primitives.csproj (System.Net.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
 
#pragma warning disable SA1648 // TODO: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3595
 
namespace System.Net
{
    /// <devdoc>
    ///   <para>
    ///     Provides an Internet Protocol (IP) address.
    ///   </para>
    /// </devdoc>
    public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable, IUtf8SpanParsable<IPAddress>
    {
        public static readonly IPAddress Any = new ReadOnlyIPAddress([0, 0, 0, 0]);
        public static readonly IPAddress Loopback = new ReadOnlyIPAddress([127, 0, 0, 1]);
        public static readonly IPAddress Broadcast = new ReadOnlyIPAddress([255, 255, 255, 255]);
        public static readonly IPAddress None = Broadcast;
 
        internal const uint LoopbackMaskHostOrder = 0xFF000000;
 
        public static readonly IPAddress IPv6Any = new IPAddress((ReadOnlySpan<byte>)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0);
        public static readonly IPAddress IPv6Loopback = new IPAddress((ReadOnlySpan<byte>)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 0);
        public static readonly IPAddress IPv6None = IPv6Any;
 
        private static readonly IPAddress s_loopbackMappedToIPv6 = new IPAddress((ReadOnlySpan<byte>)[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1], 0);
 
        /// <summary>
        /// For IPv4 addresses, this field stores the Address.
        /// For IPv6 addresses, this field stores the ScopeId.
        /// Instead of accessing this field directly, use the <see cref="PrivateAddress"/> or <see cref="PrivateScopeId"/> properties.
        /// </summary>
        private uint _addressOrScopeId;
 
        /// <summary>
        /// This field is only used for IPv6 addresses. A null value indicates that this instance is an IPv4 address.
        /// </summary>
        private readonly ushort[]? _numbers;
 
        /// <summary>
        /// A lazily initialized cache of the result of calling <see cref="ToString"/>.
        /// </summary>
        private string? _toString;
 
        /// <summary>
        /// A lazily initialized cache of the <see cref="GetHashCode"/> value.
        /// </summary>
        private int _hashCode;
 
        internal const int NumberOfLabels = IPAddressParserStatics.IPv6AddressBytes / 2;
 
        [MemberNotNullWhen(false, nameof(_numbers))]
        private bool IsIPv4
        {
            get { return _numbers == null; }
        }
 
        [MemberNotNullWhen(true, nameof(_numbers))]
        private bool IsIPv6
        {
            get { return _numbers != null; }
        }
 
        internal uint PrivateAddress
        {
            get
            {
                Debug.Assert(IsIPv4);
                return _addressOrScopeId;
            }
            private set
            {
                Debug.Assert(IsIPv4);
                _toString = null;
                _hashCode = 0;
                _addressOrScopeId = value;
            }
        }
 
        internal uint PrivateIPv4Address
        {
            get
            {
                Debug.Assert(IsIPv4 || IsIPv4MappedToIPv6);
                if (IsIPv4)
                {
                    return _addressOrScopeId;
                }
                uint address = (uint)_numbers[6] << 16 | (uint)_numbers[7];
                return (uint)HostToNetworkOrder(unchecked((int)address));
            }
        }
 
        private uint PrivateScopeId
        {
            get
            {
                Debug.Assert(IsIPv6);
                return _addressOrScopeId;
            }
            set
            {
                Debug.Assert(IsIPv6);
                _toString = null;
                _hashCode = 0;
                _addressOrScopeId = value;
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     Initializes a new instance of the <see cref='System.Net.IPAddress'/>
        ///     class with the specified address.
        ///   </para>
        /// </devdoc>
        public IPAddress(long newAddress)
        {
            ArgumentOutOfRangeException.ThrowIfGreaterThan((ulong)newAddress, 0x00000000FFFFFFFF, nameof(newAddress));
 
            PrivateAddress = (uint)newAddress;
        }
 
        /// <devdoc>
        ///   <para>
        ///     Constructor for an IPv6 Address with a specified Scope.
        ///   </para>
        /// </devdoc>
        public IPAddress(byte[] address, long scopeid) :
            this(new ReadOnlySpan<byte>(address ?? ThrowAddressNullException()), scopeid)
        {
        }
 
        public IPAddress(ReadOnlySpan<byte> address, long scopeid)
        {
            if (address.Length != IPAddressParserStatics.IPv6AddressBytes)
            {
                throw new ArgumentException(SR.dns_bad_ip_address, nameof(address));
            }
 
            // Consider: Since scope is only valid for link-local and site-local
            //           addresses we could implement some more robust checking here
            ArgumentOutOfRangeException.ThrowIfGreaterThan((ulong)scopeid, 0x00000000FFFFFFFF, nameof(scopeid));
 
            _numbers = ReadUInt16NumbersFromBytes(address);
            PrivateScopeId = (uint)scopeid;
        }
 
        internal IPAddress(ReadOnlySpan<ushort> numbers, uint scopeid)
        {
            Debug.Assert(numbers.Length == NumberOfLabels);
 
            _numbers = numbers.ToArray();
            PrivateScopeId = scopeid;
        }
 
        private IPAddress(ushort[] numbers, uint scopeid)
        {
            Debug.Assert(numbers != null);
            Debug.Assert(numbers.Length == NumberOfLabels);
 
            _numbers = numbers;
            PrivateScopeId = scopeid;
        }
 
        /// <devdoc>
        ///   <para>
        ///     Constructor for IPv4 and IPv6 Address.
        ///   </para>
        /// </devdoc>
        public IPAddress(byte[] address) :
            this(new ReadOnlySpan<byte>(address ?? ThrowAddressNullException()))
        {
        }
 
        public IPAddress(ReadOnlySpan<byte> address)
        {
            if (address.Length == IPAddressParserStatics.IPv4AddressBytes)
            {
                PrivateAddress = MemoryMarshal.Read<uint>(address);
            }
            else if (address.Length == IPAddressParserStatics.IPv6AddressBytes)
            {
                _numbers = ReadUInt16NumbersFromBytes(address);
            }
            else
            {
                throw new ArgumentException(SR.dns_bad_ip_address, nameof(address));
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static ushort[] ReadUInt16NumbersFromBytes(ReadOnlySpan<byte> address)
        {
            ushort[] numbers = new ushort[NumberOfLabels];
            if (Vector128.IsHardwareAccelerated && BitConverter.IsLittleEndian)
            {
                Vector128<ushort> ushorts = Vector128.Create(address).AsUInt16();
                // Reverse endianness of each ushort
                ushorts = (ushorts << 8) | (ushorts >> 8);
                ushorts.CopyTo(numbers);
            }
            else
            {
                for (int i = 0; i < numbers.Length; i++)
                {
                    numbers[i] = BinaryPrimitives.ReadUInt16BigEndian(address.Slice(i * 2));
                }
            }
 
            return numbers;
        }
 
        // We need this internally since we need to interface with winsock,
        // and winsock only understands Int32.
        internal IPAddress(int newAddress)
        {
            PrivateAddress = (uint)newAddress;
        }
 
        /// <devdoc>
        ///   <para>
        ///     Converts an IP address string to an <see cref='System.Net.IPAddress'/> instance.
        ///   </para>
        /// </devdoc>
        public static bool TryParse([NotNullWhen(true)] string? ipString, [NotNullWhen(true)] out IPAddress? address)
        {
            if (ipString == null)
            {
                address = null;
                return false;
            }
 
            address = IPAddressParser.Parse(ipString.AsSpan(), tryParse: true);
            return (address != null);
        }
 
        /// <summary>
        /// Tries to parse a span of UTF-8 characters into a value.
        /// </summary>
        /// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
        /// <param name="result">On return, contains the result of successfully parsing <paramref name="utf8Text"/> or an undefined value on failure.</param>
        /// <returns><c>true</c> if <paramref name="utf8Text"/> was successfully parsed; otherwise, <c>false</c>.</returns>
        public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPAddress? result)
        {
            result = IPAddressParser.Parse(utf8Text, tryParse: true);
            return (result != null);
        }
 
        public static bool TryParse(ReadOnlySpan<char> ipSpan, [NotNullWhen(true)] out IPAddress? address)
        {
            address = IPAddressParser.Parse(ipSpan, tryParse: true);
            return (address != null);
        }
 
        /// <inheritdoc/>
        static bool IUtf8SpanParsable<IPAddress>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
            TryParse(utf8Text, out result);
 
        /// <inheritdoc/>
        static bool IParsable<IPAddress>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
            // provider is explicitly ignored
            TryParse(s, out result);
 
        /// <inheritdoc/>
        static bool ISpanParsable<IPAddress>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
            // provider is explicitly ignored
            TryParse(s, out result);
 
        public static IPAddress Parse(string ipString)
        {
            ArgumentNullException.ThrowIfNull(ipString);
 
            return IPAddressParser.Parse(ipString.AsSpan(), tryParse: false)!;
        }
 
        /// <summary>
        /// Parses a span of UTF-8 characters into a value.
        /// </summary>
        /// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
        /// <returns>The result of parsing <paramref name="utf8Text"/>.</returns>
        public static IPAddress Parse(ReadOnlySpan<byte> utf8Text)
        {
            return IPAddressParser.Parse(utf8Text, tryParse: false)!;
        }
 
        public static IPAddress Parse(ReadOnlySpan<char> ipSpan)
        {
            return IPAddressParser.Parse(ipSpan, tryParse: false)!;
        }
 
        /// <inheritdoc/>
        static IPAddress IUtf8SpanParsable<IPAddress>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) =>
            // provider is explicitly ignored
            Parse(utf8Text);
 
        /// <inheritdoc/>
        static IPAddress ISpanParsable<IPAddress>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) =>
            // provider is explicitly ignored
            Parse(s);
 
        /// <inheritdoc/>
        static IPAddress IParsable<IPAddress>.Parse(string s, IFormatProvider? provider) =>
            // provider is explicitly ignored
            Parse(s);
 
        public bool TryWriteBytes(Span<byte> destination, out int bytesWritten)
        {
            if (IsIPv6)
            {
                if (destination.Length < IPAddressParserStatics.IPv6AddressBytes)
                {
                    bytesWritten = 0;
                    return false;
                }
 
                WriteIPv6Bytes(destination);
                bytesWritten = IPAddressParserStatics.IPv6AddressBytes;
            }
            else
            {
                if (destination.Length < IPAddressParserStatics.IPv4AddressBytes)
                {
                    bytesWritten = 0;
                    return false;
                }
 
                WriteIPv4Bytes(destination);
                bytesWritten = IPAddressParserStatics.IPv4AddressBytes;
            }
 
            return true;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void WriteIPv6Bytes(Span<byte> destination)
        {
            ushort[]? numbers = _numbers;
            Debug.Assert(numbers is { Length: NumberOfLabels });
 
            if (BitConverter.IsLittleEndian)
            {
                if (Vector128.IsHardwareAccelerated)
                {
                    Vector128<ushort> ushorts = Vector128.Create(numbers).AsUInt16();
                    ushorts = (ushorts << 8) | (ushorts >> 8);
                    ushorts.AsByte().CopyTo(destination);
                }
                else
                {
                    for (int i = 0; i < numbers.Length; i++)
                    {
                        BinaryPrimitives.WriteUInt16BigEndian(destination.Slice(i * 2), numbers[i]);
                    }
                }
            }
            else
            {
                MemoryMarshal.AsBytes<ushort>(numbers).CopyTo(destination);
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void WriteIPv4Bytes(Span<byte> destination)
        {
            uint address = PrivateAddress;
            MemoryMarshal.Write(destination, in address);
        }
 
        /// <devdoc>
        ///   <para>
        ///     Provides a copy of the IPAddress internals as an array of bytes.
        ///   </para>
        /// </devdoc>
        public byte[] GetAddressBytes()
        {
            if (IsIPv6)
            {
                Debug.Assert(_numbers is { Length: NumberOfLabels });
                byte[] bytes = new byte[IPAddressParserStatics.IPv6AddressBytes];
                WriteIPv6Bytes(bytes);
                return bytes;
            }
            else
            {
                byte[] bytes = new byte[IPAddressParserStatics.IPv4AddressBytes];
                WriteIPv4Bytes(bytes);
                return bytes;
            }
        }
 
        public AddressFamily AddressFamily
        {
            get
            {
                return IsIPv4 ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6;
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     IPv6 Scope identifier. This is really a uint32, but that isn't CLS compliant
        ///   </para>
        /// </devdoc>
        public long ScopeId
        {
            get
            {
                // Not valid for IPv4 addresses
                if (IsIPv4)
                {
                    ThrowSocketOperationNotSupported();
                }
 
                return PrivateScopeId;
            }
            set
            {
                // Not valid for IPv4 addresses
                if (IsIPv4)
                {
                    ThrowSocketOperationNotSupported();
                }
 
                // Consider: Since scope is only valid for link-local and site-local
                //           addresses we could implement some more robust checking here
                ArgumentOutOfRangeException.ThrowIfNegative(value);
                ArgumentOutOfRangeException.ThrowIfGreaterThan(value, 0x00000000FFFFFFFF);
 
                PrivateScopeId = (uint)value;
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     Converts the Internet address to either standard dotted quad format
        ///     or standard IPv6 representation.
        ///   </para>
        /// </devdoc>
        public override string ToString()
        {
            string? toString = _toString;
            if (toString is null)
            {
                Span<char> span = stackalloc char[IPAddressParser.MaxIPv6StringLength];
                int length = IsIPv4 ?
                    IPAddressParser.FormatIPv4Address(_addressOrScopeId, span) :
                    IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, span);
                _toString = toString = new string(span.Slice(0, length));
            }
 
            return toString;
        }
 
        /// <inheritdoc/>
        string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
            // format and provider are explicitly ignored
            ToString();
 
        public bool TryFormat(Span<char> destination, out int charsWritten) =>
            TryFormatCore(destination, out charsWritten);
 
        /// <summary>Tries to format the current IP address into the provided span.</summary>
        /// <param name="utf8Destination">When this method returns, the IP address as a span of UTF-8 bytes.</param>
        /// <param name="bytesWritten">When this method returns, the number of bytes written into the <paramref name="utf8Destination"/>.</param>
        /// <returns><see langword="true" /> if the formatting was successful; otherwise, <see langword="false" />.</returns>
        public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) =>
            TryFormatCore(utf8Destination, out bytesWritten);
 
        /// <inheritdoc/>
        bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
            // format and provider are explicitly ignored
            TryFormatCore(destination, out charsWritten);
 
        /// <inheritdoc/>
        bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
            // format and provider are explicitly ignored
            TryFormatCore(utf8Destination, out bytesWritten);
 
        private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
        {
            if (IsIPv4)
            {
                if (destination.Length >= IPAddressParser.MaxIPv4StringLength)
                {
                    charsWritten = IPAddressParser.FormatIPv4Address(_addressOrScopeId, destination);
                    return true;
                }
            }
            else
            {
                if (destination.Length >= IPAddressParser.MaxIPv6StringLength)
                {
                    charsWritten = IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, destination);
                    return true;
                }
            }
 
            Span<TChar> tmpDestination = stackalloc TChar[IPAddressParser.MaxIPv6StringLength];
            Debug.Assert(tmpDestination.Length >= IPAddressParser.MaxIPv4StringLength);
 
            int written = IsIPv4 ?
                IPAddressParser.FormatIPv4Address(PrivateAddress, tmpDestination) :
                IPAddressParser.FormatIPv6Address(_numbers, PrivateScopeId, tmpDestination);
 
            if (tmpDestination.Slice(0, written).TryCopyTo(destination))
            {
                charsWritten = written;
                return true;
            }
 
            charsWritten = 0;
            return false;
        }
 
        public static long HostToNetworkOrder(long host)
        {
            return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(host) : host;
        }
 
        public static int HostToNetworkOrder(int host)
        {
            return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(host) : host;
        }
 
        public static short HostToNetworkOrder(short host)
        {
            return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(host) : host;
        }
 
        public static long NetworkToHostOrder(long network)
        {
            return HostToNetworkOrder(network);
        }
 
        public static int NetworkToHostOrder(int network)
        {
            return HostToNetworkOrder(network);
        }
 
        public static short NetworkToHostOrder(short network)
        {
            return HostToNetworkOrder(network);
        }
 
        public static bool IsLoopback(IPAddress address)
        {
            ArgumentNullException.ThrowIfNull(address);
 
            if (address.IsIPv6)
            {
                // Do Equals test for IPv6 addresses
                return address.Equals(IPv6Loopback) || address.Equals(s_loopbackMappedToIPv6);
            }
            else
            {
                long LoopbackMask = (uint)HostToNetworkOrder(unchecked((int)LoopbackMaskHostOrder));
                return ((address.PrivateAddress & LoopbackMask) == (Loopback.PrivateAddress & LoopbackMask));
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     Determines if an address is an IPv6 Multicast address
        ///   </para>
        /// </devdoc>
        public bool IsIPv6Multicast
        {
            get
            {
                return IsIPv6 && ((_numbers[0] & 0xFF00) == 0xFF00);
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     Determines if an address is an IPv6 Link Local address
        ///   </para>
        /// </devdoc>
        public bool IsIPv6LinkLocal
        {
            get
            {
                return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFE80);
            }
        }
 
        /// <devdoc>
        ///   <para>
        ///     Determines if an address is an IPv6 Site Local address
        ///   </para>
        /// </devdoc>
        public bool IsIPv6SiteLocal
        {
            get
            {
                return IsIPv6 && ((_numbers[0] & 0xFFC0) == 0xFEC0);
            }
        }
 
        public bool IsIPv6Teredo
        {
            get
            {
                return IsIPv6 &&
                       (_numbers[0] == 0x2001) &&
                       (_numbers[1] == 0);
            }
        }
 
        /// <summary>Gets whether the address is an IPv6 Unique Local address.</summary>
        public bool IsIPv6UniqueLocal
        {
            get
            {
                return IsIPv6 && ((_numbers[0] & 0xFE00) == 0xFC00);
            }
        }
 
        // 0:0:0:0:0:FFFF:x.x.x.x
        public bool IsIPv4MappedToIPv6
        {
            get
            {
                return !IsIPv4 && _numbers.AsSpan(0, 6).SequenceEqual((ReadOnlySpan<ushort>)[0, 0, 0, 0, 0, 0xFFFF]);
            }
        }
 
        [Obsolete("IPAddress.Address is address family dependent and has been deprecated. Use IPAddress.Equals to perform comparisons instead.")]
        public long Address
        {
            get
            {
                if (AddressFamily == AddressFamily.InterNetworkV6)
                {
                    ThrowSocketOperationNotSupported();
                }
 
                return PrivateAddress;
            }
            set
            {
                if (AddressFamily == AddressFamily.InterNetworkV6)
                {
                    ThrowSocketOperationNotSupported();
                }
 
                if (PrivateAddress != value)
                {
                    if (this is ReadOnlyIPAddress)
                    {
                        ThrowSocketOperationNotSupported();
                    }
 
                    PrivateAddress = unchecked((uint)value);
                }
            }
        }
 
        /// <summary>Compares two IP addresses.</summary>
        public override bool Equals([NotNullWhen(true)] object? comparand)
        {
            return comparand is IPAddress address && Equals(address);
        }
 
        internal bool Equals(IPAddress comparand)
        {
            Debug.Assert(comparand != null);
 
            // Compare families before address representations
            if (AddressFamily != comparand.AddressFamily)
            {
                return false;
            }
 
            if (IsIPv6)
            {
                // For IPv6 addresses, we must compare the full 128-bit representation and the scope IDs.
                // We give JIT a hint that the arrays are always of length IPv6AddressShorts, so it
                // can unroll the comparison. JIT probably could do it on its own, but that requires
                // complex inter-procedural optimizations.
                Debug.Assert(_numbers.Length == IPAddressParserStatics.IPv6AddressShorts);
                Debug.Assert(comparand._numbers!.Length == IPAddressParserStatics.IPv6AddressShorts);
 
                ReadOnlySpan<ushort> left = _numbers.AsSpan(0, IPAddressParserStatics.IPv6AddressShorts);
                ReadOnlySpan<ushort> right = comparand._numbers.AsSpan(0, IPAddressParserStatics.IPv6AddressShorts);
                return left.SequenceEqual(right) && PrivateScopeId == comparand.PrivateScopeId;
            }
 
            // For IPv4 addresses, compare the integer representation.
            return comparand.PrivateAddress == PrivateAddress;
        }
 
        public override int GetHashCode()
        {
            if (_hashCode == 0)
            {
                // For IPv4 addresses, we calculate the hashcode based on address bytes.
                // For IPv6 addresses, we also factor in scope ID.
                if (IsIPv6)
                {
                    ReadOnlySpan<byte> numbers = MemoryMarshal.AsBytes<ushort>(_numbers);
                    _hashCode = HashCode.Combine(
                        MemoryMarshal.Read<uint>(numbers),
                        MemoryMarshal.Read<uint>(numbers.Slice(4)),
                        MemoryMarshal.Read<uint>(numbers.Slice(8)),
                        MemoryMarshal.Read<uint>(numbers.Slice(12)),
                        _addressOrScopeId);
                }
                else
                {
                    _hashCode = HashCode.Combine(_addressOrScopeId);
                }
            }
 
            return _hashCode;
        }
 
        // IPv4 192.168.1.1 maps as ::FFFF:192.168.1.1
        public IPAddress MapToIPv6()
        {
            if (IsIPv6)
            {
                return this;
            }
 
            uint address = (uint)NetworkToHostOrder(unchecked((int)PrivateAddress));
            ushort[] labels = new ushort[NumberOfLabels];
            labels[5] = 0xFFFF;
            labels[6] = (ushort)(address >> 16);
            labels[7] = (ushort)address;
            return new IPAddress(labels, 0);
        }
 
        // Takes the last 4 bytes of an IPv6 address and converts it to an IPv4 address.
        // This does not restrict to address with the ::FFFF: prefix because other types of
        // addresses display the tail segments as IPv4 like Terado.
        public IPAddress MapToIPv4()
        {
            if (IsIPv4)
            {
                return this;
            }
 
            uint address = (uint)_numbers[6] << 16 | (uint)_numbers[7];
            return new IPAddress((uint)HostToNetworkOrder(unchecked((int)address)));
        }
 
        [DoesNotReturn]
        private static byte[] ThrowAddressNullException() => throw new ArgumentNullException("address");
 
        [DoesNotReturn]
        private static void ThrowSocketOperationNotSupported() => throw new SocketException(SocketError.OperationNotSupported);
 
        private sealed class ReadOnlyIPAddress : IPAddress
        {
            public ReadOnlyIPAddress(ReadOnlySpan<byte> newAddress) : base(newAddress)
            { }
        }
    }
}