packages feed

dnsbase-1.0.2.0: internal/Net/DNSBase/RData/Internal/XNAME.hs

-- |
-- Module      : Net.DNSBase.RData.Internal.XNAME
-- Description : Internal: shared codec for domain-valued RR types
-- Copyright   : (c) Viktor Dukhovni, 2026
-- License     : BSD-3-Clause
-- Maintainer  : ietf-dane@dukhovni.org
-- Stability   : unstable
--
-- The 'X_domain' newtype represents the RFC 1035 RR types whose
-- RDATA is a single domain name: 'T_ns', 'T_cname', 'T_ptr' and
-- the obsolete mailbox-pointer types 'T_md', 'T_mf', 'T_mb',
-- 'T_mg', 'T_mr'.  'T_dname' (RFC 6672) lives here too because it
-- has the same shape — a single 'Domain' — but it gets its own
-- newtype because its wire-form codec differs (no name
-- compression on encode).
--
-- The public API is in "Net.DNSBase.RData.XNAME" for the
-- non-obsolete subset and in "Net.DNSBase.RData.Obsolete" for the
-- old mailbox-pointer types.
{-# LANGUAGE
    MagicHash
  , UndecidableInstances
  #-}

module Net.DNSBase.RData.Internal.XNAME
    ( -- * Domain-name-valued RR types.
      -- ** Well-known (from RFC1035)
      X_domain(T_NS, T_CNAME, T_PTR, T_MB, T_MD, T_MF, T_MG, T_MR)
    , T_ns, T_cname, T_ptr, T_mb, T_md, T_mf, T_mg, T_mr
    , type XdomainConName
      -- ** @DNAME@
    , T_dname(..)
    ) where

import GHC.Exts (proxy#)
import GHC.TypeLits as TL (TypeError, ErrorMessage(..))
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal')

import Net.DNSBase.Decode.Internal.Domain
import Net.DNSBase.Encode.Internal.State
import Net.DNSBase.Internal.Domain
import Net.DNSBase.Internal.Nat16
import Net.DNSBase.Internal.Present
import Net.DNSBase.Internal.RData
import Net.DNSBase.Internal.RRTYPE
import Net.DNSBase.Internal.Util

type XdomainConName :: Nat -> Symbol
type family XdomainConName n where
    XdomainConName N_ns      = "T_NS"
    XdomainConName N_cname   = "T_CNAME"
    XdomainConName N_ptr     = "T_PTR"
    XdomainConName N_md      = "T_MD"
    XdomainConName N_mf      = "T_MF"
    XdomainConName N_mb      = "T_MB"
    XdomainConName N_mg      = "T_MG"
    XdomainConName N_mr      = "T_MR"
    XdomainConName n         = TypeError
                             ( ShowType n
                               :<>: TL.Text " is not an RFC1035 domain-valued RRTYPE" )

-- | X_domain specialised to @NS@ records.
type T_ns      = X_domain N_ns
-- | X_domain specialised to @CNAME@ records.
type T_cname   = X_domain N_cname
-- | X_domain specialised to @PTR@ records.
type T_ptr     = X_domain N_ptr
-- | X_domain specialised to @MD@ records.
type T_md      = X_domain N_md
-- | X_domain specialised to @MF@ records.
type T_mf      = X_domain N_mf
-- | X_domain specialised to @MB@ records.
type T_mb      = X_domain N_mb
-- | X_domain specialised to @MG@ records.
type T_mg      = X_domain N_mg
-- | X_domain specialised to @MR@ records.
type T_mr      = X_domain N_mr

-- | Authoritative name server for a delegated zone
-- ([RFC 1035 section 3.3.11](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.11)).
pattern  T_NS :: Domain -> T_ns
pattern  T_NS d = (X_DOMAIN d :: T_ns)
{-# COMPLETE T_NS #-}
-- | Canonical-name alias for the owner name
-- ([RFC 1035 section 3.3.1](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.1)).
pattern  T_CNAME :: Domain -> T_cname
pattern  T_CNAME d = (X_DOMAIN d :: T_cname)
{-# COMPLETE T_CNAME #-}
-- | Domain-name pointer, typically used for reverse mapping
-- ([RFC 1035 section 3.3.12](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.12)).
pattern  T_PTR :: Domain -> T_ptr
pattern  T_PTR d = (X_DOMAIN d :: T_ptr)
{-# COMPLETE T_PTR #-}
-- | Mail destination
-- ([RFC 1035 section 3.3.4](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.4);
-- obsolete — use 'Net.DNSBase.RData.SRV.T_mx').
pattern  T_MD :: Domain -> T_md
pattern  T_MD d = (X_DOMAIN d :: T_md)
{-# COMPLETE T_MD #-}
-- | Mail forwarder
-- ([RFC 1035 section 3.3.5](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.5);
-- obsolete — use 'Net.DNSBase.RData.SRV.T_mx').
pattern  T_MF :: Domain -> T_mf
pattern  T_MF d = (X_DOMAIN d :: T_mf)
{-# COMPLETE T_MF #-}
-- | Mailbox domain
-- ([RFC 1035 section 3.3.3](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.3);
-- obsolete).
pattern  T_MB :: Domain -> T_mb
pattern  T_MB d = (X_DOMAIN d :: T_mb)
{-# COMPLETE T_MB #-}
-- | Mail group member
-- ([RFC 1035 section 3.3.6](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.6);
-- obsolete).
pattern  T_MG :: Domain -> T_mg
pattern  T_MG d = (X_DOMAIN d :: T_mg)
{-# COMPLETE T_MG #-}
-- | Mail rename
-- ([RFC 1035 section 3.3.8](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.8);
-- obsolete).
pattern  T_MR :: Domain -> T_mr
pattern  T_MR d = (X_DOMAIN d :: T_mr)
{-# COMPLETE T_MR #-}

-- | Shared wire-format representation for the RFC 1035 RR types
-- whose RDATA is a single domain name: @NS@
-- ([section 3.3.11](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.11)),
-- @CNAME@
-- ([section 3.3.1](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.1)),
-- @PTR@
-- ([section 3.3.12](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.12)),
-- and the obsolete mailbox-pointer types @MB@, @MD@, @MF@, @MG@,
-- @MR@ (RFC 1035 sections 3.3.3-3.3.8).  The type parameter @n@
-- (one of 'N_ns', 'N_cname', 'N_ptr', 'N_mb', 'N_md', 'N_mf',
-- 'N_mg', 'N_mr') determines the RR type.  Each has its own type
-- synonym ('T_ns', 'T_cname', ...) and matching pattern synonym
-- ('T_NS', 'T_CNAME', ...).
--
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > /                 DOMAINNAME                    /
-- > /                                               /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- Although all these RR types share a common underlying
-- representation, the constructors are not shared and the types
-- are not mutually coercible — this is deliberate, to catch
-- RR-type confusion at compile time.
--
-- The target domain is subject to wire-form name compression on
-- encode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- and canonicalises to lower case
-- ([RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2)).
-- The 'Eq' and 'Ord' instances compare in canonical wire form
-- (via 'equalWireHost' / 'compareWireHost'), so 'Ord' is
-- canonical.  Presentation preserves the original case.
type X_domain :: Nat -> Type
type role X_domain nominal
newtype X_domain n = X_DOMAIN Domain

instance (Nat16 n, KnownSymbol (XdomainConName n)) => Show (X_domain n) where
    showsPrec p (X_DOMAIN d) = showsP p $
        showString (symbolVal' (proxy# @(XdomainConName n))) . showChar ' '
        . shows' d

-- | Case-insensitive wire-form equality.
instance Eq (X_domain f) where
    a == b = coerce a `equalWireHost` coerce b

-- | Case-insensitive wire-form order.
instance Ord (X_domain f) where
    a `compare` b = coerce a `compareWireHost` coerce b

-- | Presentation form preserves case.
instance Presentable (X_domain f) where
    present = present @Domain . coerce

-- | Name compression used on input and output.
instance (Typeable n, Nat16 n, KnownSymbol (XdomainConName n))
    => KnownRData (X_domain n) where
    rdType _ = RRTYPE $ natToWord16 n
    {-# INLINE rdType #-}
    rdEncode = putDomain . coerce
    cnEncode = putSizedBuilder . mbWireForm . canonicalise . coerce
    rdDecode _ _ = const do
        RData . X_DOMAIN @n <$> getDomain

-- | The @DNAME@ resource record
-- ([RFC 6672 section 2.1](https://tools.ietf.org/html/rfc6672#section-2.1))
-- — redirection for a subtree of the domain-name space: a 'Domain'
-- naming the target subtree under which queries are rewritten.
--
-- The target field is not subject to wire-form name compression
-- on encode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- but canonicalises to lower case
-- ([RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2)).
-- The 'Eq' and 'Ord' instances compare in canonical wire form
-- (via 'equalWireHost' / 'compareWireHost'), so 'Ord' is
-- canonical.  Presentation preserves the original case.
--
-- See 'X_domain' for the sibling family of RFC 1035 single-domain
-- RR types, which differ from @DNAME@ in that they do use name
-- compression on encode.
newtype T_dname = T_DNAME Domain -- ^ Target 'Domain'
    deriving (Show)

-- | Case-insensitive wire-form equality.
instance Eq T_dname where
    a == b = coerce a `equalWireHost` coerce b

-- | Case-insensitive wire-form order.
instance Ord T_dname where
    a `compare` b = coerce a `compareWireHost` coerce b

-- | Presentation form preserves case.
instance Presentable T_dname where
    present = present @Domain . coerce

-- | Name compression used on input only.
instance KnownRData T_dname where
    rdType _ = DNAME
    {-# INLINE rdType #-}
    rdEncode = putSizedBuilder . mbWireForm . coerce
    cnEncode = putSizedBuilder . mbWireForm . canonicalise . coerce
    rdDecode _ _ = const do
        RData . T_DNAME <$> getDomainNC