packages feed

cacophony-0.6.0: examples/echo-server/Server.hs

{-# LANGUAGE OverloadedStrings, RecordWildCards, GADTs, RankNTypes #-}
module Server
  ( startServer
  ) where

import Control.AutoUpdate         (mkAutoUpdate, defaultUpdateSettings, updateAction)
import Control.Exception          (handle)
import Control.Monad              (void)
import Data.Bits                  ((.&.), shiftR)
import Data.ByteString            (ByteString, pack, length)
import qualified Data.ByteString.Char8 as C8 (pack)
import Data.IORef
import Data.Maybe                 (fromMaybe)
import Data.Monoid                ((<>))
import Network.Simple.TCP
import Prelude hiding             (log, length)
import System.Timeout

import Crypto.Noise.Cipher
import Crypto.Noise.Curve
import Crypto.Noise.Handshake

import Handshakes
import Log
import Parse
import Types

mkHandshakeKeys :: Curve d
                => ServerOpts
                -> CurveType d
                -> HandshakeKeys d
mkHandshakeKeys ServerOpts{..} CTCurve25519 =
  HandshakeKeys { hkPrologue     = soPrologue
                , hkPSK          = soPSK
                , hkLocalStatic  = soLocal25519
                , hkRemoteStatic = soRemote25519
                }

mkHandshakeKeys ServerOpts{..} CTCurve448 =
  HandshakeKeys { hkPrologue     = soPrologue
                , hkPSK          = soPSK
                , hkLocalStatic  = soLocal448
                , hkRemoteStatic = soRemote448
                }

prependLength :: ByteString
              -> ByteString
prependLength msg = pack w16len <> msg
  where
    len    = length msg
    w16len = fmap fromIntegral [(len .&. 0xFF00) `shiftR` 8, len .&. 0xFF]

writeSocket :: Socket
            -> ByteString
            -> IO ()
writeSocket s msg = send s $ prependLength msg

readSocket :: IORef ByteString
           -> Socket
           -> IO ByteString
readSocket bufRef sock = fromMaybe (error "connection reset") <$> parseSocket bufRef sock messageParser

mkProtocolName :: ServerOpts
               -> Header
               -> String
mkProtocolName ServerOpts{..} (hp, WrapCipherType cipherT, WrapCurveType curveT, WrapHashType hashT) =
  psk <> show hp <> "_" <> show cipherT <> "_" <> show curveT <> "_" <> show hashT
  where
    psk = maybe "Noise_" (const "NoisePSK_") soPSK

messageLoop :: Cipher c
            => IORef ByteString
            -> Socket
            -> SendingCipherState c
            -> ReceivingCipherState c
            -> IO ()
messageLoop bufRef sock = loop
  where
    loop scs rcs = do
      maybeMessage <- parseSocket bufRef sock messageParser
      case maybeMessage of
        Nothing -> return ()
        Just m  -> do
          let (pt, rcs') = decryptPayload m rcs
              (ct, scs') = encryptPayload pt scs
          writeSocket sock ct
          loop scs' rcs'

startServer :: ServerOpts
            -> IO ()
startServer opts@ServerOpts{..} = do
  logHandle <- maybe openStdOut openLog soLogFile
  au <- mkAutoUpdate defaultUpdateSettings { updateAction = getDateTime }
  let logEx = logException logHandle au
      log   = logMsg logHandle au

  serve HostAny soPort $ \(sock, ip) -> handle (logEx ip) $ do
    log ip "connection established"

    leftoverBufRef <- newIORef ""
    th <- timeout 60000000 $ parseSocket leftoverBufRef sock headerParser
    case th of
      Nothing -> log ip "timeout waiting for header"
      Just maybeHeader ->
        case maybeHeader of
          Nothing -> do
            send sock "failed to parse header\n"
            log ip "failed to parse header"

          Just h@(hp, WrapCipherType cipherT, WrapCurveType curveT, WrapHashType hashT) -> do
            payloadRef <- newIORef ""

            log ip $ "client selected " <> C8.pack (mkProtocolName opts h)
            let hk = mkHandshakeKeys opts curveT
                hs = mkHandshake hk hp cipherT hashT
                hc = HandshakeCallbacks (writeSocket sock)
                                        (readSocket leftoverBufRef sock)
                                        (modifyIORef' payloadRef . const)
                                        (readIORef payloadRef)
                                        (\_ -> return True)
            (encryptionCipherState, decryptionCipherState) <- runHandshake hs hc

            void . timeout 60000000 $ messageLoop leftoverBufRef sock encryptionCipherState decryptionCipherState

    log ip "connection closed"