145 lines
3.2 KiB
C#
145 lines
3.2 KiB
C#
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<Domain>
|
|
{
|
|
public static Domain FromArray(byte[] message, int offset, out int endOffset)
|
|
{
|
|
IList<byte[]> labels = new List<byte[]>();
|
|
bool endOffsetAssigned = false;
|
|
endOffset = 0;
|
|
byte lengthOrPointer;
|
|
HashSet<int> visitedOffsetPointers = new HashSet<int>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|