using skyscraper5.DNS.Protocol.Utils; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace skyscraper5.DNS.Protocol { public class Domain : IComparable { public static Domain FromArray(byte[] message, int offset, out int endOffset) { IList labels = new List(); bool endOffsetAssigned = false; endOffset = 0; byte lengthOrPointer; HashSet visitedOffsetPointers = new HashSet(); while ((lengthOrPointer = message[offset++]) > 0) { // Two highest bits are set (pointer) if (lengthOrPointer.GetBitValueAt(6, 2) == 3) { if (!endOffsetAssigned) { endOffsetAssigned = true; endOffset = offset + 1; } ushort pointer = lengthOrPointer.GetBitValueAt(0, 6); offset = (pointer << 8) | message[offset]; if (visitedOffsetPointers.Contains(offset)) { throw new ArgumentException("Compression pointer loop detected"); } visitedOffsetPointers.Add(offset); continue; } if (lengthOrPointer.GetBitValueAt(6, 2) != 0) { throw new IndexOutOfRangeException("Unexpected bit pattern in label length"); } byte length = lengthOrPointer; byte[] label = new byte[length]; Array.Copy(message, offset, label, 0, length); labels.Add(label); offset += length; } if (!endOffsetAssigned) { endOffset = offset; } return new Domain(labels.ToArray()); } public int CompareTo(Domain? other) { int length = Math.Min(labels.Length, other.labels.Length); for (int i = 0; i < length; i++) { int v = CompareTo(this.labels[i], other.labels[i]); if (v != 0) return v; } return this.labels.Length - other.labels.Length; } private static int CompareTo(byte[] a, byte[] b) { int length = Math.Min(a.Length, b.Length); for (int i = 0; i < length; i++) { int v = CompareTo(a[i], b[i]); if (v != 0) return v; } return a.Length - b.Length; } private static int CompareTo(byte a, byte b) { if (IsASCIIAlphabet(a) && IsASCIIAlphabet(b)) { a &= ASCII_UPPERCASE_MASK; b &= ASCII_UPPERCASE_MASK; } return a - b; } private static bool IsASCIIAlphabet(byte b) { return (ASCII_UPPERCASE_FIRST <= b && b <= ASCII_UPPERCASE_LAST) || (ASCII_LOWERCASE_FIRST <= b && b <= ASCII_LOWERCASE_LAST); } private const byte ASCII_UPPERCASE_FIRST = 65; private const byte ASCII_UPPERCASE_LAST = 90; private const byte ASCII_LOWERCASE_FIRST = 97; private const byte ASCII_LOWERCASE_LAST = 122; private const byte ASCII_UPPERCASE_MASK = 223; public Domain(byte[][] labels) { this.labels = labels; } private byte[][] labels; public int Size { get { return labels.Sum(l => l.Length) + labels.Length + 1; } } public static Domain FromArray(byte[] message, int offset) { return FromArray(message, offset, out offset); } [DebuggerStepThrough] public string ToString(Encoding encoding) { return string.Join(".", labels.Select(label => encoding.GetString(label))); } [DebuggerStepThrough] public override string ToString() { return ToString(Encoding.ASCII); } } }