///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   DnsFmt.cs
//
//  Note:
//
//      Constants taken from http://www.iana.org/assignments/dns-parameters
//
//  Caveat Emptor:
//
//      This implementation only supports message compression for messages
//      received and not for outbound messages. (RFC1035 section 4.1.4)
//

using System;
using System.Collections;
using System.Text;
using System.Diagnostics;

using System.Net.IP;
using Drivers.Net;

using Microsoft.Contracts;

namespace NetStack.Protocols.Dns
{
    public enum Class : ushort
    {
        // 0 Reserved             // [IANA]
        Internet = 1,             // [RFC1035]
        // 2 Unassigned           // [IANA]
        Chaos    = 3,             // [RFC1035]
        Hesiod   = 4,             // [RFC1035]
        // 5-253 Unassigned       // [IANA]
        None     = 254,           // [RFC2136]
        Any      = 255            // [RFC1035]
        // 256-65534 Unassigned   // [IANA]
        // 65535 Reserved         // [IANA]
    }

    public enum Type : ushort
    {
        A       = 1,    // a host address                          [RFC1035]
        NS      = 2,    // an authoritative name server            [RFC1035]
        MD      = 3,    // a mail destination (Obsolete - use MX)  [RFC1035]
        MF      = 4,    // a mail forwarder (Obsolete - use MX)    [RFC1035]
        CNAME   = 5,    // the canonical name for an alias         [RFC1035]
        SOA     = 6,    // marks the start of a zone of authority  [RFC1035]
        MB      = 7,    // a mailbox domain name (EXPERIMENTAL)    [RFC1035]
        MG      = 8,    // a mail group member (EXPERIMENTAL)      [RFC1035]
        MR      = 9,    // a mail rename domain name (EXPERIMENTAL)[RFC1035]
        NULL    = 10,    // a null RR (EXPERIMENTAL)               [RFC1035]
        WKS     = 11,    // a well known service description       [RFC1035]
        PTR     = 12,    // a domain name pointer                  [RFC1035]
        HINFO   = 13,    // host information                       [RFC1035]
        MINFO   = 14,    // mailbox or mail list information       [RFC1035]
        MX      = 15,    // mail exchange                          [RFC1035]
        TXT     = 16,    // text strings                           [RFC1035]
        RP      = 17,    // for Responsible Person                 [RFC1183]
        AFSDB   = 18,    // for AFS Data Base location             [RFC1183]
        X25     = 19,    // for X.25 PSDN address                  [RFC1183]
        ISDN    = 20,    // for ISDN address                       [RFC1183]
        RT      = 21,    // for Route Through                      [RFC1183]
        NSAP    = 22,    // for NSAP address, NSAP style A record  [RFC1706]
        NSAPPTR = 23,
        SIG     = 24,    // for security signature                 [RFC2931]
        KEY     = 25,    // for security key                       [RFC2535]
        PX      = 26,    // X.400 mail mapping information         [RFC2163]
        GPOS    = 27,    // Geographical Position                  [RFC1712]
        AAAA    = 28,    // IP6 Address                            [Thomson]
        LOC     = 29,    // Location Information                   [Vixie]
        NXT     = 30,    // Next Domain - OBSOLETE        [RFC2535, RFC3755]
        EID     = 31,    // Endpoint Identifier                    [Patton]
        NIMLOC  = 32,    // Nimrod Locator                         [Patton]
        SRV     = 33,    // Server Selection                       [RFC2782]
        ATMA    = 34,    // ATM Address                            [Dobrowski]
        NAPTR   = 35,    // Naming Authority Pointer      [RFC2168, RFC2915]
        KX      = 36,    // Key Exchanger                          [RFC2230]
        CERT    = 37,    // CERT                                   [RFC2538]
        A6      = 38,    // A6                                     [RFC2874]
        DNAME   = 39,    // DNAME                                  [RFC2672]
        SINK    = 40,    // SINK                                   [Eastlake]
        OPT     = 41,    // OPT                                    [RFC2671]
        APL     = 42,    // APL                                    [RFC3123]
        DS      = 43,    // Delegation Signer                      [RFC3658]
        SSHFP   = 44,    // SSH Key Fingerprint  [RFC-ietf-secsh-dns-05.txt]
        RRSIG   = 46,    // RRSIG                                  [RFC3755]
        NSEC    = 47,    // NSEC                                   [RFC3755]
        DNSKEY  = 48,    // DNSKEY                                 [RFC3755]
        UINFO   = 100,   //                                  [IANA-Reserved]
        UID     = 101,   //                                  [IANA-Reserved]
        GID     = 102,   //                                  [IANA-Reserved]
        UNSPEC  = 103,   //                                  [IANA-Reserved]
        TKEY    = 249,   // Transaction Key                        [RFC2930]
        TSIG    = 250,   // Transaction Signature                  [RFC2845]
        IXFR    = 251,   // incremental transfer                   [RFC1995]
        AXFR    = 252,   // transfer of an entire zone             [RFC1035]
        MAILB   = 253,   // mailbox-related RRs (MB, MG or MR)     [RFC1035]
        MAILA   = 254,   // mail agent RRs (Obsolete - see MX)     [RFC1035]
        ALL     = 255,   // A request for all records              [RFC1035]
    }

