]> gitweb @ CieloNegro.org - haskell-dns.git/blobdiff - Network/DNS/Message.hs
QueryType and QueryClass should also be exported.
[haskell-dns.git] / Network / DNS / Message.hs
index be0b79a33800a32dcc769c7ff68129f322261bf4..17c037f7dba99a18a3c4457f670a5aaa017b5a54 100644 (file)
@@ -10,21 +10,47 @@ module Network.DNS.Message
     , DomainName
     , DomainLabel
     , TTL
     , DomainName
     , DomainLabel
     , TTL
+
+    , QueryType
+    , QueryClass
     , RecordType
     , RecordType
-    , RecordClass(..)
+    , RecordClass
+
+    , SOAFields(..)
+    , WKSFields(..)
 
 
-    , SomeQT
+    , SomeQ
     , SomeRR
     , SomeRR
-    , SomeRT
 
     , A(..)
     , NS(..)
 
     , A(..)
     , NS(..)
+    , MD(..)
+    , MF(..)
     , CNAME(..)
     , CNAME(..)
+    , SOA(..)
+    , MB(..)
+    , MG(..)
+    , MR(..)
+    , NULL(..)
+    , WKS(..)
+    , PTR(..)
     , HINFO(..)
     , HINFO(..)
+    , MINFO(..)
+    , MX(..)
+    , TXT(..)
+
+    , AXFR(..)
+    , MAILB(..)
+    , MAILA(..)
+    , ANY(..)
+
+    , IN(..)
+    , CS(..)
+    , CH(..)
+    , HS(..)
 
     , mkDomainName
 
     , mkDomainName
-    , wrapQueryType
-    , wrapRecordType
+    , wrapQuestion
     , wrapRecord
     )
     where
     , wrapRecord
     )
     where
@@ -34,7 +60,7 @@ import           Control.Monad
 import           Data.Binary
 import           Data.Binary.BitPut as BP
 import           Data.Binary.Get as G
 import           Data.Binary
 import           Data.Binary.BitPut as BP
 import           Data.Binary.Get as G
-import           Data.Binary.Put as P
+import           Data.Binary.Put as P'
 import           Data.Binary.Strict.BitGet as BG
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
 import           Data.Binary.Strict.BitGet as BG
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
@@ -42,7 +68,12 @@ import qualified Data.ByteString.Lazy as LBS
 import           Data.Typeable
 import qualified Data.IntMap as IM
 import           Data.IntMap (IntMap)
 import           Data.Typeable
 import qualified Data.IntMap as IM
 import           Data.IntMap (IntMap)
+import qualified Data.IntSet as IS
+import           Data.IntSet (IntSet)
+import qualified Data.Map as M
+import           Data.Map (Map)
 import           Data.Word
 import           Data.Word
+import           Network.DNS.Packer as P
 import           Network.DNS.Unpacker as U
 import           Network.Socket
 
 import           Network.DNS.Unpacker as U
 import           Network.Socket
 
