File: System\Net\DnsRecordParsing.cs
Web Access
Project: src\runtime\src\libraries\System.Net.NameResolution\src\System.Net.NameResolution.csproj (System.Net.NameResolution)
// 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;

namespace System.Net
{
    // Typed RDATA accessors over parsed DNS records.

    internal readonly ref struct DnsARecordData
    {
        public ReadOnlySpan<byte> AddressBytes { get; }

        internal DnsARecordData(ReadOnlySpan<byte> addressBytes)
        {
            AddressBytes = addressBytes;
        }

        public IPAddress ToIPAddress() => new IPAddress(AddressBytes);
    }

    internal readonly ref struct DnsAAAARecordData
    {
        public ReadOnlySpan<byte> AddressBytes { get; }

        internal DnsAAAARecordData(ReadOnlySpan<byte> addressBytes)
        {
            AddressBytes = addressBytes;
        }

        public IPAddress ToIPAddress() => new IPAddress(AddressBytes);
    }

    internal readonly ref struct DnsCNameRecordData
    {
        public DnsEncodedName CName { get; }

        internal DnsCNameRecordData(DnsEncodedName cname)
        {
            CName = cname;
        }
    }

    internal readonly ref struct DnsMxRecordData
    {
        public ushort Preference { get; }
        public DnsEncodedName Exchange { get; }

        internal DnsMxRecordData(ushort preference, DnsEncodedName exchange)
        {
            Preference = preference;
            Exchange = exchange;
        }
    }

    internal readonly ref struct DnsSrvRecordData
    {
        public ushort Priority { get; }
        public ushort Weight { get; }
        public ushort Port { get; }
        public DnsEncodedName Target { get; }

        internal DnsSrvRecordData(ushort priority, ushort weight, ushort port, DnsEncodedName target)
        {
            Priority = priority;
            Weight = weight;
            Port = port;
            Target = target;
        }
    }

    internal readonly ref struct DnsSoaRecordData
    {
        public DnsEncodedName PrimaryNameServer { get; }
        public DnsEncodedName ResponsibleMailbox { get; }
        public uint SerialNumber { get; }
        public uint RefreshInterval { get; }
        public uint RetryInterval { get; }
        public uint ExpireLimit { get; }
        public uint MinimumTtl { get; }

        internal DnsSoaRecordData(DnsEncodedName primaryNameServer, DnsEncodedName responsibleMailbox,
            uint serialNumber, uint refreshInterval, uint retryInterval,
            uint expireLimit, uint minimumTtl)
        {
            PrimaryNameServer = primaryNameServer;
            ResponsibleMailbox = responsibleMailbox;
            SerialNumber = serialNumber;
            RefreshInterval = refreshInterval;
            RetryInterval = retryInterval;
            ExpireLimit = expireLimit;
            MinimumTtl = minimumTtl;
        }
    }

    internal readonly ref struct DnsTxtRecordData
    {
        private readonly ReadOnlySpan<byte> _data;

        internal DnsTxtRecordData(ReadOnlySpan<byte> data)
        {
            _data = data;
        }

        public DnsTxtEnumerator EnumerateStrings() => new DnsTxtEnumerator(_data);
    }

    internal ref struct DnsTxtEnumerator
    {
        private ReadOnlySpan<byte> _remaining;
        private ReadOnlySpan<byte> _current;

        internal DnsTxtEnumerator(ReadOnlySpan<byte> data)
        {
            _remaining = data;
            _current = default;
        }

        public readonly ReadOnlySpan<byte> Current => _current;

        public bool MoveNext()
        {
            if (_remaining.Length == 0)
            {
                return false;
            }

            int len = _remaining[0];
            if (1 + len > _remaining.Length)
            {
                return false;
            }

            _current = _remaining.Slice(1, len);
            _remaining = _remaining[(1 + len)..];
            return true;
        }

        public readonly DnsTxtEnumerator GetEnumerator() => this;
    }

    internal readonly ref struct DnsPtrRecordData
    {
        public DnsEncodedName Name { get; }

        internal DnsPtrRecordData(DnsEncodedName name)
        {
            Name = name;
        }
    }

    internal readonly ref struct DnsNsRecordData
    {
        public DnsEncodedName Name { get; }

        internal DnsNsRecordData(DnsEncodedName name)
        {
            Name = name;
        }
    }

