Current section

Files

Jump to
dns_erlang src dns_decode.erl
Raw

src/dns_decode.erl

-module(dns_decode).
-moduledoc false.
-include_lib("dns_erlang/include/dns.hrl").
-define(CLASS_IS_IN(T), (T =:= ?DNS_CLASS_IN orelse T =:= ?DNS_CLASS_NONE)).
%% RFC1876§2: in LOC latitude/longitude, 2^31 encodes the equator/prime meridian
-define(LOC_REFERENCE_POINT, (1 bsl 31)).
-export([decode/1, decode_query/1]).
-export([
decode_message_questions/3,
decode_message_additional/3,
decode_message_body/3,
decode_rrdata/3
]).
-ifdef(TEST).
-export([
decode_rrdata/4,
decode_optrrdata/1,
decode_svcb_svc_params/1
]).
-endif.
-elvis([
{elvis_style, max_function_arity, #{ignore => [{dns_decode, create_message_from_header, 14}]}}
]).
-compile(
{inline, [
decode_bool/1,
round_pow/1,
create_message_from_header/14,
decode_bmp_byte/3,
bmp_bit/3
]}
).
-spec decode(dns:message_bin()) ->
dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}.
decode(
<<Id:16, QR:1, OC:4, AA:1, TC:1, RD:1, RA:1, 0:1, AD:1, CD:1, RC:4, QC:16, ANC:16, AUC:16,
ADC:16, Rest0/binary>> = MsgBin
) ->
Msg0 = create_message_from_header(Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC),
decode_body(MsgBin, Rest0, Msg0);
decode(<<_/binary>> = MsgBin) ->
{formerr, undefined, MsgBin}.
-spec decode_query(dns:message_bin()) ->
dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}.
decode_query(
<<Id:16, QR:1, OC:4, AA:1, TC:1, RD:1, RA:1, 0:1, AD:1, CD:1, RC:4, QC:16, ANC:16, AUC:16,
ADC:16, Rest0/binary>> = MsgBin
) ->
%% Header validation for DNS queries to prevent DoS attacks.
%% - QR bit check: QR must be 0 for queries
%% - TC bit check: queries should never be truncated
%% - OC bit check: each opcode has its own sensible combination of section counts
%% - RCODE in queries is typically 0 (NOERROR) but most servers don't enforce this validation
case {QR, TC, OC, QC, ANC, AUC, ADC} of
%% RFC 1035: Standard queries must have exactly 1 question and no answers.
%% RFC 9619: A DNS message with OPCODE = 0 MUST NOT include a QDCOUNT parameter whose value
%% is greater than 1. It follows that the Question section of a DNS message with OPCODE = 0
%% MUST NOT contain more than one question.
%% rfc1995 §3: an IXFR query carries the client's current SOA in the authority section, so
%% one authority record is legitimate here. The qtype lives in the question section, which
%% this header guard has not read yet, so the bound is on the count rather than on IXFR
%% specifically -- one record is bounded work, which is all the guard is defending.
{0, 0, ?DNS_OPCODE_QUERY, 1, 0, AUC, _} when AUC =< 1 ->
Msg0 = create_message_from_header(
Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC
),
zone_transfer_notimp(decode_body(MsgBin, Rest0, Msg0));
%% RFC 7873: Cookie-only queries may have QDCOUNT=0 when an OPT record with a COOKIE option
%% is present in the additional section.
{0, 0, ?DNS_OPCODE_QUERY, 0, 0, 0, ADC} when ADC > 0 ->
%% Allow QC=0 for cookie-only queries (RFC 7873)
Msg0 = create_message_from_header(
Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC
),
decode_body(MsgBin, Rest0, Msg0);
%% Expected: QR=0, TC=0, QC=1 (typically), ANC>=0 (may contain SOA), AUC>=0
%% rfc1996 §3.7, 3.11: NOTIFY may contain SOA record in Answer section.
{0, 0, ?DNS_OPCODE_NOTIFY, 1, _, _, _} when 0 =:= AUC orelse 1 =:= AUC ->
Msg0 = create_message_from_header(
Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC
),
decode_body(MsgBin, Rest0, Msg0);
%% NOTIFY (opcode 4) with invalid counts - reject with FORMERR
%% rfc1996 §3.7: a NOTIFY carries exactly one question (QDCOUNT=1) and at most one
%% SOA in the authority section. Anything else (e.g. QDCOUNT > 1 from malformed
%% traffic) must be rejected rather than fall through the case and crash.
{0, 0, ?DNS_OPCODE_NOTIFY, _, _, _, _} ->
{formerr, undefined, MsgBin};
%% IQUERY (opcode 1) - RFC 1035 (obsolete per rfc3425)
%% STATUS (opcode 2) - RFC 1035 (Not commonly supported)
%% UPDATE (opcode 5) - rfc2136 (we don't serve it; §3.1 allows NOTIMP)
%% DSO (opcode 6) - rfc8490 (we don't support it)
{0, 0, _, _, _, _, _} when
?DNS_OPCODE_IQUERY =:= OC orelse
?DNS_OPCODE_STATUS =:= OC orelse
?DNS_OPCODE_UPDATE =:= OC orelse
?DNS_OPCODE_DSO =:= OC
->
create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0);
%% Standard Query with invalid counts - reject with FORMERR
%% This catches QUERY opcode with QC != 1, ANC > 0, or AUC > 0
{0, 0, ?DNS_OPCODE_QUERY, _, _, _, _} ->
{formerr, undefined, MsgBin};
%% QR=1 (response) - reject with FORMERR
%% RFC 1035: QR=0 indicates query, QR=1 indicates response.
{1, _, _, _, _, _, _} ->
{formerr, undefined, MsgBin};
%% TC=1 (truncated) - reject with FORMERR
%% RFC 1035: TC bit indicates truncation due to message size limits.
%% Truncation is a response mechanism; queries should not be truncated.
{_, 1, _, _, _, _, _} ->
{formerr, undefined, MsgBin};
%% Reserved/Unassigned opcodes (3, 7-15) - return NOTIMP
%% IANA DNS Opcodes Registry: Opcodes 3 and 7-15 are reserved/unassigned.
_ when OC =:= 3 orelse (OC >= 7 andalso OC =< 15) ->
create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0)
end;
decode_query(MsgBin) ->
{formerr, undefined, MsgBin}.
%% rfc5936 AXFR and rfc1995 IXFR are zone transfers, not queries: the response is a stream of
%% messages over a held TCP connection, which a caller that gets one message back cannot produce.
%% rfc1035 §4.1.1 defines NOTIMP as "the name server does not support the requested kind of query",
%% which is exactly the case here. A server that does implement transfers decodes with `decode/1`
%% and drives its own transfer loop, as it must anyway.
%%
%% This runs on the decoded message rather than ahead of it: the qtype lives in the question
%% section, and reading it before `decode_body/3` would parse the questions twice on every query.
-spec zone_transfer_notimp(Result) -> Result when
Result :: dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}.
zone_transfer_notimp(#dns_message{questions = [#dns_query{type = ?DNS_TYPE_AXFR}]} = Msg) ->
{notimp, notimp_from_query(Msg), <<>>};
zone_transfer_notimp(#dns_message{questions = [#dns_query{type = ?DNS_TYPE_IXFR}]} = Msg) ->
{notimp, notimp_from_query(Msg), <<>>};
zone_transfer_notimp(Result) ->
Result.
%% Same shape as `create_notimp_message/7`: id, opcode, rd and cd carry over, every section is
%% dropped -- an IXFR query arrives with the client's SOA in its authority section (rfc1995 §3),
%% which must not be echoed back.
-spec notimp_from_query(dns:message()) -> dns:message().
notimp_from_query(#dns_message{} = Msg) ->
Msg#dns_message{
qr = true,
aa = false,
tc = false,
ra = false,
ad = false,
rc = ?DNS_RCODE_NOTIMP,
anc = 0,
answers = [],
auc = 0,
authority = [],
adc = 0,
additional = []
}.
%% Helper function to create a minimal message struct for NOTIMP response
%% Returns {notimp, Message, Binary} where Message contains fields needed
%% to construct NOTIMP response:
%% - id: Preserved from query (required for response matching)
%% - oc: Preserved from query (required to echo opcode in response)
%% - rd: Preserved from query (required per DNS protocol)
%% - cd: Preserved from query (if present)
%% - rc: Set to NOTIMP (4)
%% - qr: Set to true (response)
%% - qc, questions: Parsed if possible, otherwise 0/[]
%% - anc, auc, adc: Set to 0 (empty sections)
-spec create_notimp_message(
dns:message_bin(),
dns:uint16(),
dns:opcode(),
0 | 1,
0 | 1,
dns:uint16(),
binary()
) -> {notimp, dns:message(), binary()}.
create_notimp_message(MsgBin, Id, OC, RD, CD, QC, Rest0) ->
%% Try to parse question section if present (for Query/Notify opcodes)
%% For IQUERY/STATUS, question format may differ, so we may not parse it
{Questions, Rest} =
case decode_message_questions(MsgBin, Rest0, QC) of
{Qs, Rest1} ->
{Qs, Rest1};
_ ->
%% Parsing failed, return empty question list
%% The response can still be sent with qc=0
{[], Rest0}
end,
Msg = #dns_message{
id = Id,
%% Response
qr = true,
%% Preserve original opcode
oc = OC,
%% Preserve RD bit
rd = decode_bool(RD),
%% Preserve CD bit if present
cd = decode_bool(CD),
rc = ?DNS_RCODE_NOTIMP,
qc = length(Questions),
questions = Questions
},
{notimp, Msg, Rest}.
%% Helper function to create a dns_message record from parsed header fields
-spec create_message_from_header(
dns:uint16(),
0 | 1,
dns:opcode(),
0 | 1,
0 | 1,
0 | 1,
0 | 1,
0 | 1,
0 | 1,
dns:rcode(),
dns:uint16(),
dns:uint16(),
dns:uint16(),
dns:uint16()
) -> dns:message().
create_message_from_header(Id, QR, OC, AA, TC, RD, RA, AD, CD, RC, QC, ANC, AUC, ADC) ->
#dns_message{
id = Id,
qr = decode_bool(QR),
oc = OC,
aa = decode_bool(AA),
tc = decode_bool(TC),
rd = decode_bool(RD),
ra = decode_bool(RA),
ad = decode_bool(AD),
cd = decode_bool(CD),
rc = RC,
qc = QC,
anc = ANC,
auc = AUC,
adc = ADC
}.
%% The #dns_message{} record is updated exactly once, on completion — updating it
%% per section would copy the full 19-word record four times per message.
-spec decode_body(dns:message_bin(), binary(), dns:message()) ->
dns:message() | {dns:decode_error(), dns:message() | undefined, binary()}.
decode_body(MsgBin, Rest0, #dns_message{qc = QC} = Msg0) ->
case decode_message_questions(MsgBin, Rest0, QC, []) of
{Questions, Rest1} ->
decode_body_answers(MsgBin, Rest1, Msg0, Questions);
{Error, Questions, Rest} ->
{Error, Msg0#dns_message{questions = Questions}, Rest}
end.
-spec decode_body_answers(dns:message_bin(), binary(), dns:message(), dns:questions()) ->
dns:message() | {dns:decode_error(), dns:message(), binary()}.
decode_body_answers(MsgBin, Body, #dns_message{anc = ANC} = Msg0, Questions) ->
case decode_message_body(MsgBin, Body, ANC) of
{Answers, Rest} ->
decode_body_authority(MsgBin, Rest, Msg0, Questions, Answers);
{Error, Answers, Rest} ->
{Error, Msg0#dns_message{questions = Questions, answers = Answers}, Rest}
end.
-spec decode_body_authority(
dns:message_bin(), binary(), dns:message(), dns:questions(), dns:answers()
) ->
dns:message() | {dns:decode_error(), dns:message(), binary()}.
decode_body_authority(MsgBin, Body, #dns_message{auc = AUC} = Msg0, Questions, Answers) ->
case decode_message_body(MsgBin, Body, AUC) of
{Authority, Rest} ->
decode_body_additional(MsgBin, Rest, Msg0, Questions, Answers, Authority);
{Error, Authority, Rest} ->
{Error,
Msg0#dns_message{
questions = Questions,
answers = Answers,
authority = Authority
},
Rest}
end.
-spec decode_body_additional(
dns:message_bin(), binary(), dns:message(), dns:questions(), dns:answers(), dns:authority()
) ->
dns:message() | {dns:decode_error(), dns:message(), binary()}.
decode_body_additional(
MsgBin, Body, #dns_message{adc = ADC} = Msg0, Questions, Answers, Authority
) ->
case decode_message_additional(MsgBin, Body, ADC) of
{Additional, Rest} ->
Msg = Msg0#dns_message{
questions = Questions,
answers = Answers,
authority = Authority,
additional = Additional
},
decode_finished(MsgBin, Rest, Msg);
{Error, Additional, Rest} ->
{Error,
Msg0#dns_message{
questions = Questions,
answers = Answers,
authority = Authority,
additional = Additional
},
Rest}
end.
-spec decode_finished(dns:message_bin(), binary(), dns:message()) ->
dns:message() | {dns:decode_error(), dns:message(), binary()}.
decode_finished(_MsgBin, <<>>, #dns_message{} = Msg) ->
Msg;
decode_finished(_MsgBin, Bin, #dns_message{} = Msg) when is_binary(Bin) ->
{trailing_garbage, Msg, Bin}.
-spec decode_message_questions(dns:message_bin(), binary(), dns:uint16()) ->
{dns:questions(), binary()} | {dns:decode_error(), dns:questions(), binary()}.
decode_message_questions(MsgBin, DataBin, Count) ->
decode_message_questions(MsgBin, DataBin, Count, []).
-spec decode_message_questions(dns:message_bin(), binary(), dns:uint16(), dns:questions()) ->
{dns:questions(), binary()} | {dns:decode_error(), dns:questions(), binary()}.
decode_message_questions(_MsgBin, DataBin, 0, RRs) ->
{lists:reverse(RRs), DataBin};
decode_message_questions(_MsgBin, <<>>, _Count, RRs) ->
{truncated, lists:reverse(RRs), <<>>};
decode_message_questions(MsgBin, DataBin, Count, RRs) ->
try dns_domain:from_wire(MsgBin, DataBin) of
{Name, <<Type:16, Class:16, RB/binary>>} ->
R = #dns_query{name = Name, type = Type, class = Class},
decode_message_questions(MsgBin, RB, Count - 1, [R | RRs]);
{_Name, _Bin} ->
{truncated, lists:reverse(RRs), DataBin}
catch
Error when is_atom(Error) ->
{Error, lists:reverse(RRs), DataBin};
_:_ ->
{formerr, lists:reverse(RRs), DataBin}
end.
-spec decode_message_additional(dns:message_bin(), binary(), dns:uint16()) ->
{dns:additional(), binary()} | {dns:decode_error(), [dns:optrr() | dns:rr()], binary()}.
decode_message_additional(MsgBin, DataBin, Count) when
is_binary(MsgBin), is_binary(DataBin), is_integer(Count), 0 =< Count, Count =< 65535
->
do_decode_message_additional(MsgBin, DataBin, Count, []).
-spec do_decode_message_additional(dns:message_bin(), binary(), integer(), dns:additional()) ->
{dns:additional(), binary()} | {dns:decode_error(), [dns:optrr() | dns:rr()], binary()}.
do_decode_message_additional(MsgBin, DataBin, Count, RRs) ->
do_decode_message_additional(MsgBin, DataBin, Count, RRs, false).
-spec do_decode_message_additional(
dns:message_bin(), binary(), integer(), dns:additional(), boolean()
) ->
{dns:additional(), binary()} | {dns:decode_error(), [dns:optrr() | dns:rr()], binary()}.
do_decode_message_additional(_MsgBin, DataBin, 0, RRs, _SeenOptRR) ->
{lists:reverse(RRs), DataBin};
do_decode_message_additional(_MsgBin, <<>>, _Count, RRs, _SeenOptRR) ->
{truncated, lists:reverse(RRs), <<>>};
do_decode_message_additional(MsgBin, DataBin, Count, RRs, SeenOptRR) ->
%% The record parse is the `try` protected expression so a malformed rdata — e.g. a bad EDNS
%% COOKIE (RFC 7873), which raises `bad_cookie` — surfaces as a decode-error tuple instead of
%% escaping. The recursion stays in the `of` body to remain in tail position.
try
case dns_domain:from_wire(MsgBin, DataBin) of
{Name,
<<?DNS_TYPE_OPT:16/unsigned, UPS:16/unsigned, ExtRcode:8, Version:8, DNSSEC:1,
_Z:15, EDataLen:16, EDataBin:EDataLen/binary, RemBin/binary>>} ->
ok = check_optrr(Name, SeenOptRR),
{
#dns_optrr{
udp_payload_size = UPS,
ext_rcode = ExtRcode,
version = Version,
dnssec = decode_bool(DNSSEC),
data = decode_optrrdata(EDataBin)
},
RemBin
};
{Name,
<<Type:16/unsigned, Class:16/unsigned, TTL:32/signed, Len:16, RdataBin:Len/binary,
RemBin/binary>>} ->
{
#dns_rr{
name = Name,
type = Type,
class = Class,
ttl = TTL,
data = decode_rrdata(MsgBin, Class, Type, RdataBin)
},
RemBin
};
{_Name, _Bin} ->
truncated
end
of
{#dns_optrr{} = RR, Rest} ->
do_decode_message_additional(MsgBin, Rest, Count - 1, [RR | RRs], true);
{RR, Rest} ->
do_decode_message_additional(MsgBin, Rest, Count - 1, [RR | RRs], SeenOptRR);
truncated ->
{truncated, lists:reverse(RRs), DataBin}
catch
Error when is_atom(Error) ->
{Error, lists:reverse(RRs), DataBin};
_:_ ->
{formerr, lists:reverse(RRs), DataBin}
end.
%% RFC6891§6.1.1: an OPT RR's owner name MUST be root
-spec check_optrr(dns:dname(), boolean()) -> ok.
check_optrr(<<>>, false) -> ok;
check_optrr(<<>>, true) -> error(multiple_optrr);
check_optrr(_Name, _SeenOptRR) -> error(bad_optrr_name).
-spec decode_message_body(dns:message_bin(), binary(), dns:uint16()) ->
{[dns:rr()], binary()} | {dns:decode_error(), [dns:rr()], binary()}.
decode_message_body(MsgBin, DataBin, Count) when
is_binary(MsgBin), is_binary(DataBin), is_integer(Count), 0 =< Count, Count =< 65535
->
do_decode_message_body(MsgBin, DataBin, Count, []).
-spec do_decode_message_body(dns:message_bin(), binary(), integer(), [dns:rr()]) ->
{[dns:rr()], binary()} | {dns:decode_error(), [dns:rr()], binary()}.
do_decode_message_body(_MsgBin, DataBin, 0, RRs) ->
{lists:reverse(RRs), DataBin};
do_decode_message_body(_MsgBin, <<>>, _Count, RRs) ->
{truncated, lists:reverse(RRs), <<>>};
do_decode_message_body(MsgBin, DataBin, Count, RRs) ->
%% As in `do_decode_message_additional/4`: the parse is the protected expression so a failure
%% decoding an RR's rdata (e.g. a bad compression pointer reached via `from_wire/2`) surfaces
%% as a decode-error tuple, while the recursion stays in the `of` body (tail position).
try
case dns_domain:from_wire(MsgBin, DataBin) of
{Name,
<<Type:16/unsigned, Class:16/unsigned, TTL:32/signed, Len:16, RdataBin:Len/binary,
RemBin/binary>>} ->
{
#dns_rr{
name = Name,
type = Type,
class = Class,
ttl = TTL,
data = decode_rrdata(MsgBin, Class, Type, RdataBin)
},
RemBin
};
{_Name, _Bin} ->
truncated
end
of
{RR, Rest} ->
do_decode_message_body(MsgBin, Rest, Count - 1, [RR | RRs]);
truncated ->
{truncated, lists:reverse(RRs), DataBin}
catch
Error when is_atom(Error) ->
{Error, lists:reverse(RRs), DataBin};
_:_ ->
{formerr, lists:reverse(RRs), DataBin}
end.
-spec decode_optrrdata(binary()) -> [dns:optrr_elem()].
decode_optrrdata(Bin) ->
decode_optrrdata(Bin, []).
-spec decode_optrrdata(binary(), [dns:optrr_elem()]) -> [dns:optrr_elem()].
decode_optrrdata(<<>>, Opts) ->
lists:reverse(Opts);
decode_optrrdata(<<EOptNum:16, EOptLen:16, EOptBin:EOptLen/binary, Rest/binary>>, Opts) ->
NewOpt = do_decode_optrrdata(EOptNum, EOptBin),
decode_optrrdata(Rest, [NewOpt | Opts]).
-spec do_decode_optrrdata(dns:uint16(), binary()) -> dns:optrr_elem().
do_decode_optrrdata(?DNS_EOPTCODE_LLQ, <<1:16, OC:16, EC:16, Id:64, LeaseLife:32>>) ->
#dns_opt_llq{opcode = OC, errorcode = EC, id = Id, leaselife = LeaseLife};
do_decode_optrrdata(?DNS_EOPTCODE_NSID, <<Data/binary>>) ->
#dns_opt_nsid{data = Data};
do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary>>) ->
#dns_opt_owner{seq = S, primary_mac = PMAC, _ = <<>>};
do_decode_optrrdata(?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary>>) ->
#dns_opt_owner{
seq = S,
primary_mac = PMAC,
wakeup_mac = WMAC,
password = <<>>
};
do_decode_optrrdata(
?DNS_EOPTCODE_OWNER, <<0:8, S:8, PMAC:6/binary, WMAC:6/binary, Password/binary>>
) ->
#dns_opt_owner{
seq = S,
primary_mac = PMAC,
wakeup_mac = WMAC,
password = Password
};
do_decode_optrrdata(?DNS_EOPTCODE_UL, <<Time:32>>) ->
#dns_opt_ul{lease = Time};
do_decode_optrrdata(?DNS_EOPTCODE_ECS, <<FAMILY:16, SRCPL:8, SCOPEPL:8, Payload/binary>>) ->
#dns_opt_ecs{
family = FAMILY,
source_prefix_length = SRCPL,
scope_prefix_length = SCOPEPL,
address = Payload
};
do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, <<ClientCookie:8/binary>>) ->
#dns_opt_cookie{client = ClientCookie};
do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, <<ClientCookie:8/binary, ServerCookie/binary>>) when
8 =< byte_size(ServerCookie), byte_size(ServerCookie) =< 32
->
#dns_opt_cookie{client = ClientCookie, server = ServerCookie};
do_decode_optrrdata(?DNS_EOPTCODE_COOKIE, _) ->
erlang:error(bad_cookie);
do_decode_optrrdata(?DNS_EOPTCODE_EDE, <<InfoCode:16, ExtraText/binary>>) ->
#dns_opt_ede{info_code = InfoCode, extra_text = ExtraText};
do_decode_optrrdata(?DNS_EOPTCODE_EDE, <<>>) ->
#dns_opt_ede{info_code = 0, extra_text = <<>>};
do_decode_optrrdata(EOpt, <<Bin/binary>>) ->
#dns_opt_unknown{id = EOpt, bin = Bin}.
-spec decode_rrdata(dns:message_bin(), dns:uint16(), dns:uint16()) -> dns:rrdata().
decode_rrdata(MsgBin, Class, Type) ->
decode_rrdata(MsgBin, Class, Type, MsgBin).
-spec decode_rrdata(dns:message_bin(), dns:uint16(), dns:uint16(), binary()) -> dns:rrdata().
%% rfc2136 §2.4, §2.5: in an UPDATE's prerequisite and update sections an RR with CLASS ANY or
%% NONE carries no RDATA, so RDLENGTH=0 is the intended encoding rather than a truncated record.
decode_rrdata(_MsgBin, Class, _Type, <<>>) when
?DNS_CLASS_ANY =:= Class orelse ?DNS_CLASS_NONE =:= Class
->
<<>>;
decode_rrdata(_MsgBin, _Class, Type, <<>>) ->
empty_rrdata(Type);
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_A, <<A, B, C, D>>) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_a{ip = {A, B, C, D}};
decode_rrdata(
_MsgBin, Class, ?DNS_TYPE_AAAA, <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>
) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_aaaa{ip = {A, B, C, D, E, F, G, H}};
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_EUI48, Bin) when
?CLASS_IS_IN(Class) andalso 6 =:= byte_size(Bin)
->
#dns_rrdata_eui48{address = Bin};
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_EUI64, Bin) when
?CLASS_IS_IN(Class) andalso 8 =:= byte_size(Bin)
->
#dns_rrdata_eui64{address = Bin};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_AFSDB, <<Subtype:16, Bin/binary>>) ->
#dns_rrdata_afsdb{
subtype = Subtype,
hostname = decode_dnameonly(MsgBin, Bin)
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CAA, <<Flags:8, Len:8, Bin/binary>>) ->
<<Tag:Len/binary, Value/binary>> = Bin,
#dns_rrdata_caa{flags = Flags, tag = Tag, value = Value};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CERT, <<Type:16, KeyTag:16, Alg, Bin/binary>>) ->
#dns_rrdata_cert{type = Type, keytag = KeyTag, alg = Alg, cert = Bin};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_CNAME, Bin) ->
#dns_rrdata_cname{dname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_DHCID, Bin) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_dhcid{data = Bin};
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_OPENPGPKEY, Bin) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_openpgpkey{data = Bin};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_URI,
<<Priority:16, Weight:16, Target/binary>>
) ->
%% RFC7553§4.5: validated as a URI but stored verbatim; normalizing the target
%% changed the canonical RDATA an RRSIG covers.
case uri_string:normalize(Target) of
{error, Reason, _} ->
erlang:error({bad_uri, Target, Reason});
_Normalized ->
#dns_rrdata_uri{
priority = Priority,
weight = Weight,
target = Target
}
end;
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_RESINFO, Bin) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_resinfo{data = decode_text(Bin)};
decode_rrdata(_MsgBin, Class, ?DNS_TYPE_WALLET, Bin) when ?CLASS_IS_IN(Class) ->
#dns_rrdata_wallet{data = decode_text(Bin)};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_DLV, <<KeyTag:16, Alg:8, DigestType:8, Digest/binary>>) ->
#dns_rrdata_dlv{
keytag = KeyTag,
alg = Alg,
digest_type = DigestType,
digest = Digest
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_DNAME, Bin) ->
#dns_rrdata_dname{dname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(
_MsgBin, _Class, ?DNS_TYPE_DNSKEY, <<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) when
AlgNum =:= ?DNS_ALG_RSASHA1 orelse
AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse
AlgNum =:= ?DNS_ALG_RSASHA256 orelse
AlgNum =:= ?DNS_ALG_RSASHA512
->
{Key, KeyTag} = decode_rsa_key(PublicKey, Bin),
#dns_rrdata_dnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = Key,
keytag = KeyTag
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_DNSKEY,
<<Flags:16, Protocol:8, AlgNum:8, T, Q:20/unit:8, KeyBin/binary>> = Bin
) when
(AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso
T =< 8
->
{Key, KeyTag} = decode_dsa_key(T, Q, KeyBin, Bin),
#dns_rrdata_dnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = Key,
keytag = KeyTag
};
decode_rrdata(
_MsgBin, _Class, ?DNS_TYPE_DNSKEY, <<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) when
(AlgNum =:= ?DNS_ALG_ECDSAP256SHA256 andalso 64 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ECDSAP384SHA384 andalso 96 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ED25519 andalso 32 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ED448 andalso 57 =:= byte_size(PublicKey))
->
#dns_rrdata_dnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = PublicKey,
keytag = bin_to_key_tag(Bin)
};
decode_rrdata(
_MsgBin, _Class, ?DNS_TYPE_DNSKEY, <<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) ->
#dns_rrdata_dnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = PublicKey,
keytag = bin_to_key_tag(Bin)
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_CDNSKEY,
<<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) when
AlgNum =:= ?DNS_ALG_RSASHA1 orelse
AlgNum =:= ?DNS_ALG_NSEC3RSASHA1 orelse
AlgNum =:= ?DNS_ALG_RSASHA256 orelse
AlgNum =:= ?DNS_ALG_RSASHA512
->
{Key, KeyTag} = decode_rsa_key(PublicKey, Bin),
#dns_rrdata_cdnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = Key,
keytag = KeyTag
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_CDNSKEY,
<<Flags:16, Protocol:8, AlgNum:8, T, Q:20/unit:8, KeyBin/binary>> = Bin
) when
(AlgNum =:= ?DNS_ALG_DSA orelse AlgNum =:= ?DNS_ALG_NSEC3DSA) andalso
T =< 8
->
{Key, KeyTag} = decode_dsa_key(T, Q, KeyBin, Bin),
#dns_rrdata_cdnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = Key,
keytag = KeyTag
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_CDNSKEY,
<<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) when
(AlgNum =:= ?DNS_ALG_ECDSAP256SHA256 andalso 64 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ECDSAP384SHA384 andalso 96 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ED25519 andalso 32 =:= byte_size(PublicKey)) orelse
(AlgNum =:= ?DNS_ALG_ED448 andalso 57 =:= byte_size(PublicKey))
->
#dns_rrdata_cdnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = PublicKey,
keytag = bin_to_key_tag(Bin)
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_CDNSKEY,
<<Flags:16, Protocol:8, AlgNum:8, PublicKey/binary>> = Bin
) ->
#dns_rrdata_cdnskey{
flags = Flags,
protocol = Protocol,
alg = AlgNum,
public_key = PublicKey,
keytag = bin_to_key_tag(Bin)
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_DS, <<KeyTag:16, Alg:8, DigestType:8, Digest/binary>>) ->
#dns_rrdata_ds{
keytag = KeyTag,
alg = Alg,
digest_type = DigestType,
digest = Digest
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_CDS, <<KeyTag:16, Alg:8, DigestType:8, Digest/binary>>) ->
#dns_rrdata_cds{
keytag = KeyTag,
alg = Alg,
digest_type = DigestType,
digest = Digest
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_ZONEMD, <<Serial:32, Scheme:8, Alg:8, Hash/binary>>) ->
#dns_rrdata_zonemd{
serial = Serial,
scheme = Scheme,
algorithm = Alg,
hash = Hash
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_HINFO, Bin) ->
[CPU, OS] = decode_text(Bin),
#dns_rrdata_hinfo{cpu = CPU, os = OS};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_IPSECKEY,
<<Precedence:8, 0:8, Algorithm:8, PublicKey/binary>>
) ->
#dns_rrdata_ipseckey{
precedence = Precedence,
alg = Algorithm,
gateway = <<>>,
public_key = PublicKey
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_IPSECKEY,
<<Precedence:8, 1:8, Algorithm:8, A:8, B:8, C:8, D:8, PublicKey/binary>>
) ->
#dns_rrdata_ipseckey{
precedence = Precedence,
alg = Algorithm,
gateway = {A, B, C, D},
public_key = PublicKey
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_IPSECKEY,
<<Precedence:8, 2:8, Algorithm:8, A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16,
PublicKey/binary>>
) ->
#dns_rrdata_ipseckey{
precedence = Precedence,
alg = Algorithm,
gateway = {A, B, C, D, E, F, G, H},
public_key = PublicKey
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_IPSECKEY, <<Precedence:8, 3:8, Algorithm:8, Bin/binary>>) ->
{Gateway, PublicKey} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_ipseckey{
precedence = Precedence,
alg = Algorithm,
gateway = Gateway,
public_key = PublicKey
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_KEY,
<<Type:2, 0:1, XT:1, 0:2, NamType:2, 0:4, Sig:4, Protocol:8, Alg:8, PublicKey/binary>>
) ->
#dns_rrdata_key{
type = Type,
xt = XT,
name_type = NamType,
sig = Sig,
protocol = Protocol,
alg = Alg,
public_key = PublicKey
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_KX, <<Preference:16, Bin/binary>>) ->
#dns_rrdata_kx{
preference = Preference,
exchange = decode_dnameonly(MsgBin, Bin)
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_LOC,
<<0:8, SizeB:4, SizeE:4, HorizB:4, HorizE:4, VertB:4, VertE:4, LatPre:32, LonPre:32, AltPre:32>>
) when
%% RFC1876§2: both nibbles are 0..9; only the exponents used to be checked
SizeB < 10 andalso HorizB < 10 andalso VertB < 10 andalso
SizeE < 10 andalso HorizE < 10 andalso VertE < 10
->
#dns_rrdata_loc{
size = SizeB * (round_pow(SizeE)),
horiz = HorizB * (round_pow(HorizE)),
vert = VertB * (round_pow(VertE)),
lat = decode_loc_point(LatPre),
lon = decode_loc_point(LonPre),
alt = AltPre - 10000000
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MB, Bin) ->
#dns_rrdata_mb{madname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MG, Bin) ->
#dns_rrdata_mg{madname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MINFO, Bin) when is_binary(Bin) ->
{RMB, EMB} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_minfo{rmailbx = RMB, emailbx = decode_dnameonly(MsgBin, EMB)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MR, Bin) ->
#dns_rrdata_mr{newname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_MX, <<Preference:16, Bin/binary>>) ->
#dns_rrdata_mx{
preference = Preference,
exchange = decode_dnameonly(MsgBin, Bin)
};
decode_rrdata(
MsgBin,
_Class,
?DNS_TYPE_NAPTR,
<<Order:16, Preference:16, Bin/binary>>
) ->
{Bin1, Flags} = decode_string(Bin),
{Bin2, Services} = decode_string(Bin1),
{Bin3, RawRegexp} = decode_string(Bin2),
Regexp = decode_naptr_regexp(RawRegexp),
#dns_rrdata_naptr{
order = Order,
preference = Preference,
flags = Flags,
services = Services,
regexp = Regexp,
replacement = decode_dnameonly(MsgBin, Bin3)
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NS, Bin) ->
#dns_rrdata_ns{dname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NSEC, Bin) ->
{NextDName, TypeBMP} = dns_domain:from_wire(MsgBin, Bin),
Types = decode_nsec_types(TypeBMP),
#dns_rrdata_nsec{next_dname = NextDName, types = Types};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_CSYNC,
<<SOASerial:32, Flags:16, TypeBMP/binary>>
) ->
Types = decode_nsec_types(TypeBMP),
#dns_rrdata_csync{
soa_serial = SOASerial,
flags = Flags,
types = Types
};
decode_rrdata(
MsgBin,
_Class,
?DNS_TYPE_DSYNC,
<<RRType:16, Scheme:8, Port:16, TargetBin/binary>>
) ->
Target = decode_dnameonly(MsgBin, TargetBin),
#dns_rrdata_dsync{
rrtype = RRType,
scheme = Scheme,
port = Port,
target = Target
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_NSEC3,
<<HashAlg:8, _FlagsZ:7, OptOut:1, Iterations:16, SaltLen:8/unsigned, Salt:SaltLen/binary-unit:8,
HashLen:8/unsigned, Hash:HashLen/binary-unit:8, TypeBMP/binary>>
) ->
#dns_rrdata_nsec3{
hash_alg = HashAlg,
opt_out = decode_bool(OptOut),
iterations = Iterations,
salt = Salt,
hash = Hash,
types = decode_nsec_types(TypeBMP)
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_NSEC3PARAM,
<<Alg:8, Flags:8, Iterations:16, SaltLen:8, Salt:SaltLen/binary>>
) ->
#dns_rrdata_nsec3param{
hash_alg = Alg,
flags = Flags,
iterations = Iterations,
salt = Salt
};
decode_rrdata(
_MsgBin, _Class, ?DNS_TYPE_TLSA, <<Usage:8, Selector:8, MatchingType:8, Certificate/binary>>
) ->
#dns_rrdata_tlsa{
usage = Usage,
selector = Selector,
matching_type = MatchingType,
certificate = Certificate
};
decode_rrdata(
_MsgBin, _Class, ?DNS_TYPE_SMIMEA, <<Usage:8, Selector:8, MatchingType:8, Certificate/binary>>
) ->
#dns_rrdata_smimea{
usage = Usage,
selector = Selector,
matching_type = MatchingType,
certificate = Certificate
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_NXT, Bin) ->
{NxtDName, BMP} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_nxt{dname = NxtDName, types = decode_nxt_bmp(BMP)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_PTR, Bin) ->
#dns_rrdata_ptr{dname = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_RP, Bin) ->
{Mbox, TxtBin} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_rp{mbox = Mbox, txt = decode_dnameonly(MsgBin, TxtBin)};
decode_rrdata(
MsgBin,
_Class,
?DNS_TYPE_RRSIG,
<<Type:16, Alg:8, Labels:8, TTL:32, Expire:32, Inception:32, KeyTag:16, Bin/binary>>
) ->
{SigName, Sig} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_rrsig{
type_covered = Type,
alg = Alg,
labels = Labels,
original_ttl = TTL,
expiration = Expire,
inception = Inception,
keytag = KeyTag,
signers_name = SigName,
signature = Sig
};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_RT, <<Pref:16, Bin/binary>>) ->
#dns_rrdata_rt{preference = Pref, host = decode_dnameonly(MsgBin, Bin)};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_SOA, Bin) ->
{MName, RNBin} = dns_domain:from_wire(MsgBin, Bin),
{RName, Rest} = dns_domain:from_wire(MsgBin, RNBin),
<<Ser:32, Ref:32, Ret:32, Exp:32, Min:32>> = Rest,
#dns_rrdata_soa{
mname = MName,
rname = RName,
serial = Ser,
refresh = Ref,
retry = Ret,
expire = Exp,
minimum = Min
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_SPF, Bin) ->
#dns_rrdata_spf{spf = decode_text(Bin)};
decode_rrdata(
MsgBin,
_Class,
?DNS_TYPE_SRV,
<<Pri:16, Wght:16, Port:16, Bin/binary>>
) ->
#dns_rrdata_srv{
priority = Pri,
weight = Wght,
port = Port,
target = decode_dnameonly(MsgBin, Bin)
};
decode_rrdata(
_MsgBin,
_Class,
?DNS_TYPE_SSHFP,
<<Alg:8, FPType:8, FingerPrint/binary>>
) ->
#dns_rrdata_sshfp{alg = Alg, fp_type = FPType, fp = FingerPrint};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_SVCB, <<SvcPriority:16, Bin/binary>>) ->
{TargetName, SvcParamsBin} = dns_domain:from_wire(MsgBin, Bin),
SvcParams = decode_svcb_svc_params(SvcParamsBin),
#dns_rrdata_svcb{svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_HTTPS, <<SvcPriority:16, Bin/binary>>) ->
{TargetName, SvcParamsBin} = dns_domain:from_wire(MsgBin, Bin),
SvcParams = decode_svcb_svc_params(SvcParamsBin),
#dns_rrdata_https{svc_priority = SvcPriority, target_name = TargetName, svc_params = SvcParams};
decode_rrdata(MsgBin, _Class, ?DNS_TYPE_TSIG, Bin) ->
{Alg,
<<Time:48, Fudge:16, MS:16, MAC:MS/bytes, MsgId:16, ErrInt:16, OtherLen:16,
Other:OtherLen/binary>>} = dns_domain:from_wire(MsgBin, Bin),
#dns_rrdata_tsig{
alg = Alg,
time = Time,
fudge = Fudge,
mac = MAC,
msgid = MsgId,
err = ErrInt,
other = Other
};
decode_rrdata(_MsgBin, _Class, ?DNS_TYPE_TXT, Bin) ->
#dns_rrdata_txt{txt = decode_text(Bin)};
decode_rrdata(_MsgBin, _Class, _Type, Bin) ->
Bin.
%% RFC1035§3.2.1: RDLENGTH counts the octets of RDATA, enforce the RDLENGTH when it is known
-spec empty_rrdata(dns:type()) -> binary().
empty_rrdata(Type) ->
case requires_rrdata(Type) of
true -> error(empty_rrdata);
false -> <<>>
end.
-spec requires_rrdata(dns:type()) -> boolean().
requires_rrdata(?DNS_TYPE_A) -> true;
requires_rrdata(?DNS_TYPE_AAAA) -> true;
requires_rrdata(?DNS_TYPE_AFSDB) -> true;
requires_rrdata(?DNS_TYPE_CAA) -> true;
requires_rrdata(?DNS_TYPE_CDNSKEY) -> true;
requires_rrdata(?DNS_TYPE_CDS) -> true;
requires_rrdata(?DNS_TYPE_CERT) -> true;
requires_rrdata(?DNS_TYPE_CNAME) -> true;
requires_rrdata(?DNS_TYPE_CSYNC) -> true;
requires_rrdata(?DNS_TYPE_DHCID) -> true;
requires_rrdata(?DNS_TYPE_DLV) -> true;
requires_rrdata(?DNS_TYPE_DNAME) -> true;
requires_rrdata(?DNS_TYPE_DNSKEY) -> true;
requires_rrdata(?DNS_TYPE_DS) -> true;
requires_rrdata(?DNS_TYPE_DSYNC) -> true;
requires_rrdata(?DNS_TYPE_EUI48) -> true;
requires_rrdata(?DNS_TYPE_EUI64) -> true;
requires_rrdata(?DNS_TYPE_HINFO) -> true;
requires_rrdata(?DNS_TYPE_HTTPS) -> true;
requires_rrdata(?DNS_TYPE_IPSECKEY) -> true;
requires_rrdata(?DNS_TYPE_KEY) -> true;
requires_rrdata(?DNS_TYPE_KX) -> true;
requires_rrdata(?DNS_TYPE_LOC) -> true;
requires_rrdata(?DNS_TYPE_MB) -> true;
requires_rrdata(?DNS_TYPE_MG) -> true;
requires_rrdata(?DNS_TYPE_MINFO) -> true;
requires_rrdata(?DNS_TYPE_MR) -> true;
requires_rrdata(?DNS_TYPE_MX) -> true;
requires_rrdata(?DNS_TYPE_NAPTR) -> true;
requires_rrdata(?DNS_TYPE_NS) -> true;
requires_rrdata(?DNS_TYPE_NSEC) -> true;
requires_rrdata(?DNS_TYPE_NSEC3) -> true;
requires_rrdata(?DNS_TYPE_NSEC3PARAM) -> true;
requires_rrdata(?DNS_TYPE_NXT) -> true;
requires_rrdata(?DNS_TYPE_OPENPGPKEY) -> true;
requires_rrdata(?DNS_TYPE_PTR) -> true;
requires_rrdata(?DNS_TYPE_RESINFO) -> true;
requires_rrdata(?DNS_TYPE_RP) -> true;
requires_rrdata(?DNS_TYPE_RRSIG) -> true;
requires_rrdata(?DNS_TYPE_RT) -> true;
requires_rrdata(?DNS_TYPE_SMIMEA) -> true;
requires_rrdata(?DNS_TYPE_SOA) -> true;
requires_rrdata(?DNS_TYPE_SPF) -> true;
requires_rrdata(?DNS_TYPE_SRV) -> true;
requires_rrdata(?DNS_TYPE_SSHFP) -> true;
requires_rrdata(?DNS_TYPE_SVCB) -> true;
requires_rrdata(?DNS_TYPE_TLSA) -> true;
requires_rrdata(?DNS_TYPE_TSIG) -> true;
requires_rrdata(?DNS_TYPE_TXT) -> true;
requires_rrdata(?DNS_TYPE_URI) -> true;
requires_rrdata(?DNS_TYPE_WALLET) -> true;
requires_rrdata(?DNS_TYPE_ZONEMD) -> true;
requires_rrdata(_Type) -> false.
%% RFC3403§4.1: the NAPTR REGEXP field is UTF-8
-spec decode_naptr_regexp(binary()) -> binary().
decode_naptr_regexp(RawRegexp) ->
case unicode:characters_to_binary(RawRegexp, utf8) of
Regexp when is_binary(Regexp) -> Regexp;
_ -> error(bad_naptr_regexp)
end.
-spec decode_dnameonly(dns:message_bin(), nonempty_binary()) -> binary().
decode_dnameonly(MsgBin, Bin) ->
case dns_domain:from_wire(MsgBin, Bin) of
{DName, <<>>} -> DName;
_ -> error(trailing_garbage)
end.
%% Helper function to decode RSA keys for DNSKEY and CDNSKEY records
-spec decode_rsa_key(binary(), binary()) -> {list(), dns:uint16()}.
decode_rsa_key(PublicKey, Bin) ->
KeyTag = bin_to_key_tag(Bin),
case rsa_fields(PublicKey) of
{ExpBin, ModBin} ->
{[binary:decode_unsigned(ExpBin), binary:decode_unsigned(ModBin)], KeyTag};
not_canonical ->
{PublicKey, KeyTag}
end.
%% RFC3110§2: exponent length in one octet, or zero then two octets.
%% Real keys are canonical -- an RSA modulus has its top bit set, and 65537 starts 0x01.
-spec rsa_fields(binary()) -> {binary(), binary()} | not_canonical.
rsa_fields(<<0, Len:16, Exp:Len/binary, Mod/binary>>) when 255 < Len -> rsa_fields(Exp, Mod);
rsa_fields(<<Len:8, Exp:Len/binary, Mod/binary>>) -> rsa_fields(Exp, Mod);
rsa_fields(<<_/binary>>) -> not_canonical.
rsa_fields(<<E, _/binary>> = Exp, <<M, _/binary>> = Mod) when 0 < E, 0 < M -> {Exp, Mod};
rsa_fields(_Exp, _Mod) -> not_canonical.
%% Helper function to decode DSA keys for DNSKEY and CDNSKEY records
-spec decode_dsa_key(byte(), non_neg_integer(), binary(), binary()) -> {list(), dns:uint16()}.
decode_dsa_key(T, Q, KeyBin, Bin) ->
S = 64 + T * 8,
<<P:S/unit:8, G:S/unit:8, Y:S/unit:8>> = KeyBin,
Key = [P, Q, G, Y],
KeyTag = bin_to_key_tag(Bin),
{Key, KeyTag}.
-spec decode_text(binary()) -> [binary()].
decode_text(<<>>) ->
[];
decode_text(<<Len, String:Len/binary, Rest/binary>>) ->
[String | decode_text(Rest)].
-spec decode_string(nonempty_binary()) -> {binary(), binary()}.
decode_string(<<Len, Bin:Len/binary, Rest/binary>>) ->
{Rest, Bin}.
-spec bin_to_key_tag(<<_:32, _:_*8>>) -> dns:uint16().
bin_to_key_tag(Binary) when is_binary(Binary) ->
do_bin_to_key_tag(Binary, 0).
-spec do_bin_to_key_tag(binary(), non_neg_integer()) -> dns:uint16().
do_bin_to_key_tag(<<>>, AC) ->
(AC + ((AC bsr 16) band 16#FFFF)) band 16#FFFF;
do_bin_to_key_tag(<<A:16, B:16, C:16, D:16, Rest/binary>>, AC) ->
do_bin_to_key_tag(Rest, AC + A + B + C + D);
do_bin_to_key_tag(<<X:16, Rest/binary>>, AC) ->
do_bin_to_key_tag(Rest, AC + X);
do_bin_to_key_tag(<<X:8>>, AC) ->
do_bin_to_key_tag(<<>>, AC + (X bsl 8)).
%% Values below the reference point are south/west, and decode to negative offsets.
-spec decode_loc_point(dns:uint32()) -> integer().
decode_loc_point(P) when is_integer(P) ->
P - ?LOC_REFERENCE_POINT.
-spec decode_nsec_types(binary()) -> [non_neg_integer()].
decode_nsec_types(Bin) ->
do_decode_nsec_types(Bin, -1, []).
%% RFC4034§4.1.2: window blocks come in increasing order; a repeat let the same
%% type be counted twice, decoding to a types list with a duplicate in it.
-spec do_decode_nsec_types(binary(), integer(), [non_neg_integer()]) -> [non_neg_integer()].
do_decode_nsec_types(<<>>, _PrevWindow, Types) ->
lists:reverse(Types);
do_decode_nsec_types(
<<WindowNum:8, BMPLength:8, BMP:BMPLength/binary, Rest/binary>>, PrevWindow, Types
) when PrevWindow < WindowNum ->
BaseNo = WindowNum * 256,
NewTypes = decode_bmp_bytes(BMP, BaseNo, Types),
do_decode_nsec_types(Rest, WindowNum, NewTypes);
do_decode_nsec_types(<<_WindowNum:8, _BMPLength:8, _/binary>>, _PrevWindow, _Types) ->
error(bad_nsec_type_bitmap).
-spec decode_bmp_bytes(binary(), non_neg_integer(), [non_neg_integer()]) ->
[non_neg_integer()].
decode_bmp_bytes(<<>>, _Num, Types) ->
Types;
decode_bmp_bytes(<<0, Rest/binary>>, Num, Types) ->
decode_bmp_bytes(Rest, Num + 8, Types);
decode_bmp_bytes(<<B, Rest/binary>>, Num, Types) ->
decode_bmp_bytes(Rest, Num + 8, decode_bmp_byte(B, Num, Types)).
%% Bits are MSB-first: bit 7 of B is type Num, bit 0 is type Num + 7.
%% Set types are prepended in ascending order; callers reverse once at the end.
-spec decode_bmp_byte(byte(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()].
decode_bmp_byte(B, Num, Acc0) ->
Acc1 = bmp_bit(B band 2#10000000, Num, Acc0),
Acc2 = bmp_bit(B band 2#01000000, Num + 1, Acc1),
Acc3 = bmp_bit(B band 2#00100000, Num + 2, Acc2),
Acc4 = bmp_bit(B band 2#00010000, Num + 3, Acc3),
Acc5 = bmp_bit(B band 2#00001000, Num + 4, Acc4),
Acc6 = bmp_bit(B band 2#00000100, Num + 5, Acc5),
Acc7 = bmp_bit(B band 2#00000010, Num + 6, Acc6),
bmp_bit(B band 2#00000001, Num + 7, Acc7).
-spec bmp_bit(non_neg_integer(), non_neg_integer(), [non_neg_integer()]) -> [non_neg_integer()].
bmp_bit(0, _Num, Acc) -> Acc;
bmp_bit(_, Num, Acc) -> [Num | Acc].
-spec decode_nxt_bmp(binary()) -> [non_neg_integer()].
decode_nxt_bmp(BMP) ->
lists:reverse(decode_bmp_bytes(BMP, 0, [])).
-spec decode_svcb_svc_params(binary()) -> dns:svcb_svc_params().
decode_svcb_svc_params(Bin) ->
dns_svcb_params:from_wire(Bin).
-spec decode_bool(0 | 1) -> boolean().
decode_bool(0) -> false;
decode_bool(1) -> true.
-spec round_pow(non_neg_integer()) -> integer().
round_pow(E) ->
element(
E + 1,
{1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000,
10_000_000_000}
).