packages feed

uniform-io 0.2.0.0 → 1.0.0.0

raw patch · 11 files changed

+623/−371 lines, 11 filesdep ~iproutedep ~uniform-ioPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: iproute, uniform-io

API changes (from Hackage documentation)

- System.IO.Uniform: accept :: BoundedPort -> IO SocketIO
- System.IO.Uniform: bindPort :: Int -> IO BoundedPort
- System.IO.Uniform: closePort :: BoundedPort -> IO ()
- System.IO.Uniform: connectTo :: IP -> Int -> IO SocketIO
- System.IO.Uniform: connectToHost :: String -> Int -> IO SocketIO
- System.IO.Uniform: data BoundedPort
- System.IO.Uniform: data FileIO
- System.IO.Uniform: data SocketIO
- System.IO.Uniform: data StdIO
- System.IO.Uniform: data TlsStream
- System.IO.Uniform: getPeer :: SocketIO -> IO (IP, Int)
- System.IO.Uniform: openFile :: String -> IO FileIO
- System.IO.Uniform: tlsCertificateChainFile :: TlsSettings -> String
- System.IO.Uniform: tlsDHParametersFile :: TlsSettings -> String
- System.IO.Uniform: tlsPrivateKeyFile :: TlsSettings -> String
- System.IO.Uniform.Streamline: instance (Functor m, Monad m) => Applicative (Streamline m)
- System.IO.Uniform.Streamline: instance Monad m => Functor (Streamline m)
- System.IO.Uniform.Streamline: instance Monad m => Monad (Streamline m)
- System.IO.Uniform.Streamline: instance MonadIO m => MonadIO (Streamline m)
- System.IO.Uniform.Streamline: instance MonadTrans Streamline
+ System.IO.Uniform: [tlsCertificateChainFile] :: TlsSettings -> String
+ System.IO.Uniform: [tlsDHParametersFile] :: TlsSettings -> String
+ System.IO.Uniform: [tlsPrivateKeyFile] :: TlsSettings -> String
+ System.IO.Uniform: data TlsIO
+ System.IO.Uniform: instance Data.Default.Class.Default System.IO.Uniform.TlsSettings
+ System.IO.Uniform: instance GHC.Read.Read System.IO.Uniform.TlsSettings
+ System.IO.Uniform: instance GHC.Show.Show System.IO.Uniform.TlsSettings
+ System.IO.Uniform: instance System.IO.Uniform.UniformIO System.IO.Uniform.External.TlsIO
+ System.IO.Uniform: instance System.IO.Uniform.UniformIO System.IO.Uniform.SomeIO
+ System.IO.Uniform: mapOverInput :: forall a io. UniformIO io => io -> Int -> (a -> ByteString -> IO a) -> a -> IO a
+ System.IO.Uniform.ByteString: data ByteStringIO
+ System.IO.Uniform.ByteString: instance System.IO.Uniform.UniformIO System.IO.Uniform.ByteString.ByteStringIO
+ System.IO.Uniform.ByteString: withByteStringIO :: ByteString -> (ByteStringIO -> IO a) -> IO (a, ByteString)
+ System.IO.Uniform.ByteString: withByteStringIO' :: ByteString -> (ByteStringIO -> IO a) -> IO (a, ByteString)
+ System.IO.Uniform.File: data FileIO
+ System.IO.Uniform.File: instance System.IO.Uniform.UniformIO System.IO.Uniform.External.FileIO
+ System.IO.Uniform.File: openFile :: String -> IO FileIO
+ System.IO.Uniform.Network: accept :: BoundedPort -> IO SocketIO
+ System.IO.Uniform.Network: bindPort :: Int -> IO BoundedPort
+ System.IO.Uniform.Network: closePort :: BoundedPort -> IO ()
+ System.IO.Uniform.Network: connectTo :: IP -> Int -> IO SocketIO
+ System.IO.Uniform.Network: connectToHost :: String -> Int -> IO SocketIO
+ System.IO.Uniform.Network: data BoundedPort
+ System.IO.Uniform.Network: data SocketIO
+ System.IO.Uniform.Network: getPeer :: SocketIO -> IO (IP, Int)
+ System.IO.Uniform.Network: instance System.IO.Uniform.UniformIO System.IO.Uniform.External.SocketIO
+ System.IO.Uniform.Std: data StdIO
+ System.IO.Uniform.Std: instance System.IO.Uniform.UniformIO System.IO.Uniform.External.StdIO
+ System.IO.Uniform.Streamline: instance (GHC.Base.Functor m, GHC.Base.Monad m) => GHC.Base.Applicative (System.IO.Uniform.Streamline.Streamline m)
+ System.IO.Uniform.Streamline: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (System.IO.Uniform.Streamline.Streamline m)
+ System.IO.Uniform.Streamline: instance Control.Monad.Trans.Class.MonadTrans System.IO.Uniform.Streamline.Streamline
+ System.IO.Uniform.Streamline: instance GHC.Base.Monad m => GHC.Base.Functor (System.IO.Uniform.Streamline.Streamline m)
+ System.IO.Uniform.Streamline: instance GHC.Base.Monad m => GHC.Base.Monad (System.IO.Uniform.Streamline.Streamline m)
- System.IO.Uniform: startTls :: UniformIO a => TlsSettings -> a -> IO TlsStream
+ System.IO.Uniform: startTls :: UniformIO a => TlsSettings -> a -> IO TlsIO

Files