    public enum OpCode : byte
    {
        Query  = 0,      //                                        [RFC1035]
        // IQUERY  = 1   //  OpCode Retired                        [RFC3425]
        Status = 2,      //                                        [RFC1035]
        // 3             // reserved                               [IANA]
        Notify = 4,      //                                        [RFC1996]
        Update = 5,      //                                        [RFC2136]
        // 6-15 available for assignment
    }

    public enum RCode : ushort
    {
        NoError          = 0,   // No Error                           [RFC1035]
        FormErr          = 1,   // Format Error                       [RFC1035]
        ServFail         = 2,   // Server Failure                     [RFC1035]
        NXDomain         = 3,   // Non-Existent Domain                [RFC1035]
        NotImp           = 4,   // Not Implemented                    [RFC1035]
        Refused          = 5,   // Query Refused                      [RFC1035]
        YXDomain         = 6,   // Name Exists when it should not     [RFC2136]
        YXRRSet          = 7,   // RR Set Exists when it should not   [RFC2136]
        NXRRSet          = 8,   // RR Set that should exist does not  [RFC2136]
        NotAuth          = 9,   // Server Not Authoritative for zone  [RFC2136]
        NotZone          = 10,  // Name not contained in zone         [RFC2136]
        Max4BitCode      = NotZone,
        //               11-15,    Available for assignment
        BADVERS          = 16,  // Bad OPT Version                    [RFC2671]
        BADSIG           = 16,  // TSIG Signature Failure             [RFC2845]
        BADKEY           = 17,  // Key not recognized                 [RFC2845]
        BADTIME          = 18,  // Signature out of time window       [RFC2845]
        BADMODE          = 19,  // Bad TKEY Mode                      [RFC2930]
        BADNAME          = 20,  // Duplicate key name                 [RFC2930]
        BADALG           = 21,  // Algorithm not supported            [RFC2930]
        // 22-3840              available for assignment
        //  0x0016-0x0F00
        // 3841-4095            Private Use
        //  0x0F01-0x0FFF
        // 4096-65535           available for assignment
        //  0x1000-0xFFFF
    }

    public enum Flags : ushort
    {
        NONE = 0,
        QR   = 0x8000, // Response (1) vs Query (0)
        AA   = 0x0400, // Authoritative (1) vs Non-authoritative (0)
        TC   = 0x0200, // Truncated
        RD   = 0x0100, // Recursion Desired
        RA   = 0x0080, // Recursion Available
        MBZ  = 0x0040, // Must-Be-Zero
        AD   = 0x0020, // Authenticated data (DNSSEC)
        CD   = 0x0010, // Checking Disabled  (DNSSEC)
        ALL  = 0x87f0, // All bit-field bits
    }

