{-|
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
    { T_soa -> Domain
soaMname   :: Domain    -- ^ Master nameserver
    , T_soa -> Domain
soaRname   :: Domain    -- ^ Responsible mailbox, local part is first label
    , T_soa -> Word32
soaSerial  :: Word32    -- ^ Zone serial number
    , T_soa -> Word32
soaRefresh :: Word32    -- ^ Frequency of secondary zone refresh
    , T_soa -> Word32
soaRetry   :: Word32    -- ^ AXFR retry interval
    , T_soa -> Word32
soaExpire  :: Word32    -- ^ Expiration time of stale secondary data
    , T_soa -> Word32
soaMinttl  :: Word32    -- ^ Negative response TTL
    } deriving (Int -> T_soa -> ShowS
[T_soa] -> ShowS
T_soa -> String
(Int -> T_soa -> ShowS)
-> (T_soa -> String) -> ([T_soa] -> ShowS) -> Show T_soa
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> T_soa -> ShowS
showsPrec :: Int -> T_soa -> ShowS
$cshow :: T_soa -> String
show :: T_soa -> String
$cshowList :: [T_soa] -> ShowS
showList :: [T_soa] -> ShowS
Show)

-- | Equality compares the mname and rname fields in canonical
-- wire form, the remaining fields pointwise.
instance Eq T_soa where
    T_soa
a == :: T_soa -> T_soa -> Bool
== T_soa
b = (T_soa -> Word32
soaSerial  T_soa
a) Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
==              (T_soa -> Word32
soaSerial  T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Domain
soaMname   T_soa
a) Domain -> Domain -> Bool
`equalWireHost` (T_soa -> Domain
soaMname   T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Domain
soaRname   T_soa
a) Domain -> Domain -> Bool
`equalWireHost` (T_soa -> Domain
soaRname   T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Word32
soaRefresh T_soa
a) Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
==              (T_soa -> Word32
soaRefresh T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Word32
soaRetry   T_soa
a) Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
==              (T_soa -> Word32
soaRetry   T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Word32
soaExpire  T_soa
a) Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
==              (T_soa -> Word32
soaExpire  T_soa
b)
          Bool -> Bool -> Bool
&& (T_soa -> Word32
soaMinttl  T_soa
a) Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
==              (T_soa -> Word32
soaMinttl  T_soa
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
    T_soa
a compare :: T_soa -> T_soa -> Ordering
`compare` T_soa
b = (T_soa -> Domain
soaMname   T_soa
a) Domain -> Domain -> Ordering
`compareWireHost` (T_soa -> Domain
soaMname   T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Domain
soaRname   T_soa
a) Domain -> Domain -> Ordering
`compareWireHost` (T_soa -> Domain
soaRname   T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Word32
soaSerial  T_soa
a) Word32 -> Word32 -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare`         (T_soa -> Word32
soaSerial  T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Word32
soaRefresh T_soa
a) Word32 -> Word32 -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare`         (T_soa -> Word32
soaRefresh T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Word32
soaRetry   T_soa
a) Word32 -> Word32 -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare`         (T_soa -> Word32
soaRetry   T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Word32
soaExpire  T_soa
a) Word32 -> Word32 -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare`         (T_soa -> Word32
soaExpire  T_soa
b)
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> (T_soa -> Word32
soaMinttl  T_soa
a) Word32 -> Word32 -> Ordering
forall a. Ord a => a -> a -> Ordering
`compare`         (T_soa -> Word32
soaMinttl  T_soa
b)

instance Presentable T_soa where
    present :: T_soa -> Builder -> Builder
present T_SOA{Word32
Domain
soaMname :: T_soa -> Domain
soaRname :: T_soa -> Domain
soaSerial :: T_soa -> Word32
soaRefresh :: T_soa -> Word32
soaRetry :: T_soa -> Word32
soaExpire :: T_soa -> Word32
soaMinttl :: T_soa -> Word32
soaMname :: Domain
soaRname :: Domain
soaSerial :: Word32
soaRefresh :: Word32
soaRetry :: Word32
soaExpire :: Word32
soaMinttl :: Word32
..} =
        Domain -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
present     Domain
soaMname
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Domain -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Domain
soaRname
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Word32
soaSerial
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Word32
soaRefresh
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Word32
soaRetry
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Word32
soaExpire
        (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Word32
soaMinttl

instance KnownRData T_soa where
    rdType :: forall b -> (b ~ T_soa) => RRTYPE
rdType _ = RRTYPE
SOA
    {-# INLINE rdType #-}
    rdEncode :: forall s. T_soa -> SPut s RData
rdEncode T_SOA{Word32
Domain
soaMname :: T_soa -> Domain
soaRname :: T_soa -> Domain
soaSerial :: T_soa -> Word32
soaRefresh :: T_soa -> Word32
soaRetry :: T_soa -> Word32
soaExpire :: T_soa -> Word32
soaMinttl :: T_soa -> Word32
soaMname :: Domain
soaRname :: Domain
soaSerial :: Word32
soaRefresh :: Word32
soaRetry :: Word32
soaExpire :: Word32
soaMinttl :: Word32
..}= do
        Domain -> SPut s RData
forall r s. ErrorContext r => Domain -> SPut s r
putDomain Domain
soaMname
        Domain -> SPut s RData
forall r s. ErrorContext r => Domain -> SPut s r
putDomain Domain
soaRname
        SizedBuilder -> SPut s RData
forall r s. ErrorContext r => SizedBuilder -> SPut s r
putSizedBuilder (SizedBuilder -> SPut s RData) -> SizedBuilder -> SPut s RData
forall a b. (a -> b) -> a -> b
$
               Word32 -> SizedBuilder
mbWord32 Word32
soaSerial
            SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaRefresh
            SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaRetry
            SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaExpire
            SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaMinttl
    cnEncode :: forall s. T_soa -> SPut s RData
cnEncode T_SOA{Word32
Domain
soaMname :: T_soa -> Domain
soaRname :: T_soa -> Domain
soaSerial :: T_soa -> Word32
soaRefresh :: T_soa -> Word32
soaRetry :: T_soa -> Word32
soaExpire :: T_soa -> Word32
soaMinttl :: T_soa -> Word32
soaMname :: Domain
soaRname :: Domain
soaSerial :: Word32
soaRefresh :: Word32
soaRetry :: Word32
soaExpire :: Word32
soaMinttl :: Word32
..} = SizedBuilder -> SPut s RData
forall r s. ErrorContext r => SizedBuilder -> SPut s r
putSizedBuilder (SizedBuilder -> SPut s RData) -> SizedBuilder -> SPut s RData
forall a b. (a -> b) -> a -> b
$
           Domain -> SizedBuilder
mbWireForm (Domain -> Domain
canonicalise Domain
soaMname)
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Domain -> SizedBuilder
mbWireForm (Domain -> Domain
canonicalise Domain
soaRname)
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaSerial
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaRefresh
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaRetry
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaExpire
        SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Word32 -> SizedBuilder
mbWord32 Word32
soaMinttl
    rdDecode :: forall b ->
(b ~ T_soa) => RDataExtensionVal T_soa -> Int -> SGet RData
rdDecode _ RDataExtensionVal T_soa
_ = SGet RData -> Int -> SGet RData
forall a b. a -> b -> a
const do
        soaMname <- SGet Domain
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
    { T_rp -> Domain
rpMbox :: Domain
    , T_rp -> Domain
rpTxt  :: Domain
    } deriving (Int -> T_rp -> ShowS
[T_rp] -> ShowS
T_rp -> String
(Int -> T_rp -> ShowS)
-> (T_rp -> String) -> ([T_rp] -> ShowS) -> Show T_rp
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> T_rp -> ShowS
showsPrec :: Int -> T_rp -> ShowS
$cshow :: T_rp -> String
show :: T_rp -> String
$cshowList :: [T_rp] -> ShowS
showList :: [T_rp] -> ShowS
Show)

instance Eq T_rp where
    T_rp
a == :: T_rp -> T_rp -> Bool
== T_rp
b = T_rp -> Domain
rpMbox T_rp
a Domain -> Domain -> Bool
`equalWireHost` T_rp -> Domain
rpMbox T_rp
b
          Bool -> Bool -> Bool
&& T_rp -> Domain
rpTxt  T_rp
a Domain -> Domain -> Bool
`equalWireHost` T_rp -> Domain
rpTxt  T_rp
b

instance Ord T_rp where
    T_rp
a compare :: T_rp -> T_rp -> Ordering
`compare` T_rp
b = T_rp -> Domain
rpMbox T_rp
a Domain -> Domain -> Ordering
`compareWireHost` T_rp -> Domain
rpMbox T_rp
b
                 Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> T_rp -> Domain
rpTxt  T_rp
a Domain -> Domain -> Ordering
`compareWireHost` T_rp -> Domain
rpTxt  T_rp
b

instance Presentable T_rp where
    present :: T_rp -> Builder -> Builder
present T_RP{Domain
rpMbox :: T_rp -> Domain
rpTxt :: T_rp -> Domain
rpMbox :: Domain
rpTxt :: Domain
..} = Domain -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
present Domain
rpMbox (Builder -> Builder) -> (Builder -> Builder) -> Builder -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Domain -> Builder -> Builder
forall a. Presentable a => a -> Builder -> Builder
presentSp Domain
rpTxt

instance KnownRData T_rp where
    rdType :: forall b -> (b ~ T_rp) => RRTYPE
rdType _ = RRTYPE
RP
    rdEncode :: forall s. T_rp -> SPut s RData
rdEncode T_RP{Domain
rpMbox :: T_rp -> Domain
rpTxt :: T_rp -> Domain
rpMbox :: Domain
rpTxt :: Domain
..} = SizedBuilder -> SPut s RData
forall r s. ErrorContext r => SizedBuilder -> SPut s r
putSizedBuilder (SizedBuilder -> SPut s RData) -> SizedBuilder -> SPut s RData
forall a b. (a -> b) -> a -> b
$
        Domain -> SizedBuilder
mbWireForm Domain
rpMbox SizedBuilder -> SizedBuilder -> SizedBuilder
forall a. Semigroup a => a -> a -> a
<> Domain -> SizedBuilder
mbWireForm Domain
rpTxt
    cnEncode :: forall s. T_rp -> SPut s RData
cnEncode T_RP{Domain
rpMbox :: T_rp -> Domain
rpTxt :: T_rp -> Domain
rpMbox :: Domain
rpTxt :: Domain
..} =
        T_rp -> SPut s RData
forall a s. KnownRData a => a -> SPut s RData
forall s. T_rp -> SPut s RData
rdEncode (T_rp -> SPut s RData) -> T_rp -> SPut s RData
forall a b. (a -> b) -> a -> b
$ Domain -> Domain -> T_rp
T_RP (Domain -> Domain
canonicalise Domain
rpMbox)
                        (Domain -> Domain
canonicalise Domain
rpTxt)
    rdDecode :: forall b ->
(b ~ T_rp) => RDataExtensionVal T_rp -> Int -> SGet RData
rdDecode _ RDataExtensionVal T_rp
_ = SGet RData -> Int -> SGet RData
forall a b. a -> b -> a
const do
        rpMbox <- SGet Domain
getDomain
        rpTxt  <- getDomain
        return $ RData T_RP{..}