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 { 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 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 SerializeName() { string name = Name.Normalize().Trim('.'); if (name.Length > 255) { throw new ArgumentOutOfRangeException("Name must be less than 255"); } ArrayBufferWriter 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 Serialize() { var buffer = new ArrayBufferWriter(); 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 { public TimeSpan Ttl { get; set; } public byte[] Data { get; set; } = Array.Empty(); static ResourceRecord IBinarySerializable.Deserialize(ReadOnlyMemory 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 Serialize() { var buffer = new ArrayBufferWriter(); 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; } }