+module Network.DNS.Packer
+ ( Packer
+
+ , pack
+ , pack'
+
+ , getState
+ , setState
+ , modifyState
+
+ , bytesWrote
+ , withOffset
+
+ , putByteString
+ , putLazyByteString
+
+ , putWord8
+ , putWord16be
+ , putWord32be
+
+ , putBinary
+ , liftToBinary
+ )
+ where
+
+import qualified Data.Binary as Binary
+import qualified Data.Binary.Put as Bin
+import Data.Bits
+import qualified Data.ByteString as Strict
+import qualified Data.ByteString.Lazy as Lazy
+import Data.Int
+import Data.Word
+
+
+data PackingState s
+ = PackingState {
+ stResult :: !Lazy.ByteString
+ , stBytesWrote :: !Int64
+ , stUserState :: s
+ }
+
+newtype Packer s a = P { unP :: PackingState s -> (a, PackingState s) }
+
+instance Monad (Packer s) where
+ return a = P (\ s -> (a, s))
+ m >>= k = P (\ s -> let (a, s') = unP m s
+ in
+ unP (k a) s')
+ fail err = do bytes <- get stBytesWrote
+ P (error (err
+ ++ ". Failed packing at byte position "
+ ++ show bytes))
+
+get :: (PackingState s -> a) -> Packer s a
+get f = P (\ s -> (f s, s))
+
+set :: (PackingState s -> PackingState s) -> Packer s ()
+set f = P (\ s -> ((), f s))
+
+mkState :: Lazy.ByteString -> Int64 -> s -> PackingState s
+mkState xs n s
+ = PackingState {
+ stResult = xs
+ , stBytesWrote = n
+ , stUserState = s
+ }
+
+pack' :: Packer s a -> s -> (Lazy.ByteString, s, a)
+pack' m s
+ = let (a, s') = unP m (mkState Lazy.empty 0 s)
+ in
+ (stResult s', stUserState s', a)
+
+pack :: Packer s a -> s -> Lazy.ByteString
+pack = (fst' .) . pack'
+ where
+ fst' (xs, _, _) = xs
+
+getState :: Packer s s
+getState = get stUserState
+
+setState :: s -> Packer s ()
+setState = modifyState . const
+
+modifyState :: (s -> s) -> Packer s ()
+modifyState f
+ = set $ \ st -> st { stUserState = f (stUserState st) }
+
+bytesWrote :: Integral i => Packer s i
+bytesWrote = get stBytesWrote >>= return . fromIntegral
+
+withOffset :: Int64 -> Packer s a -> Packer s a
+withOffset n m
+ = P $ \ s -> let (taken, dropped) = Lazy.splitAt n (stResult s)
+ padded = Lazy.take n (taken `Lazy.append` Lazy.repeat 0)
+ tempState = s {
+ stResult = padded
+ , stBytesWrote = stBytesWrote s - Lazy.length dropped
+ }
+ (a, tempState') = unP m tempState
+ newState = tempState {
+ stResult = replaceHead (stResult s) (stResult tempState')
+ , stBytesWrote = max (stBytesWrote s) (stBytesWrote tempState')
+ }
+ in
+ (a, newState)
+ where
+ replaceHead :: Lazy.ByteString -> Lazy.ByteString -> Lazy.ByteString
+ replaceHead world newHead
+ = let rest = Lazy.drop (Lazy.length newHead) world
+ in
+ newHead `Lazy.append` rest
+
+
+putByteString :: Strict.ByteString -> Packer s ()
+putByteString = putLazyByteString . Lazy.fromChunks . (:[])
+
+putLazyByteString :: Lazy.ByteString -> Packer s ()
+putLazyByteString xs
+ = set $ \ st -> st {
+ stResult = stResult st `Lazy.append` xs
+ , stBytesWrote = stBytesWrote st + Lazy.length xs
+ }
+
+putWord8 :: Word8 -> Packer s ()
+putWord8 w
+ = set $ \ st -> st {
+ stResult = stResult st `Lazy.snoc` w
+ , stBytesWrote = stBytesWrote st + 1
+ }
+
+putWord16be :: Word16 -> Packer s ()
+putWord16be w
+ = do putWord8 $ fromIntegral $ (w `shiftR` 8) .&. 0xFF
+ putWord8 $ fromIntegral $ w .&. 0xFF
+
+putWord32be :: Word32 -> Packer s ()
+putWord32be w
+ = do putWord8 $ fromIntegral $ (w `shiftR` 24) .&. 0xFF
+ putWord8 $ fromIntegral $ (w `shiftR` 16) .&. 0xFF
+ putWord8 $ fromIntegral $ (w `shiftR` 8) .&. 0xFF
+ putWord8 $ fromIntegral $ w .&. 0xFF
+
+
+putBinary :: Binary.Binary a => a -> Packer s ()
+putBinary = putLazyByteString . Binary.encode
+
+
+liftToBinary :: s -> Packer s a -> Bin.PutM a
+liftToBinary s m
+ = do let (a, s') = unP m (mkState Lazy.empty 0 s)
+
+ Bin.putLazyByteString (stResult s')
+ return a