packages feed

cacophony-0.7.0: examples/echo-client/Client.hs

{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
module Client
  ( runClient
  ) where

import Control.Lens           ((.~), (&))
import Data.Bits              ((.&.), shiftR)
import Data.ByteString        (ByteString, pack, length)
import qualified Data.ByteString.Char8 as C8 (pack, unpack)
import Data.ByteString.Base16 (encode)
import Data.IORef
import Data.Monoid            ((<>))
import Network.Simple.TCP
import Prelude hiding         (length)

import Crypto.Noise
import Crypto.Noise.Cipher
import Crypto.Noise.Cipher.AESGCM
import Crypto.Noise.DH
import Crypto.Noise.HandshakePatterns (noiseIK)
import Crypto.Noise.Hash
import Crypto.Noise.Hash.SHA256
import Data.ByteArray.Extend hiding (length)

import Parse

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

messageLoop :: (Cipher c, DH d, Hash h)
            => IORef ByteString
            -> Socket
            -> NoiseState c d h
            -> IO ()
messageLoop bufRef sock = loop
  where
    loop ns = do
      msg <- convert . C8.pack <$> getLine

      let (ct, ns') = either (error . show) id $ writeMessage ns msg
      writeSocket sock ct

      maybeMessage <- parseSocket bufRef sock messageParser
      case maybeMessage of
        Nothing -> return ()
        Just m  -> do
          let (pt, ns'')  = either (error . show) id $ readMessage ns' m
          putStrLn . ("received: " <>) . C8.unpack . convert $ pt

          loop ns''

runClient :: forall d. DH d
          => String
          -> String
          -> Maybe ScrubbedBytes
          -> KeyPair d
          -> PublicKey d
          -> IO ()
runClient hostname port psk localKey remoteKey =
  connect hostname port $ \(sock, _) -> do
    leftoverBufRef <- newIORef ""

    lek <- dhGenKey :: IO (KeyPair d)

    let dho = defaultHandshakeOpts noiseIK InitiatorRole
        hdr = maybe "\x00\x09\x01\x00\x00" (const "\x01\x09\x01\x00\x00") psk
        ho  = dho & hoPrologue       .~ convert hdr
                  & hoPreSharedKey   .~ psk
                  & hoLocalStatic    .~ Just localKey
                  & hoLocalEphemeral .~ Just lek
                  & hoRemoteStatic   .~ Just remoteKey

        ns :: NoiseState AESGCM d SHA256
        ns = noiseState ho

    putStrLn $ "private ephemeral: " <> (C8.unpack . encode . convert . dhSecToBytes . fst) lek

    send sock hdr
    putStrLn "connection established, begin typing"

    messageLoop leftoverBufRef sock ns

    putStrLn "connection closed"