    internal static class DnsRecordExtensions
    {
        public static bool TryParseARecord(this DnsRecord record, out DnsARecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.A || record.Data.Length != 4)
            {
                return false;
            }
            result = new DnsARecordData(record.Data);
            return true;
        }

        public static bool TryParseAAAARecord(this DnsRecord record, out DnsAAAARecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.AAAA || record.Data.Length != 16)
            {
                return false;
            }
            result = new DnsAAAARecordData(record.Data);
            return true;
        }

        public static bool TryParseCNameRecord(this DnsRecord record, out DnsCNameRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.CNAME || record.Data.Length == 0)
            {
                return false;
            }
            if (!record.TryParseSingleDnsNameRecord(out DnsEncodedName cname))
            {
                return false;
            }
            result = new DnsCNameRecordData(cname);
            return true;
        }

        public static bool TryParseMxRecord(this DnsRecord record, out DnsMxRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.MX || record.Data.Length < 3)
            {
                return false;
            }
            ushort preference = BinaryPrimitives.ReadUInt16BigEndian(record.Data);

            if (!record.TryParseSingleDnsNameRecord(out DnsEncodedName exchange, 2))
            {
                return false;
            }
            result = new DnsMxRecordData(preference, exchange);
            return true;
        }

        public static bool TryParseSrvRecord(this DnsRecord record, out DnsSrvRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.SRV || record.Data.Length < 7)
            {
                return false;
            }
            ushort priority = BinaryPrimitives.ReadUInt16BigEndian(record.Data);
            ushort weight = BinaryPrimitives.ReadUInt16BigEndian(record.Data[2..]);
            ushort port = BinaryPrimitives.ReadUInt16BigEndian(record.Data[4..]);
            if (!record.TryParseSingleDnsNameRecord(out DnsEncodedName target, 6))
            {
                return false;
            }
            result = new DnsSrvRecordData(priority, weight, port, target);
            return true;
        }

        public static bool TryParseSoaRecord(this DnsRecord record, out DnsSoaRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.SOA || record.Data.Length < 22)
            {
                return false;
            }

            if (!DnsEncodedName.TryParse(record.Message.Slice(0, record.DataOffset + record.Data.Length), record.DataOffset, out DnsEncodedName mname, out int mnameLen))
            {
                return false;
            }

            if (!DnsEncodedName.TryParse(record.Message.Slice(0, record.DataOffset + record.Data.Length), record.DataOffset + mnameLen, out DnsEncodedName rname, out int rnameLen))
            {
                return false;
            }

            ReadOnlySpan<byte> fixedData = record.Data[(mnameLen + rnameLen)..];
            if (fixedData.Length < 20)
            {
                return false;
            }

            result = new DnsSoaRecordData(mname, rname,
                BinaryPrimitives.ReadUInt32BigEndian(fixedData),
                BinaryPrimitives.ReadUInt32BigEndian(fixedData[4..]),
                BinaryPrimitives.ReadUInt32BigEndian(fixedData[8..]),
                BinaryPrimitives.ReadUInt32BigEndian(fixedData[12..]),
                BinaryPrimitives.ReadUInt32BigEndian(fixedData[16..]));
            return true;
        }

        public static bool TryParseTxtRecord(this DnsRecord record, out DnsTxtRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.TXT || record.Data.Length == 0)
            {
                return false;
            }
            result = new DnsTxtRecordData(record.Data);
            return true;
        }

        public static bool TryParsePtrRecord(this DnsRecord record, out DnsPtrRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.PTR || record.Data.Length == 0)
            {
                return false;
            }
            if (!record.TryParseSingleDnsNameRecord(out DnsEncodedName ptr))
            {
                return false;
            }
            result = new DnsPtrRecordData(ptr);
            return true;
        }

        public static bool TryParseNsRecord(this DnsRecord record, out DnsNsRecordData result)
        {
            result = default;
            if (record.Type != DnsRecordType.NS || record.Data.Length == 0)
            {
                return false;
            }
            if (!record.TryParseSingleDnsNameRecord(out DnsEncodedName ns))
            {
                return false;
            }
            result = new DnsNsRecordData(ns);
            return true;
        }

        private static bool TryParseSingleDnsNameRecord(this DnsRecord record, out DnsEncodedName name, int inRecordOffset = 0)
        {
            return
                DnsEncodedName.TryParse(record.Message.Slice(0, record.DataOffset + record.Data.Length), record.DataOffset + inRecordOffset, out name, out int bytesConsumed)
                && bytesConsumed == record.Data.Length - inRecordOffset;
        }
    }
}