-- | -- Copyright : (c) Sam Truzjan 2013 -- License : BSD3 -- Maintainer : pxqr.sta@gmail.com -- Stability : experimental -- Portability : portable -- -- KRPC messages types used in communication. All messages are -- encoded as bencode dictionary. -- -- Normally, you don't need to import this module. -- -- See -- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveDataTypeable #-} module Network.KRPC.Message ( -- * Transaction TransactionId -- * Error , ErrorCode (..) , KError(..) , serverError , decodeError , unknownMethod , unknownMessage , timeoutExpired -- * Query , KQuery(..) , MethodName -- * Response , KResponse(..) -- * Message , KMessage (..) ) where import Control.Applicative import Control.Exception.Lifted as Lifted import Data.BEncode as BE import Data.ByteString as B import Data.ByteString.Char8 as BC import Data.Typeable -- | This transaction ID is generated by the querying node and is -- echoed in the response, so responses may be correlated with -- multiple queries to the same node. The transaction ID should be -- encoded as a short string of binary numbers, typically 2 characters -- are enough as they cover 2^16 outstanding queries. type TransactionId = ByteString unknownTransaction :: TransactionId unknownTransaction = "" {----------------------------------------------------------------------- -- Error messages -----------------------------------------------------------------------} -- | Types of RPC errors. data ErrorCode -- | Some error doesn't fit in any other category. = GenericError -- | Occur when server fail to process procedure call. | ServerError -- | Malformed packet, invalid arguments or bad token. | ProtocolError -- | Occur when client trying to call method server don't know. | MethodUnknown deriving (Show, Read, Eq, Ord, Bounded, Typeable) -- | According to the table: -- instance Enum ErrorCode where fromEnum GenericError = 201 fromEnum ServerError = 202 fromEnum ProtocolError = 203 fromEnum MethodUnknown = 204 {-# INLINE fromEnum #-} toEnum 201 = GenericError toEnum 202 = ServerError toEnum 203 = ProtocolError toEnum 204 = MethodUnknown toEnum _ = GenericError {-# INLINE toEnum #-} instance BEncode ErrorCode where toBEncode = toBEncode . fromEnum {-# INLINE toBEncode #-} fromBEncode b = toEnum <$> fromBEncode b {-# INLINE fromBEncode #-} -- | Errors are sent when a query cannot be fulfilled. Error message -- can be send only from server to client but not in the opposite -- direction. -- data KError = KError { errorCode :: !ErrorCode -- ^ the type of error; , errorMessage :: !ByteString -- ^ human-readable text message; , errorId :: !TransactionId -- ^ match to the corresponding 'queryId'. } deriving (Show, Read, Eq, Ord, Typeable) -- | Errors, or KRPC message dictionaries with a \"y\" value of \"e\", -- contain one additional key \"e\". The value of \"e\" is a -- list. The first element is an integer representing the error -- code. The second element is a string containing the error -- message. -- -- Example Error Packet: -- -- > { "t": "aa", "y":"e", "e":[201, "A Generic Error Ocurred"]} -- -- or bencoded: -- -- > d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee -- instance BEncode KError where toBEncode KError {..} = toDict $ "e" .=! (errorCode, errorMessage) .: "t" .=! errorId .: "y" .=! ("e" :: ByteString) .: endDict {-# INLINE toBEncode #-} fromBEncode = fromDict $ do lookAhead $ match "y" (BString "e") (code, msg) <- field (req "e") KError code msg <$>! "t" {-# INLINE fromBEncode #-} instance Exception KError -- | Happen when some query handler fail. serverError :: SomeException -> TransactionId -> KError serverError e = KError ServerError (BC.pack (show e)) -- | Received 'queryArgs' or 'respVals' can not be decoded. decodeError :: String -> TransactionId -> KError decodeError msg = KError ProtocolError (BC.pack msg) -- | If /remote/ node send query /this/ node doesn't know about then -- this error message should be sent in response. unknownMethod :: MethodName -> TransactionId -> KError unknownMethod = KError MethodUnknown -- | A remote node has send some 'KMessage' this node is unable to -- decode. unknownMessage :: String -> KError unknownMessage msg = KError ProtocolError (BC.pack msg) unknownTransaction -- | A /remote/ node is not responding to the /our/ request the for -- specified period of time. timeoutExpired :: TransactionId -> KError timeoutExpired = KError GenericError "timeout expired" {----------------------------------------------------------------------- -- Query messages -----------------------------------------------------------------------} type MethodName = ByteString -- | Query used to signal that caller want to make procedure call to -- callee and pass arguments in. Therefore query may be only sent from -- client to server but not in the opposite direction. -- data KQuery = KQuery { queryArgs :: !BValue -- ^ values to be passed to method; , queryMethod :: !MethodName -- ^ method to call; , queryId :: !TransactionId -- ^ one-time query token. } deriving (Show, Read, Eq, Ord, Typeable) -- | Queries, or KRPC message dictionaries with a \"y\" value of -- \"q\", contain two additional keys; \"q\" and \"a\". Key \"q\" has -- a string value containing the method name of the query. Key \"a\" -- has a dictionary value containing named arguments to the query. -- -- Example Query packet: -- -- > { "t" : "aa", "y" : "q", "q" : "ping", "a" : { "msg" : "hi!" } } -- instance BEncode KQuery where toBEncode KQuery {..} = toDict $ "a" .=! queryArgs .: "q" .=! queryMethod .: "t" .=! queryId .: "y" .=! ("q" :: ByteString) .: endDict {-# INLINE toBEncode #-} fromBEncode = fromDict $ do lookAhead $ match "y" (BString "q") KQuery <$>! "a" <*>! "q" <*>! "t" {-# INLINE fromBEncode #-} {----------------------------------------------------------------------- -- Response messages -----------------------------------------------------------------------} -- | Response messages are sent upon successful completion of a -- query: -- -- * KResponse used to signal that callee successufully process a -- procedure call and to return values from procedure. -- -- * KResponse should not be sent if error occurred during RPC, -- 'KError' should be sent instead. -- -- * KResponse can be only sent from server to client. -- data KResponse = KResponse { respVals :: BValue -- ^ 'BDict' containing return values; , respId :: TransactionId -- ^ match to the corresponding 'queryId'. } deriving (Show, Read, Eq, Ord, Typeable) -- | Responses, or KRPC message dictionaries with a \"y\" value of -- \"r\", contain one additional key \"r\". The value of \"r\" is a -- dictionary containing named return values. -- -- Example Response packet: -- -- > { "t" : "aa", "y" : "r", "r" : { "msg" : "you've sent: hi!" } } -- instance BEncode KResponse where toBEncode KResponse {..} = toDict $ "r" .=! respVals .: "t" .=! respId .: "y" .=! ("r" :: ByteString) .: endDict {-# INLINE toBEncode #-} fromBEncode = fromDict $ do lookAhead $ match "y" (BString "r") KResponse <$>! "r" <*>! "t" {-# INLINE fromBEncode #-} {----------------------------------------------------------------------- -- Summed messages -----------------------------------------------------------------------} -- | Generic KRPC message. data KMessage = Q KQuery | R KResponse | E KError deriving (Show, Eq) instance BEncode KMessage where toBEncode (Q q) = toBEncode q toBEncode (R r) = toBEncode r toBEncode (E e) = toBEncode e fromBEncode b = Q <$> fromBEncode b <|> R <$> fromBEncode b <|> E <$> fromBEncode b <|> decodingError "KMessage: unknown message or message tag"