packages feed

exploring-interpreters-1.6.0.0: Language/Explorer/Tools/Protocol.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}



module Language.Explorer.Tools.Protocol where

import GHC.Generics
import Data.Monoid
import Data.Aeson
import Network.HTTP.Types.Status
import Network.HTTP.Types.Header (hContentType)
import Data.Maybe
import Control.Concurrent (forkFinally)
import qualified Control.Exception as E
import Control.Monad (unless, forever, void)
import qualified Data.ByteString.Lazy as S
import qualified Data.Attoparsec.ByteString.Lazy as AB
import qualified Data.Attoparsec.ByteString.Char8 as ABC
import Network.Socket hiding (recv)
import Network.Socket.ByteString.Lazy (recv, sendAll)
import Data.Scientific
import qualified Language.Explorer.Monadic as Ex
import Control.Monad.RWS.Lazy hiding (listen)
import Control.Monad.Trans.Except
import Data.List

type ExplorerParser p m c o = (Ex.Explorer p m c o, String -> Maybe p)
type ProcessResult = Either ErrorMessage Value

type EIP p m c o = RWST (String -> Maybe p) S.ByteString (Ex.Explorer p m c o) m

class ExplorerPostValue p c o where
    postExecute :: Ex.Explorer p m c o -> Ex.Explorer p m c o -> o -> Value
    postExecute = \_ _ _ -> Null
    postJump :: Ex.Explorer p m c o -> Ex.Explorer p m c o -> Value
    postJump = \_ _ -> Null
    postRevert :: Ex.Explorer p m c o -> Ex.Explorer p m c o -> [Ex.Ref] -> Value
    postRevert = \ _ _ _ -> Null

data RequestMessage = RequestMessage {
    jsonrpc :: String,
    req_id :: String,
    method :: String,
    params :: Maybe Value
} deriving (Show, Generic)

instance ToJSON RequestMessage where
    toEncoding = genericToEncoding defaultOptions

instance FromJSON RequestMessage where
    parseJSON = withObject "RequestMessage" $ \v -> RequestMessage
        <$> v .: "jsonrpc"
        <*> v .: "id"
        <*> v .: "method"
        <*> v .:? "params"

data ResponseMessage = ResponseMessage {
    res_id :: String,
    body :: ProcessResult
} deriving (Show)

instance ToJSON ResponseMessage where
    toJSON (ResponseMessage res_id (Left e)) = object ["id" .= res_id, "error" .= e]
    toJSON (ResponseMessage res_id (Right res)) = object ["id" .= res_id, "result" .= res]

    toEncoding (ResponseMessage res_id (Left e)) = pairs ("id" .= res_id <> "error" .= e)
    toEncoding (ResponseMessage res_id (Right res)) = pairs ("id" .= res_id <> "result" .= res)


data ErrorMessage = ErrorMessage {
    code :: Int,
    message :: String,
    error_data :: Maybe Value
} deriving (Show, Generic)

instance ToJSON ErrorMessage where
    toEncoding = genericToEncoding defaultOptions

instance FromJSON ErrorMessage
    -- No need to provide a parseJSON implementation.

-- instance Except ErrorMessage 
-- where
--     noMsg = ErrorMessage { code = 0, message = "", error_data = Nothing}
--     strMsg msg = ErrorMessage {code = 0, message = msg, error_data = Nothing }

data JumpParams = JumpParams {
    jump_ref :: Int
}

instance FromJSON JumpParams where
    parseJSON = withObject "JumpParams" $ \v -> JumpParams
        <$> v .: "reference"

data JumpResult = JumpResult {
    jump_post :: Value
}

instance ToJSON JumpResult where 
    toJSON res = object ["post" .= jump_post res]

data ExecuteParams = ExecuteParams {
    paramsProgram :: String
} deriving (Show, Generic)

instance FromJSON ExecuteParams

data ExecuteResult = ExecuteResult {
    exec_ref :: Int,
    exec_out :: Value,
    exec_post :: Value
}

instance ToJSON ExecuteResult where
    toJSON (ExecuteResult ref out post) = object ["reference" .= ref, "output" .= out, "post" .= post]

    toEncoding (ExecuteResult ref out post) = pairs ("reference" .= ref <> "output" .= out <> "post" .= post)

data RevertParams = RevertParams {
    revert_ref :: Int
}

instance FromJSON RevertParams where
    parseJSON = withObject "RevertParams" $ \v -> RevertParams
        <$> v .: "reference"

