diff options
author | Sam T <pxqr.sta@gmail.com> | 2013-05-11 13:40:58 +0400 |
---|---|---|
committer | Sam T <pxqr.sta@gmail.com> | 2013-05-11 13:40:58 +0400 |
commit | b2a81b581db7f328e0ec345104fb2fea1cae1296 (patch) | |
tree | 738da87100d190af4ee34edff603a74eb7103f45 /src/Remote/KRPC/Protocol.hs |
Initial commit.
Diffstat (limited to 'src/Remote/KRPC/Protocol.hs')
-rw-r--r-- | src/Remote/KRPC/Protocol.hs | 207 |
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 #-} | ||
15 | module 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 | |||
34 | import Control.Applicative | ||
35 | import Control.Exception.Lifted | ||
36 | import Control.Monad | ||
37 | import Control.Monad.IO.Class | ||
38 | import Control.Monad.Trans.Control | ||
39 | import Data.BEncode | ||
40 | import Data.ByteString as B | ||
41 | import qualified Data.ByteString.Lazy as LB | ||
42 | import Data.Map as M | ||
43 | import Data.Text as T | ||
44 | import Network.Socket hiding (recvFrom) | ||
45 | import Network.Socket.ByteString | ||
46 | |||
47 | |||
48 | data 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 | |||
56 | instance 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 | |||
68 | type ErrorCode = Int | ||
69 | |||
70 | errorCode :: KError -> ErrorCode | ||
71 | errorCode (GenericError _) = 201 | ||
72 | errorCode (ServerError _) = 202 | ||
73 | errorCode (ProtocolError _) = 203 | ||
74 | errorCode (MethodUnknown _) = 204 | ||
75 | |||
76 | mkKError :: ErrorCode -> Text -> KError | ||
77 | mkKError 201 = GenericError | ||
78 | mkKError 202 = ServerError | ||
79 | mkKError 203 = ProtocolError | ||
80 | mkKError 204 = MethodUnknown | ||
81 | mkKError _ = GenericError | ||
82 | |||
83 | |||
84 | |||
85 | type MethodName = ByteString | ||
86 | type ParamName = ByteString | ||
87 | |||
88 | data KQuery = KQuery { | ||
89 | queryMethod :: MethodName | ||
90 | , queryArgs :: Map ParamName BEncode | ||
91 | } deriving (Show, Read, Eq, Ord) | ||
92 | |||
93 | instance 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 | |||
107 | kquery :: MethodName -> [(ParamName, BEncode)] -> KQuery | ||
108 | kquery name args = KQuery name (M.fromList args) | ||
109 | |||
110 | |||
111 | |||
112 | |||
113 | type ValName = ByteString | ||
114 | |||
115 | newtype KResponse = KResponse (Map ValName BEncode) | ||
116 | deriving (Show, Read, Eq, Ord) | ||
117 | |||
118 | instance 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 | |||
131 | kresponse :: [(ValName, BEncode)] -> KResponse | ||
132 | kresponse = KResponse . M.fromList | ||
133 | |||
134 | |||
135 | type KRemoteAddr = (HostAddress, PortNumber) | ||
136 | |||
137 | remoteAddr :: KRemoteAddr -> SockAddr | ||
138 | remoteAddr = SockAddrInet <$> snd <*> fst | ||
139 | |||
140 | type KRemote = Socket | ||
141 | |||
142 | withRemote :: (MonadBaseControl IO m, MonadIO m) => (KRemote -> m a) -> m a | ||
143 | withRemote = bracket (liftIO (socket AF_INET Datagram defaultProtocol)) | ||
144 | (liftIO . sClose) | ||
145 | |||
146 | maxMsgSize :: Int | ||
147 | maxMsgSize = 16 * 1024 | ||
148 | |||
149 | -- TODO eliminate toStrict | ||
150 | sendMessage :: BEncodable msg => msg -> KRemoteAddr -> KRemote -> IO () | ||
151 | sendMessage msg (host, port) sock = | ||
152 | sendAllTo sock (LB.toStrict (encoded msg)) (SockAddrInet port host) | ||
153 | |||
154 | recvResponse :: KRemoteAddr -> KRemote -> IO (Either KError KResponse) | ||
155 | recvResponse 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 | |||
164 | remoteServer :: (MonadBaseControl IO remote, MonadIO remote) | ||
165 | => PortNumber | ||
166 | -> (KRemoteAddr -> KQuery -> remote (Either KError KResponse)) | ||
167 | -> remote () | ||
168 | remoteServer 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 | ||
198 | instance (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 | ||