packages feed

dnsbase-1.0.2.0: tests/extensibility.hs

{-# LANGUAGE
    OverloadedStrings
  , RecordWildCards
  , TypeApplications
  #-}
module Main (main) where

import Control.Monad.Trans.Except (ExceptT(..))
import Test.Tasty
import Test.Tasty.HUnit

import qualified Data.ByteString as B
import Data.ByteString.Short (ShortByteString)
import Data.Word (Word16)

import Net.DNSBase
import Net.DNSBase.Decode.Internal.Option (T_opt(..))
import Net.DNSBase.Decode.Internal.RData (getRData)
import Net.DNSBase.Resolver.Internal.Types (ResolvSeed(..))

----------------------------------------------------------------------
-- A custom RR-data type that overrides the library's built-in 'T_a'
-- at RRTYPE 1: decodes the four wire bytes verbatim into a
-- 'ShortByteString' instead of parsing them as an IPv4 address.

data T_ext_a = T_EXT_A ShortByteString
    deriving (Eq, Ord, Show)

instance Presentable T_ext_a where
    present _ = present @String "EXT_A"

instance KnownRData T_ext_a where
    rdType _ = A
    rdTypePres _ = present @String "EXT_A"
    rdEncode (T_EXT_A bs) = putShortByteString bs
    rdDecode _ _ = const $ RData . T_EXT_A <$> getShortNByteString 4

----------------------------------------------------------------------
-- A custom SVCB SvcParamValue type that overrides the library's
-- built-in @port@ key (codepoint 3): decodes the 16-bit value into
-- 'SPV_EXT_PORT' instead of 'SPV_PORT'.

data SPV_EXT_port = SPV_EXT_PORT Word16
    deriving (Eq, Ord, Show)

instance Presentable SPV_EXT_port where
    present (SPV_EXT_PORT p) = present @String "ext-port=" . present p

instance KnownSVCParamValue SPV_EXT_port where
    spvKey _ = SVCParamKey 3
    encodeSPV (SPV_EXT_PORT p) = put16 p
    decodeSPV _ _ = SVCParamValue . SPV_EXT_PORT <$> get16

----------------------------------------------------------------------

main :: IO ()
main = do
    (s1, s2, s3) <- getSeeds >>= either (fail . show) pure
    defaultMain $ testGroup "Extensibility"
        [ testCase "override built-in RRTYPE A"           (testOverrideRRtype s1)
        , testCase "override built-in SVCB port"          (testOverrideSvcParam s2)
        , testCase "override built-in EDE info-code name" (testOverrideEdeName s3)
        ]
  where
    ede0Name :: (Word16, ShortByteString)
    ede0Name = (0, "Custom Other Error")
    getSeeds = runDNSIO do
        s1 <- ExceptT (makeResolvSeed (registerRRtype T_ext_a defaultResolvConf))
        s2 <- ExceptT (makeResolvSeed (extendRRwithType T_svcb SPV_EXT_port defaultResolvConf))
        s3 <- ExceptT (makeResolvSeed (extendEdnsOptionWithValue O_ede ede0Name defaultResolvConf))
        pure (s1, s2, s3)

testOverrideRRtype :: ResolvSeed -> Assertion
testOverrideRRtype ResolvSeed {seedRDataMap = dm} = do
    case decodeAtWith 0 False (getRData dm Nothing 1 (B.length aWire)) aWire of
        Left err -> assertFailure $ "decode failed: " ++ show err
        Right rd -> case fromRData rd :: Maybe T_ext_a of
            Just _  -> pure ()
            Nothing -> assertFailure $
                "expected T_ext_a, got " ++ presentString rd mempty
  where
    -- | Wire form for the 4-byte A record @192.0.2.1@.
    aWire :: B.ByteString
    aWire = B.pack [0xC0, 0x00, 0x02, 0x01]

testOverrideSvcParam :: ResolvSeed -> Assertion
testOverrideSvcParam ResolvSeed {seedRDataMap = dm} = do
    case decodeAtWith 0 False (getRData dm Nothing 64 (B.length svcbWire)) svcbWire of
        Left err -> assertFailure $ "decode failed: " ++ show err
        Right rd -> case fromRData rd :: Maybe T_svcb of
            Nothing -> assertFailure $
                "expected T_svcb, got " ++ presentString rd mempty
            Just X_SVCB{..} ->
                case spvLookup @SPV_EXT_port x_svcParamValues of
                    Just (SPV_EXT_PORT 80) -> pure ()
                    Just (SPV_EXT_PORT p)  -> assertFailure $
                        "expected port 80, got " ++ show p
                    Nothing -> assertFailure $
                        "no SPV_EXT_port in "
                        ++ presentString x_svcParamValues mempty
  where
    -- | Wire form for a minimal SVCB record: priority 1, root target,
    -- one SvcParam (port = 80).
    svcbWire :: B.ByteString
    svcbWire = B.pack
        [ 0x00, 0x01   -- priority 1
        , 0x00         -- target = root
        , 0x00, 0x03   -- SvcParamKey port (3)
        , 0x00, 0x02   -- value length 2
        , 0x00, 0x50   -- port 80
        ]

testOverrideEdeName :: ResolvSeed -> Assertion
testOverrideEdeName ResolvSeed {seedRDataMap = dm, seedOptionMap = om} = do
    case decodeAtWith 0 False (getRData dm (Just om) 41 (B.length optEdeWire)) optEdeWire of
        Left err -> assertFailure $ "decode failed: " ++ show err
        Right rd -> case fromRData rd :: Maybe T_opt of
            Nothing -> assertFailure "expected T_opt for RRTYPE 41"
            Just (T_OPT opts) -> case monoOption @O_ede opts of
                [O_EDE 0 name ""] | name == "Custom Other Error" -> pure ()
                other -> assertFailure $ "wrong EDE list: " ++ show other
  where
    -- | Wire form for an OPT RDATA containing a single EDE option
    -- (option code 15, info-code 0, no extra text).
    optEdeWire :: B.ByteString
    optEdeWire = B.pack
        [ 0x00, 0x0F   -- EDE option code (15)
        , 0x00, 0x02   -- value length 2
        , 0x00, 0x00   -- info-code 0, no text
        ]