data RevertResult = RevertResult {
    revert_deleted :: [Ex.Ref],
    post_revert :: Value
}

instance ToJSON RevertResult where 
    toJSON res = object ["deleted" .= revert_deleted res, "post" .= post_revert res]

data DerefParams = DerefParams {
    deref_ref :: Int
}

instance FromJSON DerefParams where
    parseJSON = withObject "DerefParams" $ \v -> DerefParams
        <$> v .: "reference"


data TraceParams = TraceParams {
    reference :: Int
} deriving (Generic)

instance FromJSON TraceParams


data Edge = Edge {
    source :: Int,
    target :: Int,
    label  :: EdgeLabel
} deriving (Generic)

instance ToJSON Edge where
    toEncoding = genericToEncoding defaultOptions


data EdgeLabel = EdgeLabel {
    program :: Value,
    mval :: Value
} deriving (Generic)

instance ToJSON EdgeLabel where
    toEncoding = genericToEncoding defaultOptions


data ExecutionTree = ExecutionTree {
    current :: Int,
    references :: [Int],
    edges :: [Edge]
} deriving (Generic)

instance ToJSON ExecutionTree where
    toEncoding = genericToEncoding defaultOptions

data PathParams = PathParams {
    paramsSource :: Int,
    paramsTarget :: Int
} deriving (Generic)

instance FromJSON PathParams

parseErrorCode = -32700
invalidRequestCode = -32600
methodNotFoundCode = -32601
invalidParamsCode = -32602
internalErrorCode = -32603

referenceNotInTreeCode = 1
referenceRevertInvalidCode = 2
programParseErrorCode = 3
pathNonExistingCode = 4



parseError :: ErrorMessage
parseError = ErrorMessage {
    code = parseErrorCode,
    message = "Parse error",
    error_data = Nothing
}

methodNotFound :: ErrorMessage
methodNotFound = ErrorMessage {
    code = methodNotFoundCode,
    message = "Method not found",
    error_data = Nothing
}

invalidParams :: ErrorMessage
invalidParams = ErrorMessage {
    code = invalidParamsCode,
    message = "Invalid method parameter(s)",
    error_data = Nothing
}

ensureParameter :: Monad m => Maybe Value -> ExceptT ErrorMessage (EIP p m c o) Value
ensureParameter Nothing = throwE invalidParams
ensureParameter (Just v) = return v

fromResult :: Monad m => Result a -> Value -> ExceptT ErrorMessage (EIP p m c o) Value
fromResult res onSuccess = case res of
    (Error e) -> throwE invalidParams
    (Success v) -> return onSuccess

