diff --git a/src/Ae.Dns.Protocol/DnsByteExtensions.cs b/src/Ae.Dns.Protocol/DnsByteExtensions.cs index 4749b68..561a988 100644 --- a/src/Ae.Dns.Protocol/DnsByteExtensions.cs +++ b/src/Ae.Dns.Protocol/DnsByteExtensions.cs @@ -67,15 +67,20 @@ public static ReadOnlyMemory ReadBytes(ReadOnlyMemory bytes, int len return data; } - private static string ReadStringSimple(ReadOnlyMemory bytes, ref int offset) + private static string ReadStringFromBuffer(ReadOnlySpan bytes) { - byte labelLength = bytes.Span[offset]; - offset += 1; #if NETSTANDARD2_0 - var str = Encoding.ASCII.GetString(bytes.Slice(offset, labelLength).ToArray()); + return Encoding.ASCII.GetString(bytes.ToArray()); #else - var str = Encoding.ASCII.GetString(bytes.Slice(offset, labelLength).Span); + return Encoding.ASCII.GetString(bytes); #endif + } + + public static string ReadStringWithLengthPrefix(ReadOnlyMemory bytes, ref int offset) + { + byte labelLength = bytes.Span[offset]; + offset += 1; + var str = ReadStringFromBuffer(bytes.Slice(offset, labelLength).Span); offset += labelLength; return str; } @@ -106,7 +111,7 @@ private static List ReadString(ReadOnlyMemory bytes, ref int offse offset = ReadUInt16(bytes.Span[offset + 1], (byte)(bytes.Span[offset] & (1 << 6) - 1)); } - parts.Add(ReadStringSimple(bytes, ref offset)); + parts.Add(ReadStringWithLengthPrefix(bytes, ref offset)); } if (preCompressionOffset.HasValue) @@ -171,16 +176,27 @@ public static ArraySegment Slice(this ArraySegment buffer, int start return reader; } - public static void ToBytes(string value, Memory buffer, ref int offset) + private static int ToBytesNoLengthPrefix(string value, Memory buffer, ref int offset) { - // First write the value 1 byte from the offset to leave room for the length byte #if NETSTANDARD2_0 var stringBytes = Encoding.ASCII.GetBytes(value); - stringBytes.CopyTo(buffer.Slice(offset + 1, value.Length)); + stringBytes.CopyTo(buffer.Slice(offset, value.Length)); var length = stringBytes.Length; #else - var length = Encoding.ASCII.GetBytes(value, buffer.Slice(offset + 1, value.Length).Span); + var length = Encoding.ASCII.GetBytes(value, buffer.Slice(offset, value.Length).Span); #endif + // Finally advance the offset past the length and value + offset += length; + + return length; + } + + public static void ToBytes(string value, Memory buffer, ref int offset) + { + // First write the value 1 byte from the offset to leave room for the length byte + var offsetPlusOne = offset + 1; + var length = ToBytesNoLengthPrefix(value, buffer, ref offsetPlusOne); + // Then write the length before the value buffer.Span[offset] = (byte)length; diff --git a/src/Ae.Dns.Protocol/Records/DnsCaaResource.cs b/src/Ae.Dns.Protocol/Records/DnsCaaResource.cs new file mode 100644 index 0000000..8df9f0d --- /dev/null +++ b/src/Ae.Dns.Protocol/Records/DnsCaaResource.cs @@ -0,0 +1,71 @@ +using System; +using System.Text; + +namespace Ae.Dns.Protocol.Records +{ + /// + /// A Certification Authority Authorization (CAA) record is a type of DNS + /// resource record that specifies which certificate authorities (CAs) are + /// allowed to issue certificates for a domain. The CAA record format is + /// specified in RFC 6844. + /// + public sealed class DnsCaaResource : IDnsResource, IEquatable + { + /// + /// Issuer Critical: If set to '1', indicates that the corresponding + /// property tag MUST be understood if the semantics of the CAA record + /// are to be correctly interpreted by an issuer. + /// Issuers MUST NOT issue certificates for a domain if the relevant + /// CAA Resource Record set contains unknown property tags that have + /// the Critical bit set. + /// + public byte Flag { get; set; } + /// + /// The property identifier, a sequence of US-ASCII characters. + /// + public string? Tag { get; set; } + /// + /// A sequence of octets representing the property value. + /// + public ReadOnlyMemory Value { get; set; } + + /// + public bool Equals(DnsCaaResource? other) + { + if (other == null) + { + return false; + } + + return Equals(Flag, other.Flag) && Equals(Tag, other.Tag) && Value.Span.SequenceEqual(other.Value.Span); + } + + /// + public override bool Equals(object? obj) => obj is DnsCaaResource record ? Equals(record) : base.Equals(obj); + + /// + public override int GetHashCode() => HashCode.Combine(Flag, Tag, Value); + + /// + public void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) + { + var bufferEndPosition = offset + length; + Flag = bytes.Span[offset++]; + Tag = DnsByteExtensions.ReadStringWithLengthPrefix(bytes, ref offset); + Value = bytes.Slice(offset, bufferEndPosition - offset); + offset = bufferEndPosition; + } + + /// + public override string ToString() => $"{Flag} {Tag} {Encoding.ASCII.GetString(Value.ToArray())}"; + + /// + public void WriteBytes(Memory bytes, ref int offset) + { + bytes.Span[offset++] = Flag; + DnsByteExtensions.ToBytes(Tag ?? string.Empty, bytes, ref offset); + Value.CopyTo(bytes.Slice(offset)); + offset += Value.Length; + } + } +} diff --git a/src/Ae.Dns.Protocol/Records/DnsResourceFactory.cs b/src/Ae.Dns.Protocol/Records/DnsResourceFactory.cs index f42e6bb..08d3d80 100644 --- a/src/Ae.Dns.Protocol/Records/DnsResourceFactory.cs +++ b/src/Ae.Dns.Protocol/Records/DnsResourceFactory.cs @@ -28,6 +28,7 @@ public static IDnsResource CreateResource(DnsQueryType recordType) DnsQueryType.OPT => new DnsOptResource(), DnsQueryType.HTTPS => new DnsServiceBindingResource(), DnsQueryType.SVCB => new DnsServiceBindingResource(), + DnsQueryType.CAA => new DnsCaaResource(), _ => new DnsUnknownResource(), }; }