-module(dns_domain). -moduledoc """ Domain name processing module providing operations for converting between text representation, label lists, and DNS wire format. This module provides strictly reversible domain name operations for use in DNS message encoding and decoding. """. -export([split/1, join/1, join/2]). -export([from_wire/1, from_wire/2]). -export([to_wire/1, to_wire/3]). -export([to_lower/1, to_upper/1]). -export([are_equal/2, are_equal_labels/2]). -export([escape_label/1, unescape_label/1]). -doc ~'Text representation of domain name: "www.example.com".'. -type dname() :: binary(). -doc ~'Single label: "www".'. -type label() :: binary(). -doc "List of labels.". -type labels() :: [label()]. -doc "Wire format binary.". -type wire() :: binary(). -doc "Compression map: maps label sequences to positions.". -type compmap() :: #{labels() => non_neg_integer()}. -export_type([compmap/0]). -doc "Encode error types.". -type encode_error() :: {error, label_too_long, label()} | {error, name_too_long, dname()}. -doc "Decode error types.". -type decode_error() :: {error, truncated} | {error, invalid_label_length, non_neg_integer()} | {error, bad_pointer, non_neg_integer()}. -export_type([dname/0, label/0, labels/0, wire/0, encode_error/0, decode_error/0]). -doc """ Split domain name into labels. Converts a domain name string into a list of labels. Handles escaped dots and backslashes, removing escape sequences from the resulting labels. Returns an empty list for empty names or root (single dot). Raises `{invalid_dname, empty_label}` if the name contains contiguous dots. ## Examples: ```erlang 1> dns_domain:split(~"www.example.com"). [~"www", ~"example", ~"com"] 2> dns_domain:split(~"example.com."). [~"example", ~"com"] 3> dns_domain:split(~"test\.label.com"). [~"test.label", ~"com"] 4> dns_domain:split(<<>>). [] 5> dns_domain:split(~"example..com"). ** exception error: {invalid_dname, empty_label} ``` """. -spec split(dname()) -> labels(). split(Name) when is_binary(Name) -> do_split(Name, <<>>). -spec do_split(binary(), binary()) -> labels(). %% End of input - return empty list if no label accumulated, otherwise return label do_split(<<>>, <<>>) -> []; do_split(<<>>, Label) -> [Label]; do_split(<<$.>>, <<>>) -> []; do_split(<<$.>>, Label) -> [Label]; %% Match 8 bytes at once when all are safe (common case - no dots, no backslashes) do_split(<>, Label) when A =/= $., A =/= $\\, B =/= $., B =/= $\\, C =/= $., C =/= $\\, D =/= $., D =/= $\\, E =/= $., E =/= $\\, F =/= $., F =/= $\\, G =/= $., G =/= $\\, H =/= $., H =/= $\\ -> do_split(Rest, <