packages feed

haskell-abci-0.1.0.0: src/Network/ABCI/Server.hs

module Network.ABCI.Server where

import qualified Conduit                 as C
import           Control.Monad
import qualified Data.Binary             as B
import qualified Data.Bits               as B
import qualified Data.ByteString         as B
import qualified Data.ByteString.Lazy    as BL
import qualified Data.Conduit.Network    as N
import           Data.List               (unfoldr)
import qualified Data.ProtoLens.Encoding as P
import           Foundation
import qualified Prelude

import           Proto.Types

defaultHost ∷ Prelude.String
defaultHost = "*"

defaultPort ∷ Int
defaultPort = 46658

serve ∷ Prelude.String → Int → (Request → IO Response) → IO ()
serve host port func =
  N.runTCPServer (N.serverSettings port (fromString host)) $ \app →
    void $ (N.appSource app C.=$= unbuffer C.=$= handle func) C.$$+ N.appSink app

unbuffer ∷ C.ConduitM B.ByteString B.ByteString IO ()
unbuffer = go B.empty
  where go buffer = do
          val ← C.await
          case val of
            Just v  → do
              let joined = buffer `B.append` v
                  loop str =
                    if B.length str == 0 then go str else do
                      let (enc, length) = decodeLength str
                          remaining     = B.drop enc str
                      if B.length remaining >= length then do
                        let msg = B.take length remaining
                        C.yield msg
                        loop (B.drop length remaining)
                      else go str
              loop joined
            Nothing → return ()

handle ∷ (Request → IO Response) → C.ConduitM B.ByteString B.ByteString IO ()
handle func = go
  where go = do
          val ← C.await
          case val of
            Just v →
              case P.decodeMessage v of
                Right m → do
                  r ← C.liftIO $ func m
                  let encoded       = P.encodeMessage r
                      encodedLength = fromIntegral $ B.length encoded
                      lengthEncoded = B.pack $ unroll encodedLength
                      lengthLength  ∷ Word8
                      lengthLength  = fromIntegral $ B.length lengthEncoded
                      concatenated  = lengthLength `B.cons` lengthEncoded `B.append` encoded
                  C.yield concatenated
                  go
                Left _ → return ()
            Nothing → return ()

decodeLength ∷ B.ByteString → (Int, Int)
decodeLength str =
  let lengthSizeEncoded = BL.fromStrict $ B.take 1 str
      lengthSizeDecoded ∷ Word8
      lengthSizeDecoded = B.decode lengthSizeEncoded
      lengthSize        ∷ Int
      lengthSize        = fromIntegral lengthSizeDecoded
      lengthEncoded     = B.take lengthSize $ B.drop 1 str
      lengthDecoded     ∷ Integer
      lengthDecoded     = roll $ B.unpack lengthEncoded
  in (1 + lengthSize, fromIntegral lengthDecoded)

roll ∷ [Word8] → Integer
roll = foldr unstep 0 . reverse
  where
    unstep b a = a `B.shiftL` 8 B..|. fromIntegral b

unroll ∷ Integer → [Word8]
unroll = reverse . unfoldr step
  where
    step 0 = Nothing
    step i = Just (fromIntegral i, i `B.shiftR` 8)