178 lines
4.3 KiB
C#
178 lines
4.3 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using IPMeow.Lib;
|
|
|
|
namespace IPMeow.Dns;
|
|
|
|
public enum ResourceRecordType : short
|
|
{
|
|
Invalid = 0,
|
|
A = 1,
|
|
NS = 2,
|
|
CNAME = 5,
|
|
SOA = 6,
|
|
PTR = 12,
|
|
MX = 15,
|
|
TXT = 16,
|
|
RP = 17,
|
|
AAAA = 28,
|
|
LOC = 29,
|
|
SRV = 33,
|
|
NAPTR = 35,
|
|
CERT = 37,
|
|
DNAME = 39,
|
|
OPT = 41,
|
|
DS = 43,
|
|
SSHFP = 44,
|
|
RRSIG = 46,
|
|
NSEC = 47,
|
|
DNSKEY = 48,
|
|
DHCID = 49,
|
|
NSEC3 = 50,
|
|
NSEC3PARAM = 51,
|
|
TLSA = 52,
|
|
HIP = 55,
|
|
OPENPGPKEY = 61,
|
|
CSYNC = 62,
|
|
CAA = 257,
|
|
}
|
|
|
|
public enum ResourceRecordClass : short
|
|
{
|
|
Invalid = 0,
|
|
IN = 1,
|
|
CH = 3,
|
|
HS = 4,
|
|
}
|
|
|
|
public record QueryResourceRecord : IBinarySerializable<QueryResourceRecord>
|
|
{
|
|
public bool IsQuery => true;
|
|
public string Name { get; set; } = default!;
|
|
public int Labels => Name.Split('.').Length;
|
|
//UTF8
|
|
internal int NameOffset => Name.Split('.').Select(x => x.Length).Sum() + Labels + 1;
|
|
public ResourceRecordType Type { get; set; }
|
|
public ResourceRecordClass Class { get; set; }
|
|
|
|
|
|
public static string DeserializeName(BitEnumerator b)
|
|
{
|
|
StringBuilder sb = new();
|
|
byte len;
|
|
while ((len = b.TakeByte()) != 0)
|
|
{
|
|
sb.Append(Encoding.UTF8.GetString(b.TakeBytes(len))).Append('.');
|
|
}
|
|
|
|
return sb.ToString().Trim('.');
|
|
}
|
|
|
|
public static QueryResourceRecord Deserialize(ReadOnlyMemory<byte> data)
|
|
{
|
|
BitEnumerator b = new(data);
|
|
string name = DeserializeName(b);
|
|
|
|
ResourceRecordType type = (ResourceRecordType)b.TakeInt16();
|
|
ResourceRecordClass clazz = (ResourceRecordClass)b.TakeInt16();
|
|
|
|
|
|
return new QueryResourceRecord()
|
|
{
|
|
Name = name,
|
|
Class = clazz,
|
|
Type = type,
|
|
};
|
|
}
|
|
|
|
public ReadOnlySpan<byte> SerializeName()
|
|
{
|
|
string name = Name.Normalize().Trim('.');
|
|
|
|
if (name.Length > 255)
|
|
{
|
|
throw new ArgumentOutOfRangeException("Name must be less than 255");
|
|
}
|
|
|
|
ArrayBufferWriter<byte> buffer = new(NameOffset);
|
|
|
|
foreach (string s in name.Split('.'))
|
|
{
|
|
byte[] b = Encoding.UTF8.GetBytes(s);
|
|
var lenBuf = buffer.GetSpan(1);
|
|
lenBuf[0] = (byte)b.Length;
|
|
buffer.Advance(1);
|
|
buffer.Write(b);
|
|
}
|
|
var zero = buffer.GetSpan(1);
|
|
zero[0] = 0;
|
|
buffer.Advance(1);
|
|
|
|
return buffer.WrittenSpan;
|
|
}
|
|
|
|
public virtual ReadOnlyMemory<byte> Serialize()
|
|
{
|
|
var buffer = new ArrayBufferWriter<byte>();
|
|
|
|
buffer.Write(SerializeName());
|
|
BinaryPrimitives.WriteInt16BigEndian(buffer.GetSpan(), (short)Type);
|
|
buffer.Advance(2);
|
|
BinaryPrimitives.WriteInt16BigEndian(buffer.GetSpan(), (short)Class);
|
|
buffer.Advance(2);
|
|
|
|
return buffer.WrittenMemory;
|
|
}
|
|
}
|
|
|
|
public record ResourceRecord : QueryResourceRecord, IBinarySerializable<ResourceRecord>
|
|
{
|
|
public TimeSpan Ttl { get; set; }
|
|
public byte[] Data { get; set; } = Array.Empty<byte>();
|
|
|
|
static ResourceRecord IBinarySerializable<ResourceRecord>.Deserialize(ReadOnlyMemory<byte> data)
|
|
{
|
|
QueryResourceRecord qr = Deserialize(data);
|
|
BitEnumerator b = new(data);
|
|
b.Skip(qr.NameOffset);
|
|
|
|
TimeSpan ttl = TimeSpan.FromSeconds(b.TakeInt32());
|
|
short dataLen = b.TakeInt16();
|
|
byte[] rdata = b.TakeBytes(dataLen);
|
|
|
|
return new ResourceRecord()
|
|
{
|
|
Name = qr.Name,
|
|
Class = qr.Class,
|
|
Type = qr.Type,
|
|
Ttl = ttl,
|
|
Data = rdata
|
|
};
|
|
}
|
|
|
|
public override ReadOnlyMemory<byte> Serialize()
|
|
{
|
|
var buffer = new ArrayBufferWriter<byte>();
|
|
buffer.Write(base.Serialize().Span);
|
|
|
|
BinaryPrimitives.WriteInt32BigEndian(buffer.GetSpan(), (int)Ttl.TotalSeconds);
|
|
buffer.Advance(4);
|
|
|
|
//TODO: Implement RDATA
|
|
BinaryPrimitives.WriteInt16BigEndian(buffer.GetSpan(), (short)Data.Length);
|
|
buffer.Advance(2);
|
|
|
|
if (Data.Length > 0)
|
|
{
|
|
buffer.Write(Data);
|
|
}
|
|
|
|
return buffer.WrittenMemory;
|
|
}
|
|
}
|