natskell-0.0.0.1: internal/Policy/Handshake/Nats.hs
module Handshake.Nats
( HandshakeError (..)
, performHandshake
) where
import Auth.Types
import Control.Monad (unless)
import qualified Data.ByteString as BS
import Data.Maybe (fromMaybe, isJust)
import Network.ConnectionAPI
( Conn
, ConnectionAPI
, TransportOption (..)
, configure
, readData
, reader
, writeDataLazy
, writer
)
import Parser.API
( ParseStep (DropPrefix, Emit, NeedMore, Reject)
, ParsedMessage (..)
, ParserAPI
, parse
)
import State.Store
( ClientState
, config
, setServerInfo
, updateLogContextFromInfo
)
import State.Types (ClientConfig (..))
import Transformers.Transformers (Transformer (transform))
import qualified Types.Connect as Connect
import qualified Types.Info as I
data HandshakeError = HandshakeTransportError String
| HandshakeProtocolError String
| HandshakeAuthError AuthError
deriving (Eq, Show)
performHandshake :: ConnectionAPI -> ParserAPI ParsedMessage -> ClientState -> Auth -> Conn -> String -> IO (Either HandshakeError ())
performHandshake connectionApi parserApi state auth conn host = do
infoResult <- readInitialInfo
case infoResult of
Left err ->
pure (Left (HandshakeProtocolError err))
Right (info, rest) -> do
let cfg = config state
tlsRequested = isJust (tlsCert cfg) || Connect.tls_required (connectConfig cfg)
tlsRequired = fromMaybe False (I.tls_required info)
transportOption =
TransportOption
{ transportHost = host
, transportTlsRequired = tlsRequired
, transportTlsRequested = tlsRequested
, transportTlsCert = tlsCert cfg
, transportInitialBytes = rest
}
transportResult <- configure connectionApi conn transportOption
case transportResult of
Left err ->
pure (Left (HandshakeTransportError err))
Right () -> do
setServerInfo state info
updateLogContextFromInfo state info
let authContext = AuthContext { authNonce = I.nonce info }
connectPayload =
(connectConfig cfg)
{ Connect.tls_required = tlsRequired || tlsRequested
}
case validateAuth auth of
Left err ->
pure (Left (HandshakeAuthError err))
Right () ->
case buildAuthPatch auth authContext of
Left err ->
pure (Left (HandshakeAuthError err))
Right patch -> do
writeResult <-
writeDataLazy
(writer connectionApi)
conn
(transform (applyAuthPatch patch connectPayload))
case writeResult of
Left err ->
pure (Left (HandshakeTransportError err))
Right () ->
pure (Right ())
where
readInitialInfo = go mempty
where
go acc = do
result <- readData (reader connectionApi) conn 4096
case result of
Left err ->
pure (Left err)
Right chunk ->
if BS.null chunk
then pure (Left "read returned empty chunk before INFO")
else do
let bytes = acc <> chunk
case parse parserApi bytes of
NeedMore ->
go bytes
DropPrefix n _ ->
go (BS.drop n bytes)
Reject reason ->
pure (Left reason)
Emit (ParsedInfo info) rest ->
pure (Right (info, rest))
Emit (ParsedErr err) _ ->
pure (Left ("server error before INFO: " ++ show err))
Emit _ rest -> do
unless (BS.null rest) (pure ())
go rest