@@ -50,7 +81,7 @@ import           Network.Socket
 data Message
     = Message {
         msgHeader      :: !Header
 data Message
     = Message {
         msgHeader      :: !Header
-      , msgQuestions   :: ![Question]
+      , msgQuestions   :: ![SomeQ]
       , msgAnswers     :: ![SomeRR]
       , msgAuthorities :: ![SomeRR]
       , msgAdditionals :: ![SomeRR]
       , msgAnswers     :: ![SomeRR]
       , msgAuthorities :: ![SomeRR]
       , msgAdditionals :: ![SomeRR]
@@ -98,50 +129,103 @@ data ResponseCode
     | Refused
     deriving (Show, Eq)
 
     | Refused
     deriving (Show, Eq)
 
-data Question
+data (QueryType qt, QueryClass qc) => Question qt qc
     = Question {
         qName  :: !DomainName
     = Question {
         qName  :: !DomainName
-      , qType  :: !SomeQT
-      , qClass :: !RecordClass
+      , qType  :: !qt
+      , qClass :: !qc
       }
       }
-    deriving (Show, Eq)
+    deriving (Typeable)
 
 
-type SomeQT = SomeRT
+instance (QueryType qt, QueryClass qc) => Show (Question qt qc) where
+    show q = "Question { qName = " ++ show (qName q) ++
+             ", qType = " ++ show (qType q) ++
+             ", qClass = " ++ show (qClass q) ++ " }"
 
 
-putQ :: Question -> Put
-putQ q
-    = do putDomainName $ qName q
-         putSomeRT $ qType q
-         put $ qClass q
+instance (QueryType qt, QueryClass qc) => Eq (Question qt qc) where
+    a == b = qName  a == qName  b &&
+             qType  a == qType  b &&
+             qClass a == qClass b
 
 
-getQ :: Unpacker DecompTable Question
-getQ = do nm <- getDomainName
-          ty <- getSomeRT
-          cl <- getBinary
-          return Question {
-                       qName  = nm
-                     , qType  = ty
-                     , qClass = cl
-                     }
+data SomeQ = forall qt qc. (QueryType qt, QueryClass qc) => SomeQ (Question qt qc)
 
 
+instance Show SomeQ where
+    show (SomeQ q) = show q
 
 
-newtype DomainName  = DN [DomainLabel] deriving (Eq, Show, Typeable)
-type DomainLabel    = BS.ByteString
+instance Eq SomeQ where
+    (SomeQ a) == (SomeQ b) = Just a == cast b
 
 
-nameToLabels :: DomainName -> [DomainLabel]
-nameToLabels (DN ls) = ls
+data SomeQT = forall qt. QueryType qt => SomeQT qt
 
 
-labelsToName :: [DomainLabel] -> DomainName
-labelsToName = DN
+instance Show SomeQT where
+    show (SomeQT qt) = show qt
+
+instance Eq SomeQT where
+    (SomeQT a) == (SomeQT b) = Just a == cast b
+
+data SomeQC = forall qc. QueryClass qc => SomeQC qc
+
+instance Show SomeQC where
+    show (SomeQC qc) = show qc
+
+instance Eq SomeQC where
+    (SomeQC a) == (SomeQC b) = Just a == cast b
+
+putSomeQ :: SomeQ -> Packer CompTable ()
+putSomeQ (SomeQ q)
+    = do putDomainName $ qName q
+         putQueryType  $ qType q
+         putQueryClass $ qClass q
+
+getSomeQ :: Unpacker DecompTable SomeQ
+getSomeQ
+    = do nm <- getDomainName
+         ty <- getSomeQT
+         cl <- getSomeQC
+         case (ty, cl) of
+           (SomeQT qt, SomeQC qc)
+               -> return $ SomeQ $ Question {
+                         qName  = nm
+                       , qType  = qt
+                       , qClass = qc
+                       }
+
+getSomeQT :: Unpacker s SomeQT
+getSomeQT = do n <- liftM fromIntegral U.getWord16be
+               case IM.lookup n defaultQTTable of
+                 Just sqt
+                     -> return sqt
+                 Nothing
+                     -> fail ("Unknown query type: " ++ show n)
+
+getSomeQC :: Unpacker s SomeQC
+getSomeQC = do n <- liftM fromIntegral U.getWord16be
+               case IM.lookup n defaultQCTable of
+                 Just sqc
+                     -> return sqc
+                 Nothing
+                     -> fail ("Unknown query class: " ++ show n)
+
+
+newtype DomainName  = DN [DomainLabel] deriving (Eq, Show, Ord, Typeable)
+type DomainLabel    = BS.ByteString
 
 rootName :: DomainName
 rootName = DN [BS.empty]
 
 
 rootName :: DomainName
 rootName = DN [BS.empty]
 
+isRootName :: DomainName -> Bool
+isRootName (DN [_]) = True
+isRootName _        = False
+
 consLabel :: DomainLabel -> DomainName -> DomainName
 consLabel x (DN ys) = DN (x:ys)
 
 consLabel :: DomainLabel -> DomainName -> DomainName
 consLabel x (DN ys) = DN (x:ys)
 
+unconsLabel :: DomainName -> (DomainLabel, DomainName)
+unconsLabel (DN (x:xs)) = (x, DN xs)
+unconsLabel x           = error ("Illegal use of unconsLabel: " ++ show x)
+
 mkDomainName :: String -> DomainName
 mkDomainName :: String -> DomainName
-mkDomainName = labelsToName . mkLabels [] . notEmpty
+mkDomainName = DN . mkLabels [] . notEmpty
     where
       notEmpty :: String -> String
       notEmpty xs = assert (not $ null xs) xs
     where
       notEmpty :: String -> String
       notEmpty xs = assert (not $ null xs) xs
@@ -153,39 +237,26 @@ mkDomainName = labelsToName . mkLabels [] . notEmpty
                                 -> mkLabels (C8.pack l : soFar) rest
                             _   -> error ("Illegal domain name: " ++ xs)
 
                                 -> mkLabels (C8.pack l : soFar) rest
                             _   -> error ("Illegal domain name: " ++ xs)
 
-data RecordClass
-    = IN
-    | CS -- Obsolete
-    | CH
-    | HS
-    | AnyClass -- Only for queries
-    deriving (Show, Eq)
 
 
+class (Show rc, Eq rc, Typeable rc) => RecordClass rc where
+    rcToInt :: rc -> Int
 
 
-data RecordType rt dt => ResourceRecord rt dt
+    putRecordClass :: rc -> Packer s ()
+    putRecordClass = P.putWord16be . fromIntegral . rcToInt
+
+
+data (RecordType rt dt, RecordClass rc) => ResourceRecord rt rc dt
     = ResourceRecord {
         rrName  :: !DomainName
       , rrType  :: !rt
     = ResourceRecord {
         rrName  :: !DomainName
       , rrType  :: !rt
-      , rrClass :: !RecordClass
+      , rrClass :: !rc
       , rrTTL   :: !TTL
       , rrData  :: !dt
       }
     deriving (Show, Eq, Typeable)
 
 
       , rrTTL   :: !TTL
       , rrData  :: !dt
       }
     deriving (Show, Eq, Typeable)
 
 
-putRR :: forall rt dt. RecordType rt dt => ResourceRecord rt dt -> Put
-putRR rr = do putDomainName $ rrName rr
-              putRecordType $ rrType  rr
-              put $ rrClass rr
-              putWord32be $ rrTTL rr
-
-              let dat = runPut $
-                        putRecordData (undefined :: rt) (rrData rr)
-              putWord16be $ fromIntegral $ LBS.length dat
-              putLazyByteString dat
-
-
-data SomeRR = forall rt dt. RecordType rt dt => SomeRR (ResourceRecord rt dt)
+data SomeRR = forall rt rc dt. (RecordType rt dt, RecordClass rc) => SomeRR (ResourceRecord rt rc dt)
 
 instance Show SomeRR where
     show (SomeRR rr) = show rr
 
 instance Show SomeRR where
     show (SomeRR rr) = show rr
@@ -194,19 +265,22 @@ instance Eq SomeRR where
     (SomeRR a) == (SomeRR b) = Just a == cast b
 
 
     (SomeRR a) == (SomeRR b) = Just a == cast b
 
 
-putSomeRR :: SomeRR -> Put
-putSomeRR (SomeRR rr) = putRR rr
+putSomeRR :: SomeRR -> Packer CompTable ()
+putSomeRR (SomeRR rr) = putResourceRecord rr
 
 getSomeRR :: Unpacker DecompTable SomeRR
 
 getSomeRR :: Unpacker DecompTable SomeRR
-getSomeRR = do srt <- U.lookAhead $
-                      do getDomainName -- skip
-                         getSomeRT
-               case srt of
-                 SomeRT rt
-                     -> getResourceRecord rt >>= return . SomeRR
-
+getSomeRR = do (srt, src) <- U.lookAhead $
+                             do getDomainName -- skip
+                                srt <- getSomeRT
+                                src <- getSomeRC
+                                return (srt, src)
+               case (srt, src) of
+                 (SomeRT rt, SomeRC rc)
+                     -> getResourceRecord rt rc >>= return . SomeRR
+
+type CompTable   = Map DomainName Int
 type DecompTable = IntMap DomainName
 type DecompTable = IntMap DomainName
-type TTL = Word32
+type TTL         = Word32
 
 getDomainName :: Unpacker DecompTable DomainName
 getDomainName = worker
 
 getDomainName :: Unpacker DecompTable DomainName
 getDomainName = worker
@@ -217,7 +291,7 @@ getDomainName = worker
                hdr    <- getLabelHeader
                case hdr of
                  Offset n
                hdr    <- getLabelHeader
                case hdr of
                  Offset n
-                     -> do dt <- getState
+                     -> do dt <- U.getState
                            case IM.lookup n dt of
                              Just name
                                  -> return name
                            case IM.lookup n dt of
                              Just name
                                  -> return name
@@ -229,7 +303,7 @@ getDomainName = worker
                      -> do label <- U.getByteString n
                            rest  <- worker
                            let name = consLabel label rest
                      -> do label <- U.getByteString n
                            rest  <- worker
                            let name = consLabel label rest
-                           modifyState $ IM.insert offset name
+                           U.modifyState $ IM.insert offset name
                            return name
 
       getLabelHeader :: Unpacker s LabelHeader
                            return name
 
       getLabelHeader :: Unpacker s LabelHeader
@@ -262,45 +336,121 @@ getCharString :: Unpacker s BS.ByteString
 getCharString = do len <- U.getWord8
                    U.getByteString (fromIntegral len)
 
 getCharString = do len <- U.getWord8
                    U.getByteString (fromIntegral len)
 
-putCharString :: BS.ByteString -> Put
-putCharString = putDomainLabel
+putCharString :: BS.ByteString -> Packer s ()
+putCharString xs = do P.putWord8 $ fromIntegral $ BS.length xs
+                      P.putByteString xs
 
 data LabelHeader
     = Offset !Int
     | Length !Int
 
 
 data LabelHeader
     = Offset !Int
     | Length !Int
 
-putDomainName :: DomainName -> Put
-putDomainName = mapM_ putDomainLabel . nameToLabels
+putDomainName :: DomainName -> Packer CompTable ()
+putDomainName name
+    = do ct <- P.getState
+         case M.lookup name ct of
+           Just n
+               -> do let ptr = runBitPut $
+                               do putBit True
+                                  putBit True
+                                  putNBits 14 n
+                     P.putLazyByteString ptr
+           Nothing
+               -> do offset <- bytesWrote
+                     P.modifyState $ M.insert name offset
+
+                     let (label, rest) = unconsLabel name
+
+                     putCharString label
+
+                     if isRootName rest then
+                         P.putWord8 0
+                       else
+                         putDomainName rest
+
+class (Show qt, Eq qt, Typeable qt) => QueryType qt where
+    qtToInt :: qt -> Int
+
+    putQueryType :: qt -> Packer s ()
+    putQueryType = P.putWord16be . fromIntegral . qtToInt
+
+instance RecordType rt dt => QueryType rt where
+    qtToInt = rtToInt
+
+class (Show qc, Eq qc, Typeable qc) => QueryClass qc where
+    qcToInt :: qc -> Int
+
+    putQueryClass :: qc -> Packer s ()
+    putQueryClass = P.putWord16be . fromIntegral . qcToInt
+
+instance RecordClass rc => QueryClass rc where
+    qcToInt = rcToInt
 
 
-putDomainLabel :: DomainLabel -> Put
-putDomainLabel l
-    = do putWord8 $ fromIntegral $ BS.length l
-         P.putByteString l
 
 class (Show rt, Show dt, Eq rt, Eq dt, Typeable rt, Typeable dt) => RecordType rt dt | rt -> dt where
     rtToInt       :: rt -> Int
 
 class (Show rt, Show dt, Eq rt, Eq dt, Typeable rt, Typeable dt) => RecordType rt dt | rt -> dt where
     rtToInt       :: rt -> Int
-    putRecordData :: rt -> dt -> Put
+    putRecordData :: rt -> dt -> Packer CompTable ()
     getRecordData :: rt -> Unpacker DecompTable dt
 
     getRecordData :: rt -> Unpacker DecompTable dt
 
-    putRecordType :: rt -> Put
-    putRecordType = putWord16be . fromIntegral . rtToInt
-
-    getResourceRecord :: rt -> Unpacker DecompTable (ResourceRecord rt dt)
-    getResourceRecord rt
+    putRecordType :: rt -> Packer s ()
+    putRecordType = P.putWord16be . fromIntegral . rtToInt
+
+    putRecordDataWithLength :: rt -> dt -> Packer CompTable ()
+    putRecordDataWithLength rt dt
+        = do -- First, write a dummy data length.
+             offset <- bytesWrote
+             P.putWord16be 0
+
+             -- Second, write data.
+             putRecordData rt dt
+
+             -- Third, rewrite the dummy length to an actual value.
+             offset' <- bytesWrote
+             let len = offset' - offset - 2
+             if len <= 0xFFFF then
+                 withOffset offset
+                    $ P.putWord16be $ fromIntegral len
+               else
+                 fail ("putRecordData " ++ show rt ++ " wrote " ++ show len
+                       ++ " bytes, which is way too long")
+
+    putResourceRecord :: RecordClass rc => ResourceRecord rt rc dt -> Packer CompTable ()
+    putResourceRecord rr
+        = do putDomainName  $ rrName  rr
+             putRecordType  $ rrType  rr
+             putRecordClass $ rrClass rr
+             P.putWord32be  $ rrTTL   rr
+             putRecordDataWithLength (rrType rr) (rrData rr)
+
+    getRecordDataWithLength :: rt -> Unpacker DecompTable dt
+    getRecordDataWithLength rt
+        = do len     <- U.getWord16be
+             offset  <- U.bytesRead
+             dat     <- getRecordData rt
+             offset' <- U.bytesRead
+
+             let consumed = offset' - offset
+             when (consumed /= len)
+                      $ fail ("getRecordData " ++ show rt ++ " consumed " ++ show consumed ++
+                              " bytes but it had to consume " ++ show len ++ " bytes")
+
+             return dat
+
+    getResourceRecord :: RecordClass rc => rt -> rc -> Unpacker DecompTable (ResourceRecord rt rc dt)
+    getResourceRecord rt rc
         = do name     <- getDomainName
              U.skip 2 -- record type
         = do name     <- getDomainName
              U.skip 2 -- record type
-             cl       <- getBinary
+             U.skip 2 -- record class
              ttl      <- U.getWord32be
              ttl      <- U.getWord32be
-             U.skip 2 -- data length
-             dat      <- getRecordData rt
+             dat      <- getRecordDataWithLength rt
              return $ ResourceRecord {
                           rrName  = name
                         , rrType  = rt
              return $ ResourceRecord {
                           rrName  = name
                         , rrType  = rt
-                        , rrClass = cl
+                        , rrClass = rc
                         , rrTTL   = ttl
                         , rrData  = dat
                         }
 
                         , rrTTL   = ttl
                         , rrData  = dat
                         }
 
+
 data SomeRT = forall rt dt. RecordType rt dt => SomeRT rt
 
 instance Show SomeRT where
 data SomeRT = forall rt dt. RecordType rt dt => SomeRT rt
 
 instance Show SomeRT where
@@ -309,9 +459,6 @@ instance Show SomeRT where
 instance Eq SomeRT where
     (SomeRT a) == (SomeRT b) = Just a == cast b
 
 instance Eq SomeRT where
     (SomeRT a) == (SomeRT b) = Just a == cast b
 
-putSomeRT :: SomeRT -> Put
-putSomeRT (SomeRT rt) = putRecordType rt
-
 getSomeRT :: Unpacker s SomeRT
 getSomeRT = do n <- liftM fromIntegral U.getWord16be
                case IM.lookup n defaultRTTable of
 getSomeRT :: Unpacker s SomeRT
 getSomeRT = do n <- liftM fromIntegral U.getWord16be
                case IM.lookup n defaultRTTable of
@@ -320,10 +467,48 @@ getSomeRT = do n <- liftM fromIntegral U.getWord16be
                  Just srt
                      -> return srt
 
                  Just srt
                      -> return srt
 
+data SomeRC = forall rc. RecordClass rc => SomeRC rc
+
+instance Show SomeRC where
+    show (SomeRC rc) = show rc
+
+instance Eq SomeRC where
+    (SomeRC a) == (SomeRC b) = Just a == cast b
+
+getSomeRC :: Unpacker s SomeRC
+getSomeRC = do n <- liftM fromIntegral U.getWord16be
+               case IM.lookup n defaultRCTable of
+                 Nothing
+                     -> fail ("Unknown resource record class: " ++ show n)
+                 Just src
+                     -> return src
+
+
+data SOAFields
+    = SOAFields {
+        soaMasterNameServer   :: !DomainName
+      , soaResponsibleMailbox :: !DomainName
+      , soaSerialNumber       :: !Word32
+      , soaRefreshInterval    :: !Word32
+      , soaRetryInterval      :: !Word32
+      , soaExpirationLimit    :: !Word32
+      , soaMinimumTTL         :: !Word32
+      }
+    deriving (Show, Eq, Typeable)
+
+data WKSFields
+    = WKSFields {
+        wksAddress  :: !HostAddress
+      , wksProtocol :: !ProtocolNumber
+      , wksServices :: !IntSet
+      }
+    deriving (Show, Eq, Typeable)
+
+
 data A = A deriving (Show, Eq, Typeable)
 instance RecordType A HostAddress where
     rtToInt       _ = 1
 data A = A deriving (Show, Eq, Typeable)
 instance RecordType A HostAddress where
     rtToInt       _ = 1
-    putRecordData _ = putWord32be
+    putRecordData _ = P.putWord32be
     getRecordData _ = U.getWord32be
 
 data NS = NS deriving (Show, Eq, Typeable)
     getRecordData _ = U.getWord32be
 
 data NS = NS deriving (Show, Eq, Typeable)
@@ -332,67 +517,231 @@ instance RecordType NS DomainName where
     putRecordData _ = putDomainName
     getRecordData _ = getDomainName
 
     putRecordData _ = putDomainName
     getRecordData _ = getDomainName
 
+data MD = MD deriving (Show, Eq, Typeable)
+instance RecordType MD DomainName where
+    rtToInt       _ = 3
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
+data MF = MF deriving (Show, Eq, Typeable)
+instance RecordType MF DomainName where
+    rtToInt       _ = 4
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
 data CNAME = CNAME deriving (Show, Eq, Typeable)
 instance RecordType CNAME DomainName where
     rtToInt       _ = 5
     putRecordData _ = putDomainName
     getRecordData _ = getDomainName
 
 data CNAME = CNAME deriving (Show, Eq, Typeable)
 instance RecordType CNAME DomainName where
     rtToInt       _ = 5
     putRecordData _ = putDomainName
     getRecordData _ = getDomainName
 
+data SOA = SOA deriving (Show, Eq, Typeable)
+instance RecordType SOA SOAFields where
+    rtToInt       _ = 6
+    putRecordData _ = \ soa ->
+                      do putDomainName $ soaMasterNameServer soa
+                         putDomainName $ soaResponsibleMailbox soa
+                         P.putWord32be $ soaSerialNumber soa
+                         P.putWord32be $ soaRefreshInterval soa
+                         P.putWord32be $ soaRetryInterval soa
+                         P.putWord32be $ soaExpirationLimit soa
+                         P.putWord32be $ soaMinimumTTL soa
+    getRecordData _ = do master  <- getDomainName
+                         mail    <- getDomainName
+                         serial  <- U.getWord32be
+                         refresh <- U.getWord32be
+                         retry   <- U.getWord32be
+                         expire  <- U.getWord32be
+                         ttl     <- U.getWord32be
+                         return SOAFields {
+                                      soaMasterNameServer   = master
+                                    , soaResponsibleMailbox = mail
+                                    , soaSerialNumber       = serial
+                                    , soaRefreshInterval    = refresh
+                                    , soaRetryInterval      = retry
+                                    , soaExpirationLimit    = expire
+                                    , soaMinimumTTL         = ttl
+                                    }
+
+data MB = MB deriving (Show, Eq, Typeable)
+instance RecordType MB DomainName where
+    rtToInt       _ = 7
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
+data MG = MG deriving (Show, Eq, Typeable)
+instance RecordType MG DomainName where
+    rtToInt       _ = 8
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
+data MR = MR deriving (Show, Eq, Typeable)
+instance RecordType MR DomainName where
+    rtToInt       _ = 9
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
+data NULL = NULL deriving (Show, Eq, Typeable)
+instance RecordType NULL BS.ByteString where
+    rtToInt                 _ = 10
+    putRecordData         _ _ = fail "putRecordData NULL can't be defined"
+    getRecordData           _ = fail "getRecordData NULL can't be defined"
+    putRecordDataWithLength _ = \ dat ->
+                                do P.putWord16be $ fromIntegral $ BS.length dat
+                                   P.putByteString dat
+    getRecordDataWithLength _ = do len <- U.getWord16be
+                                   U.getByteString $ fromIntegral len
+
+data WKS = WKS deriving (Show, Eq, Typeable)
+instance RecordType WKS WKSFields where
+    rtToInt       _ = 11
+    putRecordData _ = \ wks ->
+                      do P.putWord32be $ wksAddress wks
+                         P.putWord8 $ fromIntegral $ wksProtocol wks
+                         P.putLazyByteString $ toBitmap $ wksServices wks
+        where
+          toBitmap :: IntSet -> LBS.ByteString
+          toBitmap is
+              = let maxPort   = IS.findMax is
+                    range     = [0 .. maxPort]
+                    isAvail p = p `IS.member` is
+                in
+                  runBitPut $ mapM_ putBit $ map isAvail range
+    getRecordData _ = fail "getRecordData WKS can't be defined"
+
+    getRecordDataWithLength _
+        = do len   <- U.getWord16be
+             addr  <- U.getWord32be
+             proto <- liftM fromIntegral U.getWord8
+             bits  <- U.getByteString $ fromIntegral $ len - 4 - 1
+             return WKSFields {
+                          wksAddress  = addr
+                        , wksProtocol = proto
+                        , wksServices = fromBitmap bits
+                        }
+        where
+          fromBitmap :: BS.ByteString -> IntSet
+          fromBitmap bs
+              = let Right is = runBitGet bs $ worker 0 IS.empty
+                in
+                  is
+
+          worker :: Int -> IntSet -> BitGet IntSet
+          worker pos is
+              = do remain <- BG.remaining
+                   if remain == 0 then
+                       return is
+                     else
+                       do bit <- getBit
+                          if bit then
+                              worker (pos + 1) (IS.insert pos is)
+                            else
+                              worker (pos + 1) is
+
+
+data PTR = PTR deriving (Show, Eq, Typeable)
+instance RecordType PTR DomainName where
+    rtToInt       _ = 12
+    putRecordData _ = putDomainName
+    getRecordData _ = getDomainName
+
 data HINFO = HINFO deriving (Show, Eq, Typeable)
 instance RecordType HINFO (BS.ByteString, BS.ByteString) where
 data HINFO = HINFO deriving (Show, Eq, Typeable)
 instance RecordType HINFO (BS.ByteString, BS.ByteString) where
-    rtToInt       _           = 13
-    putRecordData _ (cpu, os) = do putCharString cpu
-                                   putCharString os
-    getRecordData _           = do cpu <- getCharString
-                                   os  <- getCharString
-                                   return (cpu, os)
-
-
-{-
-data RecordType
-    = A
-    | NS
-    | MD
-    | MF
-    | CNAME
-    | SOA
-    | MB
-    | MG
-    | MR
-    | NULL
-    | WKS
-    | PTR
-    | HINFO
-    | MINFO
-    | MX
-    | TXT
-
-    -- Only for queries:
-    | AXFR
-    | MAILB -- Obsolete
-    | MAILA -- Obsolete
-    | AnyType
-    deriving (Show, Eq)
--}
+    rtToInt       _ = 13
+    putRecordData _ = \ (cpu, os) ->
+                      do putCharString cpu
+                         putCharString os
+    getRecordData _ = do cpu <- getCharString
+                         os  <- getCharString
+                         return (cpu, os)
+
+data MINFO = MINFO deriving (Show, Eq, Typeable)
+instance RecordType MINFO (DomainName, DomainName) where
+    rtToInt       _ = 14
+    putRecordData _ = \ (r, e) ->
+                      do putDomainName r
+                         putDomainName e
+    getRecordData _ = do r <- getDomainName
+                         e <- getDomainName
+                         return (r, e)
+
+data MX = MX deriving (Show, Eq, Typeable)
+instance RecordType MX (Word16, DomainName) where
+    rtToInt       _ = 15
+    putRecordData _ = \ (pref, exch) ->
+                      do P.putWord16be pref
+                         putDomainName exch
+    getRecordData _ = do pref <- U.getWord16be
+                         exch <- getDomainName
+                         return (pref, exch)
+
+data TXT = TXT deriving (Show, Eq, Typeable)
+instance RecordType TXT [BS.ByteString] where
+    rtToInt       _ = 16
+    putRecordData _ = mapM_ putCharString
+    getRecordData _ = fail "getRecordData TXT can't be defined"
+
+    getRecordDataWithLength _ = U.getWord16be >>= worker [] . fromIntegral
+        where
+          worker :: [BS.ByteString] -> Int -> Unpacker s [BS.ByteString]
+          worker soFar 0 = return (reverse soFar)
+          worker soFar n = do str <- getCharString
+                              worker (str : soFar) (0 `max` n - 1 - BS.length str)
+
+data AXFR = AXFR deriving (Show, Eq, Typeable)
+instance QueryType AXFR where
+    qtToInt _ = 252
+
+data MAILB = MAILB deriving (Show, Eq, Typeable)
+instance QueryType MAILB where
+    qtToInt _ = 253
+
+data MAILA = MAILA deriving (Show, Eq, Typeable)
+instance QueryType MAILA where
+    qtToInt _ = 254
+
+data ANY = ANY deriving (Show, Eq, Typeable)
+instance QueryType ANY where
+    qtToInt _ = 255
+instance QueryClass ANY where
+    qcToInt _ = 255
+
+data IN = IN deriving (Show, Eq, Typeable)
+instance RecordClass IN where
+    rcToInt _ = 1
+
+data CS = CS deriving (Show, Eq, Typeable)
+instance RecordClass CS where
+    rcToInt _ = 2
+
+data CH = CH deriving (Show, Eq, Typeable)
+instance RecordClass CH where
+    rcToInt _ = 3
+
+data HS = HS deriving (Show, Eq, Typeable)
+instance RecordClass HS where
+    rcToInt _ = 4
+
 
 instance Binary Message where
 
 instance Binary Message where
-    put m = do put $ msgHeader m
-               putWord16be $ fromIntegral $ length $ msgQuestions m
-               putWord16be $ fromIntegral $ length $ msgAnswers m
-               putWord16be $ fromIntegral $ length $ msgAuthorities m
-               putWord16be $ fromIntegral $ length $ msgAdditionals m
-               mapM_ putQ  $ msgQuestions m
+    put m = P.liftToBinary M.empty $
+            do putBinary $ msgHeader m
+               P.putWord16be $ fromIntegral $ length $ msgQuestions m
+               P.putWord16be $ fromIntegral $ length $ msgAnswers m
+               P.putWord16be $ fromIntegral $ length $ msgAuthorities m
+               P.putWord16be $ fromIntegral $ length $ msgAdditionals m
+               mapM_ putSomeQ  $ msgQuestions m
                mapM_ putSomeRR $ msgAnswers m
                mapM_ putSomeRR $ msgAuthorities m
                mapM_ putSomeRR $ msgAdditionals m
 
                mapM_ putSomeRR $ msgAnswers m
                mapM_ putSomeRR $ msgAuthorities m
                mapM_ putSomeRR $ msgAdditionals m
 
-    get = liftToBinary IM.empty $
+    get = U.liftToBinary IM.empty $
           do hdr  <- getBinary
              nQ   <- liftM fromIntegral U.getWord16be
              nAns <- liftM fromIntegral U.getWord16be
              nAth <- liftM fromIntegral U.getWord16be
              nAdd <- liftM fromIntegral U.getWord16be
           do hdr  <- getBinary
              nQ   <- liftM fromIntegral U.getWord16be
              nAns <- liftM fromIntegral U.getWord16be
              nAth <- liftM fromIntegral U.getWord16be
              nAdd <- liftM fromIntegral U.getWord16be
-             qs   <- replicateM nQ   getQ
+             qs   <- replicateM nQ   getSomeQ
              anss <- replicateM nAns getSomeRR
              aths <- replicateM nAth getSomeRR
              adds <- replicateM nAdd getSomeRR
              anss <- replicateM nAns getSomeRR
              aths <- replicateM nAth getSomeRR
              adds <- replicateM nAdd getSomeRR
@@ -405,8 +754,8 @@ instance Binary Message where
                         }
 
 instance Binary Header where
                         }
 
 instance Binary Header where
