packages feed

dnsbase-1.0.0.0: src/Net/DNSBase/RData/SOA.hs

{-|
Module      : Net.DNSBase.RData.SOA
Description : Zone administration records (SOA and RP)
Copyright   : (c) Viktor Dukhovni, 2026
License     : BSD-3-Clause
Maintainer  : ietf-dane@dukhovni.org
Stability   : unstable

Two zone-administration RR types: 'T_soa' is the mandatory
zone-apex record describing zone serial, refresh / retry /
expire / negative-TTL timing, and the responsible operator's
mailbox.  'T_rp' is an optional record pointing at a person
responsible for a name plus a separate TXT record with free-form
contact details.
-}
{-# LANGUAGE RecordWildCards #-}

module Net.DNSBase.RData.SOA
    ( T_soa(..)
    , T_rp(..)
    ) where

import Net.DNSBase.Internal.Util

import Net.DNSBase.Decode.Domain
import Net.DNSBase.Decode.State
import Net.DNSBase.Domain
import Net.DNSBase.Encode.Metric
import Net.DNSBase.Encode.State
import Net.DNSBase.Present
import Net.DNSBase.RData
import Net.DNSBase.RRTYPE

-- | The @SOA@ resource record
-- ([RFC 1035 section 3.3.13](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13))
-- — the mandatory zone-apex record carrying zone serial, refresh,
-- retry, expire, and negative-response TTL alongside the master
-- nameserver and the operator's mailbox.  Returned in negative
-- responses to convey the negative-cache TTL, and in the @AXFR@
-- zone-transfer protocol.
--
-- The mname and rname fields are subject to wire-form name
-- compression on encode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- and canonicalise to lower case
-- ([RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2)).
-- The 'Eq' and 'Ord' instances compare the two domain fields in
-- their canonical wire form (via 'equalWireHost' /
-- 'compareWireHost') and the remaining fields in wire-encoding
-- order, so 'Ord' is canonical: it agrees with lexicographic
-- ordering of the full canonical wire form.
--
-- See 'T_rp' for the responsible-person record.
data T_soa = T_SOA
    { soaMname   :: Domain    -- ^ Master nameserver
    , soaRname   :: Domain    -- ^ Responsible mailbox, local part is first label
    , soaSerial  :: Word32    -- ^ Zone serial number
    , soaRefresh :: Word32    -- ^ Frequency of secondary zone refresh
    , soaRetry   :: Word32    -- ^ AXFR retry interval
    , soaExpire  :: Word32    -- ^ Expiration time of stale secondary data
    , soaMinttl  :: Word32    -- ^ Negative response TTL
    } deriving (Show)

-- | Equality compares the mname and rname fields in canonical
-- wire form, the remaining fields pointwise.
instance Eq T_soa where
    a == b = (soaSerial  a) ==              (soaSerial  b)
          && (soaMname   a) `equalWireHost` (soaMname   b)
          && (soaRname   a) `equalWireHost` (soaRname   b)
          && (soaRefresh a) ==              (soaRefresh b)
          && (soaRetry   a) ==              (soaRetry   b)
          && (soaExpire  a) ==              (soaExpire  b)
          && (soaMinttl  a) ==              (soaMinttl  b)

-- | Canonical: compares the mname and rname fields in canonical
-- wire form and the remaining fields in wire-encoding order, so
-- the result matches lexicographic comparison of the canonical
-- wire form.
instance Ord T_soa where
    a `compare` b = (soaMname   a) `compareWireHost` (soaMname   b)
                 <> (soaRname   a) `compareWireHost` (soaRname   b)
                 <> (soaSerial  a) `compare`         (soaSerial  b)
                 <> (soaRefresh a) `compare`         (soaRefresh b)
                 <> (soaRetry   a) `compare`         (soaRetry   b)
                 <> (soaExpire  a) `compare`         (soaExpire  b)
                 <> (soaMinttl  a) `compare`         (soaMinttl  b)

instance Presentable T_soa where
    present T_SOA{..} =
        present     soaMname
        . presentSp soaRname
        . presentSp soaSerial
        . presentSp soaRefresh
        . presentSp soaRetry
        . presentSp soaExpire
        . presentSp soaMinttl

instance KnownRData T_soa where
    rdType _ = SOA
    {-# INLINE rdType #-}
    rdEncode T_SOA{..}= do
        putDomain soaMname
        putDomain soaRname
        putSizedBuilder $
               mbWord32 soaSerial
            <> mbWord32 soaRefresh
            <> mbWord32 soaRetry
            <> mbWord32 soaExpire
            <> mbWord32 soaMinttl
    cnEncode T_SOA{..} = putSizedBuilder $
           mbWireForm (canonicalise soaMname)
        <> mbWireForm (canonicalise soaRname)
        <> mbWord32 soaSerial
        <> mbWord32 soaRefresh
        <> mbWord32 soaRetry
        <> mbWord32 soaExpire
        <> mbWord32 soaMinttl
    rdDecode _ _ = const do
        soaMname <- getDomain
        soaRname <- getDomain
        soaSerial <- get32
        soaRefresh <- get32
        soaRetry <- get32
        soaExpire <- get32
        soaMinttl <- get32
        return $ RData T_SOA{..}

-- | The @RP@ resource record
-- ([RFC 1183 section 2.2](https://www.rfc-editor.org/rfc/rfc1183.html#section-2.2))
-- — the responsible person for a name: an mbox-dname encoding an
-- email address (the first label is the local part, per the
-- usual DNS mailbox-name convention), and a txt-dname pointing
-- at a 'Net.DNSBase.RData.TXT.T_txt' record with free-form contact details.
--
-- >  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- >  /                   mbox-dname                  /
-- >  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- >  /                   txt-dname                   /
-- >  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- Neither field is wire-form name-compressed on encode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- but compression is tolerated on decode.  Both fields
-- canonicalise to lower case
-- ([RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2),
-- [RFC 6840 section 5.1](https://datatracker.ietf.org/doc/html/rfc6840#section-5.1)).
-- The 'Eq' and 'Ord' instances compare both fields in canonical
-- wire form (via 'equalWireHost' / 'compareWireHost'); since the
-- field order matches the wire encoding, 'Ord' is canonical.
--
-- See 'T_soa' for the mandatory zone-apex record.
data T_rp = T_RP
    { rpMbox :: Domain
    , rpTxt  :: Domain
    } deriving (Show)

instance Eq T_rp where
    a == b = rpMbox a `equalWireHost` rpMbox b
          && rpTxt  a `equalWireHost` rpTxt  b

instance Ord T_rp where
    a `compare` b = rpMbox a `compareWireHost` rpMbox b
                 <> rpTxt  a `compareWireHost` rpTxt  b

instance Presentable T_rp where
    present T_RP{..} = present rpMbox . presentSp rpTxt

instance KnownRData T_rp where
    rdType _ = RP
    rdEncode T_RP{..} = putSizedBuilder $
        mbWireForm rpMbox <> mbWireForm rpTxt
    cnEncode T_RP{..} =
        rdEncode $ T_RP (canonicalise rpMbox)
                        (canonicalise rpTxt)
    rdDecode _ _ = const do
        rpMbox <- getDomain
        rpTxt  <- getDomain
        return $ RData T_RP{..}