jump :: (Monad m, ExplorerPostValue p c o) => Value -> ExceptT ErrorMessage (EIP p m c o) Value
jump v = case (fromJSON v) :: Result JumpParams of
    (Error e) -> throwE invalidParams
    (Success v') -> do
        ex <- lift $ get
        case Ex.jump (jump_ref v') ex of
            Just ex' -> do
                lift $ put $ ex'
                return . toJSON . JumpResult $ postJump ex ex'
            Nothing -> throwE ErrorMessage { code = referenceNotInTreeCode, message = "", error_data = Nothing }

execute :: (Eq o, Monoid o, ToJSON o, Eq p, ExplorerPostValue p c o) => Value -> ExceptT ErrorMessage (EIP p IO c o) Value
execute v = case (fromJSON v) :: Result ExecuteParams of
    (Error e) -> throwE invalidParams
    (Success v') -> do
        parser <- lift $ ask
        let pl = parser $ paramsProgram v'
        case pl of
            Just prog -> do
                ex <- lift $ get
                (ex', output) <- liftIO $ Ex.execute prog ex
                lift $ put ex'
                return $ toJSON $ ExecuteResult { exec_ref = Ex.currRef ex', exec_out = toJSON output, exec_post = postExecute ex ex' output }
            Nothing -> throwE ErrorMessage { code = programParseErrorCode, message = "", error_data = Nothing }

allRefs :: Ex.Explorer p IO c o -> [(Ex.Ref, c)]
allRefs ex = refs
    where
        (_, refs, _) = Ex.executionGraph ex


revert :: (Eq o, Monoid o, Eq p, ExplorerPostValue p c o) => Value -> ExceptT ErrorMessage (EIP p IO c o) Value
revert v = case (fromJSON v) :: Result RevertParams of
    (Error e) -> throwE invalidParams
    (Success v) -> do
        ex <- lift $ get
        case Ex.revert (revert_ref v) ex of
            Just ex' -> do
                lift $ put ex'
                return $ toJSON $ RevertResult { revert_deleted = deleted, post_revert = postRevert ex ex' deleted}
                where 
                    refs = map fst (allRefs ex)
                    refs' = map fst (allRefs ex')
                    deleted = (refs \\ refs')
            Nothing -> throwE ErrorMessage { code = referenceRevertInvalidCode, message = "", error_data = Nothing }

deref :: (Eq o, Monoid o, Eq p, ToJSON c) => Value -> ExceptT ErrorMessage (EIP p IO c o) Value
deref v = case (fromJSON v) :: Result DerefParams of
    (Error e) -> throwE invalidParams
    (Success v) -> do
        ex <- lift $ get
        case Ex.deref ex (deref_ref v) of
            (Just conf) -> return $ toJSON conf
            Nothing -> throwE ErrorMessage { code = referenceNotInTreeCode, message = "", error_data = Nothing}

executionTree :: (ToJSON o, ToJSON p) => ExceptT ErrorMessage (EIP p IO c o) Value
executionTree = do
    ex <- lift $ get
    let (curr, nodes, edges) = Ex.executionGraph ex
    return $ toJSON $ ExecutionTree 
        { current = fst curr
        , references = map fst nodes
        , edges = map (\(s, (p, o), t) -> Edge { source = fst s
        , label = EdgeLabel { program = toJSON p, mval = toJSON o}
        , target = fst t} ) edges}

getCurrentReference :: ExceptT ErrorMessage (EIP p IO c o) Value
getCurrentReference = do
    ex <- lift $ get
    return $ toJSON $ Ex.currRef ex

getAllReferences :: ExceptT ErrorMessage (EIP p IO c o) Value
getAllReferences = do
    ex <- lift $ get
    return $ toJSON $ map fst (allRefs ex)

getTrace :: (ToJSON p, ToJSON o) => Maybe Value -> ExceptT ErrorMessage (EIP p IO c o) Value
getTrace (Just r) = case (fromJSON r) :: Result TraceParams of
    (Error e) -> throwE invalidParams
    (Success v) -> do
        ex <- lift $ get
        let path = Ex.getPathFromTo ex 1 (reference (v :: TraceParams)) -- Fix hardcode 1(it's initialRef).
        return $ toJSON $ map (\(s, (p, o), t) -> Edge { source = fst s, target = fst t, label = EdgeLabel { program = toJSON p, mval = toJSON o} }) path
getTrace Nothing = do
    ex <- lift $ get
    let trace = Ex.getTrace ex
    return $ toJSON $ map (\(s, (p, o), t) -> Edge { source = fst s, target = fst t, label = EdgeLabel { program = toJSON p, mval = toJSON o} }) trace

getPath :: (ToJSON o, ToJSON p) => Value -> ExceptT ErrorMessage (EIP p IO c o) Value
getPath val = case (fromJSON val) :: Result PathParams of
    (Error e) -> throwE ErrorMessage { code = pathNonExistingCode, message = "", error_data = Nothing}
    (Success v) -> do
        ex <- lift $ get
        let path = Ex.getPathFromTo ex (paramsSource v) (paramsTarget v)
        return $ toJSON $ map (\(s, (p, o), t) -> Edge { source = fst s, target = fst t, label = EdgeLabel { program = toJSON p, mval = toJSON o} }) path

getLeaves :: ExceptT ErrorMessage (EIP p IO c o) Value
getLeaves = do
    ex <- lift $ get
    return $ toJSON $ map fst (Ex.leaves ex)

methodDispatch :: (Eq o, Monoid o, ToJSON o, ToJSON p, Eq p, ToJSON c, ExplorerPostValue p c o) => String -> Maybe Value -> ExceptT ErrorMessage (EIP p IO c o) Value
methodDispatch "jump" mval = ensureParameter mval >>= jump
methodDispatch "execute" mval = ensureParameter mval >>= execute
methodDispatch "revert" mval = ensureParameter mval >>= revert
methodDispatch "deref" mval = ensureParameter mval >>= deref
methodDispatch "getTrace" mval = getTrace mval
methodDispatch "getPath" mval = ensureParameter mval >>= getPath
methodDispatch "getExecutionTree" _ = executionTree
methodDispatch "getCurrentReference" _ = getCurrentReference
methodDispatch "getAllReferences" _ = getAllReferences
methodDispatch "getLeaves" _ = getLeaves
methodDispatch _ _ = throwE methodNotFound

handleRequest :: (Eq o, Monoid o, ToJSON o, ToJSON o, ToJSON p, Eq p, ToJSON c, ExplorerPostValue p c o) => Maybe RequestMessage -> EIP p IO c o ResponseMessage
handleRequest (Just msg) = do
    res <- runExceptT $ methodDispatch (method msg) (params msg)
    return $ ResponseMessage { res_id = req_id msg, body = res }
handleRequest Nothing = return $ ResponseMessage { res_id = "0", body = Left parseError { message = "NOthing" }}


handleRequest' :: (Eq o, Monoid o, ToJSON o, Eq p, ToJSON p, ToJSON c, ExplorerPostValue p c o) => S.ByteString -> EIP p IO c o ResponseMessage
handleRequest' body =
    case decode body of
        (Just m) -> handleRequest m
        Nothing -> return invalidHeader

invalidHeader :: ResponseMessage
invalidHeader = ResponseMessage { res_id = "0", body = Left parseError {message = "Headeer"} }

parseHeader :: AB.Parser (Int, String)
parseHeader = do
    AB.string "Content-Length:"
    AB.skipMany (ABC.char ' ')
    res <- ABC.scientific
    AB.skipMany (ABC.char ' ')
    ABC.char '\r'
    ABC.char '\n'
    ABC.string "Content-Type:"
    AB.skipMany (ABC.char ' ')
    typ <- AB.manyTill ABC.letter_ascii (ABC.char '\r')
    AB.skipMany (ABC.char ' ')
    ABC.char '\n'
    ABC.char '\r'
    ABC.char '\n'
    return $ (fromJust $ ((toBoundedInteger res) :: Maybe Int), typ)


intProg :: Int -> Int -> IO (Maybe Int, ())
intProg x y = do
    putStrLn . show $ y
    return (Just x, ())

intParse :: String -> Maybe Int
intParse _ = Just 1

-- TODO: Handle incorrect request.
-- TODO: Send correct error messages.
serve :: (Eq o, Monoid o, ToJSON o, Eq p, ToJSON p, ToJSON c, ExplorerPostValue p c o) => String -> Ex.Explorer p IO c o -> (String -> Maybe p) -> IO ()
serve port ex parser = withSocketsDo $ do
    addr <- resolve port
    E.bracket (open addr) close loop
  where
    resolve port = do
        let hints = defaultHints {
                addrFlags = [AI_PASSIVE]
              , addrSocketType = Stream
              }
        addr:_ <- getAddrInfo (Just hints) Nothing (Just port)
        return addr
    open addr = do
        sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
        setSocketOption sock ReuseAddr 1
        bind sock (addrAddress addr)
        -- If the prefork technique is not used,
        -- set CloseOnExec for the security reasons.
        fd <- fdSocket sock
        setCloseOnExecIfNeeded fd
        listen sock 10
        return sock
    loop sock = forever $ do
        (conn, peer) <- accept sock
        putStrLn $ "Connection from " ++ show peer
        forkFinally (talk ex parser conn) (\_ -> close conn)
    talk ex parser conn = do
        putStrLn "Hello receiving"
        msg <- recv conn 1024
        unless (S.null msg) $ do
            ex' <- acceptCommand ex parser conn msg
            talk ex' parser conn

acceptCommand ex parser conn command = do
    let res = AB.parse parseHeader command
    putStrLn $ show res
    (result, toParse) <- case res of
        (AB.Done rem (val, _)) -> do
            case S.length rem < (fromIntegral val) of
                True -> do
                    msg <- recv conn 1024
                    return (Nothing, S.append command msg)
                False -> do
                    let command = S.take (fromIntegral val) rem
                    putStrLn "-------------------------"
                    putStrLn $ show command
                    putStrLn "-------------------------"
                    out <- runRWST (handleRequest' command) parser ex
                    return (Just out, S.drop (fromIntegral val) rem)
        (AB.Fail _ _ "not enough input") -> do
            msg <- recv conn 1024
            return (Nothing, S.append command msg)
        _ ->  return (Just (invalidHeader, ex, ""), "")
    case result of
        Nothing -> if toParse == "" then return ex else acceptCommand ex parser conn toParse
        Just (resp, ex', log) -> do
            let encoded_resp = encode resp
            let full_resp = S.concat ["Content-Length:", encode $ S.length encoded_resp, "\r\nContent-Type: jrpcei\r\n\r\n", encoded_resp]
            sendAll conn full_resp
            putStrLn $ show full_resp
            putStrLn $ show toParse
            if toParse == "" then return ex' else acceptCommand ex' parser conn toParse