summaryrefslogtreecommitdiff
path: root/src/Remote/KRPC/Protocol.hs
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/KRPC/Protocol.hs
Initial commit.
Diffstat (limited to 'src/Remote/KRPC/Protocol.hs')
-rw-r--r--src/Remote/KRPC/Protocol.hs207
1 files changed, 207 insertions, 0 deletions
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