{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Network.DNS.Message where
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Function
import Data.List (groupBy)
import Data.String
import Numeric (showHex)
import Prelude
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import Data.Bits
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Compat
data IPv6 = IPv6 !Word64 !Word64
deriving (Eq,Ord,Read)
instance Show IPv6 where
showsPrec p (IPv6 hi lo) = showParen (p >= 11) (showString "IPv6 0x" . showHex hi . showString " 0x" . showHex lo)
instance Binary IPv6 where
put (IPv6 hi lo) = putWord64be hi >> putWord64be lo
get = IPv6 <$> getWord64be <*> getWord64be
data IPv4 = IPv4 !Word32
deriving (Eq,Ord,Read)
instance Show IPv4 where
showsPrec p (IPv4 n) = showParen (p >= 11) (showString "IPv4 0x" . showHex n)
instance Binary IPv4 where
put (IPv4 w) = putWord32be w
get = IPv4 <$> getWord32be
newtype Name = Name BS.ByteString
deriving (Read,Show,Eq,Ord)
newtype CharStr = CharStr BS.ByteString
deriving (Eq,Ord,IsString)
instance Show CharStr where
showsPrec p (CharStr bs) = showsPrec p bs
instance Read CharStr where
readsPrec p = map (\(x,y) -> (CharStr x,y)) <$> readsPrec p
instance Binary CharStr where
put (CharStr bs)
| BS.length bs > 0xff = fail "putString: string too long"
| otherwise = do
putWord8 (fromIntegral $ BS.length bs)
putByteString bs
get = do
len' <- getWord8
CharStr <$> getByteString (fromIntegral len')
data Msg l
= Msg
{ msgHeader :: !MsgHeader
, msgQD :: [MsgQuestion l]
, msgAN, msgNS, msgAR :: [MsgRR l]
} deriving (Read,Show,Functor,Foldable,Traversable)
data MsgHeader
= MsgHeader
{ mhId :: !Word16
, mhFlags :: !MsgHeaderFlags
, mhQDCount :: !Word16
, mhANCount :: !Word16
, mhNSCount :: !Word16
, mhARCount :: !Word16
} deriving (Read,Show)
data MsgQuestion l
= MsgQuestion !l !Type !Class
deriving (Eq,Read,Show,Functor,Foldable,Traversable)
data MsgHeaderFlags
= MsgHeaderFlags
{ mhQR :: !QR
, mhOpcode :: !Word8
, mhAA :: !Bool
, mhTC :: !Bool
, mhRD :: !Bool
, mhRA :: !Bool
, mhZ :: !Bool
, mhAD :: !Bool
, mhCD :: !Bool
, mhRCode :: !Word8
} deriving (Read,Show)
data MsgRR l
= MsgRR
{ rrName :: !l
, rrClass :: !Class
, rrTTL :: !TTL
, rrData :: !(RData l)
} deriving (Eq,Read,Show,Functor,Foldable,Traversable)
data RData l
= RDataA !IPv4
| RDataAAAA !IPv6
| RDataCNAME !l
| RDataPTR !l
| RDataHINFO !CharStr !CharStr
| RDataNS !l
| RDataMX !Word16 !l
| RDataTXT ![CharStr]
| RDataSPF ![CharStr]
| RDataSOA !l !l !Word32 !Word32 !Word32 !Word32 !Word32
| RDataSRV !(SRV l)
| RDataAFSDB !Word16 !l
| RDataNAPTR !Word16 !Word16 !CharStr !CharStr !CharStr !l
| RDataURI !Word16 !Word16 !BS.ByteString
| RDataRRSIG !Word16 !Word8 !Word8 !Word32 !Word32 !Word32 !Word16 !l !BS.ByteString
| RDataDNSKEY !Word16 !Word8 !Word8 !BS.ByteString
| RDataDS !Word16 !Word8 !Word8 !BS.ByteString
| RDataNSEC !l !(Set Type)
| RDataSSHFP !Word8 !Word8 !BS.ByteString
| RDataNSEC3PARAM !Word8 !Word8 !Word16 !CharStr
| RDataNSEC3 !Word8 !Word8 !Word16 !CharStr !CharStr !(Set Type)
| RDataCAA !Word8 !CharStr !BS.ByteString
| RDataOPT !BS.ByteString
| RData !Type !BS.ByteString
deriving (Eq,Read,Show,Functor,Foldable,Traversable)
data SRV l = SRV { srvPriority :: !Word16
, srvWeight :: !Word16
, srvPort :: !Word16
, srvTarget :: !l
} deriving (Eq,Read,Show,Functor,Foldable,Traversable)
decodeMessage' :: BS.ByteString -> Maybe (Msg Labels)
decodeMessage' bs = do
(rest, _, v) <- either handleParseFail Just $
decodeOrFail (fromStrict bs)
guard (BSL.null rest)
let ofss = Set.fromList $ mapMaybe labelsPtr (toList v)
ofsmap <- retrieveLabelPtrs bs ofss
traverse (resolveLabelPtr ofsmap) v
where
handleParseFail (rest, n, e) = error $ show (e, n, BSL.length rest, BS.length bs) ++ "\n" ++ show (B16.encode $ toStrict rest)
decodeMessage :: IsLabels n => BS.ByteString -> Maybe (Msg n)
decodeMessage = fmap (fmap fromLabels) . decodeMessage'
encodeMessage' :: Msg Labels -> BS.ByteString
encodeMessage' m = toStrict $ encode (fmap labels2labelsPtr m)
encodeMessage :: IsLabels n => Msg n -> Maybe BS.ByteString
encodeMessage m = encodeMessage' <$> traverse toLabels m
instance Binary l => Binary (Msg l) where
get = do
hdr@MsgHeader{..} <- get
Msg hdr <$> replicateM (fromIntegral mhQDCount) get
<*> replicateM (fromIntegral mhANCount) get
<*> replicateM (fromIntegral mhNSCount) get
<*> replicateM (fromIntegral mhARCount) get
put (Msg hdr qds ans nss ars) = do
put hdr
mapM_ put qds
mapM_ put ans
mapM_ put nss
mapM_ put ars
instance Binary MsgHeader where
get = MsgHeader <$> getWord16be
<*> get
<*> getWord16be
<*> getWord16be
<*> getWord16be
<*> getWord16be
put (MsgHeader{..}) = do
putWord16be mhId
put mhFlags
putWord16be mhQDCount
putWord16be mhANCount
putWord16be mhNSCount
putWord16be mhARCount
instance Binary MsgHeaderFlags where
put = putWord16be . encodeFlags
get = decodeFlags <$> getWord16be
decodeFlags :: Word16 -> MsgHeaderFlags
decodeFlags w = MsgHeaderFlags{..}
where
mhQR = if testBit w 15 then IsResponse else IsQuery
mhOpcode = shiftR' 11 .&. 0xf
mhAA = testBit w 10
mhTC = testBit w 9
mhRD = testBit w 8
mhRA = testBit w 7
mhZ = testBit w 6
mhAD = testBit w 5
mhCD = testBit w 4
mhRCode = fromIntegral w .&. 0xf
shiftR' = fromIntegral . shiftR w
encodeFlags :: MsgHeaderFlags -> Word16
encodeFlags MsgHeaderFlags{..} =
(case mhQR of
IsResponse -> bit 15
IsQuery -> 0) .|.
(fromIntegral mhOpcode `shiftL` 11) .|.
(if mhAA then bit 10 else 0) .|.
(if mhTC then bit 9 else 0) .|.
(if mhRD then bit 8 else 0) .|.
(if mhRA then bit 7 else 0) .|.
(if mhZ then bit 6 else 0) .|.
(if mhAD then bit 5 else 0) .|.
(if mhCD then bit 4 else 0) .|.
(fromIntegral mhRCode)
data QR = IsQuery | IsResponse
deriving (Eq,Read,Show)
infixr 5 :.:
type Label = BS.ByteString
data Labels = !Label :.: !Labels | Root
deriving (Read,Show,Eq,Ord)
labelsToList :: Labels -> [Label]
labelsToList (x :.: xs) = x : labelsToList xs
labelsToList Root = [""]
class IsLabels s where
toLabels :: s -> Maybe Labels
fromLabels :: Labels -> s
instance IsLabels Labels where
fromLabels = id
toLabels ls
| all isLabelValid (init (labelsToList ls)) = Just ls
| otherwise = Nothing
where
isLabelValid l = not (BS.null l) && BS.length l < 0x40
instance IsLabels Name where
fromLabels = labels2name
toLabels = name2labels
toName :: IsLabels n => n -> Maybe Name
toName = fmap fromLabels . toLabels
name2labels :: Name -> Maybe Labels
name2labels (Name n)
| all (\l -> not (BS.null l) && BS.length l < 0x40) n' = Just $! foldr (:.:) Root n'
| otherwise = Nothing
where
n' | BS.isSuffixOf "." n = BS.split 0x2e (BS.init n)
| otherwise = BS.split 0x2e n
labels2name :: Labels -> Name
labels2name Root = Name "."
labels2name ls = Name (BS.intercalate "." $ labelsToList ls)
data LabelsPtr = Label !Label !LabelsPtr
| LPtr !Word16
| LNul
deriving (Eq,Read,Show)
labels2labelsPtr :: Labels -> LabelsPtr
labels2labelsPtr Root = LNul
labels2labelsPtr (l :.: rest) = Label l (labels2labelsPtr rest)
instance Binary LabelsPtr where
get = go []
where
go acc = do
l0 <- getLabel
case l0 of
Right bs | BS.null bs -> pure (foldr Label LNul $ reverse acc)
| otherwise -> go (bs:acc)
Left ofs -> pure (foldr Label (LPtr ofs) $ reverse acc)
getLabel :: Get (Either Word16 BS.ByteString)
getLabel = do
len <- getWord8
if len >= 0x40
then do
when (len .&. 0xc0 /= 0xc0) $ fail ("invalid length octet " ++ show len)
ofs <- fromIntegral <$> getWord8
pure $ Left $ (fromIntegral (len .&. 0x3f) `shiftL` 8) .|. ofs
else Right <$> getByteString (fromIntegral len)
put LNul = putWord8 0
put (Label l next)
| BS.length l < 1 || BS.length l >= 0x40 = error "put (Label {}): invalid label size"
| otherwise = do
putWord8 (fromIntegral (BS.length l))
putByteString l
put next
put (LPtr ofs)
| ofs < 0x4000 = putWord16be (0xc000 .|. ofs)
| otherwise = error "put (LPtr {}): invalid offset"
labelsSize :: LabelsPtr -> Word16
labelsSize = fromIntegral . go 0
where
go n (LPtr _) = n+2
go n LNul = n+1
go n (Label bs rest) = go (n + 1 + BS.length bs) rest
labelsPtr :: LabelsPtr -> Maybe Word16
labelsPtr (Label _ ls) = labelsPtr ls
labelsPtr LNul = Nothing
labelsPtr (LPtr ofs) = Just ofs
instance Binary l => Binary (MsgQuestion l) where
get = MsgQuestion <$> get <*> get <*> get
put (MsgQuestion l qt cls) = put l >> put qt >> put cls
instance Binary l => Binary (MsgRR l) where
get = do
rrName <- get
rrType <- get
rrClass <- get
rrTTL <- get
rrData <- getRData rrType
pure (MsgRR {..})
put (MsgRR{..}) = do
put rrName
put (either id typeFromSym $ rdType rrData)
put rrClass
put rrTTL
putRData rrData
getRData :: Binary l => Type -> Get (RData l)
getRData qt = do
len <- fromIntegral <$> getWord16be
let unknownRdata = RData qt <$> getByteString len
getByteStringRest = consumeRestWith getByteString
consumeRestWith act = do
curofs <- fromIntegral <$> bytesRead
act (len - curofs)
isolate len $
case typeToSym qt of
Nothing -> unknownRdata
Just ts -> case ts of
TypeA -> RDataA <$> get
TypeAFSDB -> RDataAFSDB <$> getWord16be
<*> get
TypeNS -> RDataNS <$> get
TypeCNAME -> RDataCNAME <$> get
TypeSOA -> RDataSOA <$> get
<*> get
<*> getWord32be
<*> getWord32be
<*> getWord32be
<*> getWord32be
<*> getWord32be
TypePTR -> RDataPTR <$> get
TypeHINFO -> RDataHINFO <$> get
<*> get
TypeMX -> RDataMX <$> getWord16be
<*> get
TypeTXT -> RDataTXT <$> getUntilEmpty
TypeSPF -> RDataSPF <$> getUntilEmpty
TypeAAAA -> RDataAAAA <$> get
TypeSRV -> RDataSRV <$> get
TypeNAPTR -> RDataNAPTR <$> getWord16be
<*> getWord16be
<*> get
<*> get
<*> get
<*> get
TypeRRSIG -> RDataRRSIG <$> getWord16be
<*> getWord8
<*> getWord8
<*> getWord32be
<*> getWord32be
<*> getWord32be
<*> getWord16be
<*> get
<*> getByteStringRest
TypeDNSKEY -> RDataDNSKEY <$> getWord16be
<*> getWord8
<*> getWord8
<*> getByteString (len - 4)
TypeDS -> RDataDS <$> getWord16be
<*> getWord8
<*> getWord8
<*> getByteString (len - 4)
TypeNSEC -> RDataNSEC <$> get
<*> decodeNsecTypeMap
TypeURI -> RDataURI <$> getWord16be
<*> getWord16be
<*> getByteString (len - 4)
TypeSSHFP -> RDataSSHFP <$> getWord8
<*> getWord8
<*> getByteString (len - 2)
TypeNSEC3PARAM -> RDataNSEC3PARAM <$> getWord8
<*> getWord8
<*> getWord16be
<*> get
TypeNSEC3 -> RDataNSEC3 <$> getWord8
<*> getWord8
<*> getWord16be
<*> get
<*> get
<*> decodeNsecTypeMap
TypeCAA -> RDataCAA <$> getWord8
<*> get
<*> getByteStringRest
TypeOPT -> RDataOPT <$> getByteString len
TypeANY -> unknownRdata
putRData :: Binary l => RData l -> Put
putRData rd = do
let rdata = runPut (putRData' rd)
rdataLen = BSL.length rdata
unless (rdataLen < 0x10000) $
fail "rdata too large"
putWord16be (fromIntegral rdataLen)
putLazyByteString rdata
putRData' :: Binary l => RData l -> Put
putRData' rd = case rd of
RDataA ip4 -> put ip4
RDataAAAA ip6 -> put ip6
RDataCNAME cname -> put cname
RDataOPT d -> putByteString d
RDataMX prio l -> putWord16be prio >> put l
RDataSOA l1 l2 w1 w2 w3 w4 w5 -> do
put l1
put l2
putWord32be w1
putWord32be w2
putWord32be w3
putWord32be w4
putWord32be w5
RDataPTR l -> put l
RDataNS l -> put l
RDataTXT ss -> mapM_ put ss
RDataSPF ss -> mapM_ put ss
RDataSRV srv -> put srv
RDataAFSDB w l -> putWord16be w >> put l
RDataHINFO s1 s2 -> put s1 >> put s2
RDataRRSIG w1 w2 w3 w4 w5 w6 w7 l s -> do
putWord16be w1
putWord8 w2
putWord8 w3
putWord32be w4
putWord32be w5
putWord32be w6
putWord16be w7
put l
putByteString s
RDataDNSKEY w1 w2 w3 s -> do
putWord16be w1
putWord8 w2
putWord8 w3
putByteString s
RDataNSEC3PARAM w1 w2 w3 s -> do
putWord8 w1
putWord8 w2
putWord16be w3
put s
RDataNSEC3 w1 w2 w3 s1 s2 tm -> do
putWord8 w1
putWord8 w2
putWord16be w3
put s1
put s2
encodeNsecTypeMap tm
RDataCAA fl s1 s2 -> do
putWord8 fl
put s1
putByteString s2
RDataURI w1 w2 s -> do
putWord16be w1
putWord16be w2
putByteString s
RDataDS w1 w2 w3 s -> do
putWord16be w1
putWord8 w2
putWord8 w3
putByteString s
RDataNSEC l tm -> do
put l
encodeNsecTypeMap tm
RDataNAPTR w1 w2 s1 s2 s3 l -> do
putWord16be w1
putWord16be w2
put s1
put s2
put s3
put l
RDataSSHFP w1 w2 s -> do
putWord8 w1
putWord8 w2
putByteString s
RData _ raw -> putByteString raw
instance Binary l => Binary (SRV l) where
get = SRV <$> getWord16be
<*> getWord16be
<*> getWord16be
<*> get
put (SRV w1 w2 w3 l) = do
putWord16be w1
putWord16be w2
putWord16be w3
put l
decodeNsecTypeMap :: Get (Set Type)
decodeNsecTypeMap = do
r <- concat <$> untilEmptyWith decode1
pure (Set.fromList r)
where
decode1 = do
wi <- getWord8
l <- getWord8
unless (0 < l && l <= 32) $
fail "invalid bitmap length"
bmap <- getByteString (fromIntegral l)
let winofs = (fromIntegral wi)*0x100 :: Int
lst = [ Type (fromIntegral (winofs+j*8+7-i))
| (j,x) <- zip [0..] (BS.unpack bmap)
, i <- [7,6..0]
, testBit x i ]
pure lst
encodeNsecTypeMap :: Set Type -> Put
encodeNsecTypeMap bmap = do
when (Set.null bmap) $ fail "invalid empty type-map"
forM_ (Map.toList bmap') $ \(wi, tm) -> do
putWord8 wi
put (CharStr $ BS.pack tm)
where
bmap' = fmap set2bitmap . splitToBlocks $ Set.map (\(Type w)->w) bmap
set2bitmap :: Set Word8 -> [Word8]
set2bitmap = go 0 0 . Set.toList
where
go _ acc [] = if acc == 0 then [] else [acc]
go j acc (i:is)
| j' > j = acc : go (j+1) 0 (i:is)
| j' == j = go j' (acc .|. bit (7 - fromIntegral i')) is
| otherwise = error "set2bitmap: the impossible happened"
where
(j',i') = i `quotRem` 8
splitToBlocks :: Set Word16 -> Map Word8 (Set Word8)
splitToBlocks js = Map.fromList $ map (\xs -> (fst $ head xs, Set.fromList (map snd xs))) js'
where
hi16 :: Word16 -> Word8
hi16 = fromIntegral . flip shiftR 8
lo16 :: Word16 -> Word8
lo16 = fromIntegral . (.&. 0xff)
js' :: [[(Word8,Word8)]]
js' = groupBy ((==) `on` fst) (map ((,) <$> hi16 <*> lo16) (Set.toList js))
retrieveLabelPtr :: BS.ByteString -> Word16 -> Maybe LabelsPtr
retrieveLabelPtr msg ofs
= case decodeOrFail (fromStrict $ BS.drop (fromIntegral ofs) msg) of
Left _ -> Nothing
Right (_, _, v) -> Just v
retrieveLabelPtrs :: BS.ByteString -> Set Word16 -> Maybe (Map Word16 LabelsPtr)
retrieveLabelPtrs msg ofss0 = go =<< lupPtrs1 ofss0
where
go :: Map Word16 LabelsPtr -> Maybe (Map Word16 LabelsPtr)
go m0 = do
let missingOfss = Set.fromList (mapMaybe labelsPtr (toList m0)) Set.\\ Map.keysSet m0
if Set.null missingOfss
then pure m0
else do
m1 <- lupPtrs1 missingOfss
go (Map.union m0 m1)
lupPtrs1 :: Set Word16 -> Maybe (Map Word16 LabelsPtr)
lupPtrs1 ofss1 = Map.fromList . zip (toList ofss1) <$> traverse (retrieveLabelPtr msg) (toList ofss1)
resolveLabelPtr :: Map Word16 LabelsPtr -> LabelsPtr -> Maybe Labels
resolveLabelPtr ofsmap = go 0 []
where
go :: Int -> [BS.ByteString] -> LabelsPtr -> Maybe Labels
go !n acc (Label x ls) = go (n+1+BS.length x) (x:acc) ls
go n acc LNul
| n < 255 = Just $! foldr (:.:) Root (reverse acc)
| otherwise = Nothing
go n acc (LPtr ofs)
| n < 255 = go n acc =<< lup ofs
| otherwise = Nothing
lup :: Word16 -> Maybe LabelsPtr
lup ofs = Map.lookup ofs ofsmap
newtype Type = Type Word16
deriving (Eq,Ord,Read,Show)
instance Binary Type where
put (Type w) = putWord16be w
get = Type <$> getWord16be
newtype Class = Class Word16
deriving (Eq,Ord,Read,Show)
classIN :: Class
classIN = Class 1
instance Binary Class where
put (Class w) = putWord16be w
get = Class <$> getWord16be
newtype TTL = TTL Int32
deriving (Eq,Ord,Read,Show)
instance Binary TTL where
put (TTL i) = putInt32be i
get = TTL <$> getInt32be
data TypeSym
= TypeA
| TypeAAAA
| TypeAFSDB
| TypeANY
| TypeCAA
| TypeCNAME
| TypeDNSKEY
| TypeDS
| TypeHINFO
| TypeMX
| TypeNAPTR
| TypeNS
| TypeNSEC
| TypeNSEC3
| TypeNSEC3PARAM
| TypeOPT
| TypePTR
| TypeRRSIG
| TypeSOA
| TypeSPF
| TypeSRV
| TypeSSHFP
| TypeTXT
| TypeURI
deriving (Eq,Ord,Enum,Bounded,Read,Show)
typeFromSym :: TypeSym -> Type
typeFromSym ts = Type $ case ts of
TypeA -> 1
TypeNS -> 2
TypeCNAME -> 5
TypeSOA -> 6
TypePTR -> 12
TypeHINFO -> 13
TypeMX -> 15
TypeTXT -> 16
TypeAFSDB -> 18
TypeAAAA -> 28
TypeSRV -> 33
TypeNAPTR -> 35
TypeOPT -> 41
TypeDS -> 43
TypeSSHFP -> 44
TypeRRSIG -> 46
TypeNSEC -> 47
TypeDNSKEY -> 48
TypeNSEC3 -> 50
TypeNSEC3PARAM -> 51
TypeSPF -> 99
TypeANY -> 255
TypeURI -> 256
TypeCAA -> 257
typeToSym :: Type -> Maybe TypeSym
typeToSym (Type w) = case w of
1 -> Just TypeA
2 -> Just TypeNS
5 -> Just TypeCNAME
6 -> Just TypeSOA
12 -> Just TypePTR
13 -> Just TypeHINFO
15 -> Just TypeMX
16 -> Just TypeTXT
18 -> Just TypeAFSDB
28 -> Just TypeAAAA
33 -> Just TypeSRV
35 -> Just TypeNAPTR
41 -> Just TypeOPT
43 -> Just TypeDS
44 -> Just TypeSSHFP
46 -> Just TypeRRSIG
47 -> Just TypeNSEC
48 -> Just TypeDNSKEY
50 -> Just TypeNSEC3
51 -> Just TypeNSEC3PARAM
99 -> Just TypeSPF
255 -> Just TypeANY
256 -> Just TypeURI
257 -> Just TypeCAA
_ -> Nothing
rdType :: RData l -> Either Type TypeSym
rdType rd = case rd of
RDataA {} -> Right TypeA
RDataAAAA {} -> Right TypeAAAA
RDataAFSDB {} -> Right TypeAFSDB
RDataCAA {} -> Right TypeCAA
RDataCNAME {} -> Right TypeCNAME
RDataDNSKEY {} -> Right TypeDNSKEY
RDataDS {} -> Right TypeDS
RDataHINFO {} -> Right TypeHINFO
RDataMX {} -> Right TypeMX
RDataNAPTR {} -> Right TypeNAPTR
RDataNS {} -> Right TypeNS
RDataNSEC {} -> Right TypeNSEC
RDataNSEC3 {} -> Right TypeNSEC3
RDataNSEC3PARAM {} -> Right TypeNSEC3PARAM
RDataOPT {} -> Right TypeOPT
RDataPTR {} -> Right TypePTR
RDataRRSIG {} -> Right TypeRRSIG
RDataSOA {} -> Right TypeSOA
RDataSRV {} -> Right TypeSRV
RDataTXT {} -> Right TypeTXT
RDataSPF {} -> Right TypeSPF
RDataURI {} -> Right TypeURI
RDataSSHFP {} -> Right TypeSSHFP
RData ty _ -> maybe (Left ty) Right (typeToSym ty)
getUntilEmpty :: Binary a => Get [a]
getUntilEmpty = untilEmptyWith get
untilEmptyWith :: Get a -> Get [a]
untilEmptyWith g = go []
where
go acc = do
e <- isEmpty
if e
then pure (reverse acc)
else do
v <- g
go (v : acc)