dnsbase-1.0.0.0: src/Net/DNSBase/RData/Obsolete.hs
{-|
Module : Net.DNSBase.RData.Obsolete
Description : Obsolete RR types retained for wire-form parsing
Copyright : (c) Viktor Dukhovni, 2026
License : BSD-3-Clause
Maintainer : ietf-dane@dukhovni.org
Stability : unstable
A grab-bag of RR types that are no longer used in current zone
data but appear in historic records and zone-data archives.
They are defined here so wire-form parsers can read stray
examples without failing. No new deployment should use any of
these types.
The module gathers four loose groups:
* Early host-name and mailbox pointers: 'T_md', 'T_mf', 'T_mb',
'T_mg', 'T_mr' (the shared codec 'X_domain' is re-exported
here).
* RFC 1035 and RFC 1183 records that never saw wide use:
'T_minfo', 'T_x25', 'T_isdn', 'T_rt'.
* OSI / X.400-mapping records, all deprecated by RFC 9121:
'T_nsap', 'T_nsapptr', 'T_px'.
* Other one-offs: 'T_gpos' (early geographic location,
superseded by 'LOC'), 'T_kx' (Key Exchange), 'T_a6'
(chained IPv6 addressing; obsoleted by RFC 6563).
'T_wks' (Well-Known Services) is re-exported here for
convenience; its definition lives in "Net.DNSBase.RData.WKS".
The pre-RFC-4034 records in this module lower-case their
embedded domains in canonical form; 'T_nsapptr' is the
exception (its derived instances compare the wire bytes
case-sensitively).
-}
{-# LANGUAGE RecordWildCards #-}
module Net.DNSBase.RData.Obsolete
( -- * Obsolete RR types
-- ** Obsolete RR types representing a host name or mailbox.
X_domain(T_MD, T_MF, T_MB, T_MG, T_MR)
, T_md
, T_mf
, T_mb
, T_mg
, T_mr
-- ** Other obsolete RR types.
, T_wks(..)
, WksProto(..)
, T_minfo(..)
, T_x25(..)
, T_isdn(..)
, T_rt(..)
, T_nsap(..)
, T_nsapptr(..)
, T_px(..)
, T_gpos(..)
, T_kx(..)
, T_a6(T_A6)
) where
import qualified Data.ByteString.Short as SB
import Net.DNSBase.Internal.Util
import Net.DNSBase.RData.Internal.XNAME
import Net.DNSBase.Bytes
import Net.DNSBase.Decode.Domain
import Net.DNSBase.Decode.State
import Net.DNSBase.Domain
import Net.DNSBase.Encode.State
import Net.DNSBase.Present
import Net.DNSBase.RData
import Net.DNSBase.RData.WKS
import Net.DNSBase.RRTYPE
import Net.DNSBase.Text
-- | The @MINFO@ resource record
-- ([RFC 1035 section 3.3.7](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.7))
-- — mailing-list request and owner addresses: two 'Domain'
-- fields, the request mailbox and the owner (bounce) mailbox.
--
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / RMAILBX /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / EMAILBX /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- Both 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 both domain fields in
-- canonical wire form (via 'equalWireHost' / 'compareWireHost'),
-- so 'Ord' is canonical.
data T_minfo = T_MINFO
{ minfoRmailbx :: Domain -- ^ Request address
, minfoEmailbx :: Domain -- ^ Owner (bounce) address
} deriving (Show)
-- | Case-insensitive wire-form equality.
instance Eq T_minfo where
a == b = (minfoRmailbx a) `equalWireHost` (minfoRmailbx b)
&& (minfoEmailbx a) `equalWireHost` (minfoEmailbx b)
-- | Case-insensitive wire-form order.
instance Ord T_minfo where
a `compare` b = (minfoRmailbx a) `compareWireHost` (minfoRmailbx b)
<> (minfoEmailbx a) `compareWireHost` (minfoEmailbx b)
instance Presentable T_minfo where
present T_MINFO{..} =
present minfoRmailbx
. presentSp minfoEmailbx
instance KnownRData T_minfo where
rdType _ = MINFO
{-# INLINE rdType #-}
rdEncode T_MINFO{..} = do
-- Subject to name compression.
putDomain minfoRmailbx
putDomain minfoEmailbx
cnEncode T_MINFO{..} = putSizedBuilder $
mbWireForm (canonicalise minfoRmailbx)
<> mbWireForm (canonicalise minfoEmailbx)
rdDecode _ _ = const do
-- Subject to name compression.
minfoRmailbx <- getDomain
minfoEmailbx <- getDomain
return $ RData $ T_MINFO{..}
-- | The @X25@ resource record
-- ([RFC 1183 section 3.1](https://www.rfc-editor.org/rfc/rfc1183.html#section-3.1))
-- — an X.25 PSDN address held as a single DNS
-- /character-string/. RFC 1183 calls for at least four ASCII
-- digits, but the constructor does not enforce that — callers may
-- store any byte sequence that fits in a character-string (up to
-- 255 bytes).
--
-- The 'Ord' instance compares the payload as a DNS
-- character-string (length-prefixed lexicographic), so it agrees
-- with the canonical wire-form ordering of
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2).
newtype T_x25 = T_X25 SB.ShortByteString
deriving (Eq, Show)
instance Ord T_x25 where
(T_X25 a) `compare` (T_X25 b) = comparing DnsText a b
instance Presentable T_x25 where
present (T_X25 str) = present @DnsText (coerce str)
instance KnownRData T_x25 where
rdType _ = X25
{-# INLINE rdType #-}
rdEncode = putShortByteStringLen8 . coerce
rdDecode _ _ = const $ RData . T_X25 <$> getShortByteStringLen8
-- | The @ISDN@ resource record
-- ([RFC 1183 section 3.2](https://www.rfc-editor.org/rfc/rfc1183.html#section-3.2))
-- — the ISDN number of the owner host, with an optional
-- Direct-Dial-In subaddress. Both fields are DNS
-- /character-strings/; only the address is mandatory.
--
-- The 'Ord' instance compares both fields as DNS
-- character-strings. With the trailing DDI absent, the wire form
-- is shorter than any encoding with a present DDI, so 'Ord' agrees
-- with the canonical wire-form ordering of
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2).
data T_isdn = T_ISDN SB.ShortByteString (Maybe SB.ShortByteString)
deriving (Eq, Show)
instance Ord T_isdn where
(T_ISDN aa ad) `compare` (T_ISDN ba bd) = aa `strCompare` ba
<> ad `mstrCompare` bd
where
strCompare = comparing DnsText
mstrCompare = comparing (fmap DnsText)
instance Presentable T_isdn where
present (T_ISDN address ddi) =
present @DnsText (coerce address)
. maybe id (presentSp @DnsText . coerce) ddi
instance KnownRData T_isdn where
rdType _ = ISDN
{-# INLINE rdType #-}
rdEncode (T_ISDN address ddi) = do
putShortByteStringLen8 address
mapM_ putShortByteStringLen8 ddi
rdDecode _ _ len = do
pos0 <- getPosition
address <- getShortByteStringLen8
used <- subtract pos0 <$> getPosition
ddi <- if | used == len -> pure Nothing
| otherwise -> Just <$> getShortByteStringLen8
pure $ RData $ T_ISDN address ddi
-- | The @RT@ resource record
-- ([RFC 1183 section 3.3](https://www.rfc-editor.org/rfc/rfc1183.html#section-3.3))
-- — Route-Through: a 16-bit preference and a 'Domain' naming an
-- intermediate host that will route packets to the owner. Used
-- in the X.25 and ISDN era for hosts without their own wide-area
-- addresses.
--
-- The route-through domain is not subject to wire-form name
-- compression on encode but compression is tolerated on decode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4)).
-- It 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 the domain in canonical
-- wire form (via 'equalWireHost' / 'compareWireHost'), so 'Ord'
-- is canonical.
data T_rt = T_RT Word16 Domain
deriving (Show)
instance Eq T_rt where
(T_RT pa ra) == (T_RT pb rb) = pa == pb && ra `equalWireHost` rb
instance Ord T_rt where
(T_RT pa ra) `compare` (T_RT pb rb) =
pa `compare` pb
<> ra `compareWireHost` rb
instance Presentable T_rt where
present (T_RT pref router) = present pref . presentSp router
instance KnownRData T_rt where
rdType _ = RT
{-# INLINE rdType #-}
rdEncode (T_RT pref router) = putSizedBuilder $
mbWord16 pref <> mbWireForm router
cnEncode (T_RT pref router) =
rdEncode $ T_RT pref (canonicalise router)
rdDecode _ _ = const do
pref <- get16
router <- getDomain
pure $ RData $ T_RT pref router
-- | The @NSAP@ resource record
-- ([RFC 1706 section 5](https://www.rfc-editor.org/rfc/rfc1706.html#section-5);
-- deprecated by [RFC 9121](https://www.rfc-editor.org/rfc/rfc9121))
-- — mapped a domain name to an OSI Network Service Access Point
-- address. An opaque byte string carrying the NSAP value
-- verbatim; presented in zone-file syntax as a @0x@-prefixed hex
-- literal.
--
-- Derived 'Ord' compares the raw bytes, which matches the
-- canonical wire-form ordering of
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2).
newtype T_nsap = T_NSAP SB.ShortByteString
deriving (Eq, Ord)
instance Show T_nsap where
showsPrec p a = showString "0x" . showsPrec @Bytes16 p (coerce a)
instance Presentable T_nsap where
present a = present @String "0x" . present @Bytes16 (coerce a)
instance KnownRData T_nsap where
rdType _ = NSAP
{-# INLINE rdType #-}
rdEncode = putSizedBuilder . mbShortByteString . coerce
rdDecode _ _ = RData . T_NSAP <.> getShortNByteString
-- | The @NSAPPTR@ resource record
-- ([RFC 1348](https://www.rfc-editor.org/rfc/rfc1348#page-2);
-- obsoleted by @PTR@ in
-- [RFC 1706 section 6](https://www.rfc-editor.org/rfc/rfc1706.html#section-6),
-- deprecated by [RFC 9121](https://www.rfc-editor.org/rfc/rfc9121))
-- — the OSI counterpart of @PTR@, mapping an NSAP-derived owner
-- name to a domain name.
--
-- The target domain is not subject to wire-form name compression
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4))
-- and is /not/ in the
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2)
-- list of types that lower-case their RDATA names. Derived 'Eq'
-- and 'Ord' therefore compare the wire bytes verbatim
-- (case-sensitively).
newtype T_nsapptr = T_NSAPPTR Domain -- ^ Target 'Domain'
deriving (Eq, Ord, Show)
instance Presentable T_nsapptr where
present = present @Domain . coerce
instance KnownRData T_nsapptr where
rdType _ = NSAPPTR
{-# INLINE rdType #-}
rdEncode = putSizedBuilder . mbWireForm . coerce
rdDecode _ _ = const do
RData . T_NSAPPTR <$> getDomainNC
-- | The @PX@ resource record
-- ([RFC 2163 section 4](https://www.rfc-editor.org/rfc/rfc2163.html#section-4);
-- deprecated by [RFC 9121](https://www.rfc-editor.org/rfc/rfc9121))
-- — points at X.400/RFC 822 mapping information: a 16-bit
-- preference and two 'Domain' fields naming the RFC 822 (SMTP)
-- and X.400 sides of the mapping.
--
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > | PREFERENCE |
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / MAP822 /
-- > / /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / MAPX400 /
-- > / /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- Neither domain field is subject to wire-form name compression
-- on encode but compression is tolerated on decode
-- ([RFC 3597 section 4](https://datatracker.ietf.org/doc/html/rfc3597#section-4)).
-- Both 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 both domains in canonical
-- wire form (via 'equalWireHost' / 'compareWireHost'), so 'Ord'
-- is canonical.
data T_px = T_PX
{ pxPref :: Word16
, pxMap822 :: Domain
, pxMapX400 :: Domain
} deriving (Show)
instance Eq T_px where
a == b = pxPref a == pxPref b
&& pxMap822 a `equalWireHost` pxMap822 b
&& pxMapX400 a `equalWireHost` pxMapX400 b
instance Ord T_px where
a `compare` b = pxPref a `compare` pxPref b
<> pxMap822 a `compareWireHost` pxMap822 b
<> pxMapX400 a `compareWireHost` pxMapX400 b
instance Presentable T_px where
present T_PX{..} =
present pxPref
. presentSp pxMap822
. presentSp pxMapX400
instance KnownRData T_px where
rdType _ = PX
{-# INLINE rdType #-}
rdEncode T_PX{..} = putSizedBuilder $
mbWord16 pxPref
<> mbWireForm pxMap822
<> mbWireForm pxMapX400
cnEncode T_PX{..} =
rdEncode $ T_PX pxPref
(canonicalise pxMap822)
(canonicalise pxMapX400)
rdDecode _ _ = const do
pxPref <- get16
pxMap822 <- getDomain
pxMapX400 <- getDomain
pure $ RData T_PX{..}
-- | The @GPOS@ resource record
-- ([RFC 1712 section 3](https://www.rfc-editor.org/rfc/rfc1712.html#section-3))
-- — an early geographical-location record: longitude, latitude,
-- and altitude as three DNS /character-strings/ holding decimal
-- floating-point text. Superseded by @LOC@-style records and not
-- used in modern zone data.
--
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / LONGITUDE /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / LATITUDE /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / ALTITUDE /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- The 'Ord' instance compares the three fields as DNS
-- character-strings, agreeing with the canonical wire-form
-- ordering of
-- [RFC 4034 section 6.2](https://datatracker.ietf.org/doc/html/rfc4034#section-6.2).
data T_gpos = T_GPOS
{ gposLongitude :: SB.ShortByteString
, gposLatitude :: SB.ShortByteString
, gposAltitude :: SB.ShortByteString
} deriving (Eq, Show)
instance Ord T_gpos where
a `compare` b = gposLongitude a `strCompare` gposLongitude b
<> gposLatitude a `strCompare` gposLatitude b
<> gposAltitude a `strCompare` gposAltitude b
where
strCompare = comparing DnsText
instance Presentable T_gpos where
present T_GPOS{..} =
present @DnsText (coerce gposLongitude)
. presentSp @DnsText (coerce gposLatitude)
. presentSp @DnsText (coerce gposAltitude)
instance KnownRData T_gpos where
rdType _ = GPOS
{-# INLINE rdType #-}
rdEncode T_GPOS{..} = putSizedBuilder $
mbShortByteStringLen8 gposLongitude
<> mbShortByteStringLen8 gposLatitude
<> mbShortByteStringLen8 gposAltitude
rdDecode _ _ = const do
gposLongitude <- getShortByteStringLen8
gposLatitude <- getShortByteStringLen8
gposAltitude <- getShortByteStringLen8
pure $ RData T_GPOS{..}
-- | The @KX@ resource record
-- ([RFC 2230 section 3.1](https://www.rfc-editor.org/rfc/rfc2230.html#section-3.1))
-- — names a Key Exchange host for the owner: a 16-bit preference
-- and a 'Domain' naming the exchanger. Defined for the DNSSEC
-- precursor work; never widely deployed.
--
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > | PREFERENCE |
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-- > / EXCHANGER /
-- > / /
-- > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
--
-- The exchanger field is not subject to wire-form name compression
-- ([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 the exchanger in canonical
-- wire form (via 'equalWireHost' / 'compareWireHost'), so 'Ord'
-- is canonical.
data T_kx = T_KX
{ kxPref :: Word16
, kxExch :: Domain
} deriving (Show)
instance Eq T_kx where
a == b = kxPref a == kxPref b
&& kxExch a `equalWireHost` kxExch b
instance Ord T_kx where
a `compare` b = kxPref a `compare` kxPref b
<> kxExch a `compareWireHost` kxExch b
instance Presentable T_kx where
present T_KX{..} = present kxPref . presentSp kxExch
instance KnownRData T_kx where
rdType _ = KX
{-# INLINE rdType #-}
rdEncode T_KX{..} = putSizedBuilder $
mbWord16 kxPref
<> mbWireForm kxExch
cnEncode rd@(T_KX{kxExch = d}) =
rdEncode rd {kxExch = canonicalise d}
rdDecode _ _ = const do
kxPref <- get16
kxExch <- getDomainNC
pure $ RData T_KX{..}
-- | The @A6@ resource record
-- ([RFC 2874 section 3.1](https://www.rfc-editor.org/rfc/rfc2874.html#section-3.1);
-- obsoleted by [RFC 6563](https://www.rfc-editor.org/rfc/rfc6563.html))
-- — an experimental chained IPv6 addressing scheme. Each record
-- carries a prefix length, an address suffix containing the
-- low-order bits, and a 'Domain' naming where to look up the
-- remaining prefix bits. Resolution walked the chain to assemble
-- the full address. The deployment experience reported in RFC
-- 6563 led to A6 being abandoned in favour of plain 'Net.DNSBase.RData.A.T_aaaa'
-- records.
--
-- > +-----------+------------------+-------------------+
-- > |Prefix len.| Address suffix | Prefix name |
-- > | (1 octet) | (0..16 octets) | (0..255 octets) |
-- > +-----------+------------------+-------------------+
--
-- The wire encoding rules are:
--
-- * The prefix length is an unsigned octet between 0 and 128.
-- * The address-suffix field carries exactly enough octets to
-- hold @128 - /prefix-length/@ bits, with up to seven leading
-- pad bits set to zero so the field is an integral number of
-- bytes.
-- * The prefix-name field is a wire-form 'Domain'. It is absent
-- when the prefix length is zero (the suffix already holds the
-- whole address); the suffix field is absent when the prefix
-- length is 128 (the whole address comes from the chain).
-- * The prefix-name is not subject to wire-form name compression
-- ([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 the prefix-name field in
-- canonical wire form (via 'toHost'), so 'Ord' is canonical.
data T_a6 = T_a6
{ a6Prefix :: Word8
, a6Suffix :: IPv6
, a6Domain :: Maybe Domain
} deriving (Show)
instance Eq T_a6 where
a == b = ( a6Prefix a) == ( a6Prefix b)
&& ( a6Suffix a) == ( a6Suffix b)
&& (toHost <$> a6Domain a) == (toHost <$> a6Domain b)
instance Ord T_a6 where
a `compare` b = ( a6Prefix a) `compare` ( a6Prefix b)
<> ( a6Suffix a) `compare` ( a6Suffix b)
<> (toHost <$> a6Domain a) `compare` (toHost <$> a6Domain b)
-- | /Smart constructor/ for @A6@ records:
--
-- - Silently caps the prefix length to 128
-- - Ignores the domain when the prefix length is 0
-- - Otherwise, uses the root domain if no domain is provided
--
pattern T_A6 :: Word8 -> IPv6 -> Maybe Domain -> T_a6
pattern T_A6 prefix suffix domain <- T_a6 prefix suffix domain where
T_A6 prefix suffix domain = T_a6{..}
where
a6Prefix = bool 128 prefix (prefix <= 128)
a6Domain | prefix == 0 = Nothing
| otherwise = Just $! fromMaybe RootDomain domain
a6Suffix | (s0, s1, s2, s3) <- fromIPv6w suffix
= toIPv6w (s0 .&. m0, s1 .&. m1, s2 .&. m2, s3 .&. m3)
where
(m0, m1, m2, m3) = mask128 $ fromIntegral a6Prefix
instance Presentable T_a6 where
present T_a6{..} =
present a6Prefix
. presentSp a6Suffix
. maybe id presentSp a6Domain
instance KnownRData T_a6 where
rdType _ = A6
{-# INLINE rdType #-}
rdEncode T_a6{..} = putSizedBuilder $
mbWord8 a6Prefix
<> mconcat [mbWord8 (fromIntegral w) | w <- bytesFromIPv6]
<> maybe mempty mbWireForm a6Domain
where
npad = fromIntegral a6Prefix `shiftR` 3
bytesFromIPv6 = drop npad $ fromIPv6b a6Suffix
cnEncode rd@(T_a6{a6Domain = Just d}) =
rdEncode rd {a6Domain = Just (canonicalise d)}
cnEncode rd = rdEncode rd
rdDecode _ _ = const do
a6Prefix <- get8
when (a6Prefix > 128) do
failSGet "A6 prefix exceeds 128"
let npad = fromIntegral a6Prefix `shiftR` 3
a6Suffix <- bytesToIPv6 npad <$> getNBytes (16 - npad)
a6Domain <- if | a6Prefix == 0 -> pure Nothing
| otherwise -> Just <$> getDomainNC
pure $ RData T_a6{..}
where
bytesToIPv6 npad = toIPv6b . (replicate npad 0 ++) . map fromIntegral
--------------------
-- | Mask upper @p@ bits of IPv6 address.
mask128 :: Int -> (Word32, Word32, Word32, Word32)
mask128 p | p < 64 = (w0, w1, m32, m32)
| otherwise = (0, 0, w2, w3)
where
m64 = 0xffff_ffff_ffff_ffff :: Word64
m32 = 0xffff_ffff :: Word32
hi = m64 `shiftR` p
lo = m64 `shiftR` (p - 64)
w0 = fromIntegral @Word64 @Word32 (hi `shiftR` 32)
w1 = fromIntegral @Word64 @Word32 hi
w2 = fromIntegral @Word64 @Word32 (lo `shiftR` 32)
w3 = fromIntegral @Word64 @Word32 lo