IPMeow/src/IPMeow.Dns/ResourceRecord.cs

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;
}
}