    /// <summary>
    /// Convert between textual and binary DNS names.
    /// </summary>
    ///
    /// <remarks>
    /// <para>
    /// RFC1035 describes how names should be packed in DNS
    /// queries and responses.  The method is described as:
    /// </para>
    /// <para>
    /// Domain names in messages are expressed in terms of a
    /// sequence of labels.  Each label is represented as a one
    /// octet length field followed by that number of octets.
    /// Since every domain name ends with the null label of the
    /// root, a domain name is terminated by a length byte of
    /// zero.  The high order two bits of every length octet
    /// must be zero, and the remaining six bits of the length
    /// field limit the label to 63 octets or less.
    /// </para>
    /// </remarks>
    public class LabelEncoding
    {
        private static ASCIIEncoding ascii = new ASCIIEncoding();

        private LabelEncoding() {}

        static public byte[] GetBytes(string! s)
        {
            byte[] result = new byte[GetByteCount(s)];
            PutBytes(s, result, 0);
            return result;
        }

        public static int PutBytes(string! input,
                                   byte[]! bytes,
                                   int     byteIndex)
        {
            if (input.Length == 0) {
                bytes[byteIndex] = 0;
                return 1;
            }

            ascii.GetBytes(input, 0, input.Length, bytes, byteIndex + 1);
            int outIndex = byteIndex;
            int finalIndex = byteIndex + input.Length + 1;
            bytes[finalIndex] = 0;

            for (int i = byteIndex + 1; i <= finalIndex; i++) {
                if (bytes[i] == (byte)'.' || bytes[i] == 0) {
                    int blockLength = i - outIndex - 1;
                    if (blockLength > Format.MaxLabelLength)
                        throw new ArgumentException();
                    bytes[outIndex] = (byte)(i - outIndex - 1);
                    outIndex = i++;
                }
            }
            return finalIndex + 1 - byteIndex;
        }

        static public int GetByteCount(string! s)
        {
            return (s.Length > 0) ? (2 + s.Length) : 1;
        }

        /// <summary>
        /// Convert name from sequence of bytes to string.
        /// </summary>
        ///
        /// <returns>
        /// Returns number of bytes consumed, or 0 if input is invalid.
        ///</returns>
        public static int GetString(byte[]!     bytes,
                                    int         byteIndex,
                                    out string  output)
        {
            int startIndex = byteIndex;

            if (bytes[byteIndex] == 0)
                goto fail;

            // Validation and copy to mutable buffer pass
            while (bytes[byteIndex] != 0) {
                if (byteIndex - startIndex - 1 > Format.MaxNameLength)
                    goto fail;

                int offset = (int) bytes[byteIndex];
                if ((offset & 0xc0) != 0)
                    goto fail;

                int newByteIndex = byteIndex + offset + 1;
                if (newByteIndex > bytes.Length)
                    goto fail;
                byteIndex = newByteIndex;
            }

            // Copy data into a local buffer.  At first glance,
            // you might realize the unpacking could be done in
            // place.  However, because of RFC1035 name
            // compression some other block may point to the raw
            // data in the packet and expect it to be in packed
            // format.
            byte [] workBytes = new byte [byteIndex + 1 - startIndex];
            Array.Copy(bytes, startIndex, workBytes, 0, workBytes.Length);

            // Modify length bytes to '.' chars
            int workIndex = 0;
            while (workBytes[workIndex] != 0) {
                byte offset = workBytes[workIndex];
                workBytes[workIndex] = (byte)'.';
                workIndex += (int)offset + 1;
            }
            Debug.Assert(workIndex + 1 == workBytes.Length);
            output = ascii.GetString(workBytes, 1, workIndex - 1);

            return workBytes.Length;

          fail:
            output = "";
            return 0;
        }
    }

    public class InvalidDnsFormatException : SystemException
    {
        public InvalidDnsFormatException(String message)
            : base(message)
        {
        }

        public InvalidDnsFormatException(String message,
                                         Exception innerException)
            : base(message, innerException)
        {
        }

        public InvalidDnsFormatException(String format, params object [] args)
            : base (String.Format(format, args))
        {
        }
    }

    public class Query
    {
        public readonly string Name;
        public readonly Type   Type;
        public readonly Class  Class;