-    put h = do putWord16be $ hdMessageID h
-               putLazyByteString flags
+    put h = do P'.putWord16be $ hdMessageID h
+               P'.putLazyByteString flags
         where
           flags = runBitPut $
                   do putNBits 1 $ fromEnum $ hdMessageType h
         where
           flags = runBitPut $
                   do putNBits 1 $ fromEnum $ hdMessageType h
@@ -476,88 +825,83 @@ instance Enum ResponseCode where
     toEnum 5 = Refused
     toEnum _ = undefined
 
     toEnum 5 = Refused
     toEnum _ = undefined
 
-{-
-instance Enum RecordType where
-    fromEnum A       = 1
-    fromEnum NS      = 2
-    fromEnum MD      = 3
-    fromEnum MF      = 4
-    fromEnum CNAME   = 5
-    fromEnum SOA     = 6
-    fromEnum MB      = 7
-    fromEnum MG      = 8
-    fromEnum MR      = 9
-    fromEnum NULL    = 10
-    fromEnum WKS     = 11
-    fromEnum PTR     = 12
-    fromEnum HINFO   = 13
-    fromEnum MINFO   = 14
-    fromEnum MX      = 15
-    fromEnum TXT     = 16
-    fromEnum AXFR    = 252
-    fromEnum MAILB   = 253
-    fromEnum MAILA   = 254
-    fromEnum AnyType = 255
-
-    toEnum 1  = A
-    toEnum 2  = NS
-    toEnum 3  = MD
-    toEnum 4  = MF
-    toEnum 5  = CNAME
-    toEnum 6  = SOA
-    toEnum 7  = MB
-    toEnum 8  = MG
-    toEnum 9  = MR
-    toEnum 10 = NULL
-    toEnum 11 = WKS
-    toEnum 12 = PTR
-    toEnum 13 = HINFO
-    toEnum 14 = MINFO
-    toEnum 15 = MX
-    toEnum 16 = TXT
-    toEnum 252 = AXFR
-    toEnum 253 = MAILB
-    toEnum 254 = MAILA
-    toEnum 255 = AnyType
-    toEnum _  = undefined
--}
-
-instance Enum RecordClass where
-    fromEnum IN       = 1
-    fromEnum CS       = 2
-    fromEnum CH       = 3
-    fromEnum HS       = 4
-    fromEnum AnyClass = 255
-
-    toEnum 1   = IN
-    toEnum 2   = CS
-    toEnum 3   = CH
-    toEnum 4   = HS
-    toEnum 255 = AnyClass
-    toEnum _   = undefined
-
-instance Binary RecordClass where
-    get = liftM (toEnum . fromIntegral) G.getWord16be
-    put = putWord16be . fromIntegral . fromEnum
-
 
 defaultRTTable :: IntMap SomeRT
 defaultRTTable = IM.fromList $ map toPair $
 
 defaultRTTable :: IntMap SomeRT
 defaultRTTable = IM.fromList $ map toPair $
