summaryrefslogtreecommitdiff
path: root/src/Remote
diff options
context:
space:
mode:
authorSam T <pxqr.sta@gmail.com>2013-05-11 13:40:58 +0400
committerSam T <pxqr.sta@gmail.com>2013-05-11 13:40:58 +0400
commitb2a81b581db7f328e0ec345104fb2fea1cae1296 (patch)
tree738da87100d190af4ee34edff603a74eb7103f45 /src/Remote
Initial commit.
Diffstat (limited to 'src/Remote')
-rw-r--r--src/Remote/KRPC.hs132
-rw-r--r--src/Remote/KRPC/Method.hs76
-rw-r--r--src/Remote/KRPC/Protocol.hs207
3 files changed, 415 insertions, 0 deletions
diff --git a/src/Remote/KRPC.hs b/src/Remote/KRPC.hs
new file mode 100644
index 00000000..a6318ccd
--- /dev/null
+++ b/src/Remote/KRPC.hs
@@ -0,0 +1,132 @@
1-- |
2-- Copyright : (c) Sam T. 2013
3-- License : MIT
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- This module provides remote procedure call.
9--
10{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE FlexibleContexts, DeriveDataTypeable #-}
12{-# LANGUAGE ExplicitForAll, KindSignatures #-}
13module Remote.KRPC
14 ( module Remote.KRPC.Method, RemoteAddr
15
16 -- * Client
17 , call, async, await
18
19 -- * Server
20 , handler, server
21 ) where
22
23import Control.Exception
24import Control.Monad
25import Control.Monad.Trans.Control
26import Control.Monad.IO.Class
27import Data.BEncode
28import Data.List as L
29import Data.Map as M
30import Data.Text as T
31import Data.Typeable
32import Network
33
34import Remote.KRPC.Protocol
35import Remote.KRPC.Method
36
37
38data RPCException = RPCException KError
39 deriving (Show, Eq, Typeable)
40
41instance Exception RPCException
42
43
44type RemoteAddr = KRemoteAddr
45
46
47queryCall :: BEncodable param
48 => KRemote -> KRemoteAddr
49 -> Method remote param result -> param -> IO ()
50queryCall sock addr m arg = sendMessage q addr sock
51 where
52 q = kquery (L.head (methodName m)) [(L.head (methodParams m), toBEncode arg)]
53
54getResult :: BEncodable result
55 => KRemote -> KRemoteAddr
56 -> Method remote param result -> IO result
57getResult sock addr m = do
58 resp <- recvResponse addr sock
59 case resp of
60 Left e -> throw (RPCException e)
61 Right (KResponse dict) -> do
62 let valName = L.head (methodVals m)
63 case M.lookup valName dict of
64 Just val | Right res <- fromBEncode val -> return res
65 Nothing -> throw (RPCException (ProtocolError msg))
66 where
67 msg = T.concat
68 [ "Unable to find return value: ", T.pack (show valName), "\n"
69 , "in response: ", T.pack (show dict)
70 ]
71
72-- TODO async call
73-- | Makes remote procedure call. Throws RPCException if server
74-- returns error or decode error occurred.
75--
76call :: (MonadBaseControl IO host, MonadIO host)
77 => (BEncodable param, BEncodable result)
78 => RemoteAddr
79 -> Method remote param result
80 -> param
81 -> host result
82call addr m arg = liftIO $ withRemote $ \sock -> do
83 queryCall sock addr m arg
84 getResult sock addr m
85
86
87newtype Async result = Async { waitResult :: IO result }
88
89async :: MonadIO host
90 => (BEncodable param, BEncodable result)
91 => RemoteAddr
92 -> Method remote param result
93 -> param
94 -> host (Async result)
95async addr m arg = do
96 liftIO $ withRemote $ \sock ->
97 queryCall sock addr m arg
98 return $ Async $ withRemote $ \sock ->
99 getResult sock addr m
100
101await :: MonadIO host => Async result -> host result
102await = liftIO . waitResult
103
104-- TODO better name
105type MHandler remote = Method remote BEncode (Result BEncode)
106
107handler :: forall (remote :: * -> *) (param :: *) (result :: *).
108 (BEncodable param, BEncodable result)
109 => Monad remote
110 => Method remote param result
111 -> Method remote BEncode (Result BEncode)
112handler m = m { methodBody = \x -> do
113 case fromBEncode x of
114 Right a -> liftM (Right . toBEncode) (methodBody m a)
115 Left e -> return (Left e)
116 }
117
118-- TODO: allow forkIO
119server :: (MonadBaseControl IO remote, MonadIO remote)
120 => PortNumber
121 -> [MHandler remote]
122 -> remote ()
123server servport ms = remoteServer servport $ \_ q -> do
124 let name = queryMethod q
125 let args = queryArgs q
126 let m = L.head ms
127 res <- methodBody m (snd (L.head (M.toList args)))
128 case res of
129 Left r -> return (Left (ProtocolError (T.pack r)))
130 Right r -> do
131 let retName = L.head (methodVals m)
132 return (Right (kresponse [(retName, r)]))
diff --git a/src/Remote/KRPC/Method.hs b/src/Remote/KRPC/Method.hs
new file mode 100644
index 00000000..f4b0bb9a
--- /dev/null
+++ b/src/Remote/KRPC/Method.hs
@@ -0,0 +1,76 @@
1{-# LANGUAGE OverloadedStrings #-}
2module Remote.KRPC.Method
3 ( Method(methodName, methodParams, methodVals, methodBody)
4
5 -- * Construction
6 , method
7
8 -- * Predefined methods
9 , idM, composeM, concatM
10 ) where
11
12import Prelude hiding ((.), id)
13import Control.Category
14import Control.Monad
15
16import Remote.KRPC.Protocol
17
18-- | The
19--
20-- * argument: type of method parameter
21--
22-- * remote: A monad used by server-side.
23--
24-- * result: type of return value of the method.
25--
26data Method remote param result = Method {
27 -- | Name used in query and
28 methodName :: [MethodName]
29
30 -- | Description of each method parameter in right to left order.
31 , methodParams :: [ParamName]
32
33 -- | Description of each method return value in right to left order.
34 , methodVals :: [ValName]
35
36 -- | Description of method body.
37 , methodBody :: param -> remote result
38 }
39
40instance Monad remote => Category (Method remote) where
41 id = idM
42 (.) = composeM
43
44-- | Remote identity function. Could be used for echo servers for example.
45--
46-- idM = method "id" ["x"] ["y"] return
47--
48idM :: Monad m => Method m a a
49idM = method "id" ["x"] ["y"] return
50
51-- | Pipelining of two or more methods.
52--
53-- NOTE: composed methods will work only with this implementation of
54-- KRPC, so both server and client should use this implementation,
55-- otherwise you more likely get the 'ProtocolError'.
56--
57composeM :: Monad m => Method m b c -> Method m a b -> Method m a c
58composeM g h = Method (methodName g ++ methodName h)
59 (methodParams h)
60 (methodVals g)
61 (methodBody h >=> methodBody g)
62
63-- | Concat list of list. Could be used for performance tests.
64--
65-- concatM = method "concat" ["xxs"] ["xs"] $ return . Prelude.concat
66--
67concatM :: Monad m => Method m [[a]] [a]
68concatM = method "concat" ["xxs"] ["xs"] $ return . Prelude.concat
69
70
71method :: MethodName
72 -> [ParamName]
73 -> [ValName]
74 -> (param -> remote result)
75 -> Method remote param result
76method name = Method [name]
diff --git a/src/Remote/KRPC/Protocol.hs b/src/Remote/KRPC/Protocol.hs
new file mode 100644
index 00000000..0aa7e100
--- /dev/null
+++ b/src/Remote/KRPC/Protocol.hs
@@ -0,0 +1,207 @@
1-- |
2-- Copyright : (c) Sam T. 2013
3-- License : MIT
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- This module provides straightforward implementation of KRPC
9-- protocol. In many situations Network.KRPC should be prefered
10-- since it gives more safe, convenient and high level api.
11--
12-- > See http://www.bittorrent.org/beps/bep_0005.html#krpc-protocol
13--
14{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}
15module Remote.KRPC.Protocol
16 (
17 -- * Error
18 KError(..), errorCode, mkKError
19
20 -- * Query
21 , KQuery(..), MethodName, ParamName, kquery
22
23 -- * Response
24 , KResponse(..), ValName, kresponse
25 , sendMessage, recvResponse
26
27 -- * Remote
28 , KRemote, KRemoteAddr, withRemote, remoteServer
29
30 -- * Re-exports
31 , encode, encoded, decode, decoded, toBEncode, fromBEncode
32 ) where
33
34import Control.Applicative
35import Control.Exception.Lifted
36import Control.Monad
37import Control.Monad.IO.Class
38import Control.Monad.Trans.Control
39import Data.BEncode
40import Data.ByteString as B
41import qualified Data.ByteString.Lazy as LB
42import Data.Map as M
43import Data.Text as T
44import Network.Socket hiding (recvFrom)
45import Network.Socket.ByteString
46
47
48data KError
49 = GenericError { errorMessage :: Text }
50 | ServerError { errorMessage :: Text }
51 -- | Malformed packet, invalid arguments or bad token.
52 | ProtocolError { errorMessage :: Text }
53 | MethodUnknown { errorMessage :: Text }
54 deriving (Show, Read, Eq, Ord)
55
56instance BEncodable KError where
57 toBEncode e = fromAssocs
58 [ "y" --> ("e" :: ByteString)
59 , "e" --> (errorCode e, errorMessage e)
60 ]
61
62 fromBEncode (BDict d)
63 | M.lookup "y" d == Just (BString "e") =
64 uncurry mkKError <$> d >-- "e"
65
66 fromBEncode _ = decodingError "KError"
67
68type ErrorCode = Int
69
70errorCode :: KError -> ErrorCode
71errorCode (GenericError _) = 201
72errorCode (ServerError _) = 202
73errorCode (ProtocolError _) = 203
74errorCode (MethodUnknown _) = 204
75
76mkKError :: ErrorCode -> Text -> KError
77mkKError 201 = GenericError
78mkKError 202 = ServerError
79mkKError 203 = ProtocolError
80mkKError 204 = MethodUnknown
81mkKError _ = GenericError
82
83
84
85type MethodName = ByteString
86type ParamName = ByteString
87
88data KQuery = KQuery {
89 queryMethod :: MethodName
90 , queryArgs :: Map ParamName BEncode
91 } deriving (Show, Read, Eq, Ord)
92
93instance BEncodable KQuery where
94 toBEncode (KQuery m args) = fromAssocs
95 [ "y" --> ("q" :: ByteString)
96 , "q" --> m
97 , "a" --> BDict args
98 ]
99
100 fromBEncode (BDict d)
101 | M.lookup "y" d == Just (BString "q") =
102 KQuery <$> d >-- "q"
103 <*> d >-- "a"
104
105 fromBEncode _ = decodingError "KQuery"
106
107kquery :: MethodName -> [(ParamName, BEncode)] -> KQuery
108kquery name args = KQuery name (M.fromList args)
109
110
111
112
113type ValName = ByteString
114
115newtype KResponse = KResponse (Map ValName BEncode)
116 deriving (Show, Read, Eq, Ord)
117
118instance BEncodable KResponse where
119 toBEncode (KResponse vals) = fromAssocs
120 [ "y" --> ("r" :: ByteString)
121 , "r" --> vals
122 ]
123
124
125 fromBEncode (BDict d)
126 | M.lookup "y" d == Just (BString "r") =
127 KResponse <$> d >-- "r"
128
129 fromBEncode _ = decodingError "KDict"
130
131kresponse :: [(ValName, BEncode)] -> KResponse
132kresponse = KResponse . M.fromList
133
134
135type KRemoteAddr = (HostAddress, PortNumber)
136
137remoteAddr :: KRemoteAddr -> SockAddr
138remoteAddr = SockAddrInet <$> snd <*> fst
139
140type KRemote = Socket
141
142withRemote :: (MonadBaseControl IO m, MonadIO m) => (KRemote -> m a) -> m a
143withRemote = bracket (liftIO (socket AF_INET Datagram defaultProtocol))
144 (liftIO . sClose)
145
146maxMsgSize :: Int
147maxMsgSize = 16 * 1024
148
149-- TODO eliminate toStrict
150sendMessage :: BEncodable msg => msg -> KRemoteAddr -> KRemote -> IO ()
151sendMessage msg (host, port) sock =
152 sendAllTo sock (LB.toStrict (encoded msg)) (SockAddrInet port host)
153
154recvResponse :: KRemoteAddr -> KRemote -> IO (Either KError KResponse)
155recvResponse addr sock = do
156 connect sock (remoteAddr addr)
157 (raw, _) <- recvFrom sock maxMsgSize
158 return $ case decoded raw of
159 Right resp -> Right resp
160 Left decE -> Left $ case decoded raw of
161 Right kerror -> kerror
162 _ -> ProtocolError (T.pack decE)
163
164remoteServer :: (MonadBaseControl IO remote, MonadIO remote)
165 => PortNumber
166 -> (KRemoteAddr -> KQuery -> remote (Either KError KResponse))
167 -> remote ()
168remoteServer servport action = bracket (liftIO bind) (liftIO . sClose) loop
169 where
170 bind = do
171 sock <- socket AF_INET Datagram defaultProtocol
172 bindSocket sock (SockAddrInet servport iNADDR_ANY)
173 return sock
174
175 loop sock = forever $ do
176 (bs, addr) <- liftIO $ recvFrom sock maxMsgSize
177
178 case addr of
179 SockAddrInet port host ->
180 case decoded bs of
181 Right query -> do
182 res <- action (host, port) query
183 case res of
184 Right resp -> liftIO $ sendMessage resp (host, port) sock
185 Left err -> liftIO $ sendMessage err (host, port) sock
186
187 Left decodeE -> liftIO $ sendMessage rpcE (host, port) sock
188 where
189 rpcE = ProtocolError $ T.concat
190 ["Unable to decode query: ", T.pack (show bs), "\n"
191 ,"Specifically: ", T.pack decodeE
192 ]
193 _ -> return ()
194
195
196
197-- TODO to bencodable
198instance (BEncodable a, BEncodable b) => BEncodable (a, b) where
199 {-# SPECIALIZE instance (BEncodable a, BEncodable b) => BEncodable (a, b) #-}
200 toBEncode (a, b) = BList [toBEncode a, toBEncode b]
201 {-# INLINE toBEncode #-}
202
203 fromBEncode be = case fromBEncode be of
204 Right [a, b] -> (,) <$> fromBEncode a <*> fromBEncode b
205 Right _ -> decodingError "Unable to decode a pair."
206 Left e -> Left e
207 {-# INLINE fromBEncode #-} \ No newline at end of file