        public Query(string queryName, Type queryType, Class queryClass)
        {
            if (queryName == null)
                throw new ArgumentNullException("queryName");

            this.Name  = queryName;
            this.Type  = queryType;
            this.Class = queryClass;
        }

        public int Size
        {
            // Packed name size plus 2 bytes Type, 2 bytes Class
            get { return LabelEncoding.GetByteCount(Name) + 4; }
        }

        public int Write(byte[]! buffer, ref int offset)
        {
            offset += LabelEncoding.PutBytes(Name, buffer, offset);
            offset += NetworkBitConverter.PutBytes((ushort)Type,
                                                   buffer, offset);
            offset += NetworkBitConverter.PutBytes((ushort)Class,
                                                   buffer, offset);
            return offset;
        }

        public static Query! Parse(byte []! buffer, ref int offset)
        {
            string name;

            int used = LabelEncoding.GetString(buffer, offset, out name);
            if (used == 0)
                throw new InvalidDnsFormatException("name");
            offset += used;

            ushort qType = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            ushort qClass = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            return new Query(name, (Type) qType, (Class) qClass);
        }

        public static bool operator == (Query lhs, Query rhs)
        {
            if (lhs == null && rhs == null) return true;
            if (lhs == null) return false;
            if (rhs == null) return false;
            return (lhs.Name  == rhs.Name &&
                    lhs.Type  == rhs.Type &&
                    lhs.Class == rhs.Class);
        }

        public static bool operator != (Query lhs, Query rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(Object other)
        {
            Query query = other as Query;
            return ((Object.ReferenceEquals(query, null) == false) &&
                    (this == query));
        }

        public override int GetHashCode()
        {
            return this.Name.GetHashCode() ^ ((int)Type << 16) ^ (int)Class;
        }
    }

    public class ResourceRecord
    {
        public readonly string   Name;
        public readonly Type     Type;
        public readonly Class    Class;
        public readonly uint     TtlSeconds;
        public readonly byte[]   RData;

        public ResourceRecord(string name,
                              Type   recordType,
                              Class  recordClass,
                              uint   ttlSeconds,
                              byte[] rdata)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            this.Name       = name;
            this.Type       = recordType;
            this.Class      = recordClass;
            this.TtlSeconds = ttlSeconds;
            this.RData      = rdata;
        }

        public int Size
        {
            get {
                int baseLength = LabelEncoding.GetByteCount(Name) + 10;
                if (RData == null)
                    return baseLength;
                return baseLength + RData.Length;
            }
        }

        public int Write(byte []! buffer, ref int offset)
        {
            offset += LabelEncoding.PutBytes(Name, buffer, offset);
            offset += NetworkBitConverter.PutBytes((ushort) Type,
                                                   buffer, offset);
            offset += NetworkBitConverter.PutBytes((ushort) Class,
                                                    buffer, offset);
            offset += NetworkBitConverter.PutBytes(TtlSeconds, buffer, offset);

            if (RData == null) {
                offset += NetworkBitConverter.PutBytes((ushort)0,
                                                        buffer, offset);
            }
            else {
                offset += NetworkBitConverter.PutBytes((ushort)RData.Length,
                                                        buffer, offset);
                RData.CopyTo(buffer, offset);
                offset += RData.Length;
            }
            return offset;
        }

        public static ResourceRecord Parse(byte []! buffer, ref int offset)
        {
            string name;
            if ((buffer[offset] & 0xc0) == 0xc0) {
                // Detected compression.  Name is at offset in
                // lower 14 bits of next 2 bytes.
                int start = NetworkBitConverter.ToInt16(buffer, offset);
                if (LabelEncoding.GetString(buffer, start & 0x3fff,
                                            out name) == 0)
                    throw new InvalidDnsFormatException("name");
                offset += 2;
            }
            else {
                int used = LabelEncoding.GetString(buffer, offset, out name);
                if (used == 0)
                    throw new InvalidDnsFormatException("name");
                offset += used;
            }

            ushort rType = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            ushort rClass = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            uint ttlSeconds = NetworkBitConverter.ToUInt32(buffer, offset);
            offset += 4;

            ushort rdataLength = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            byte [] rdata = new byte[rdataLength];
            Array.Copy(buffer, offset, rdata, 0, rdataLength);
            offset += rdataLength;

            return new ResourceRecord(name, (Type) rType, (Class) rClass,
                                      ttlSeconds, rdata);
        }

