{-# language DerivingStrategies #-} {-# language LambdaCase #-} {-# language DeriveAnyClass #-} module TlsChannel ( M , TransportException(..) , SendException , ReceiveException , showsPrecSendException , showsPrecReceiveException , Resource , NetworkException(..) , send , receive , tryTls ) where import Data.Bytes (Bytes) import Data.ByteString (ByteString) import Data.Bytes.Chunks (Chunks) import Control.Exception (Exception,IOException,try,throwIO) import Network.Socket (Socket) import Foreign.C.Error (Errno) import qualified Data.Bytes as Bytes import qualified Data.List as List import qualified Data.Bytes.Chunks as Chunks import qualified Network.Socket as N import qualified Network.Unexceptional.ByteString as NBS import qualified Network.TLS as Tls import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString as ByteString import qualified Foreign.C.Error.Describe as Describe type M = IO type Resource = Tls.Context type SendException = TransportException type ReceiveException = TransportException data TransportException = Network !Errno | System !IOException | TlsException !Tls.TLSException showsPrecErrno :: Int -> Errno -> String -> String showsPrecErrno _ e s = Describe.string e ++ (' ' : s) showsPrecSendException :: Int -> SendException -> String -> String showsPrecSendException = showsPrec showsPrecReceiveException :: Int -> ReceiveException -> String -> String showsPrecReceiveException = showsPrec instance Show TransportException where showsPrec d (Network e) = showParen (d > 10) (showString "Network " . showsPrecErrno 11 e) showsPrec d (System e) = showParen (d > 10) (showString "System " . showsPrec 11 e) showsPrec d (TlsException e) = showParen (d > 10) (showString "TlsException " . showsPrec 11 e) data NetworkException = NetworkException !Errno deriving anyclass (Exception) instance Show NetworkException where show (NetworkException e) = Describe.string e -- | There are three types of exceptions that we can get when -- sending/receiving data, so we nest the call to sendData in three -- try statements to catch all the possible exceptions. send :: Tls.Context -> Chunks -> IO (Either TransportException ()) send ctx ch = tryTls $ Tls.sendData ctx (LBS.fromStrict (Chunks.concatByteString ch)) receive :: Tls.Context -> M (Either TransportException Bytes) receive a = tryTls (Tls.recvData a) >>= \case Left err -> pure (Left err) Right b -> pure $! Right $! Bytes.fromByteString b tryTls :: IO a -> IO (Either TransportException a) tryTls action = do e0 <- try $ try $ try $ action case e0 of Left (NetworkException err) -> pure (Left (Network err)) Right e1 -> case e1 of Left (err :: Tls.TLSException) -> pure (Left (TlsException err)) Right e2 -> case e2 of Left (err :: IOException) -> pure (Left (System err)) Right a -> pure $! Right a