diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Renzo Carbonara
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Renzo Carbonara nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PEOPLE b/PEOPLE
new file mode 100644
--- /dev/null
+++ b/PEOPLE
@@ -0,0 +1,5 @@
+The following people have participated in creating this library, either
+by directly contributing code or by providing thoughtful input in
+discussions about the library design.
+
+Renzo Carbonara
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# pipes-network-tls
+
+Utilities to deal with TLS-secured network connections using the
+**pipes** and **pipes-safe** libraries.
+
+Currently, only TCP sockets are supported.
+
+Check the source or rendered Haddocks for extensive documentation.
+
+This code is licensed under the terms of the so called **3-clause BSD
+license**. Read the file named ``LICENSE`` found in this same directory
+for details.
+
+See the ``PEOPLE`` file to learn about the people involved in this
+effort.
+
+## Building the development version
+
+Use [cabal-meta](http://hackage.haskell.org/package/cabal-meta) and
+[cabal-dev](http://hackage.haskell.org/package/cabal-dev):
+
+    cabal-meta --dev install
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/examples/tls-echo.hs b/examples/tls-echo.hs
new file mode 100644
--- /dev/null
+++ b/examples/tls-echo.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import           Control.Applicative
+import           Control.Proxy              ((>->))
+import qualified Control.Proxy              as P
+import           Control.Proxy.TCP.TLS      (contextReadS, contextWriteD)
+import qualified Data.ByteString.Char8      as B
+import           Data.Certificate.X509      (X509)
+import           Data.Char                  (toUpper)
+import           Data.Monoid                ((<>))
+import qualified Network.Simple.TCP.TLS     as Z
+import qualified Network.Socket             as NS
+import qualified Network.TLS                as T
+import           Network.TLS.Extra          as TE
+import           System.Console.GetOpt
+import           System.Environment         (getProgName, getArgs)
+import qualified Data.CertificateStore      as C
+
+server :: Z.Credential -> Z.HostPreference -> NS.ServiceName
+       -> Maybe C.CertificateStore -> IO ()
+server cred hp port mcs = do
+    let ss = Z.makeServerSettings cred mcs
+    Z.serve ss hp port $ \(ctx,caddr) -> do
+       putStrLn $ show caddr <> " joined."
+       P.runProxy $ contextReadS ctx >-> P.mapD (B.map toUpper) >-> contextWriteD ctx
+       putStrLn $ show caddr <> " quit."
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case getOpt RequireOrder options args of
+      (actions, [hostname,port], _) -> do
+        opts <- foldl (>>=) (return defaultOptions) actions
+        let !cred = Z.Credential (optServerCert opts) (optServerKey opts) []
+        server cred (Z.Host hostname) port
+               (C.makeCertificateStore . pure <$> optCACert opts)
+      (_,_,msgs) -> do
+        pn <- getProgName
+        let header = "Usage: " <> pn <> " [OPTIONS] HOSTNAME PORT"
+        error $ concat msgs ++ usageInfo header options
+
+--------------------------------------------------------------------------------
+-- The boring stuff below is related to command line parsing
+
+data Options = Options
+  { optServerCert :: X509
+  , optServerKey  :: T.PrivateKey
+  , optCACert     :: Maybe X509
+  } deriving (Show)
+
+defaultOptions :: Options
+defaultOptions = Options
+  { optServerCert = error "Missing optServerCert"
+  , optServerKey  = error "Missing optServerKey"
+  , optCACert     = Nothing
+  }
+
+options :: [OptDescr (Options -> IO Options)]
+options =
+  [ Option [] ["cert"]   (ReqArg readServerCert "FILE") "Server certificate"
+  , Option [] ["key"]    (ReqArg readServerKey  "FILE") "Server private key"
+  , Option [] ["cacert"] (OptArg readCACert     "FILE")
+    "CA certificate to verify a client certificate, if given"
+  ]
+
+readServerCert :: FilePath -> Options -> IO Options
+readServerCert arg opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optServerCert = cert }
+
+readServerKey :: FilePath -> Options -> IO Options
+readServerKey arg opt = do
+    key <- TE.fileReadPrivateKey arg
+    return $ opt { optServerKey = key }
+
+readCACert :: Maybe FilePath -> Options -> IO Options
+readCACert Nothing    opt = return opt
+readCACert (Just arg) opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optCACert = Just cert }
+
diff --git a/examples/tls-tunnel.hs b/examples/tls-tunnel.hs
new file mode 100644
--- /dev/null
+++ b/examples/tls-tunnel.hs
@@ -0,0 +1,143 @@
+{-# LANGUAGE BangPatterns #-}
+
+-- Yeah, yeah... I know. This code could be a bit more organized.
+
+module Main (main) where
+
+import           Control.Concurrent.Async   as A
+import           Control.Applicative
+import           Control.Proxy              ((>->))
+import qualified Control.Proxy              as P
+import qualified Control.Proxy.TCP.TLS      as Pt
+import           Data.Certificate.X509      (X509)
+import           Data.Maybe                 (maybeToList)
+import           Data.Monoid                ((<>))
+import qualified Network.Socket             as NS
+import qualified Network.TLS                as T
+import           Network.TLS.Extra          as TE
+import           System.Certificate.X509    (getSystemCertificateStore)
+import           System.Console.GetOpt
+import           System.Environment         (getProgName, getArgs)
+import qualified Data.CertificateStore      as C
+
+runTlsTunnel
+  :: Pt.ServerSettings       -- ^Local server settings
+  -> Pt.HostPreference       -- ^Local host to bind
+  -> NS.ServiceName          -- ^Local port to bind
+  -> Pt.ClientSettings       -- ^Client to remote server settings.
+  -> NS.HostName             -- ^Remote host name to connect to
+  -> NS.ServiceName          -- ^Remote tcp port to connect to
+  -> IO ()
+runTlsTunnel sS sHp sPort cS cHost cPort = do
+    Pt.serve sS sHp sPort $ \(sCtx, sAddr) -> do
+        let sMsg = show sAddr
+        putStrLn $ sMsg <> " joined."
+        putStrLn $ sMsg <> " is being tunneled to " <> show (cHost, cPort)
+        Pt.connect cS cHost cPort $ \(cCtx, cAddr) -> do
+            let cMsg = "Secure connection to " <> show cAddr
+            putStrLn $ cMsg <> " established."
+            a1 <- A.async . P.runProxy $ Pt.contextReadS sCtx >-> Pt.contextWriteD cCtx
+            P.runProxy $ Pt.contextReadS cCtx >-> Pt.contextWriteD sCtx
+            A.wait a1
+            putStrLn $ cMsg <> " closed."
+        putStrLn $ sMsg <> " quit."
+
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case getOpt RequireOrder options args of
+      (actions, [locHost,locPort,remHost,remPort], _) -> do
+        opts <- foldl (>>=) (return defaultOptions) actions
+        let !sCred = Pt.Credential (optLocalCert opts) (optLocalKey opts) []
+            smcStore = C.makeCertificateStore . pure <$> optLocalCACert opts
+            sS = Pt.makeServerSettings sCred smcStore
+        ccStore <- case optRemoteCACert opts of
+                     Nothing -> getSystemCertificateStore
+                     Just ca -> return $ C.makeCertificateStore [ca]
+        let !cCreds = maybeToList $ Pt.Credential <$> optRemoteCert opts
+                                                  <*> optRemoteKey opts
+                                                  <*> pure []
+            cS = Pt.makeClientSettings cCreds (Nothing) ccStore
+        runTlsTunnel sS (Pt.Host locHost) locPort cS remHost remPort
+      (_,_,msgs) -> do
+        pn <- getProgName
+        let header = "Usage: " <> pn
+              <> " [OPTIONS] LOCAL-HOST LOCAL-PORT REMOTE-HOST REMOTE-PORT"
+        error $ concat msgs ++ usageInfo header options
+
+
+--------------------------------------------------------------------------------
+-- The boring stuff below is related to command line parsing
+
+
+data Options = Options
+  { optLocalCert    :: X509
+  , optLocalKey     :: T.PrivateKey
+  , optLocalCACert  :: Maybe X509
+  , optRemoteCert   :: Maybe X509
+  , optRemoteKey    :: Maybe T.PrivateKey
+  , optRemoteCACert :: Maybe X509
+  } deriving (Show)
+
+defaultOptions :: Options
+defaultOptions = Options
+  { optLocalCert    = error "Missing optLocalCert"
+  , optLocalKey     = error "Missing optLocalKey"
+  , optLocalCACert  = Nothing
+  , optRemoteCert   = Nothing
+  , optRemoteKey    = Nothing
+  , optRemoteCACert = Nothing
+  }
+
+options :: [OptDescr (Options -> IO Options)]
+options =
+  [ Option [] ["lcert"]   (ReqArg readLocalCert    "FILE")
+    "Local server certificate"
+  , Option [] ["lkey"]    (ReqArg readLocalKey     "FILE")
+    "Local server private key"
+  , Option [] ["lcacert"] (OptArg readLocalCACert  "FILE")
+    "If given, request a client certificate for incomming connections\
+    \ and verify it against this CA."
+  , Option [] ["rcert"]   (OptArg readRemoteCert   "FILE")
+    "Certificate to provide to remote server if requested"
+  , Option [] ["rkey"]    (OptArg readRemoteKey    "FILE")
+    "Key to use together with 'rcert', if requested"
+  , Option [] ["rcacert"] (OptArg readRemoteCACert "FILE")
+    "If given, verify the remote server certificate using this CA,\
+    \ otherwise use the operating system default CAs."
+  ]
+
+readLocalCert :: FilePath -> Options -> IO Options
+readLocalCert arg opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optLocalCert = cert }
+
+readLocalKey :: FilePath -> Options -> IO Options
+readLocalKey arg opt = do
+    key <- TE.fileReadPrivateKey arg
+    return $ opt { optLocalKey = key }
+
+readLocalCACert :: Maybe FilePath -> Options -> IO Options
+readLocalCACert Nothing    opt = return opt
+readLocalCACert (Just arg) opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optLocalCACert = Just cert }
+
+readRemoteCert :: Maybe FilePath -> Options -> IO Options
+readRemoteCert Nothing    opt = return opt
+readRemoteCert (Just arg) opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optRemoteCert = Just cert }
+
+readRemoteKey :: Maybe FilePath -> Options -> IO Options
+readRemoteKey Nothing    opt = return opt
+readRemoteKey (Just arg) opt = do
+    key <- TE.fileReadPrivateKey arg
+    return $ opt { optRemoteKey = Just key }
+
+readRemoteCACert :: Maybe FilePath -> Options -> IO Options
+readRemoteCACert Nothing    opt = return opt
+readRemoteCACert (Just arg) opt = do
+    cert <- TE.fileReadCertificate arg
+    return $ opt { optRemoteCACert = Just cert }
diff --git a/pipes-network-tls.cabal b/pipes-network-tls.cabal
new file mode 100644
--- /dev/null
+++ b/pipes-network-tls.cabal
@@ -0,0 +1,54 @@
+name:               pipes-network-tls
+version:            0.1.0.0
+license:            BSD3
+license-file:       LICENSE
+copyright:          Copyright (c) Renzo Carbonara 2013
+author:             Renzo Carbonara
+maintainer:         renzocarbonaraλgmail.com
+stability:          Experimental
+tested-with:        GHC == 7.4.1
+homepage:           https://github.com/k0001/pipes-network-tls
+bug-reports:        https://github.com/k0001/pipes-network-tls/issues
+category:           Pipes, Network
+build-type:         Simple
+synopsis:           TLS-secured network connections support for pipes.
+cabal-version:      >=1.8
+extra-source-files:
+  README.md
+  PEOPLE
+  examples/tls-echo.hs
+  examples/tls-tunnel.hs
+description:
+  Use TLS-secured network connections together with the @pipes@ ecosystem.
+  .
+  This package is organized using the following namespaces:
+  .
+  * "Control.Proxy.TCP.TLS" exports 'Control.Proxy.Proxy's and functions for
+  establishing and using TLS-secured TCP connections.
+  .
+  * "Control.Proxy.TCP.TLS.Safe" is similar to "Control.Proxy.TCP.TLS", except
+  the exported 'Control.Proxy.Proxy's themselves can obtain new TLS resources
+  safely by using the facilities providied by the @pipes-safe@ package.
+
+source-repository head
+    type: git
+    location: git://github.com/k0001/pipes-network-tls.git
+
+library
+    hs-source-dirs: src
+    build-depends:
+        base                (==4.*),
+        bytestring          (>=0.9.2.1),
+        network,
+        network-simple-tls  (>=0.1 && <0.2),
+        pipes               (>=3.3 && <3.4),
+        pipes-safe          (>=1.2 && <1.3),
+        pipes-network       (>=0.5 && <0.6),
+        tls                 (>=1.1 && <1.2),
+        transformers        (>=0.2 && <0.4)
+    exposed-modules:
+        Control.Proxy.TCP.TLS
+        Control.Proxy.TCP.TLS.Safe
+    ghc-options: -Wall -fno-warn-unused-do-bind
+
+
diff --git a/src/Control/Proxy/TCP/TLS.hs b/src/Control/Proxy/TCP/TLS.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Proxy/TCP/TLS.hs
@@ -0,0 +1,181 @@
+-- | This module exports functions that allow you to use TLS-secured
+-- TCP connections as streams, as well as utilities to connect to a
+-- TLS-enabled TCP server or running your own.
+--
+-- If you need to safely connect to a TLS-enabled TCP server or run your own
+-- /within/ a pipes pipeline, then you /must/ use the functions exported from
+-- the module "Control.Proxy.TCP.TLS.Safe" instead.
+--
+-- This module re-exports many functions and types from "Network.Simple.TCP.TLS"
+-- module in the @network-simple@ package. You might refer to that module for
+-- more documentation.
+
+module Control.Proxy.TCP.TLS (
+  -- * Client side
+  -- $client-side
+    S.connect
+  , S.ClientSettings
+  , S.getDefaultClientSettings
+  , S.makeClientSettings
+
+  -- * Server side
+  -- $server-side
+  , S.serve
+  , S.ServerSettings
+  , S.makeServerSettings
+  -- ** Listening
+  , S.listen
+  -- ** Accepting
+  , S.accept
+  , S.acceptFork
+
+  -- * TLS context streams
+  -- $socket-streaming
+  , contextReadS
+  , contextWriteD
+  -- ** Timeouts
+  -- $socket-streaming-timeout
+  , contextReadTimeoutS
+  , contextWriteTimeoutD
+
+  -- * Exports
+  , S.HostPreference(..)
+  , S.Credential(..)
+  , Timeout(..)
+  ) where
+
+import           Control.Monad.Trans.Class
+import qualified Control.Proxy                  as P
+import           Control.Proxy.TCP              (Timeout(..))
+import qualified Control.Proxy.Trans.Either     as PE
+import qualified Data.ByteString                as B
+import           Data.Monoid
+import qualified Network.Simple.TCP.TLS         as S
+import qualified Network.TLS                    as T
+import           System.Timeout                 (timeout)
+
+--------------------------------------------------------------------------------
+
+-- $client-side
+--
+-- Here's how you could run a simple TLS-secured TCP client:
+--
+-- > import Control.Proxy.TCP.TLS
+-- >
+-- > settings <- getDefaultClientSettings
+-- > connect settings "www.example.org" "443" $ \(tlsCtx, remoteAddr) -> do
+-- >   putStrLn $ "Secure connection established to " ++ show remoteAddr
+-- >   -- now you may use tlsCtx as you please within this scope, possibly with
+-- >   -- the contextReadS or contextWriteD proxies explained below.
+
+--------------------------------------------------------------------------------
+
+-- $server-side
+--
+-- Here's how you could run a simple TLS-secured TCP server that handles in
+-- different threads each incoming connection to port @4433@ at hostname
+-- @example.org@. You will need a X509 certificate and a private key appropiate
+-- to be used at that hostname.
+--
+-- > import Control.Proxy.TCP.TLS
+-- > import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
+-- >
+-- > cert <- fileReadCertificate "~/example.org.crt"
+-- > pkey <- fileReadPrivateKey  "~/example.org.key"
+-- > let cred = Credential cert pkey []
+-- >     settings = makeServerSettings cred Nothing
+-- >
+-- > serve settings (Host "example.org") "4433" $ \(tlsCtx, remoteAddr) -> do
+-- >   putStrLn $ "Secure connection established from " ++ show remoteAddr
+-- >   -- now you may use tlsCtx as you please within this scope, possibly with
+-- >   -- the contextReadS or contextWriteD proxies explained below.
+--
+-- If you need more control on the way your server runs, then you can use more
+-- advanced functions such as 'listen', 'accept' and 'acceptFork'.
+
+--------------------------------------------------------------------------------
+
+-- $socket-streaming
+--
+-- Once you have an established TLS connection 'T.Context', then you can use the
+-- following 'P.Proxy's to interact with the other connection end using streams.
+
+-- | Receives decrypted bytes from the remote end, sending them downstream.
+--
+-- Up to @16384@ decrypted bytes will be received at once. The TLS connection is
+-- automatically renegotiated if a /ClientHello/ message is received.
+--
+-- If the remote peer closes its side of the connection or EOF is reached,
+-- this proxy returns.
+contextReadS
+  :: P.Proxy p
+  => T.Context          -- ^Established TLS connection context.
+  -> () -> P.Producer p B.ByteString IO ()
+contextReadS ctx = P.runIdentityK loop where
+    loop () = do
+      mbs <- lift (S.recv ctx)
+      case mbs of
+        Just bs -> P.respond bs >>= loop
+        Nothing -> return ()
+{-# INLINABLE contextReadS #-}
+
+-- | Encrypts and sends to the remote end the bytes received from upstream,
+-- then forwards such same bytes downstream.
+--
+-- If the remote peer closes its side of the connection, this proxy returns.
+--
+-- Requests from downstream are forwarded upstream.
+contextWriteD
+  :: P.Proxy p
+  => T.Context          -- ^Established TLS connection context.
+  -> x -> p x B.ByteString x B.ByteString IO r
+contextWriteD ctx = P.runIdentityK loop where
+    loop x = do
+      a <- P.request x
+      lift (S.send ctx a)
+      P.respond a >>= loop
+{-# INLINABLE contextWriteD #-}
+
+--------------------------------------------------------------------------------
+
+-- $socket-streaming-timeout
+--
+-- These proxies behave like the similarly named ones above, except they support
+-- timing out the interaction with the remote end.
+
+-- | Like 'contextReadS', except it throws a 'Timeout' exception in the
+-- 'PE.EitherP' proxy transformer if receiving data from the remote end takes
+-- more time than specified.
+contextReadTimeoutS
+  :: P.Proxy p
+  => Int                -- ^Timeout in microseconds (1/10^6 seconds).
+  -> T.Context          -- ^Established TLS connection context.
+  -> () -> P.Producer (PE.EitherP Timeout p) B.ByteString IO ()
+contextReadTimeoutS wait ctx = loop where
+    loop () = do
+      mmbs <- lift (timeout wait (S.recv ctx))
+      case mmbs of
+        Just (Just bs) -> P.respond bs >>= loop
+        Just Nothing   -> return ()
+        Nothing        -> PE.throw ex
+    ex = Timeout $ "contextReadTimeoutS: " <> show wait <> " microseconds."
+{-# INLINABLE contextReadTimeoutS #-}
+
+-- | Like 'contextWriteD', except it throws a 'Timeout' exception in the
+-- 'PE.EitherP' proxy transformer if sending data to the remote end takes
+-- more time than specified.
+contextWriteTimeoutD
+  :: P.Proxy p
+  => Int                -- ^Timeout in microseconds (1/10^6 seconds).
+  -> T.Context          -- ^Established TLS connection context.
+  -> x -> (PE.EitherP Timeout p) x B.ByteString x B.ByteString IO r
+contextWriteTimeoutD wait ctx = loop where
+    loop x = do
+      a <- P.request x
+      m <- lift (timeout wait (S.send ctx a))
+      case m of
+        Just () -> P.respond a >>= loop
+        Nothing -> PE.throw ex
+    ex = Timeout $ "contextWriteTimeoutD: " <> show wait <> " microseconds."
+{-# INLINABLE contextWriteTimeoutD #-}
+
diff --git a/src/Control/Proxy/TCP/TLS/Safe.hs b/src/Control/Proxy/TCP/TLS/Safe.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Proxy/TCP/TLS/Safe.hs
@@ -0,0 +1,476 @@
+{-# LANGUAGE Rank2Types #-}
+
+-- | This module exports functions that allow you to use TLS-secured
+-- TCP connections as 'P.Proxy' streams, as well as utilities to connect to a
+-- TLS-enabled TCP server or running your own, possibly within the pipeline
+-- itself by relying on the facilities provided by 'P.ExceptionP' from the
+-- @pipes-safe@ library.
+--
+-- If you don't need to establish new TLS connections within your pipeline,
+-- then consider using the simpler and similar functions exported by
+-- "Control.Proxy.TCP.TLS".
+--
+-- This module re-exports many functions and types from "Network.Simple.TCP.TLS"
+-- module in the @network-simple@ package. You might refer to that module for
+-- more documentation.
+
+module Control.Proxy.TCP.TLS.Safe (
+  -- * Client side
+  -- $client-side
+    connect
+  , S.ClientSettings
+  , S.getDefaultClientSettings
+  , S.makeClientSettings
+  -- ** Streaming
+  -- $client-streaming
+  , connectReadS
+  , connectWriteD
+
+  -- * Server side
+  -- $server-side
+  , serve
+  , S.ServerSettings
+  , S.makeServerSettings
+  -- ** Listening
+  , listen
+  -- ** Accepting
+  , accept
+  , acceptFork
+  -- ** Streaming
+  -- $server-streaming
+  , serveReadS
+  , serveWriteD
+
+  -- * Socket streams
+  -- $socket-streaming
+  , contextReadS
+  , contextWriteD
+
+  -- * Exports
+  , S.HostPreference(..)
+  , S.Credential(..)
+  , Timeout(..)
+  ) where
+
+
+import           Control.Concurrent              (ThreadId)
+import qualified Control.Exception               as E
+import           Control.Monad
+import qualified Control.Proxy                   as P
+import qualified Control.Proxy.Safe              as P
+import           Control.Proxy.TCP.Safe          (listen, Timeout(..))
+import qualified Data.ByteString                 as B
+import           Data.Monoid
+import qualified GHC.IO.Exception                as Eg
+import qualified Network.Socket                  as NS
+import qualified Network.Simple.TCP.TLS          as S
+import qualified Network.TLS                     as T
+import           System.Timeout                  (timeout)
+
+--------------------------------------------------------------------------------
+
+-- $client-side
+--
+-- Here's how you could run a simple TLS-secured TCP client:
+--
+-- > import Control.Proxy.TCP.TLS.Safe
+-- >
+-- > settings <- getDefaultClientSettings
+-- > connect settings "www.example.org" "443" $ \(tlsCtx, remoteAddr) -> do
+-- >   tryIO . putStrLn $ "Secure connection established to " ++ show remoteAddr
+-- >   -- now you may use tlsCtx as you please within this scope, possibly with
+-- >   -- the contextReadS or contextWriteD proxies explained below.
+--
+-- You might prefer to use the simpler but less general solutions offered by
+-- 'connectReadS' and 'connectWriteD', so check those too.
+
+--------------------------------------------------------------------------------
+
+-- | Connect to a TLS-secured TCP server and use the connection.
+--
+-- A TLS handshake is performed immediately after establishing the TCP
+-- connection.
+--
+-- The connection is properly closed when done or in case of exceptions. If you
+-- need to manage the lifetime of the connection resources yourself, then use
+-- 'connectTls' instead.
+connect
+  :: (P.Proxy p, Monad m)
+  => (forall x. P.SafeIO x -> m x) -- ^Monad morphism.
+  -> S.ClientSettings              -- ^TLS settings.
+  -> NS.HostName                   -- ^Server hostname.
+  -> NS.ServiceName                -- ^Server service port.
+  -> ((T.Context, NS.SockAddr) -> P.ExceptionP p a' a b' b m r)
+                          -- ^Computation to run in a different thread
+                          -- once a TLS-secured connection is established. Takes
+                          -- the TLS connection context and remote end address.
+  -> P.ExceptionP p a' a b' b m r
+connect morph cs host port  k = do
+    P.bracket morph (S.connectTls cs host port)
+                    (contextCloseNoVanish . fst)
+                    (useTls morph k)
+
+--------------------------------------------------------------------------------
+
+-- $client-streaming
+--
+-- The following proxies allow you to easily connect to a TLS-secured TCP server
+-- and immediately interact with it using streams, all at once, instead of
+-- having to perform the individual steps separately.
+
+--------------------------------------------------------------------------------
+
+-- | Connect to a TLS-secured TCP server and send downstream the decrypted bytes
+-- received from the remote end.
+--
+-- Up to @16384@ decrypted bytes will be received at once. The TLS connection is
+-- automatically renegotiated if a /ClientHello/ message is received.
+--
+-- If an optional timeout is given and receiveing data from the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- If the remote peer closes its side of the connection of EOF is reached, this
+-- proxy returns.
+--
+-- The connection is closed when done or in case of exceptions.
+--
+-- Using this proxy you can write code like the following, which prints whatever
+-- is received through a TLS-secured TCP connection to a given server listening
+-- at hostname "example.org" on port 4433:
+--
+-- >>> settings <- getDefaultClientSettings
+-- >>> let src = connectReadS Nothing settings "www.example.org" "4433"
+-- >>> runSafeIO . runProxy . runEitherK $ src >-> try . printD
+connectReadS
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> S.ClientSettings   -- ^TLS settings.
+  -> NS.HostName
+  -> NS.ServiceName     -- ^Server service port.
+  -> () -> P.Producer (P.ExceptionP p) B.ByteString P.SafeIO ()
+connectReadS mwait cs host port = \() -> do
+   connect id cs host port $ \(ctx,_) -> do
+     contextReadS mwait ctx ()
+
+-- | Connects to a TLS-secured TCP server, encrypts and sends to the remote end
+-- the bytes received from upstream, then forwards such same bytes downstream.
+--
+-- Requests from downstream are forwarded upstream.
+--
+-- If an optional timeout is given and sending data to the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- The connection is properly closed when done or in case of exceptions.
+--
+-- Using this proxy you can write code like the following, which sends data to a
+-- TLS-secured TCP server listening at hostname "example.org" on port 4433:
+--
+-- >>> :set -XOverloadedStrings
+-- >>> settings <- getDefaultClientSettings
+-- >>> let dst = connectWriteS Nothing settings "www.example.org" "4433"
+-- >>> runSafeIO . runProxy . runEitherK $ fromListS ["He","llo\r\n"] >-> dst
+connectWriteD
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> S.ClientSettings   -- ^TLS settings.
+  -> NS.HostName        -- ^Server host name.
+  -> NS.ServiceName     -- ^Server service port.
+  -> x -> (P.ExceptionP p) x B.ByteString x B.ByteString P.SafeIO r
+connectWriteD mwait cs hp port = \x -> do
+   connect id cs hp port $ \(ctx,_) ->
+     contextWriteD mwait ctx x
+
+--------------------------------------------------------------------------------
+
+-- $server-side
+--
+-- Here's how you could run a simple TLS-secured TCP server that handles in
+-- different threads each incoming connection to port @4433@ at hostname
+-- @example.org@. You will need a X509 certificate and a private key appropiate
+-- to be used at that hostname.
+--
+-- > import Control.Proxy.TCP.TLS.Safe
+-- > import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
+-- >
+-- > cert <- fileReadCertificate "~/example.org.crt"
+-- > pkey <- fileReadPrivateKey  "~/example.org.key"
+-- > let cred = Credential cert pkey []
+-- >     settings = makeServerSettings cred Nothing
+-- >
+-- > serve settings (Host "example.org") "4433" $ \(tlsCtx, remoteAddr) -> do
+-- >   tryIO . putStrLn $ "Secure connection established from " ++ show remoteAddr
+-- >   -- now you may use tlsCtx as you please within this scope, possibly with
+-- >   -- the contextReadS or contextWriteD proxies explained below.
+--
+-- You might prefer to use the simpler but less general solutions offered by
+-- 'serveReadS' and 'serveWriteD', or if you need to control the way your
+-- server runs, then you can use more advanced functions such as 'listen',
+-- 'accept' and 'acceptFork', so check those functions too.
+
+--------------------------------------------------------------------------------
+
+-- | Start a TLS-secured TCP server that accepts incoming connections and
+-- handles each of them concurrently, in different threads.
+--
+-- A TLS handshake is performed immediately after establishing each TCP
+-- connection.
+--
+-- Any acquired network resources are properly closed and discarded when done or
+-- in case of exceptions.
+--
+-- Note: This function binds a listening socket, accepts an connection, performs
+-- a TLS handshake and then safely closes the connection. You don't need to
+-- perform any of those steps manually.
+serve
+  :: (P.Proxy p, Monad m)
+  => (forall x. P.SafeIO x -> m x) -- ^Monad morphism.
+  -> S.ServerSettings              -- ^TLS settings.
+  -> S.HostPreference              -- ^Preferred host to bind.
+  -> NS.ServiceName                -- ^Service port to bind.
+  -> ((T.Context, NS.SockAddr) -> IO ())
+                          -- ^Computation to run in a different thread
+                          -- once an incomming connection is accepted and a
+                          -- TLS-secured communication is established. Takes the
+                          -- TLS connection context and remote end address.
+  -> P.ExceptionP p a' a b' b m r
+serve morph ss hp port k = do
+   listen morph hp port $ \(lsock,_) -> do
+     forever $ acceptFork morph ss lsock k
+
+--------------------------------------------------------------------------------
+
+-- | Accept a single incoming TLS-secured TCP connection and use it.
+--
+-- A TLS handshake is performed immediately after establishing each TCP
+-- connection.
+--
+-- The connection properly closed when done or in case of exceptions.
+accept
+  :: (P.Proxy p, Monad m)
+  => (forall x. P.SafeIO x -> m x) -- ^Monad morphism.
+  -> S.ServerSettings              -- ^TLS settings.
+  -> NS.Socket                     -- ^Listening and bound socket.
+  -> ((T.Context, NS.SockAddr) -> P.ExceptionP p a' a b' b m r)
+                          -- ^Computation to run once an incomming connection is
+                          -- accepted and a TLS-secured communication is
+                          -- established. Takes the TLS connection context and
+                          -- remote end address.
+  -> P.ExceptionP p a' a b' b m r
+accept morph ss lsock k = do
+    P.bracket morph (S.acceptTls ss lsock)
+                    (contextCloseNoVanish . fst)
+                    (useTls morph k)
+{-# INLINABLE accept #-}
+
+-- | Like 'accept', except it uses a different thread to performs the TLS
+-- handshake and run the given computation.
+acceptFork
+  :: (P.Proxy p, Monad m)
+  => (forall x. P.SafeIO x -> m x) -- ^Monad morphism.
+  -> S.ServerSettings              -- ^TLS settings.
+  -> NS.Socket                     -- ^Listening and bound socket.
+  -> ((T.Context, NS.SockAddr) -> IO ())
+                          -- ^Computation to run in a different thread
+                          -- once an incomming connection is accepted and a
+                          -- TLS-secured communication is established. Takes the
+                          -- TLS connection context and remote end address.
+  -> P.ExceptionP p a' a b' b m ThreadId
+acceptFork morph ss lsock k = P.hoist morph . P.tryIO $ S.acceptFork ss lsock k
+{-# INLINABLE acceptFork #-}
+
+--------------------------------------------------------------------------------
+
+-- $server-streaming
+--
+-- The following proxies allow you to easily run a TLS-secured TCP server and
+-- immediately interact with incoming connections using streams, all at once,
+-- instead of having to perform the individual steps separately.
+
+--------------------------------------------------------------------------------
+
+-- | Binds a listening TCP socket, accepts a single TLS-secured connection and
+-- sends downstream any decrypted bytes received from the remote end.
+--
+-- Up to @16384@ decrypted bytes will be received at once. The TLS connection is
+-- automatically renegotiated if a /ClientHello/ message is received.
+--
+-- If an optional timeout is given and receiveing data from the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- If the remote peer closes its side of the connection of EOF is reached,  this
+-- proxy returns.
+--
+-- Both the listening and connection sockets are closed when done or in case of
+-- exceptions.
+--
+-- Using this proxy you can write code like the following, which prints data
+-- received from a TLS-secured TCP connection to the hostname "example.org" at
+-- port 4433:
+--
+-- >>> import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
+-- >>> cert <- fileReadCertificate "~/example.org.crt"
+-- >>> pkey <- fileReadPrivateKey  "~/example.org.key"
+-- >>> let settings = makeServerSettings cert pkey Nothing
+-- >>> let src = serveReadS Nothing settings (Host "example.org") "4433"
+-- >>> runSafeIO . runProxy . runEitherK $ src >-> try . printD
+serveReadS
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> S.ServerSettings   -- ^TLS settings.
+  -> S.HostPreference   -- ^Preferred host to bind.
+  -> NS.ServiceName     -- ^Service port to bind.
+  -> () -> P.Producer (P.ExceptionP p) B.ByteString P.SafeIO ()
+serveReadS mwait ss hp port = \() -> do
+   listen id hp port $ \(lsock,_) -> do
+     accept id ss lsock $ \(csock,_) -> do
+       contextReadS mwait csock ()
+
+-- | Binds a listening TCP socket, accepts a single TLS-secured connection,
+-- sends to the remote end the bytes received from upstream and then forwards
+-- such sames bytesdownstream.
+--
+-- Requests from downstream are forwarded upstream.
+--
+-- If an optional timeout is given and sending data to the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- If the remote peer closes its side of the connection, this proxy returns.
+--
+-- Both the listening and connection sockets are closed when done or in case of
+-- exceptions.
+--
+-- Using this proxy you can write straightforward code like the following, which
+-- sends data to an incoming TLS-secured TCP connection to the hostname
+-- "example.org" at port 4433:
+--
+-- >>> :set -XOverloadedStrings
+-- >>> import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
+-- >>> cert <- fileReadCertificate "~/example.org.crt"
+-- >>> pkey <- fileReadPrivateKey  "~/example.org.key"
+-- >>> let settings = makeServerSettings cert pkey Nothing
+-- >>> let dst = serveWriteD Nothing settings "example.org" "4433"
+-- >>> runSafeIO . runProxy . runEitherK $ fromListS ["He","llo\r\n"] >-> dst
+serveWriteD
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> S.ServerSettings   -- ^TLS settings.
+  -> S.HostPreference   -- ^Preferred host to bind.
+  -> NS.ServiceName     -- ^Service port to bind.
+  -> x -> (P.ExceptionP p) x B.ByteString x B.ByteString P.SafeIO r
+serveWriteD mwait ss hp port = \x -> do
+   listen id hp port $ \(lsock,_) -> do
+     accept id ss lsock $ \(csock,_) -> do
+       contextWriteD mwait csock x
+
+--------------------------------------------------------------------------------
+
+-- $socket-streaming
+--
+-- Once you have a an established TLS 'T.Context', you can use the following
+-- 'P.Proxy's to interact with the other connection end using pipes streams.
+
+--------------------------------------------------------------------------------
+
+-- | Receives decrypted bytes from the remote end, sending them downstream.
+--
+-- Up to @16384@ decrypted bytes will be received at once. The TLS connection is
+-- automatically renegotiated if a /ClientHello/ message is received.
+--
+-- If an optional timeout is given and receiveing data from the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- If the remote peer closes its side of the connection or EOF is reached, this
+-- proxy returns.
+contextReadS
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> T.Context          -- ^Established TLS connection context.
+  -> () -> P.Producer (P.ExceptionP p) B.ByteString P.SafeIO ()
+contextReadS Nothing ctx = loop where
+    loop () = do
+      mbs <- P.tryIO (S.recv ctx)
+      case mbs of
+        Nothing -> return ()
+        Just bs -> P.respond bs >>= loop
+contextReadS (Just wait) ctx = loop where
+    loop () = do
+      mmbs <- P.tryIO (timeout wait (S.recv ctx))
+      case mmbs of
+        Nothing        -> P.throw ex
+        Just Nothing   -> return ()
+        Just (Just bs) -> P.respond bs >>= loop
+    ex = Timeout $ "contextReadS: " <> show wait <> " microseconds."
+{-# INLINABLE contextReadS #-}
+
+-- | Encrypts and sends to the remote end the bytes received from upstream,
+-- then forwards such same bytes downstream.
+--
+-- If an optional timeout is given and sending data to the remote end takes
+-- more time that such timeout, then throw a 'Timeout' exception in the
+-- 'P.ExceptionP' proxy transformer.
+--
+-- If the remote peer closes its side of the connection, this proxy returns.
+--
+-- Requests from downstream are forwarded upstream.
+contextWriteD
+  :: P.Proxy p
+  => Maybe Int          -- ^Optional timeout in microseconds (1/10^6 seconds).
+  -> T.Context          -- ^Established TLS connection context.
+  -> x -> (P.ExceptionP p) x B.ByteString x B.ByteString P.SafeIO r
+contextWriteD Nothing ctx = loop where
+    loop x = do
+      a <- P.request x
+      P.tryIO (S.send ctx a)
+      P.respond a >>= loop
+contextWriteD (Just wait) ctx = loop where
+    loop x = do
+      a <- P.request x
+      m <- P.tryIO (timeout wait (S.send ctx a))
+      case m of
+        Just () -> P.respond a >>= loop
+        Nothing -> P.throw ex
+    ex = Timeout $ "contextWriteD: " <> show wait <> " microseconds."
+{-# INLINABLE contextWriteD #-}
+
+
+
+--------------------------------------------------------------------------------
+-- Internal stuff
+
+
+-- | Perform a TLS 'T.handshake' on the given 'T.Context', then perform the
+-- given action, and at last say 'T.bye' and close the TLS connection, even in
+-- case of exceptions. Like 'S.useTls', except it runs within 'P.ExceptionP'.
+--
+-- This function discards 'Eg.ResourceVanished' exceptions that will happen when
+-- trying to say 'T.bye' if the remote end has done it before.
+useTls
+  :: (Monad m, P.Proxy p)
+  => (forall x. P.SafeIO x -> m x) -- ^Monad morphism.
+  -> ((T.Context, NS.SockAddr) -> P.ExceptionP p a' a b' b m r)
+  -> (T.Context, NS.SockAddr) -> P.ExceptionP p a' a b' b m r
+useTls morph k = \conn@(ctx,_) -> do
+    P.bracket_ morph (T.handshake ctx) (byeNoVanish ctx) (k conn)
+{-# INLINABLE useTls #-}
+
+
+-- | Like `T.bye`, except it ignores `ResourceVanished` exceptions.
+byeNoVanish :: T.Context -> IO ()
+byeNoVanish ctx =
+    E.handle (\Eg.IOError{Eg.ioe_type=Eg.ResourceVanished} -> return ())
+             (T.bye ctx)
+{-# INLINABLE byeNoVanish #-}
+
+-- | Like `T.contextClose`, except it ignores `ResourceVanished` exceptions.
+contextCloseNoVanish :: T.Context -> IO ()
+contextCloseNoVanish = \ctx ->
+    E.handle (\Eg.IOError{Eg.ioe_type=Eg.ResourceVanished} -> return ())
+             (T.contextClose ctx)
+{-# INLINABLE contextCloseNoVanish #-}
+
+
