summaryrefslogtreecommitdiff
path: root/src/Network/KRPC
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/KRPC')
-rw-r--r--src/Network/KRPC/Manager.hs179
-rw-r--r--src/Network/KRPC/Message.hs47
-rw-r--r--src/Network/KRPC/Method.hs61
3 files changed, 284 insertions, 3 deletions
diff --git a/src/Network/KRPC/Manager.hs b/src/Network/KRPC/Manager.hs
new file mode 100644
index 00000000..9aa1bea7
--- /dev/null
+++ b/src/Network/KRPC/Manager.hs
@@ -0,0 +1,179 @@
1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3module Network.KRPC.Manager
4 ( MonadKRPC (..)
5 , newManager
6 , query
7 , handler
8 ) where
9
10import Control.Applicative
11import Control.Arrow
12import Control.Concurrent
13--import Control.Exception hiding (Handler)
14import Control.Exception.Lifted as Lifted hiding (Handler)
15import Control.Monad
16import Control.Monad.Trans.Control
17import Control.Monad.IO.Class
18import Data.BEncode as BE
19import Data.ByteString.Char8 as BC
20import Data.ByteString.Lazy as BL
21import Data.IORef
22import Data.List as L
23import Data.Map as M
24import Network.KRPC.Message
25import Network.KRPC.Method
26import Network.Socket
27import Network.Socket.ByteString as BS
28
29
30type KResult = Either KError KResponse
31
32type TransactionCounter = IORef Int
33type CallId = (TransactionId, SockAddr)
34type CallRes = MVar KResult
35type PendingCalls = IORef (Map CallId CallRes)
36
37type HandlerBody m = SockAddr -> BValue -> m (BE.Result BValue)
38type Handler m = (MethodName, HandlerBody m)
39
40data Manager m = Manager
41 { sock :: !Socket
42 , transactionCounter :: {-# UNPACK #-} !TransactionCounter
43 , pendingCalls :: {-# UNPACK #-} !PendingCalls
44 , handlers :: [Handler m]
45 }
46
47class (MonadBaseControl IO m, MonadIO m) => MonadKRPC m where
48 getManager :: m (Manager a)
49
50sockAddrFamily :: SockAddr -> Family
51sockAddrFamily (SockAddrInet _ _ ) = AF_INET
52sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6
53sockAddrFamily (SockAddrUnix _ ) = AF_UNIX
54
55seedTransaction :: Int
56seedTransaction = 0
57
58newManager :: SockAddr -> IO (Manager a)
59newManager servAddr = do
60 sock <- bindServ
61 tran <- newIORef seedTransaction
62 calls <- newIORef M.empty
63 return $ Manager sock tran calls []
64 where
65 bindServ = do
66 let family = sockAddrFamily servAddr
67 sock <- socket family Datagram defaultProtocol
68 when (family == AF_INET6) $ do
69 setSocketOption sock IPv6Only 0
70 bindSocket sock servAddr
71 return sock
72
73sendMessage :: MonadIO m => BEncode a => Socket -> SockAddr -> a -> m ()
74sendMessage sock addr a =
75 liftIO $ sendManyTo sock (BL.toChunks (BE.encode a)) addr
76
77{-----------------------------------------------------------------------
78-- Client
79-----------------------------------------------------------------------}
80
81genTransactionId :: TransactionCounter -> IO TransactionId
82genTransactionId ref = do
83 cur <- atomicModifyIORef' ref $ \ cur -> (succ cur, cur)
84 return $ BC.pack (show cur)
85
86registerQuery :: CallId -> PendingCalls -> IO CallRes
87registerQuery cid ref = do
88 ares <- newEmptyMVar
89 atomicModifyIORef' ref $ \ m -> (M.insert cid ares m, ())
90 return ares
91
92unregisterQuery :: CallId -> PendingCalls -> IO ()
93unregisterQuery cid ref = do
94 atomicModifyIORef' ref $ \ m -> (M.delete cid m, ())
95
96queryResponse :: BEncode a => CallRes -> IO a
97queryResponse ares = do
98 res <- readMVar ares
99 case res of
100 Left e -> throwIO e
101 Right (KResponse {..}) ->
102 case fromBEncode respVals of
103 Left e -> throwIO (KError ProtocolError (BC.pack e) respId)
104 Right a -> return a
105
106query :: forall m a b. (MonadKRPC m, KRPC a b) => SockAddr -> a -> m b
107query addr params = do
108 Manager {..} <- getManager
109 liftIO $ do
110 tid <- genTransactionId transactionCounter
111 let Method name = method :: Method a b
112 let q = KQuery (toBEncode params) name tid
113 ares <- registerQuery (tid, addr) pendingCalls
114 sendMessage sock addr q
115 `onException` unregisterQuery (tid, addr) pendingCalls
116 queryResponse ares
117
118{-----------------------------------------------------------------------
119-- Handlers
120-----------------------------------------------------------------------}
121
122handler :: forall m a b. (KRPC a b, MonadKRPC m)
123 => (SockAddr -> a -> m b) -> Handler m
124handler body = (name, wrapper)
125 where
126 Method name = method :: Method a b
127 wrapper addr args =
128 case fromBEncode args of
129 Left e -> return $ Left e
130 Right a -> (Right . toBEncode) <$> body addr a
131
132runHandler :: MonadKRPC m => HandlerBody m -> SockAddr -> KQuery -> m KResult
133runHandler handler addr KQuery {..} = wrapper `Lifted.catch` failback
134 where
135 wrapper = ((`decodeError` queryId) +++ (`KResponse` queryId))
136 <$> handler addr queryArgs
137 failback e = return $ Left $ serverError e queryId
138
139dispatchHandler :: MonadKRPC m => KQuery -> SockAddr -> m KResult
140dispatchHandler q @ KQuery {..} addr = do
141 Manager {..} <- getManager
142 case L.lookup queryMethod handlers of
143 Nothing -> return $ Left $ unknownMethod queryMethod queryId
144 Just handler -> runHandler handler addr q
145
146{-----------------------------------------------------------------------
147-- Listener
148-----------------------------------------------------------------------}
149
150handleQuery :: MonadKRPC m => KQuery -> SockAddr -> m ()
151handleQuery q addr = do
152 Manager {..} <- getManager
153 res <- dispatchHandler q addr
154 sendMessage sock addr $ either toBEncode toBEncode res
155
156handleResponse :: MonadKRPC m => KResult -> SockAddr -> m ()
157handleResponse result addr = do
158 Manager {..} <- getManager
159 mcall <- undefined (addr, respId) pendingCalls
160 case mcall of
161 Nothing -> return ()
162 Just ares -> liftIO $ putMVar ares result
163
164handleMessage :: MonadKRPC m => KMessage -> SockAddr -> m ()
165handleMessage (Q q) = handleQuery q
166handleMessage (R r) = handleResponse (Right r)
167handleMessage (E e) = handleResponse (Left e)
168
169maxMsgSize :: Int
170maxMsgSize = 64 * 1024
171
172listener :: MonadKRPC m => m ()
173listener = do
174 Manager {..} <- getManager
175 forever $ do
176 (bs, addr) <- liftIO $ BS.recvFrom sock maxMsgSize
177 case BE.decode bs of
178 Left e -> liftIO $ sendMessage sock addr $ unknownMessage e
179 Right m -> handleMessage m addr
diff --git a/src/Network/KRPC/Message.hs b/src/Network/KRPC/Message.hs
index a70c2ea9..3bbfb1db 100644
--- a/src/Network/KRPC/Message.hs
+++ b/src/Network/KRPC/Message.hs
@@ -19,10 +19,17 @@
19{-# LANGUAGE DefaultSignatures #-} 19{-# LANGUAGE DefaultSignatures #-}
20{-# LANGUAGE DeriveDataTypeable #-} 20{-# LANGUAGE DeriveDataTypeable #-}
21module Network.KRPC.Message 21module Network.KRPC.Message
22 ( -- * Error 22 ( -- * Transaction
23 ErrorCode (..) 23 TransactionId
24 , unknownTransaction
25
26 -- * Error
27 , ErrorCode (..)
24 , KError(..) 28 , KError(..)
25 , serverError 29 , serverError
30 , decodeError
31 , unknownMethod
32 , unknownMessage
26 33
27 -- * Query 34 -- * Query
28 , KQuery(..) 35 , KQuery(..)
@@ -30,6 +37,9 @@ module Network.KRPC.Message
30 37
31 -- * Response 38 -- * Response
32 , KResponse(..) 39 , KResponse(..)
40
41 -- * Message
42 , KMessage (..)
33 ) where 43 ) where
34 44
35import Control.Applicative 45import Control.Applicative
@@ -47,6 +57,9 @@ import Data.Typeable
47-- are enough as they cover 2^16 outstanding queries. 57-- are enough as they cover 2^16 outstanding queries.
48type TransactionId = ByteString 58type TransactionId = ByteString
49 59
60unknownTransaction :: TransactionId
61unknownTransaction = ""
62
50{----------------------------------------------------------------------- 63{-----------------------------------------------------------------------
51-- Error messages 64-- Error messages
52-----------------------------------------------------------------------} 65-----------------------------------------------------------------------}
@@ -120,6 +133,15 @@ instance Exception KError
120serverError :: SomeException -> TransactionId -> KError 133serverError :: SomeException -> TransactionId -> KError
121serverError e = KError ServerError (BC.pack (show e)) 134serverError e = KError ServerError (BC.pack (show e))
122 135
136decodeError :: String -> TransactionId -> KError
137decodeError msg = KError ProtocolError (BC.pack msg)
138
139unknownMethod :: MethodName -> TransactionId -> KError
140unknownMethod = KError MethodUnknown
141
142unknownMessage :: String -> KError
143unknownMessage msg = KError ProtocolError (BC.pack msg) ""
144
123{----------------------------------------------------------------------- 145{-----------------------------------------------------------------------
124-- Query messages 146-- Query messages
125-----------------------------------------------------------------------} 147-----------------------------------------------------------------------}
@@ -183,4 +205,23 @@ instance BEncode KResponse where
183 fromBEncode = fromDict $ do 205 fromBEncode = fromDict $ do
184 lookAhead $ match "y" (BString "r") 206 lookAhead $ match "y" (BString "r")
185 KResponse <$>! "r" <*>! "t" 207 KResponse <$>! "r" <*>! "t"
186 {-# INLINE fromBEncode #-} \ No newline at end of file 208 {-# INLINE fromBEncode #-}
209
210{-----------------------------------------------------------------------
211-- Summed messages
212-----------------------------------------------------------------------}
213
214data KMessage
215 = Q KQuery
216 | R KResponse
217 | E KError
218
219instance BEncode KMessage where
220 toBEncode (Q q) = toBEncode q
221 toBEncode (R r) = toBEncode r
222 toBEncode (E e) = toBEncode e
223
224 fromBEncode b =
225 Q <$> fromBEncode b
226 <|> R <$> fromBEncode b
227 <|> E <$> fromBEncode b
diff --git a/src/Network/KRPC/Method.hs b/src/Network/KRPC/Method.hs
new file mode 100644
index 00000000..54aa8ef0
--- /dev/null
+++ b/src/Network/KRPC/Method.hs
@@ -0,0 +1,61 @@
1{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE MultiParamTypeClasses #-}
3{-# LANGUAGE FunctionalDependencies #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6module Network.KRPC.Method
7 ( Method (..)
8 , KRPC (..)
9 ) where
10
11import Data.BEncode (BEncode)
12import Data.Monoid
13import Data.String
14import Data.Typeable
15import Network.KRPC.Message
16
17
18-- | Method datatype used to describe name, parameters and return
19-- values of procedure. Client use a method to /invoke/, server
20-- /implements/ the method to make the actual work.
21--
22-- We use the following fantom types to ensure type-safiety:
23--
24-- * param: Type of method parameters. Ordinary Tuple type used
25-- to specify more than one parameter, so for example @Method
26-- (Int, Int) result@ will take two arguments.
27--
28-- * result: Type of return value of the method. Similarly,
29-- tuple used to specify more than one return value, so for
30-- exsample @Method (Foo, Bar) (Bar, Foo)@ will take two arguments
31-- and return two values.
32--
33newtype Method param result = Method MethodName
34 deriving (Eq, Ord, IsString, BEncode)
35
36instance (Typeable a, Typeable b) => Show (Method a b) where
37 showsPrec _ = showsMethod
38
39showsMethod :: forall a. forall b. Typeable a => Typeable b
40 => Method a b -> ShowS
41showsMethod (Method name) =
42 shows name <>
43 showString " :: " <>
44 shows paramsTy <>
45 showString " -> " <>
46 shows valuesTy
47 where
48 impossible = error "KRPC.showsMethod: impossible"
49 paramsTy = typeOf (impossible :: a)
50 valuesTy = typeOf (impossible :: b)
51
52-- | Example:
53-- @
54-- data Ping = Ping Text deriving BEncode
55-- data Pong = Pong Text deriving BEncode
56--
57-- instance KRPC Ping Pong where
58-- method = "ping"
59-- @
60class (BEncode req, BEncode resp) => KRPC req resp | req -> resp where
61 method :: Method req resp