{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | A simple protocol for sending serializable data over handles -- -- Please note that this is a very simple implementation that works -- fine for most of that data, however, the size of the data you might -- send at one go is limited to MAX_WORD32 bytes. We use 'cereal' for -- serialization. module System.Restricted.Worker.Protocol ( sendData , getData , getDataSafe , DecodeResult , ProtocolException(..) ) where import Control.Applicative ((<$>)) import Control.Exception (IOException, catch, throw) import Control.Monad.Trans (lift) import Control.Monad.Trans.Either (hoistEither, left, runEitherT) import Data.ByteString (ByteString, hGet, hPut) import qualified Data.ByteString as BS import Data.Serialize (Serialize, decode, encode) import Data.Word (Word32) import GHC.IO.Handle (Handle, hFlush) import System.Restricted.Worker.Types -- | Result of the deserialization type DecodeResult a = Either String a -- | Send some serialiazable data over a handle. -- Returns 'ByteString' representing the encoded data. May throw -- 'ProtocolException' sendData :: Serialize a => Handle -> a -> IO ByteString sendData h d = sendData' h d `catch` \(e :: IOException) -> throw (HandleException e) -- | Read the data from a handle and deserialize it. -- May throw 'ProtocolException' getData :: Serialize a => Handle -> IO a getData h = getData' h `catch` \(e :: IOException) -> throw (HandleException e) sendData' :: Serialize a => Handle -> a -> IO ByteString sendData' hndl datum = do let encoded = encode datum let len = (fromIntegral . BS.length $ encoded) :: Word32 hPut hndl (encode len) hFlush hndl hPut hndl encoded hFlush hndl return encoded getData' :: Serialize a => Handle -> IO a getData' hndl = do lenD :: DecodeResult Word32 <- decode <$> hGet hndl 4 let len = case lenD of Right i -> fromIntegral i Left str -> throw (ConversionException $ "length\n" ++ str) res <- decode <$> hGet hndl len case res of Left str -> throw (ConversionException $ "Deserialization error:\n" ++ str) Right x -> return x -- | Safe version of 'getData' that doesn't throw 'ProtocolException' getDataSafe :: Serialize a => Handle -> IO (DecodeResult a) getDataSafe hndl = runEitherT $ do lenD :: DecodeResult Word32 <- decode <$> lift (hGet hndl 4) case lenD of Left str -> left $ "Conversion error while reading length: " ++ str Right len -> hoistEither =<< decode <$> (lift (hGet hndl (fromIntegral len)))