        public static bool operator == (ResourceRecord lhs, ResourceRecord rhs)
        {
            if (lhs == null && rhs == null) return true;
            if (lhs == null) return false;
            if (rhs == null) return false;

            if (lhs.Name != rhs.Name || lhs.Type != rhs.Type ||
                lhs.Class != rhs.Class || lhs.TtlSeconds != rhs.TtlSeconds)
                return false;
            if (lhs.RData == null)
                return rhs.RData == null;
            if (rhs.RData == null)
                return false;
            if (lhs.RData.Length != rhs.RData.Length)
                return false;
            for (int i = 0; i < lhs.RData.Length; i++)
                if (lhs.RData[i] != rhs.RData[i])
                    return false;
            return true;
        }

        public static bool operator != (ResourceRecord lhs, ResourceRecord rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(Object other)
        {
            ResourceRecord rr = other as ResourceRecord;
            return ((Object.ReferenceEquals(rr, null) == false) &&
                    (rr == this));
        }

        public override int GetHashCode()
        {
            int hashCode = this.Name.GetHashCode();
            hashCode ^= (int)Type << 16;
            hashCode ^= (int)Class;
            if (RData != null) {
                hashCode ^= RData.Length;
                for (int i = 0; i < RData.Length; i ++) {
                    hashCode = hashCode ^ (hashCode << 1);
                    hashCode += (int)RData[i];
                }
            }
            return hashCode;
        }
    }

    public class Format
    {
        public const ushort ServerPort = 53;

        public const int MaxLabelLength      = 63;
        public const int MinLabelLength      =  1;
        public const int MaxNameLength       = 255;
        public const int MaxUdpMessageLength = 512;

        private const int OpCodeRoll = 11;
        private const int OpCodeMask = 0x0f;
        private const int RCodeMask  = 0x0f;

        private ushort     id;
        private Flags      flags;
        private OpCode     opCode;
        private RCode      rCode;
        private ArrayList! queries;
        private ArrayList! answerRRs;
        private ArrayList! authorityRRs;
        private ArrayList! additionalRRs;

        private static ushort nextId = 0;

        public Format(ushort id, Flags flags, OpCode opCode, RCode rCode)
        {
            this.id            = id;
            this.queries       = new ArrayList();
            this.answerRRs     = new ArrayList();
            this.authorityRRs  = new ArrayList();
            this.additionalRRs = new ArrayList();
            this.SetFlags(flags);
            this.SetOpCode(opCode);
            this.SetRCode(rCode);
        }

        public Format()
            : this (++nextId, Flags.NONE, OpCode.Query, RCode.NoError)
        {
        }

        public Flags GetFlags()
        {
            return flags;
        }

        [Delayed]
        public void SetFlags(Flags newFlags)
        {
            if ((newFlags & Flags.ALL) != newFlags)
                throw new ArgumentException("Invalid flags present");
            flags = newFlags;
        }

        public OpCode GetOpCode()
        {
            return opCode;
        }

        [Delayed]
        public void SetOpCode(OpCode newOpCode)
        {
            switch (newOpCode) {
                case OpCode.Query:  break;
                case OpCode.Status: break;
                case OpCode.Notify: break;
                case OpCode.Update: break;
                default:
                    throw new ArgumentException("Invalid OpCode");
            }
            opCode = newOpCode;
        }

        public RCode GetRCode()
        {
            return rCode;
        }

        [Delayed]
        public void SetRCode(RCode newRCode)
        {
            if (newRCode > RCode.Max4BitCode)
                throw new ArgumentException("Invalid RCode");
            rCode = newRCode;
        }

        public ArrayList! Queries
        {
            get { return queries; }
        }

        public ArrayList! AnswerRRs
        {
            get { return answerRRs; }
        }