-                 [ wrapRecordType A
-                 , wrapRecordType NS
-                 , wrapRecordType CNAME
-                 , wrapRecordType HINFO
+                 [ SomeRT A
+                 , SomeRT NS
+                 , SomeRT MD
+                 , SomeRT MF
+                 , SomeRT CNAME
+                 , SomeRT SOA
+                 , SomeRT MB
+                 , SomeRT MG
+                 , SomeRT MR
+                 , SomeRT NULL
+                 , SomeRT WKS
+                 , SomeRT PTR
+                 , SomeRT HINFO
+                 , SomeRT MINFO
+                 , SomeRT MX
+                 , SomeRT TXT
                  ]
     where
       toPair :: SomeRT -> (Int, SomeRT)
       toPair srt@(SomeRT rt) = (rtToInt rt, srt)
 
                  ]
     where
       toPair :: SomeRT -> (Int, SomeRT)
       toPair srt@(SomeRT rt) = (rtToInt rt, srt)
 
+defaultQTTable :: IntMap SomeQT
+defaultQTTable = mergeWithRTTable defaultRTTable $ IM.fromList $ map toPair $
+                 [ SomeQT AXFR
+                 , SomeQT MAILB
+                 , SomeQT MAILA
+                 , SomeQT ANY
+                 ]
+    where
+      toPair :: SomeQT -> (Int, SomeQT)
+      toPair sqt@(SomeQT qt) = (qtToInt qt, sqt)
+
+      mergeWithRTTable :: IntMap SomeRT -> IntMap SomeQT -> IntMap SomeQT
+      mergeWithRTTable rts qts
+          = IM.union (toQTTable rts) qts
+
+      toQTTable :: IntMap SomeRT -> IntMap SomeQT
+      toQTTable = IM.map toSomeQT
+
+      toSomeQT :: SomeRT -> SomeQT
+      toSomeQT (SomeRT rt) = SomeQT rt
+
+defaultRCTable :: IntMap SomeRC
+defaultRCTable = IM.fromList $ map toPair $
+                 [ SomeRC IN
+                 , SomeRC CS
+                 , SomeRC CH
+                 , SomeRC HS
+                 ]
+    where
+      toPair :: SomeRC -> (Int, SomeRC)
+      toPair src@(SomeRC rc) = (rcToInt rc, src)
+
+defaultQCTable :: IntMap SomeQC
+defaultQCTable = mergeWithRCTable defaultRCTable $ IM.fromList $ map toPair $
+                 [ SomeQC ANY
+                 ]
+    where
+      toPair :: SomeQC -> (Int, SomeQC)
+      toPair sqc@(SomeQC qc) = (qcToInt qc, sqc)
+
+      mergeWithRCTable :: IntMap SomeRC -> IntMap SomeQC -> IntMap SomeQC
+      mergeWithRCTable rcs qcs
+          = IM.union (toQCTable rcs) qcs
+
+      toQCTable :: IntMap SomeRC -> IntMap SomeQC
+      toQCTable = IM.map toSomeQC
+
+      toSomeQC :: SomeRC -> SomeQC
+      toSomeQC (SomeRC rc) = SomeQC rc
 
 
-wrapQueryType :: RecordType rt dt => rt -> SomeQT
-wrapQueryType = SomeRT
 
 
-wrapRecordType :: RecordType rt dt => rt -> SomeRT
-wrapRecordType = SomeRT
+wrapQuestion :: (QueryType qt, QueryClass qc) => Question qt qc -> SomeQ
+wrapQuestion = SomeQ
 
 
-wrapRecord :: RecordType rt dt => ResourceRecord rt dt -> SomeRR
+wrapRecord :: (RecordType rt dt, RecordClass rc) => ResourceRecord rt rc dt -> SomeRR
 wrapRecord = SomeRR
\ No newline at end of file
 wrapRecord = SomeRR
\ No newline at end of file