diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e5fd325
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,329 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
diff --git a/Ae.DnsResolver.sln b/Ae.DnsResolver.sln
new file mode 100644
index 0000000..369c163
--- /dev/null
+++ b/Ae.DnsResolver.sln
@@ -0,0 +1,46 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29806.167
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ae.DnsResolver.Console", "src\Ae.DnsResolver.Console\Ae.DnsResolver.Console.csproj", "{E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0DE60274-AA05-4E64-A7C1-175005219903}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ae.DnsResolver", "src\Ae.DnsResolver\Ae.DnsResolver\Ae.DnsResolver.csproj", "{0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACB95C7B-DFE8-411A-8CC5-059505530ADB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ae.DnsResolver.Tests", "tests\Ae.DnsResolver.Tests\Ae.DnsResolver.Tests\Ae.DnsResolver.Tests.csproj", "{2408600D-30F7-4221-AAA1-4288B8050D30}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2408600D-30F7-4221-AAA1-4288B8050D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2408600D-30F7-4221-AAA1-4288B8050D30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2408600D-30F7-4221-AAA1-4288B8050D30}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2408600D-30F7-4221-AAA1-4288B8050D30}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF} = {0DE60274-AA05-4E64-A7C1-175005219903}
+ {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44} = {0DE60274-AA05-4E64-A7C1-175005219903}
+ {2408600D-30F7-4221-AAA1-4288B8050D30} = {ACB95C7B-DFE8-411A-8CC5-059505530ADB}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2ABE6B23-17A6-4850-8DE8-02A4BF5684DC}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj b/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj
new file mode 100644
index 0000000..03acfc8
--- /dev/null
+++ b/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ae.DnsResolver.Console/Program.cs b/src/Ae.DnsResolver.Console/Program.cs
new file mode 100644
index 0000000..ff1df61
--- /dev/null
+++ b/src/Ae.DnsResolver.Console/Program.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace Ae.DnsResolver
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ DoWork().GetAwaiter().GetResult();
+ Console.WriteLine("Hello World!");
+ }
+
+ public struct RecordType
+ {
+ public string Name;
+ public Qtype Type;
+ public Qclass Class;
+ }
+
+ public static RecordType ToRecordType(DnsMessage message)
+ {
+ return new RecordType
+ {
+ Name = string.Join(".", message.Labels),
+ Type = message.Qtype,
+ Class = message.Qclass
+ };
+ }
+
+ private static ConcurrentDictionary> _cache = new ConcurrentDictionary>();
+
+ private static async Task DoWork()
+ {
+ var client = new UdpClient("1.1.1.1", 53);
+
+ var listener = new UdpClient(53);
+
+ _ = Task.Run(async () =>
+ {
+ while (true)
+ {
+ var result = await client.ReceiveAsync();
+
+ var test = string.Join(",", result.Buffer.Select(x => x));
+
+ //var message = DnsMessageReader.ReadDnsResponse(result.Buffer);
+
+ //Console.WriteLine(message);
+
+ //if (_cache.TryGetValue(ToRecordType(message), out var completionSource))
+ //{
+ // completionSource.SetResult(result.Buffer);
+ //}
+ }
+ });
+
+ while (true)
+ {
+ UdpReceiveResult r;
+ try
+ {
+ r = await listener.ReceiveAsync();
+ }
+ catch (SocketException e)
+ {
+ Console.WriteLine("Connection forcibly closed");
+ continue;
+ }
+
+ Respond(r, client, listener);
+ }
+ }
+
+ public static async void Respond(UdpReceiveResult r, UdpClient client, UdpClient listener)
+ {
+ var message = DnsMessageReader.ReadDnsMessage(r.Buffer);
+
+ Console.WriteLine(message);
+
+ var completionSource = _cache.GetOrAdd(ToRecordType(message), key =>
+ {
+ client.SendAsync(r.Buffer, r.Buffer.Length);
+ return new TaskCompletionSource();
+ });
+
+ var result = await completionSource.Task;
+
+ await listener.SendAsync(result, result.Length, r.RemoteEndPoint);
+ }
+ }
+}
diff --git a/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj b/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj
new file mode 100644
index 0000000..f45bdfc
--- /dev/null
+++ b/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.1
+
+
+
diff --git a/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs b/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs
new file mode 100644
index 0000000..46469e9
--- /dev/null
+++ b/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs
@@ -0,0 +1,374 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Ae.DnsResolver
+{
+ public enum Qtype : ushort
+ {
+ A = 0x0001,
+ NS = 0x0002,
+ MD = 0x0003,
+ MF = 0x0004,
+ CNAME = 0x0005,
+ SOA = 0x0006,
+ MB = 0x0007,
+ MG = 0x0008,
+ MR = 0x0009,
+ NULL = 0x000a,
+ WKS = 0x000b,
+ PTR = 0x000c,
+ HINFO = 0x000d,
+ MINFO = 0x000e,
+ MX = 0x000f,
+ TEXT = 0x0010,
+ RP = 0x0011,
+ AFSDB = 0x0012,
+ X25 = 0x0013,
+ ISDN = 0x0014,
+ RT = 0x0015,
+ NSAP = 0x0016,
+ NSAPPTR = 0x0017,
+ SIG = 0x0018,
+ KEY = 0x0019,
+ PX = 0x001a,
+ GPOS = 0x001b,
+ AAAA = 0x001c,
+ LOC = 0x001d,
+ NXT = 0x001e,
+ EID = 0x001f,
+ NIMLOC = 0x0020,
+ SRV = 0x0021,
+ ATMA = 0x0022,
+ NAPTR = 0x0023,
+ KX = 0x0024,
+ CERT = 0x0025,
+ A6 = 0x0026,
+ DNAME = 0x0027,
+ SINK = 0x0028,
+ OPT = 0x0029,
+ DS = 0x002B,
+ RRSIG = 0x002E,
+ NSEC = 0x002F,
+ DNSKEY = 0x0030,
+ DHCID = 0x0031,
+ UINFO = 0x0064,
+ UID = 0x0065,
+ GID = 0x0066,
+ UNSPEC = 0x0067,
+ ADDRS = 0x00f8,
+ TKEY = 0x00f9,
+ TSIG = 0x00fa,
+ IXFR = 0x00fb,
+ AXFR = 0x00fc,
+ MAILB = 0x00fd,
+ MAILA = 0x00fe,
+ ALL = 0x00ff,
+ ANY = 0x00ff,
+ WINS = 0xff01,
+ WINSR = 0xff02,
+ NBSTAT = WINSR
+ }
+
+ public enum Qclass : short
+ {
+ None = 0,
+ IN = 1,
+ CS = 2,
+ CH = 3,
+ HS = 4
+ }
+
+ public abstract class DnsMessage
+ {
+ public short Id;
+ public short Header;
+ public short Qdcount;
+ public short Ancount;
+ public short Nscount;
+ public short Arcount;
+
+ public string[] Labels;
+ public Qtype Qtype;
+ public Qclass Qclass;
+ }
+
+ public static class EndianExtensions
+ {
+ private static short SwapEndian(this short val)
+ {
+ return BitConverter.IsLittleEndian ? (short)((val << 8) | (val >> 8)) : val;
+ }
+
+ private static ushort SwapEndian(this ushort val)
+ {
+ return BitConverter.IsLittleEndian ? (ushort)((val << 8) | (val >> 8)) : val;
+ }
+
+ private static uint SwapEndian(this uint val)
+ {
+ return BitConverter.IsLittleEndian ? (uint)((val << 16) | (val >> 16)) : val;
+ }
+
+ public static byte ReadByte(this byte[] bytes, ref int offset)
+ {
+ return bytes[offset++];
+ }
+
+ public static short ReadInt16(this byte[] bytes, ref int offset)
+ {
+ offset += sizeof(short);
+ return BitConverter.ToInt16(bytes, offset - sizeof(short)).SwapEndian();
+ }
+
+ public static ushort ReadUInt16(this byte[] bytes, ref int offset)
+ {
+ offset += sizeof(ushort);
+ return BitConverter.ToUInt16(bytes, offset - sizeof(ushort)).SwapEndian();
+ }
+
+ public static uint ReadUInt32(this byte[] bytes, ref int offset)
+ {
+ offset += sizeof(uint);
+ return BitConverter.ToUInt32(bytes, offset - sizeof(uint)).SwapEndian();
+ }
+
+ public static byte[] ReadBytes(this byte[] bytes, int length, ref int offset)
+ {
+ var data = new byte[length];
+ Array.Copy(bytes, offset, data, 0, length);
+ offset += length;
+ return data;
+ }
+
+ public static bool IsBitSet(this byte octet, int position)
+ {
+ // Endianness fix
+ //octet = (byte)((octet << 4) | (octet >> 4));
+ return (octet & (1 << position)) != 0;
+ //return ((octet >> position) & 1) != 0;
+ }
+
+ //public static string[] ReadString2(this byte[] bytes, ref int offset)
+ //{
+ // var parts = new List();
+
+ // while (true)
+ // {
+ // var octet = bytes[offset];
+ // if (octet == 0)
+ // {
+ // offset++;
+ // break;
+ // }
+
+ // if ((octet & 0xC0) == 0xC0)
+ // {
+ // var pointer = (int)bytes[offset + 1];
+ // parts.AddRange(DnsMessageReader.ReadName2(bytes, ref pointer));
+ // offset += 2;
+
+ // if (bytes[offset] == 0)
+ // {
+ // break;
+ // }
+ // }
+ // else
+ // {
+ // offset++;
+ // parts.Add(DnsMessageReader.ReadSingleString(bytes, octet, ref offset));
+ // }
+ // }
+
+ // return parts.ToArray();
+ //}
+
+ public static string[] ReadString(this byte[] bytes, ref int offset)
+ {
+ var parts = new List();
+
+ int compressionOffset = -1;
+ while (true)
+ {
+ // get segment length or detect termination of segments
+ int segmentLength = bytes[offset];
+
+ // compressed name
+ if ((segmentLength & 0xC0) == 0xC0)
+ {
+ offset++;
+ if (compressionOffset == -1)
+ {
+ // only record origin, and follow all pointers thereafter
+ compressionOffset = offset;
+ }
+
+ //if (segmentLength != 192)
+ {
+ var mask = (1 << 14) - 1;
+ var pointer = ((ushort)(segmentLength + (bytes[offset] << 8))).SwapEndian() & mask;
+ offset = pointer;
+ segmentLength = pointer;
+
+ if (offset != bytes[offset])
+ {
+ Debugger.Break();
+ }
+ }
+ //else
+ {
+ // move pointer to compression segment
+ //offset = bytes[offset];
+ //segmentLength = bytes[offset];
+ }
+ }
+
+ if (segmentLength == 0x00)
+ {
+ if (compressionOffset != -1)
+ {
+ offset = compressionOffset;
+ }
+ // move past end of name \0
+ offset++;
+ break;
+ }
+
+ // move pass length and get segment text
+ offset++;
+ parts.Add(Encoding.ASCII.GetString(bytes, offset, segmentLength));
+ offset += segmentLength;
+ }
+
+
+
+ return parts.ToArray();
+ }
+ }
+
+ public class DnsRequestMessage : DnsMessage
+ {
+ public override string ToString() => string.Format("REQUEST: Domain: {0}, type: {1}, class: {2}", string.Join(".", Labels), Qtype, Qclass);
+ }
+
+ public class DnsResponseMessage : DnsMessage
+ {
+ public DnsResourceRecord[] Records;
+
+ public override string ToString() => string.Format("RESPONSE: Domain: {0}, type: {1}, class: {2}, records: {3}", string.Join(".", Labels), Qtype, Qclass, Records.Length);
+ }
+
+ public class DnsResourceRecord
+ {
+ public string[] Name;
+ public Qtype Type;
+ public Qclass Class;
+ public TimeSpan Ttl;
+ public byte[] Data;
+
+ public string DataAsString => Encoding.ASCII.GetString(Data);
+ public IPAddress DataAsIp => new IPAddress(Data);
+ }
+
+ public static class DnsMessageReader
+ {
+ public static DnsMessage ReadDnsMessage(byte[] bytes)
+ {
+ var result = new DnsRequestMessage();
+ var offset = 0;
+ ReadDnsMessage(bytes, result, ref offset);
+ return result;
+ }
+
+ public static DnsResponseMessage ReadDnsResponse(byte[] bytes)
+ {
+ var result = new DnsResponseMessage();
+ var offset = 0;
+ ReadDnsMessage(bytes, result, ref offset);
+
+ var records = new List();
+
+ for (var i = 0; i < result.Ancount; i++)
+ {
+ records.Add(ReadResourceRecord(bytes, ref offset));
+ }
+
+ result.Records = records.ToArray();
+
+ return result;
+ }
+
+ private static DnsResourceRecord ReadResourceRecord(byte[] bytes, ref int offset)
+ {
+ var test = bytes.Select(x => (char)x).ToArray();
+
+ //var originalOffset = offset;
+ var resourceName = bytes.ReadString(ref offset);
+ //var expectedOffset = offset;
+ //offset = originalOffset;
+
+ //var resourceName2 = bytes.ReadString2(ref offset);
+
+ //Debug.Assert(offset == expectedOffset);
+ //Debug.Assert(resourceName.SequenceEqual(resourceName2));
+
+ var resourceType = (Qtype)bytes.ReadUInt16(ref offset);
+ var resourceClass = (Qclass)bytes.ReadUInt16(ref offset);
+ var ttl = bytes.ReadUInt32(ref offset);
+ var rdlength = bytes.ReadUInt16(ref offset);
+
+ byte[] rdata = bytes.ReadBytes(rdlength, ref offset);
+
+ if (resourceType == Qtype.CNAME)
+ {
+ rdata = bytes.ReadString(ref offset).SelectMany(x => x.ToArray()).Select(x => (byte)x).ToArray();
+ }
+
+ return new DnsResourceRecord
+ {
+ Name = resourceName,
+ Type = resourceType,
+ Class = resourceClass,
+ Ttl = TimeSpan.FromSeconds(ttl),
+ Data = rdata
+ };
+ }
+
+ public static string[] ReadName2(byte[] bytes, ref int offset)
+ {
+ var parts = new List();
+
+ var octets = bytes.ReadByte(ref offset);
+ while (octets > 0 && (octets & 0xC0) != 0xC0)
+ {
+ parts.Add(ReadSingleString(bytes, octets, ref offset));
+ octets = bytes.ReadByte(ref offset);
+ }
+
+ return parts.ToArray();
+ }
+
+ public static string ReadSingleString(byte[] bytes, int length, ref int offset)
+ {
+ return Encoding.ASCII.GetString(bytes.ReadBytes(length, ref offset));
+ }
+
+ private static DnsMessage ReadDnsMessage(byte[] bytes, DnsMessage result, ref int offset)
+ {
+ result.Id = bytes.ReadInt16(ref offset);
+ result.Header = bytes.ReadInt16(ref offset);
+ result.Qdcount = bytes.ReadInt16(ref offset);
+ result.Ancount = bytes.ReadInt16(ref offset);
+ result.Nscount = bytes.ReadInt16(ref offset);
+ result.Arcount = bytes.ReadInt16(ref offset);
+ result.Labels = ReadName2(bytes, ref offset);
+ result.Qtype = (Qtype)bytes.ReadInt16(ref offset);
+ result.Qclass = (Qclass)bytes.ReadInt16(ref offset);
+ return result;
+ }
+ }
+}
diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj
new file mode 100644
index 0000000..f4599c9
--- /dev/null
+++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs
new file mode 100644
index 0000000..306db7c
--- /dev/null
+++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs
@@ -0,0 +1,26 @@
+using Xunit;
+
+namespace Ae.DnsResolver.Tests
+{
+ public class DnsMessageReaderTests
+ {
+ [Fact]
+ public void ReadExampleArpaPacket()
+ {
+ var message = DnsMessageReader.ReadDnsResponse(SampleDnsPackets.Example1);
+
+ Assert.Equal(Qclass.IN, message.Qclass);
+ Assert.Equal(Qtype.PTR, message.Qtype);
+ Assert.Equal(new[] { "1", "0", "0", "127", "in-addr", "arpa" }, message.Labels);
+ }
+
+ [Fact]
+ public void ReadExampleExample1Packet()
+ {
+ var message = DnsMessageReader.ReadDnsResponse(SampleDnsPackets.Example2);
+
+ Assert.Equal(Qclass.IN, message.Qclass);
+ Assert.Equal(Qtype.PTR, message.Qtype);
+ }
+ }
+}
diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs
new file mode 100644
index 0000000..bbb6a5c
--- /dev/null
+++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs
@@ -0,0 +1,15 @@
+namespace Ae.DnsResolver.Tests
+{
+ public static class SampleDnsPackets
+ {
+ public static readonly byte[] Example1 = new byte[]
+ {
+ 0,1,129,131,0,1,0,0,0,1,0,0,1,49,1,48,1,48,3,49,50,55,7,105,110,45,97,100,100,114,4,97,114,112,97,0,0,12,0,1,7,105,110,45,97,100,100,114,4,97,114,112,97,0,0,6,0,1,0,0,8,144,0,56,1,98,15,105,110,45,97,100,100,114,45,115,101,114,118,101,114,115,192,48,5,110,115,116,108,100,4,105,97,110,97,3,111,114,103,0,120,103,251,159,0,0,7,8,0,0,3,132,0,9,58,128,0,0,14,16
+ };
+
+ public static readonly byte[] Example2 = new byte[]
+ {
+ 0,2,129,128,0,1,0,7,0,0,0,0,15,97,108,97,110,101,100,119,97,114,100,101,115,45,109,121,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,0,0,1,0,1,15,97,108,97,110,101,100,119,97,114,100,101,115,45,109,121,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,0,0,5,0,1,0,0,14,16,0,15,12,97,108,97,110,101,100,119,97,114,100,101,115,192,64,192,90,0,5,0,1,0,0,14,16,0,36,9,51,48,50,45,105,112,118,52,101,5,99,108,117,109,112,11,100,112,114,111,100,109,103,100,49,48,52,5,97,97,45,114,116,192,64,192,117,0,5,0,1,0,0,0,30,0,20,12,49,56,55,49,55,48,45,105,112,118,52,101,4,102,97,114,109,192,133,192,165,0,5,0,1,0,0,0,60,0,63,12,49,56,55,49,55,48,45,105,112,118,52,101,4,102,97,114,109,11,100,112,114,111,100,109,103,100,49,48,52,16,115,104,97,114,101,112,111,105,110,116,111,110,108,105,110,101,3,99,111,109,6,97,107,97,100,110,115,3,110,101,116,0,192,197,0,5,0,1,0,0,1,44,0,72,11,49,56,55,49,55,48,45,105,112,118,52,4,102,97,114,109,11,100,112,114,111,100,109,103,100,49,48,52,5,97,97,45,114,116,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,8,115,112,111,45,48,48,48,52,10,115,112,111,45,109,115,101,100,103,101,192,255,193,16,0,5,0,1,0,0,0,240,0,2,193,66,193,66,0,1,0,1,0,0,0,240,0,4,13,107,136,9
+ };
+ }
+}