-- |
-- Module      : Net.DNSBase.Decode.Internal.RData
-- Description : TBD
-- Copyright   : (c) Viktor Dukhovni, 2026
-- License     : BSD-3-Clause
-- Maintainer  : ietf-dane@dukhovni.org
-- Stability   : unstable
{-# LANGUAGE RecordWildCards #-}

module Net.DNSBase.Decode.Internal.RData
    ( getRData
    , getRR
    , fromOpaqueRData
    ) where

import qualified Data.IntMap as IM
import qualified Data.ByteString as B
import qualified Data.ByteString.Short as SB

import Net.DNSBase.Decode.Internal.Domain
import Net.DNSBase.Decode.Internal.Option
import Net.DNSBase.Decode.Internal.State
import Net.DNSBase.Internal.Domain
import Net.DNSBase.Internal.Error
import Net.DNSBase.Internal.Nat16
import Net.DNSBase.Internal.RData
import Net.DNSBase.Internal.RR
import Net.DNSBase.Internal.RRCLASS
import Net.DNSBase.Internal.RRTYPE
import Net.DNSBase.Internal.Util
import Net.DNSBase.RData.Internal.XNAME
import Net.DNSBase.Resolver.Internal.Types

-- | Performs a lookup operation for the decoder associated with a given RRTYPE
-- (as 'Word16') against the provided 'RDataMap', and runs the appropriate
-- RData decoder (defaulting to 'OpaqueRData' on lookup miss) using the
-- provided length argument.
getRData :: RDataMap        -- ^ Known decoders
         -> Maybe OptionMap -- ^ Known EDNS option decoders
         -> Word16          -- ^ 'Net.DNSBase.RRTYPE.RRTYPE' to decode, as 'Word16'
         -> Int             -- ^ Length of RData in bytes (RDLENGTH)
         -> SGet RData
getRData :: RDataMap -> Maybe OptionMap -> Word16 -> Key -> SGet RData
getRData RDataMap
_ (Just OptionMap
om) (Word16 -> RRTYPE
RRTYPE -> RRTYPE
OPT)   = OptionMap -> Key -> SGet RData
getOPTWith OptionMap
om
getRData RDataMap
_ Maybe OptionMap
Nothing   t :: Word16
t@(Word16 -> RRTYPE
RRTYPE -> RRTYPE
OPT) = Word16 -> Key -> SGet RData
opaqueDecoder Word16
t
getRData RDataMap
dm Maybe OptionMap
_ (RDataMap -> Word16 -> Maybe RDataCodec
dmLookup RDataMap
dm -> Just RDataCodec
dc) = RDataCodec -> Key -> SGet RData
decodeWith RDataCodec
dc
getRData RDataMap
_  Maybe OptionMap
_ Word16
t                        = Word16 -> Key -> SGet RData
opaqueDecoder Word16
t

decodeWith :: RDataCodec -> Int -> SGet RData
decodeWith :: RDataCodec -> Key -> SGet RData
decodeWith (RDataCodec (Proxy a
_ :: proxy a) (RDataExtensionVal a
opts :: RDataExtensionVal a)) =
    (a ~ a) => RDataExtensionVal a -> Key -> SGet RData
RDataExtensionVal a -> Key -> SGet RData
forall b -> (b ~ a) => RDataExtensionVal a -> Key -> SGet RData
forall a.
KnownRData a =>
forall b -> (b ~ a) => RDataExtensionVal a -> Key -> SGet RData
rdDecode a RDataExtensionVal a
opts

-- | Decode unknown RRs as opaque data.  This includes unexpected OPT records
-- in the answer or authority sections.
opaqueDecoder :: Word16 -> Int -> SGet RData
opaqueDecoder :: Word16 -> Key -> SGet RData
opaqueDecoder Word16
rrtype = Word16 -> ShortByteString -> RData
opaqueRData Word16
rrtype (ShortByteString -> RData)
-> (ShortByteString -> ShortByteString) -> ShortByteString -> RData
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShortByteString -> ShortByteString
forall a b. Coercible a b => a -> b
coerce (ShortByteString -> RData)
-> (Key -> SGet ShortByteString) -> Key -> SGet RData
forall (m :: * -> *) b c a.
Functor m =>
(b -> c) -> (a -> m b) -> a -> m c
<.> Key -> SGet ShortByteString
getShortNByteString

-- | Convert 'RData' to its Known equivalent of the same RRtype
-- using the types known in the provided 'ResolvSeed'.  If the input
-- value is already non-opaque, or if there's no entry for the
-- 'Net.DNSBase.RRTYPE.RRTYPE' in the provided 'ResolvSeed', the
-- input will be returned as-is.
--
-- Otherwise, this will attempt to decode the opaque record without name
-- compression, the decode may fail, and an error reason returned instead.
--
fromOpaqueRData :: ResolvSeed -> RData -> Either DNSError RData
fromOpaqueRData :: ResolvSeed -> RData -> Either DNSError RData
fromOpaqueRData ResolvSeed{NonEmpty Nameserver
RDataMap
OptionMap
ResolverConf
seedConfig :: ResolverConf
seedRDataMap :: RDataMap
seedOptionMap :: OptionMap
seedServers :: NonEmpty Nameserver
seedServers :: ResolvSeed -> NonEmpty Nameserver
seedOptionMap :: ResolvSeed -> OptionMap
seedRDataMap :: ResolvSeed -> RDataMap
seedConfig :: ResolvSeed -> ResolverConf
..} rd :: RData
rd@(RData -> RRTYPE
rdataType -> RRTYPE
t) = Word16
-> (forall (n :: Nat) -> Nat16 n => Either DNSError RData)
-> Either DNSError RData
forall r. Word16 -> (forall (n :: Nat) -> Nat16 n => r) -> r
withNat16 (RRTYPE -> Word16
forall a b. Coercible a b => a -> b
coerce RRTYPE
t) forall (n :: Nat) -> Nat16 n => Either DNSError RData
go
  where
    go :: forall (n :: Nat) -> Nat16 n => Either DNSError RData
    go :: forall (n :: Nat) -> Nat16 n => Either DNSError RData
go n | Just RDataCodec
dc <- RDataMap -> Word16 -> Maybe RDataCodec
dmLookup RDataMap
seedRDataMap (Word16 -> Maybe RDataCodec) -> Word16 -> Maybe RDataCodec
forall a b. (a -> b) -> a -> b
$ RRTYPE -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral RRTYPE
t
         , Just (OpaqueRData ShortByteString
d :: OpaqueRData n) <- RData -> Maybe (OpaqueRData n)
forall a. KnownRData a => RData -> Maybe a
fromRData RData
rd
         , ByteString
bs <- ShortByteString -> ByteString
SB.fromShort (ShortByteString -> ShortByteString
forall a b. Coercible a b => a -> b
coerce ShortByteString
d)
         , Key
len <- ByteString -> Key
B.length ByteString
bs
           = Int64 -> Bool -> SGet RData -> ByteString -> Either DNSError RData
forall a.
Int64 -> Bool -> SGet a -> ByteString -> Either DNSError a
decodeAtWith Int64
0 Bool
False (RDataCodec -> Key -> SGet RData
decodeWith RDataCodec
dc Key
len) ByteString
bs
         | Bool
otherwise
           = RData -> Either DNSError RData
forall a b. b -> Either a b
Right RData
rd

-- | Look up type-specific decoder.
dmLookup :: RDataMap -> Word16 -> Maybe RDataCodec
dmLookup :: RDataMap -> Word16 -> Maybe RDataCodec
dmLookup RDataMap
dm Word16
typ = Key -> RDataMap -> Maybe RDataCodec
forall a. Key -> IntMap a -> Maybe a
IM.lookup (Word16 -> Key
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
typ) RDataMap
dm

-- | Decoder for a resource record, shares owner names of consecutive
-- RRs or names of CNAME targets with adjacent RRs for the target
-- (intervening RRSIGs for the CNAME don't reset the target).
getRR :: RDataMap -> Maybe OptionMap -> SGet RR
getRR :: RDataMap -> Maybe OptionMap -> SGet RR
getRR RDataMap
dm Maybe OptionMap
om = do
    owner   <- SGet Domain
getLastOwner
    cname   <- getLastCname
    rrOwner <- setLastOwner =<< dedup owner cname <$> getDomain
    typ     <- get16
    rrClass <- getRRCLASS
    local (setDecodeTriple (DnsTriple rrOwner (coerce typ) rrClass)) do
        rrTTL   <- get32
        len     <- getInt16
        rrData  <- fitSGet len $
                       if | typ == coerce CNAME ->
                            RData . T_CNAME <$> (setLastCname =<< getDomain)
                          | otherwise -> getRData dm om typ len
        return RR{..}
  where
    getRRCLASS :: SGet RRCLASS
getRRCLASS = Word16 -> RRCLASS
RRCLASS (Word16 -> RRCLASS) -> SGet Word16 -> SGet RRCLASS
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SGet Word16
get16
    dedup :: a -> a -> a -> a
dedup a
n1 a
n2 a
name | a
name a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
n1 = a
n1
                     | a
name a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
n2 = a
n2
                     | Bool
otherwise = a
name