        public ArrayList! AuthorityRRs
        {
            get { return authorityRRs; }
        }

        public ArrayList! AdditionalRRs
        {
            get { return additionalRRs; }
        }

        public int Size
        {
            get
            {
                int total = 12; // Fixed component
                foreach (Query! q in queries)
                    total += q.Size;
                foreach (ResourceRecord! r in answerRRs)
                    total += r.Size;
                foreach (ResourceRecord! r in authorityRRs)
                    total += r.Size;
                foreach (ResourceRecord! r in additionalRRs)
                    total += r.Size;
                return total;
            }
        }

        private void Write(byte[]! buffer, ushort value, ref int offset)
        {
            buffer[offset++] = (byte)((int)value >> 8);
            buffer[offset++] = (byte)((int)value & 0xff);
        }

        public int Write(byte[]! buffer, ref int offset)
        {
            Debug.Assert((((int)opCode << OpCodeRoll) & (int)Flags.ALL) == 0);
            Debug.Assert((((int)rCode) & (int)Flags.ALL) == 0);

            Write(buffer, id, ref offset);

            int word2 = ((int)opCode << OpCodeRoll) | (int)flags | (int)rCode;
            Write(buffer, (ushort) word2, ref offset);

            Write(buffer, (ushort) queries.Count, ref offset);
            Write(buffer, (ushort) answerRRs.Count, ref offset);
            Write(buffer, (ushort) authorityRRs.Count, ref offset);
            Write(buffer, (ushort) additionalRRs.Count, ref offset);

            foreach (Query! q in queries)
                q.Write(buffer, ref offset);
            foreach (ResourceRecord! r in answerRRs)
                r.Write(buffer, ref offset);
            foreach (ResourceRecord! r in authorityRRs)
                r.Write(buffer, ref offset);
            foreach (ResourceRecord! r in additionalRRs)
                r.Write(buffer, ref offset);

            return offset;
        }

        public static Format Parse(byte []! buffer, ref int offset)
        {
            //
            // First extract enough information to instantiate a Dns.Format
            //
            ushort id = NetworkBitConverter.ToUInt16(buffer, offset);
            offset += 2;

            ushort flagsAndCodes = NetworkBitConverter.ToUInt16(buffer,
                                                                offset);
            offset += 2;

            Flags flags = (Flags)((int)flagsAndCodes & (int)Flags.ALL);

            OpCode opCode =
                (OpCode)(((int)flagsAndCodes >> OpCodeRoll) & OpCodeMask);

            RCode rCode = (RCode)((int)flagsAndCodes & RCodeMask);

            //
            // Instantiate Dns.Format
            //
            Format format = new Format(id, flags, opCode, rCode);

            //
            // Extract query and record counts and then instantiate
            // Query and ResourceRecord objects
            //

            ushort totalQueries = NetworkBitConverter.ToUInt16(buffer,
                                                               offset);
            offset += 2;

            ushort totalAnswerRRs = NetworkBitConverter.ToUInt16(buffer,
                                                                 offset);
            offset += 2;

            ushort totalAuthorityRRs = NetworkBitConverter.ToUInt16(buffer,
                                                                    offset);
            offset += 2;

            ushort totalAdditionalRRs =  NetworkBitConverter.ToUInt16(buffer,
                                                                      offset);
            offset += 2;

            for (ushort i = 0; i < totalQueries; i++) {
                Query query = Query.Parse(buffer, ref offset);
                format.queries.Add(query);
            }

            // XXX Parsing stops here when a query fails.
            //
            // When a query fails, servers sometimes
            // indicate resource records are present, but the
            // data that we'd expect to be a resource record
            // does not match the format defined in
            // RFC1035. Details may exist in another RFC (???).
            if (rCode != RCode.NoError)
                return format;

            for (ushort i = 0; i < totalAnswerRRs; i++) {
                ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
                format.answerRRs.Add(rr);
            }

            for (ushort i = 0; i < totalAuthorityRRs; i++) {
                ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
                format.authorityRRs.Add(rr);
            }

            for (ushort i = 0; i < totalAdditionalRRs; i++) {
                ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
                format.additionalRRs.Add(rr);
            }

            return format;
        }