src/System/IO/Uniform.hs view
@@ -1,8 +1,125 @@+{-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ForeignFunctionInterface #-}+-- {-# LANGUAGE InterruptibleFFI #-}+-- {-# LANGUAGE EmptyDataDecls #-}+ -- | -- Uniform-IO provides a typeclass for uniform access of different types of targets, -- and implementations for abstracting standard streams, files and network connections. -- This module also provides TLS wraping over other IO targets.-module System.IO.Uniform (module System.IO.Uniform.Targets) where+module System.IO.Uniform (+  UniformIO(..),+  TlsSettings(..),+  SomeIO(..), TlsIO,+  mapOverInput+  ) where +import System.IO.Uniform.External -import System.IO.Uniform.Targets+import Foreign+--import Foreign.C.Types+--import Foreign.C.String+import Foreign.C.Error+--import qualified Data.IP as IP+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+--import qualified Data.ByteString.Lazy as LBS+--import qualified Data.ByteString.Builder as BSBuild+--import qualified Data.List as L+import Control.Exception+import Control.Applicative ((<$>))+--import Data.Monoid (mappend)+--import qualified Network.Socket as Soc+import System.IO.Error+--import Control.Concurrent.MVar++import Data.Default.Class++import System.Posix.Types (Fd(..))++-- |+-- Typeclass for uniform IO targets.+class UniformIO a where+  -- | uRead fd n+  --+  --  Reads a block of at most n bytes of data from the IO target.+  --  Reading will block if there's no data available, but will return immediately+  --  if any amount of data is availble.+  --+  --  Must thow System.IO.Error.EOFError if reading beihond EOF.+  uRead  :: a -> Int -> IO ByteString+  -- | uPut fd text+  --+  --  Writes all the bytes of text into the IO target. Takes care of retrying if needed.+  uPut   :: a -> ByteString -> IO ()+  -- | fClose fd+  --+  --  Closes the IO target, releasing any allocated resource. Resources may leak if not called+  --  for every oppened fd.+  uClose :: a -> IO ()+  -- | startTLS fd+  --+  --  Starts a TLS connection over the IO target.+  startTls :: TlsSettings -> a -> IO TlsIO+  -- | isSecure fd+  --+  --  Indicates whether the data written or read from fd is secure at transport.+  isSecure :: a -> Bool+  +-- | A type that wraps any type in the UniformIO class.+data SomeIO = forall a. (UniformIO a) => SomeIO a++instance UniformIO SomeIO where+  uRead (SomeIO s) n = uRead s n+  uPut (SomeIO s) t  = uPut s t+  uClose (SomeIO s) = uClose s+  startTls set (SomeIO s) = startTls set s+  isSecure (SomeIO s) = isSecure s++-- | Settings for starttls functions.+data TlsSettings = TlsSettings {tlsPrivateKeyFile :: String, tlsCertificateChainFile :: String, tlsDHParametersFile :: String} deriving (Read, Show)++instance Default TlsSettings where+  def = TlsSettings "" "" ""+  +-- | UniformIO wrapper that applies TLS to communication on IO target.+-- This type is constructed by calling startTls on other targets.+instance UniformIO TlsIO where+  uRead s n = do+    allocaArray n (+      \b -> do+        count <- c_recvTls (tls s) b $ fromIntegral n+        if count < 0+          then throwErrno "could not read"+          else BS.packCStringLen (b, fromIntegral count)+      )+  uPut s t = do+    BS.useAsCStringLen t (+      \(str, n) -> do+        count <- c_sendTls (tls s) str $ fromIntegral n+        if count < 0+          then throwErrno "could not write"+          else return ()+      )+  uClose s = do+    d <- c_closeTls (tls s)+    f <- Fd <$> c_prepareToClose d+    closeFd f+  startTls _ s = return s+  isSecure _ = True+++-- | mapOverInput io block_size f initial+--   Reads io untill the end of file, evaluating a(i) <- f a(i-1) read_data+--   where a(0) = initial and the last value after io reaches EOF is returned.+--+--   Notice that the length of read_data might not be equal block_size.+mapOverInput :: forall a io. UniformIO io => io -> Int -> (a -> ByteString -> IO a) -> a -> IO a+mapOverInput io block f initial = do+  a <- tryIOError $ uRead io block+  case a of+    Left e -> if isEOFError e then return initial else throw e -- EOF+    Right dt -> do+      i <- f initial dt+      mapOverInput io block f i
+ src/System/IO/Uniform/ByteString.hs view
@@ -0,0 +1,74 @@+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE ForeignFunctionInterface #-}+-- {-# LANGUAGE InterruptibleFFI #-}+-- {-# LANGUAGE EmptyDataDecls #-}++module System.IO.Uniform.ByteString (+  ByteStringIO,+  withByteStringIO, withByteStringIO'+  ) where++import System.IO.Uniform+import System.IO.Uniform.External++import Foreign+--import Foreign.C.Types+--import Foreign.C.String+--import Foreign.C.Error+-- import qualified Data.IP as IP+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString.Builder as BSBuild+--import qualified Data.List as L+--import Control.Exception+--import Control.Applicative ((<$>))+import Data.Monoid (mappend)+--import qualified Network.Socket as Soc+import System.IO.Error+import Control.Concurrent.MVar++--import Data.Default.Class++--import System.Posix.Types (Fd(..))++-- | Wrapper that does UniformIO that reads and writes on the memory.+data ByteStringIO = ByteStringIO {bsioinput :: MVar (ByteString, Bool), bsiooutput :: MVar BSBuild.Builder}+instance UniformIO ByteStringIO where+  uRead s n = do+    (i, eof) <- takeMVar . bsioinput $ s+    if eof+    then do+      putMVar (bsioinput s) (i, eof)+      ioError $ mkIOError eofErrorType "read past end of input" Nothing Nothing+    else do+      let (r, i') = BS.splitAt n i+      let eof' = (BS.null r && n > 0)+      putMVar (bsioinput s) (i', eof')+      return r+  uPut s t = do+    o <- takeMVar . bsiooutput $ s+    let o' = mappend o $ BSBuild.byteString t+    putMVar (bsiooutput s) o'+  uClose _ = return ()+  startTls _ _ = return . TlsIO $ nullPtr+  isSecure _ = True++-- | withByteStringIO input f+--   Runs f with a ByteStringIO that has the given input, returns f's output and+--   the ByteStringIO output.+withByteStringIO :: ByteString -> (ByteStringIO -> IO a) -> IO (a, LBS.ByteString)+withByteStringIO input f = do+  ivar <- newMVar (input, False)+  ovar <- newMVar . BSBuild.byteString $ BS.empty+  let bsio = ByteStringIO ivar ovar+  a <- f bsio+  out <- takeMVar . bsiooutput $ bsio+  return (a, BSBuild.toLazyByteString out)++-- | The same as withByteStringIO, but returns an strict ByteString+withByteStringIO' :: ByteString -> (ByteStringIO -> IO a) -> IO (a, ByteString)+withByteStringIO' input f = do+  (a, t) <- withByteStringIO input f+  return (a, LBS.toStrict t)
+ src/System/IO/Uniform/External.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE InterruptibleFFI #-}+{-# LANGUAGE EmptyDataDecls #-}++module System.IO.Uniform.External where++import Foreign+import Foreign.C.Types+import Foreign.C.String++import System.Posix.Types (Fd(..))++data Nethandler+-- | A bounded IP port from where to accept SocketIO connections.+newtype BoundedPort = BoundedPort {lis :: (Ptr Nethandler)}+data Ds+newtype SocketIO = SocketIO {sock :: (Ptr Ds)}+newtype FileIO = FileIO {fd :: (Ptr Ds)}+data TlsDs+newtype TlsIO = TlsIO {tls :: (Ptr TlsDs)}+data StdIO++closeFd :: Fd -> IO ()+closeFd (Fd f) = c_closeFd f+            +-- | Closes a BoundedPort, and releases any resource used by it.+closePort :: BoundedPort -> IO ()+closePort p = c_closePort (lis p)+++foreign import ccall interruptible "getPort" c_getPort :: CInt -> IO (Ptr Nethandler)+foreign import ccall interruptible "createFromHandler" c_accept :: Ptr Nethandler -> IO (Ptr Ds)+foreign import ccall safe "createFromFileName" c_createFile :: CString -> IO (Ptr Ds)+foreign import ccall interruptible "createToIPv4Host" c_connect4 :: CUInt -> CInt -> IO (Ptr Ds)+foreign import ccall interruptible "createToIPv6Host" c_connect6 :: Ptr CUChar -> CInt -> IO (Ptr Ds)++foreign import ccall interruptible "startSockTls" c_startSockTls :: Ptr Ds -> CString -> CString -> CString -> IO (Ptr TlsDs)+foreign import ccall safe "getPeer" c_getPeer :: Ptr Ds -> Ptr CUInt -> Ptr CUChar -> Ptr CInt -> IO (CInt)++--foreign import ccall safe "getFd" c_getFd :: Ptr Ds -> IO CInt+--foreign import ccall safe "getTlsFd" c_getTlsFd :: Ptr TlsDs -> IO CInt+foreign import ccall safe "closeFd" c_closeFd :: CInt -> IO ()++foreign import ccall safe "prepareToClose" c_prepareToClose :: Ptr Ds -> IO CInt+foreign import ccall safe "closeHandler" c_closePort :: Ptr Nethandler -> IO ()+foreign import ccall safe "closeTls" c_closeTls :: Ptr TlsDs -> IO (Ptr Ds)++foreign import ccall interruptible "sendDs" c_send :: Ptr Ds -> Ptr CChar -> CInt -> IO CInt+foreign import ccall interruptible "stdDsSend" c_sendStd :: Ptr CChar -> CInt -> IO CInt+foreign import ccall interruptible "tlsDsSend" c_sendTls :: Ptr TlsDs -> Ptr CChar -> CInt -> IO CInt++foreign import ccall interruptible "recvDs" c_recv :: Ptr Ds -> Ptr CChar -> CInt -> IO CInt+foreign import ccall interruptible "stdDsRecv" c_recvStd :: Ptr CChar -> CInt -> IO CInt+foreign import ccall interruptible "tlsDsRecv" c_recvTls :: Ptr TlsDs -> Ptr CChar -> CInt -> IO CInt
+ src/System/IO/Uniform/File.hs view
@@ -0,0 +1,72 @@+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE ForeignFunctionInterface #-}+-- {-# LANGUAGE InterruptibleFFI #-}+-- {-# LANGUAGE EmptyDataDecls #-}++module System.IO.Uniform.File (+  FileIO,+  openFile+  ) where++import System.IO.Uniform+import System.IO.Uniform.External++import Foreign+--import Foreign.C.Types+import Foreign.C.String+import Foreign.C.Error+--import qualified Data.IP as IP+--import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+--import qualified Data.ByteString.Lazy as LBS+--import qualified Data.ByteString.Builder as BSBuild+--import qualified Data.List as L+--import Control.Exception+import Control.Applicative ((<$>))+--import Data.Monoid (mappend)+--import qualified Network.Socket as Soc+--import System.IO.Error+--import Control.Concurrent.MVar++--import Data.Default.Class++import System.Posix.Types (Fd(..))+++-- | UniformIO type for file IO.+instance UniformIO FileIO where+  uRead s n = do+    allocaArray n (+      \b -> do+        count <- c_recv (fd s) b $ fromIntegral n+        if count < 0+          then throwErrno "could not read"+          else  BS.packCStringLen (b, fromIntegral count)+      )+  uPut s t = do+    BS.useAsCStringLen t (+      \(str, n) -> do+        count <- c_send (fd s) str $ fromIntegral n+        if count < 0+          then throwErrno "could not write"+          else return ()+      )+  uClose s = do+    f <- Fd <$> c_prepareToClose (fd s)+    closeFd f+  -- Not implemented yet.+  startTls _ _ = return . TlsIO $ nullPtr+  isSecure _ = False+  +  +-- | Open a file for bidirectional IO.+openFile :: String -> IO FileIO+openFile fileName = do+  r <- withCString fileName (+    \f -> fmap FileIO $ c_createFile f+    )+  if fd r == nullPtr+    then throwErrno "could not open file"+    else return r+
+ src/System/IO/Uniform/Network.hs view
@@ -0,0 +1,166 @@+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE ForeignFunctionInterface #-}+-- {-# LANGUAGE InterruptibleFFI #-}+-- {-# LANGUAGE EmptyDataDecls #-}++module System.IO.Uniform.Network (+  SocketIO,+  BoundedPort,+  connectTo,+  connectToHost,+  bindPort,+  accept,+  closePort,+  getPeer+  ) where++import System.IO.Uniform+import System.IO.Uniform.External++import Foreign+import Foreign.C.Types+import Foreign.C.String+import Foreign.C.Error+import qualified Data.IP as IP+--import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+--import qualified Data.ByteString.Lazy as LBS+--import qualified Data.ByteString.Builder as BSBuild+import qualified Data.List as L+import Control.Exception+import Control.Applicative ((<$>))+--import Data.Monoid (mappend)+import qualified Network.Socket as Soc+import System.IO.Error+--import Control.Concurrent.MVar++--import Data.Default.Class++import System.Posix.Types (Fd(..))++-- | UniformIO IP connections.+instance UniformIO SocketIO where+  uRead s n = do+    allocaArray n (+      \b -> do+        count <- c_recv (sock s) b (fromIntegral n)+        if count < 0+          then throwErrno "could not read"+          else BS.packCStringLen (b, fromIntegral count)+      )+  uPut s t = do+    BS.useAsCStringLen t (+      \(str, n) -> do+        count <- c_send (sock s) str $ fromIntegral n+        if count < 0+          then throwErrno "could not write"+          else return ()+      )+  uClose s = do+    f <- Fd <$> c_prepareToClose (sock s)+    closeFd f+  startTls st s = withCString (tlsCertificateChainFile st) (+    \cert -> withCString (tlsPrivateKeyFile st) (+      \key -> withCString (tlsDHParametersFile st) (+        \para -> do+          r <- c_startSockTls (sock s) cert key para+          if r == nullPtr+            then throwErrno "could not start TLS"+            else return . TlsIO $ r+        )+      )+    )+  isSecure _ = False+++-- | connectToHost hostName port+--+--  Connects to the given host and port.+connectToHost :: String -> Int -> IO SocketIO+connectToHost host port = do+  ip <- getAddr+  connectTo ip port+  where+    getAddr :: IO IP.IP+    getAddr = do+      add <- Soc.getAddrInfo Nothing (Just host) Nothing+      case add of+        [] -> throwIO $ mkIOError doesNotExistErrorType "host not found" Nothing Nothing+        (a:_) -> case Soc.addrAddress a of+          Soc.SockAddrInet _ a'  -> return . IP.IPv4 . IP.fromHostAddress $ a'+          Soc.SockAddrInet6 _ _ a' _ -> return . IP.IPv6 . IP.fromHostAddress6 $ a'+          _ -> throwIO $ mkIOError doesNotExistErrorType "host not found" Nothing Nothing+++-- | ConnecctTo ipAddress port+--+--  Connects to the given port of the host at the given IP address.+connectTo :: IP.IP -> Int -> IO SocketIO+connectTo host port = do+  r <- case host of+    IP.IPv4 host' -> fmap SocketIO $ c_connect4 (fromIntegral . IP.toHostAddress $ host') (fromIntegral port)+    IP.IPv6 host' -> fmap SocketIO $ withArray (ipToArray host') (+      \add -> c_connect6 add (fromIntegral port)+      )+  if sock r == nullPtr+    then throwErrno "could not connect to host"+    else return r+  where+    ipToArray :: IP.IPv6 -> [CUChar]+    ipToArray ip = let+      (w0, w1, w2, w3) = IP.toHostAddress6 ip+      in L.concat [wtoc w0, wtoc w1, wtoc w2, wtoc w3]+    wtoc :: Word32 -> [CUChar]+    wtoc w = let+      c0 = fromIntegral $ mod w 256+      w1 = div w 256+      c1 = fromIntegral $ mod w1 256+      w2 = div w1 256+      c2 = fromIntegral $ mod w2 256+      c3 = fromIntegral $ div w2 256+      in [c3, c2, c1, c0]+  +-- | bindPort port+--  Binds to the given IP port, becoming ready to accept connections on it.+--  Binding to port numbers under 1024 will fail unless performed by the superuser,+--  once bounded, a process can reduce its privileges and still accept clients on that port.+bindPort :: Int -> IO BoundedPort+bindPort port = do+  r <- fmap BoundedPort $ c_getPort $ fromIntegral port+  if lis r == nullPtr+    then throwErrno "could not bind to port"+    else return r+  +-- | accept port+--+--  Accept clients on a port previously bound with bindPort.+accept :: BoundedPort -> IO SocketIO+accept port = do+  r <- fmap SocketIO $ c_accept (lis port)+  if sock r == nullPtr+    then throwErrno "could not accept connection"+    else return r++-- | Gets the address of the peer socket of a internet connection.+getPeer :: SocketIO -> IO (IP.IP, Int)+getPeer s = allocaArray 16 (+  \p6 -> alloca (+    \p4 -> alloca (+      \iptype -> do+        p <- c_getPeer (sock s) p4 p6 iptype+        if p == -1+          then throwErrno "could not get peer address"+          else do+          iptp <- peek iptype+          if iptp == 1+            then do --IPv6+            add <- peekArray 16 p6+            return (IP.IPv6 . IP.toIPv6b $ map fromIntegral add, fromIntegral p)+            else do --IPv4+            add <- peek p4+            return (IP.IPv4 . IP.fromHostAddress . fromIntegral $ add, fromIntegral p)+      )+    )+  )+
+ src/System/IO/Uniform/Std.hs view
@@ -0,0 +1,54 @@+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE ForeignFunctionInterface #-}+-- {-# LANGUAGE InterruptibleFFI #-}+-- {-# LANGUAGE EmptyDataDecls #-}++module System.IO.Uniform.Std (+  StdIO+  ) where++import System.IO.Uniform+import System.IO.Uniform.External++import Foreign+--import Foreign.C.Types+--import Foreign.C.String+import Foreign.C.Error+--import qualified Data.IP as IP+--import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+--import qualified Data.ByteString.Lazy as LBS+--import qualified Data.ByteString.Builder as BSBuild+--import qualified Data.List as L+--import Control.Exception+--import Control.Applicative ((<$>))+--import Data.Monoid (mappend)+--import qualified Network.Socket as Soc+--import System.IO.Error+--import Control.Concurrent.MVar++--import Data.Default.Class++--import System.Posix.Types (Fd(..))+-- | UniformIO that reads from stdin and writes to stdout.+instance UniformIO StdIO where+  uRead _ n = do+    allocaArray n (+      \b -> do+        count <- c_recvStd b (fromIntegral n)+        if count < 0+          then throwErrno "could not read"+          else BS.packCStringLen (b, fromIntegral count)+      )+  uPut _ t = do+    BS.useAsCStringLen t (+      \(str, n) -> do+        count <- c_sendStd str $ fromIntegral n+        if count < 0+          then throwErrno "could not write"+          else return ()+      )+  uClose _ = return ()+  startTls _ _ = return . TlsIO $ nullPtr+  isSecure _ = False
src/System/IO/Uniform/Streamline.hs view
@@ -6,6 +6,7 @@ module System.IO.Uniform.Streamline (Streamline, withClient, withServer, withTarget, send, receiveLine, lazyRecieveLine, lazyReceiveN, startTls, runAttoparsec, runAttoparsecAndReturn, isSecure, setTimeout, setEcho) where  import qualified System.IO.Uniform as S+import qualified System.IO.Uniform.Network as N import System.IO.Uniform (UniformIO, SomeIO(..), TlsSettings)  import Control.Monad.Trans.Class@@ -51,7 +52,7 @@ --  Connects to the given server port, runs f, and closes the connection. withServer :: MonadIO m => Streamline m a -> IP -> Int -> m a withServer f host port = do-  ds <- liftIO $ S.connectTo host port+  ds <- liftIO $ N.connectTo host port   (ret, _) <- withTarget' f $ Data (SomeIO ds) defaultTimeout "" False False   liftIO $ S.uClose ds   return ret@@ -59,10 +60,10 @@ -- | withClient f boundPort -- --  Accepts a connection at the bound port, runs f and closes the connection.-withClient :: MonadIO m => (IP -> Int -> Streamline m a) -> S.BoundedPort -> m a+withClient :: MonadIO m => (IP -> Int -> Streamline m a) -> N.BoundedPort -> m a withClient f port = do-  ds <- liftIO $ S.accept port-  (peerIp, peerPort) <- liftIO $ S.getPeer ds+  ds <- liftIO $ N.accept port+  (peerIp, peerPort) <- liftIO $ N.getPeer ds   (ret, _) <- withTarget' (f peerIp peerPort) $ Data (SomeIO ds) defaultTimeout "" False False   liftIO $ S.uClose ds   return ret
− src/System/IO/Uniform/Targets.hs
@@ -1,316 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE InterruptibleFFI #-}-{-# LANGUAGE EmptyDataDecls #-}--module System.IO.Uniform.Targets (TlsSettings(..), UniformIO(..), SocketIO, FileIO, StdIO, TlsStream, BoundedPort, SomeIO(..), connectTo, connectToHost, bindPort, accept, openFile, getPeer, closePort) where--import Foreign-import Foreign.C.Types-import Foreign.C.String-import Foreign.C.Error-import qualified Data.IP as IP-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import qualified Data.List as L-import Control.Exception-import Control.Applicative ((<$>))-import qualified Network.Socket as Soc-import System.IO.Error--import Data.Default.Class--import System.Posix.Types (Fd(..))---- | Settings for starttls functions.-data TlsSettings = TlsSettings {tlsPrivateKeyFile :: String, tlsCertificateChainFile :: String, tlsDHParametersFile :: String} deriving (Read, Show)--instance Default TlsSettings where-  def = TlsSettings "" "" ""---- |--- Typeclass for uniform IO targets.-class UniformIO a where-  -- | uRead fd n-  ---  --  Reads a block of at most n bytes of data from the IO target.-  --  Reading will block if there's no data available, but will return immediately-  --  if any amount of data is availble.-  uRead  :: a -> Int -> IO ByteString-  -- | uPut fd text-  ---  --  Writes all the bytes of text into the IO target. Takes care of retrying if needed.-  uPut   :: a -> ByteString -> IO ()-  -- | fClose fd-  ---  --  Closes the IO target, releasing any allocated resource. Resources may leak if not called-  --  for every oppened fd.-  uClose :: a -> IO ()-  -- | startTLS fd-  ---  --  Starts a TLS connection over the IO target.-  startTls :: TlsSettings -> a -> IO TlsStream-  -- | isSecure fd-  ---  --  Indicates whether the data written or read from fd is secure at transport.-  isSecure :: a -> Bool-  --- | A type that wraps any type in the UniformIO class.-data SomeIO = forall a. (UniformIO a) => SomeIO a--instance UniformIO SomeIO where-  uRead (SomeIO s) n = uRead s n-  uPut (SomeIO s) t  = uPut s t-  uClose (SomeIO s) = uClose s-  startTls set (SomeIO s) = startTls set s-  isSecure (SomeIO s) = isSecure s--data Nethandler--- | A bounded IP port from where to accept SocketIO connections.-newtype BoundedPort = BoundedPort {lis :: (Ptr Nethandler)}-data Ds-newtype SocketIO = SocketIO {sock :: (Ptr Ds)}-newtype FileIO = FileIO {fd :: (Ptr Ds)}-data TlsDs-newtype TlsStream = TlsStream {tls :: (Ptr TlsDs)}-data StdIO---- | UniformIO IP connections.-instance UniformIO SocketIO where-  uRead s n = do-    allocaArray n (-      \b -> do-        count <- c_recv (sock s) b (fromIntegral n)-        if count < 0-          then throwErrno "could not read"-          else BS.packCStringLen (b, fromIntegral count)-      )-  uPut s t = do-    BS.useAsCStringLen t (-      \(str, n) -> do-        count <- c_send (sock s) str $ fromIntegral n-        if count < 0-          then throwErrno "could not write"-          else return ()-      )-  uClose s = do-    f <- Fd <$> c_prepareToClose (sock s)-    closeFd f-  startTls st s = withCString (tlsCertificateChainFile st) (-    \cert -> withCString (tlsPrivateKeyFile st) (-      \key -> withCString (tlsDHParametersFile st) (-        \para -> do-          r <- c_startSockTls (sock s) cert key para-          if r == nullPtr-            then throwErrno "could not start TLS"-            else return . TlsStream $ r-        )-      )-    )-  isSecure _ = False-  --- | UniformIO IP connections.-instance UniformIO StdIO where-  uRead _ n = do-    allocaArray n (-      \b -> do-        count <- c_recvStd b (fromIntegral n)-        if count < 0-          then throwErrno "could not read"-          else BS.packCStringLen (b, fromIntegral count)-      )-  uPut _ t = do-    BS.useAsCStringLen t (-      \(str, n) -> do-        count <- c_sendStd str $ fromIntegral n-        if count < 0-          then throwErrno "could not write"-          else return ()-      )-  uClose _ = return ()-  startTls _ _ = return . TlsStream $ nullPtr-  isSecure _ = False-  --- | UniformIO type for file IO.-instance UniformIO FileIO where-  uRead s n = do-    allocaArray n (-      \b -> do-        count <- c_recv (fd s) b $ fromIntegral n-        if count < 0-          then throwErrno "could not read"-          else  BS.packCStringLen (b, fromIntegral count)-      )-  uPut s t = do-    BS.useAsCStringLen t (-      \(str, n) -> do-        count <- c_send (fd s) str $ fromIntegral n-        if count < 0-          then throwErrno "could not write"-          else return ()-      )-  uClose s = do-    f <- Fd <$> c_prepareToClose (fd s)-    closeFd f-  -- Not implemented yet.-  startTls _ _ = return . TlsStream $ nullPtr-  isSecure _ = False-  --- | UniformIO wrapper that applies TLS to communication on IO target.--- This type is constructed by calling startTls on other targets.-instance UniformIO TlsStream where-  uRead s n = do-    allocaArray n (-      \b -> do-        count <- c_recvTls (tls s) b $ fromIntegral n-        if count < 0-          then throwErrno "could not read"-          else BS.packCStringLen (b, fromIntegral count)-      )-  uPut s t = do-    BS.useAsCStringLen t (-      \(str, n) -> do-        count <- c_sendTls (tls s) str $ fromIntegral n-        if count < 0-          then throwErrno "could not write"-          else return ()-      )-  uClose s = do-    d <- c_closeTls (tls s)-    f <- Fd <$> c_prepareToClose d-    closeFd f-  startTls _ s = return s-  isSecure _ = True---- | connectToHost hostName port------  Connects to the given host and port.-connectToHost :: String -> Int -> IO SocketIO-connectToHost host port = do-  ip <- getAddr-  connectTo ip port-  where-    getAddr :: IO IP.IP-    getAddr = do-      add <- Soc.getAddrInfo Nothing (Just host) Nothing-      case add of-        [] -> throwIO $ mkIOError doesNotExistErrorType "host not found" Nothing Nothing-        (a:_) -> case Soc.addrAddress a of-          Soc.SockAddrInet _ a'  -> return . IP.IPv4 . IP.fromHostAddress $ a'-          Soc.SockAddrInet6 _ _ a' _ -> return . IP.IPv6 . IP.fromHostAddress6 $ a'-          _ -> throwIO $ mkIOError doesNotExistErrorType "host not found" Nothing Nothing----- | ConnecctTo ipAddress port------  Connects to the given port of the host at the given IP address.-connectTo :: IP.IP -> Int -> IO SocketIO-connectTo host port = do-  r <- case host of-    IP.IPv4 host' -> fmap SocketIO $ c_connect4 (fromIntegral . IP.toHostAddress $ host') (fromIntegral port)-    IP.IPv6 host' -> fmap SocketIO $ withArray (ipToArray host') (-      \add -> c_connect6 add (fromIntegral port)-      )-  if sock r == nullPtr-    then throwErrno "could not connect to host"-    else return r-  where-    ipToArray :: IP.IPv6 -> [CUChar]-    ipToArray ip = let-      (w0, w1, w2, w3) = IP.toHostAddress6 ip-      in L.concat [wtoc w0, wtoc w1, wtoc w2, wtoc w3]-    wtoc :: Word32 -> [CUChar]-    wtoc w = let-      c0 = fromIntegral $ mod w 256-      w1 = div w 256-      c1 = fromIntegral $ mod w1 256-      w2 = div w1 256-      c2 = fromIntegral $ mod w2 256-      c3 = fromIntegral $ div w2 256-      in [c3, c2, c1, c0]-  --- | bindPort port---  Binds to the given IP port, becoming ready to accept connections on it.---  Binding to port numbers under 1024 will fail unless performed by the superuser,---  once bounded, a process can reduce its privileges and still accept clients on that port.-bindPort :: Int -> IO BoundedPort-bindPort port = do-  r <- fmap BoundedPort $ c_getPort $ fromIntegral port-  if lis r == nullPtr-    then throwErrno "could not bind to port"-    else return r-  --- | accept port------  Accept clients on a port previously bound with bindPort.-accept :: BoundedPort -> IO SocketIO-accept port = do-  r <- fmap SocketIO $ c_accept (lis port)-  if sock r == nullPtr-    then throwErrno "could not accept connection"-    else return r-  --- | Open a file for bidirectional IO.-openFile :: String -> IO FileIO-openFile fileName = do-  r <- withCString fileName (-    \f -> fmap FileIO $ c_createFile f-    )-  if fd r == nullPtr-    then throwErrno "could not open file"-    else return r---- | Gets the address of the peer socket of a internet connection.-getPeer :: SocketIO -> IO (IP.IP, Int)-getPeer s = allocaArray 16 (-  \p6 -> alloca (-    \p4 -> alloca (-      \iptype -> do-        p <- c_getPeer (sock s) p4 p6 iptype-        if p == -1-          then throwErrno "could not get peer address"-          else do-          iptp <- peek iptype-          if iptp == 1-            then do --IPv6-            add <- peekArray 16 p6-            return (IP.IPv6 . IP.toIPv6b $ map fromIntegral add, fromIntegral p)-            else do --IPv4-            add <- peek p4-            return (IP.IPv4 . IP.fromHostAddress . fromIntegral $ add, fromIntegral p)-      )-    )-  )-    -closeFd :: Fd -> IO ()-closeFd (Fd f) = c_closeFd f-            --- | Closes a BoundedPort, and releases any resource used by it.-closePort :: BoundedPort -> IO ()-closePort p = c_closePort (lis p)--foreign import ccall interruptible "getPort" c_getPort :: CInt -> IO (Ptr Nethandler)-foreign import ccall interruptible "createFromHandler" c_accept :: Ptr Nethandler -> IO (Ptr Ds)-foreign import ccall safe "createFromFileName" c_createFile :: CString -> IO (Ptr Ds)-foreign import ccall interruptible "createToIPv4Host" c_connect4 :: CUInt -> CInt -> IO (Ptr Ds)-foreign import ccall interruptible "createToIPv6Host" c_connect6 :: Ptr CUChar -> CInt -> IO (Ptr Ds)--foreign import ccall interruptible "startSockTls" c_startSockTls :: Ptr Ds -> CString -> CString -> CString -> IO (Ptr TlsDs)-foreign import ccall safe "getPeer" c_getPeer :: Ptr Ds -> Ptr CUInt -> Ptr CUChar -> Ptr CInt -> IO (CInt)----foreign import ccall safe "getFd" c_getFd :: Ptr Ds -> IO CInt---foreign import ccall safe "getTlsFd" c_getTlsFd :: Ptr TlsDs -> IO CInt-foreign import ccall safe "closeFd" c_closeFd :: CInt -> IO ()--foreign import ccall safe "prepareToClose" c_prepareToClose :: Ptr Ds -> IO CInt-foreign import ccall safe "closeHandler" c_closePort :: Ptr Nethandler -> IO ()-foreign import ccall safe "closeTls" c_closeTls :: Ptr TlsDs -> IO (Ptr Ds)--foreign import ccall interruptible "sendDs" c_send :: Ptr Ds -> Ptr CChar -> CInt -> IO CInt-foreign import ccall interruptible "stdDsSend" c_sendStd :: Ptr CChar -> CInt -> IO CInt-foreign import ccall interruptible "tlsDsSend" c_sendTls :: Ptr TlsDs -> Ptr CChar -> CInt -> IO CInt--foreign import ccall interruptible "recvDs" c_recv :: Ptr Ds -> Ptr CChar -> CInt -> IO CInt-foreign import ccall interruptible "stdDsRecv" c_recvStd :: Ptr CChar -> CInt -> IO CInt-foreign import ccall interruptible "tlsDsRecv" c_recvTls :: Ptr TlsDs -> Ptr CChar -> CInt -> IO CInt
test/Blocking.hs view
@@ -5,7 +5,7 @@ import Distribution.TestSuite import Base (simpleTest) import Control.Concurrent(forkIO) -import qualified System.IO.Uniform as U+import System.IO.Uniform.Network import qualified System.IO.Uniform.Streamline as S import System.Timeout (timeout) import Data.ByteString (ByteString)@@ -49,7 +49,7 @@ --   the command must not block. successTimeout :: ByteString -> S.Streamline IO ByteString -> IO Progress successTimeout txt f = do-  recv <- U.bindPort 8888+  recv <- bindPort 8888   forkIO $ S.withClient (\_ _ -> do                             l <- f                             S.send l@@ -62,7 +62,7 @@                                        then return . Finished $ Pass                                        else return . Finished . Fail . C8.unpack $ t                                  ) "127.0.0.1" 8888-  U.closePort recv+  closePort recv   case r' of     Just r -> return r     Nothing -> return . Finished . Fail $ "Execution blocked"@@ -71,7 +71,7 @@ --   Does not care about the result of the command, just wether it blocks. failTimeout :: ByteString -> S.Streamline IO ByteString -> IO Progress failTimeout txt f = do-  recv <- U.bindPort 8888+  recv <- bindPort 8888   forkIO $ S.withClient (\_ _ -> do                             f                             S.send "\n"@@ -82,7 +82,7 @@                                      S.receiveLine                                      return . Finished $ Pass                                  ) "127.0.0.1" 8888-  U.closePort recv+  closePort recv   case r' of     Just r -> return r     Nothing -> return . Finished . Fail $ "Execution blocked"
test/Targets.hs view
@@ -5,36 +5,43 @@ import Distribution.TestSuite import Base (simpleTest) import Control.Concurrent(forkIO) -import qualified System.IO.Uniform as U+import System.IO.Uniform+import System.IO.Uniform.Network+import System.IO.Uniform.File+--import System.IO.Uniform.Std+import System.IO.Uniform.ByteString import System.Timeout (timeout) import qualified Data.ByteString.Char8 as C8+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS  tests :: IO [Test] tests = return [   simpleTest "network" testNetwork,   simpleTest "file" testFile,-  simpleTest "network TLS" testTls+  simpleTest "network TLS" testTls,+  simpleTest "byte string" testBS   ]  testNetwork :: IO Progress testNetwork = do-  recv <- U.bindPort 8888+  recv <- bindPort 8888   forkIO $ do-    s <- U.accept recv-    l <- U.uRead s 100-    U.uPut s l-    U.uClose s+    s <- accept recv+    l <- uRead s 100+    uPut s l+    uClose s     return ()   r' <- timeout 1000000 $ do-        s <- U.connectToHost "127.0.0.1" 8888-        let l = "abcdef\n"-        U.uPut s l-        l' <- U.uRead s 100-        U.uClose s-        if l == l'-          then return . Finished $ Pass-          else return . Finished . Fail . C8.unpack $ l'-  U.closePort recv+    s <- connectToHost "127.0.0.1" 8888+    let l = "abcdef\n"+    uPut s l+    l' <- uRead s 100+    uClose s+    if l == l'+      then return . Finished $ Pass+      else return . Finished . Fail . C8.unpack $ l'+  closePort recv   case r' of     Just r -> return r     Nothing -> return . Finished . Fail $ "Execution blocked"@@ -42,39 +49,56 @@ testFile :: IO Progress testFile = do   let file = "test/testFile"-  s <- U.openFile file+  s <- openFile file   let l = "abcde\n"-  U.uPut s l-  U.uClose s-  s' <- U.openFile file-  l' <- U.uRead s' 100-  U.uClose s'+  uPut s l+  uClose s+  s' <- openFile file+  l' <- uRead s' 100+  uClose s'   if l == l'     then return . Finished $ Pass     else return . Finished . Fail . C8.unpack $ l'  testTls :: IO Progress testTls = do-  recv <- U.bindPort 8888-  let set = U.TlsSettings "test/key.pem" "test/cert.pem" "test/dh.pem"+  recv <- bindPort 8888+  let set = TlsSettings "test/key.pem" "test/cert.pem" "test/dh.pem"   forkIO $ do-    s' <- U.accept recv-    s <- U.startTls set s'-    l <- U.uRead s 100-    U.uPut s l-    U.uClose s+    s' <- accept recv+    s <- startTls set s'+    l <- uRead s 100+    uPut s l+    uClose s     return ()   r' <- timeout 1000000 $ do-    s' <- U.connectToHost "127.0.0.1" 8888-    s <- U.startTls set s'+    s' <- connectToHost "127.0.0.1" 8888+    s <- startTls set s'     let l = "abcdef\n"-    U.uPut s l-    l' <- U.uRead s 100-    U.uClose s+    uPut s l+    l' <- uRead s 100+    uClose s     if l == l'       then return . Finished $ Pass       else return . Finished . Fail . C8.unpack $ l'-  U.closePort recv+  closePort recv   case r' of     Just r -> return r     Nothing -> return . Finished . Fail $ "Execution blocked"++testBS :: IO Progress+testBS = do+  let dt = "Some data to test ByteString"+  (len, echo) <- withByteStringIO' dt (+    \io -> let+      count = countAndEcho io :: Int -> ByteString -> IO Int+      in mapOverInput io 2 count 0+    ) :: IO (Int, ByteString)+  if dt /= echo || BS.length dt /= len+    then return . Finished . Fail $ "Failure on ByteStringIO test"+    else return . Finished $ Pass+  where+    countAndEcho :: UniformIO io => io -> Int -> ByteString -> IO Int+    countAndEcho io initial dt = do+      uPut io dt+      return $ initial + BS.length dt
uniform-io.cabal view
@@ -10,10 +10,10 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:    0.2.0.0+version:    1.0.0.0  -- A short (one-line) description of the package.-synopsis:   Uniform IO over files, network, watever.+synopsis:   Uniform IO over files, network, anything.  -- A longer description of the package. description:@@ -70,19 +70,23 @@ source-repository this   type:     git   location: https://sealgram.com/git/haskell/uniform-io-  tag:   0.2.0.0+  tag:   1.0.0.0  library   -- Modules exported by the library.   exposed-modules:       System.IO.Uniform,+      System.IO.Uniform.Network,+      System.IO.Uniform.File,+      System.IO.Uniform.Std,+      System.IO.Uniform.ByteString,       System.IO.Uniform.Streamline -  ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns+  ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns -fno-warn-orphans    -- Modules included in this library but not exported.-  other-modules:       -        System.IO.Uniform.Targets+  other-modules:+      System.IO.Uniform.External    -- LANGUAGE extensions used by modules in this package.   other-extensions:@@ -111,7 +115,7 @@   includes: ds.h   install-includes: ds.h   C-Sources: src/System/IO/Uniform/ds.c-  extra-libraries: ssl, pthread+  extra-libraries: ssl, crypto, pthread  Test-suite targets   type: detailed-0.9@@ -122,8 +126,9 @@     base >=4.7 && <5.0,     Cabal >= 1.9.2,     bytestring >=0.10 && <1.0,-    uniform-io == 0.2.0.0+    uniform-io == 1.0.0.0   ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns -threaded+  default-language: Haskell2010  Test-suite blocking   type: detailed-0.9@@ -135,5 +140,6 @@     Cabal >= 1.9.2,     bytestring >=0.10 && <1.0,     attoparsec >=0.10 && <1.0,-    uniform-io == 0.2.0.0+    uniform-io == 1.0.0.0   ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns -threaded+  default-language: Haskell2010