        private static bool ListsEqual(ArrayList lhs, ArrayList rhs)
        {
            if (lhs == null && rhs == null) 
                return true;

            if (lhs == null)
                return false;

            if (rhs == null)
                return false;

            if (lhs.Count != rhs.Count)
                return false;

            for (int i = 0; i < lhs.Count; i++)
                if (Object.Equals(lhs[i],rhs[i]) == false)
                    return false;

            return true;
        }

        public static bool operator == (Format lhs, Format rhs)
        {
            if (lhs == null && rhs == null) return true;
            if (lhs == null) return false;
            if (rhs == null) return false;
            if (lhs.id     != rhs.id     || lhs.flags  != rhs.flags  ||
                lhs.opCode != rhs.opCode || lhs.rCode  != rhs.rCode)
                return false;

            return (ListsEqual(lhs.queries, rhs.queries)             &&
                    ListsEqual(lhs.answerRRs, rhs.answerRRs)         &&
                    ListsEqual(lhs.additionalRRs, rhs.additionalRRs) &&
                    ListsEqual(lhs.authorityRRs, rhs.authorityRRs));
        }

        public static bool operator != (Format lhs, Format rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(Object other)
        {
            Format format = other as Format;
            return ((Object.ReferenceEquals(format, null) == false)
                    && (format == this));
        }

        private static int GetListHashCode(ArrayList list, int nullHash)
        {
            if (list == null)
                return nullHash;

            int hashCode = ~nullHash;
            foreach (object! o in list) {
                hashCode = hashCode ^ o.GetHashCode();
                hashCode = hashCode ^ (hashCode << 1);
            }
            return hashCode;
        }

        public override int GetHashCode()
        {
            int hashCode = (int)id << 16;
            hashCode += ((int)opCode << OpCodeRoll) | (int)flags | (int)rCode;
            hashCode ^= GetListHashCode(queries, 0x01233210);
            hashCode ^= GetListHashCode(answerRRs, 0x17017017);
            hashCode ^= GetListHashCode(authorityRRs, 0x22223333);
            hashCode ^= GetListHashCode(additionalRRs, 0x41414141);
            return hashCode;
        }

        private static bool IsSafeLabel(string! name, int begin, int end)
        {
            Debug.Assert(end > begin);

            if (end - begin > Format.MaxLabelLength ||
                end - begin < Format.MinLabelLength)
                return false;

            // The description in RFC 1035 section 2.1 prefers
            // first char to be [A-z], however this would rule out
            // entries in the IN-ADDR.ARPA domain used to lookup
            // hostnames from IP addresses.
            if (Char.IsLetterOrDigit(name[begin]) == false)
                return false;

            // Last char [A-z0-9]
            int last = end - 1;
            if (Char.IsLetterOrDigit(name[last]) == false)
                return false;

            for (int i = begin + 1; i < last; i++) {
                // Middle Chars [A-z0-9-]
                if (Char.IsLetterOrDigit(name[i]) == false &&
                    name[i] != '-')
                    return false;
            }
            return true;
        }

        public static bool IsSafeDnsName(string! name)
        {
            //
            // Grammar from RFC1035:
            //
            //   <domain> ::= <subdomain> | " "
            //
            //   <subdomain> ::= <label> | <subdomain> "." <label>
            //
            //   <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
            //
            //   <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
            //
            //   <let-dig-hyp> ::= <let-dig> | "-"
            //
            //   <let-dig> ::= <letter> | <digit>
            //
            if (name.Length > Format.MaxNameLength)
                return false;

            int labelStart = 0;
            do
            {
                int labelEnd = name.IndexOf('.', labelStart);
                if (labelEnd < 0) {
                    return IsSafeLabel(name, labelStart, name.Length);
                }
                if (IsSafeLabel(name, labelStart, labelEnd) == false) {
                    return false;
                }
                labelStart = labelEnd + 1;
            } while (labelStart < name.Length);

            return false;
        }
    }
}
