diff options
-rw-r--r-- | krpc.cabal | 14 | ||||
-rw-r--r-- | src/Network/KRPC.hs | 4 | ||||
-rw-r--r-- | src/Network/KRPC/Manager.hs | 61 | ||||
-rw-r--r-- | src/Network/KRPC/Message.hs | 45 | ||||
-rw-r--r-- | src/Network/KRPC/Method.hs | 3 | ||||
-rw-r--r-- | tests/Network/KRPC/MessageSpec.hs | 7 |
6 files changed, 99 insertions, 35 deletions
@@ -34,6 +34,11 @@ source-repository this | |||
34 | branch: master | 34 | branch: master |
35 | tag: v0.6.1.0 | 35 | tag: v0.6.1.0 |
36 | 36 | ||
37 | flag builder | ||
38 | description: Use older bytestring package and bytestring-builder. | ||
39 | default: False | ||
40 | |||
41 | |||
37 | library | 42 | library |
38 | default-language: Haskell2010 | 43 | default-language: Haskell2010 |
39 | default-extensions: PatternGuards | 44 | default-extensions: PatternGuards |
@@ -44,7 +49,6 @@ library | |||
44 | Network.KRPC.Method | 49 | Network.KRPC.Method |
45 | Network.KRPC.Manager | 50 | Network.KRPC.Manager |
46 | build-depends: base == 4.* | 51 | build-depends: base == 4.* |
47 | , bytestring >= 0.10 | ||
48 | , text >= 0.11 | 52 | , text >= 0.11 |
49 | , data-default-class | 53 | , data-default-class |
50 | , lifted-base >= 0.1.1 | 54 | , lifted-base >= 0.1.1 |
@@ -54,7 +58,13 @@ library | |||
54 | , monad-logger >= 0.3 | 58 | , monad-logger >= 0.3 |
55 | , bencoding >= 0.4.3 | 59 | , bencoding >= 0.4.3 |
56 | , network >= 2.3 | 60 | , network >= 2.3 |
61 | , cereal | ||
57 | , containers | 62 | , containers |
63 | if flag(builder) | ||
64 | build-depends: bytestring >= 0.9, bytestring-builder | ||
65 | else | ||
66 | build-depends: bytestring >= 0.10 | ||
67 | |||
58 | if impl(ghc < 7.6) | 68 | if impl(ghc < 7.6) |
59 | build-depends: ghc-prim | 69 | build-depends: ghc-prim |
60 | ghc-options: -Wall | 70 | ghc-options: -Wall |
@@ -89,4 +99,4 @@ benchmark bench | |||
89 | , monad-logger | 99 | , monad-logger |
90 | , criterion | 100 | , criterion |
91 | , krpc | 101 | , krpc |
92 | ghc-options: -O2 -fforce-recomp \ No newline at end of file | 102 | ghc-options: -O2 -fforce-recomp |
diff --git a/src/Network/KRPC.hs b/src/Network/KRPC.hs index b15927cf..d185fb4c 100644 --- a/src/Network/KRPC.hs +++ b/src/Network/KRPC.hs | |||
@@ -59,6 +59,8 @@ module Network.KRPC | |||
59 | -- ** Query | 59 | -- ** Query |
60 | , QueryFailure (..) | 60 | , QueryFailure (..) |
61 | , query | 61 | , query |
62 | , query' | ||
63 | , queryRaw | ||
62 | , getQueryCount | 64 | , getQueryCount |
63 | 65 | ||
64 | -- ** Handler | 66 | -- ** Handler |
@@ -86,4 +88,4 @@ import Data.Default.Class | |||
86 | import Network.KRPC.Message | 88 | import Network.KRPC.Message |
87 | import Network.KRPC.Method | 89 | import Network.KRPC.Method |
88 | import Network.KRPC.Manager | 90 | import Network.KRPC.Manager |
89 | import Network.Socket (SockAddr (..)) \ No newline at end of file | 91 | import Network.Socket (SockAddr (..)) |
diff --git a/src/Network/KRPC/Manager.hs b/src/Network/KRPC/Manager.hs index 4436a9ba..9477d23c 100644 --- a/src/Network/KRPC/Manager.hs +++ b/src/Network/KRPC/Manager.hs | |||
@@ -30,6 +30,8 @@ module Network.KRPC.Manager | |||
30 | -- * Queries | 30 | -- * Queries |
31 | , QueryFailure (..) | 31 | , QueryFailure (..) |
32 | , query | 32 | , query |
33 | , query' | ||
34 | , queryRaw | ||
33 | , getQueryCount | 35 | , getQueryCount |
34 | 36 | ||
35 | -- * Handlers | 37 | -- * Handlers |
@@ -49,6 +51,7 @@ import Control.Monad.Logger | |||
49 | import Control.Monad.Reader | 51 | import Control.Monad.Reader |
50 | import Control.Monad.Trans.Control | 52 | import Control.Monad.Trans.Control |
51 | import Data.BEncode as BE | 53 | import Data.BEncode as BE |
54 | import Data.BEncode.Internal as BE | ||
52 | import Data.ByteString as BS | 55 | import Data.ByteString as BS |
53 | import Data.ByteString.Char8 as BC | 56 | import Data.ByteString.Char8 as BC |
54 | import Data.ByteString.Lazy as BL | 57 | import Data.ByteString.Lazy as BL |
@@ -118,7 +121,7 @@ type KResult = Either KError KResponse | |||
118 | 121 | ||
119 | type TransactionCounter = IORef Int | 122 | type TransactionCounter = IORef Int |
120 | type CallId = (TransactionId, SockAddr) | 123 | type CallId = (TransactionId, SockAddr) |
121 | type CallRes = MVar KResult | 124 | type CallRes = MVar (BValue, KResult) |
122 | type PendingCalls = IORef (Map CallId CallRes) | 125 | type PendingCalls = IORef (Map CallId CallRes) |
123 | 126 | ||
124 | type HandlerBody h = SockAddr -> BValue -> h (BE.Result BValue) | 127 | type HandlerBody h = SockAddr -> BValue -> h (BE.Result BValue) |
@@ -163,6 +166,7 @@ sockAddrFamily :: SockAddr -> Family | |||
163 | sockAddrFamily (SockAddrInet _ _ ) = AF_INET | 166 | sockAddrFamily (SockAddrInet _ _ ) = AF_INET |
164 | sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6 | 167 | sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6 |
165 | sockAddrFamily (SockAddrUnix _ ) = AF_UNIX | 168 | sockAddrFamily (SockAddrUnix _ ) = AF_UNIX |
169 | sockAddrFamily (SockAddrCan _ ) = AF_CAN | ||
166 | 170 | ||
167 | -- | Bind socket to the specified address. To enable query handling | 171 | -- | Bind socket to the specified address. To enable query handling |
168 | -- run 'listen'. | 172 | -- run 'listen'. |
@@ -261,15 +265,6 @@ unregisterQuery cid ref = do | |||
261 | atomicModifyIORef' ref $ swap . | 265 | atomicModifyIORef' ref $ swap . |
262 | M.updateLookupWithKey (const (const Nothing)) cid | 266 | M.updateLookupWithKey (const (const Nothing)) cid |
263 | 267 | ||
264 | queryResponse :: BEncode a => CallRes -> IO a | ||
265 | queryResponse ares = do | ||
266 | res <- readMVar ares | ||
267 | case res of | ||
268 | Left (KError c m _) -> throwIO $ QueryFailed c (T.decodeUtf8 m) | ||
269 | Right (KResponse {..}) -> | ||
270 | case fromBEncode respVals of | ||
271 | Right r -> pure r | ||
272 | Left e -> throwIO $ QueryFailed ProtocolError (T.pack e) | ||
273 | 268 | ||
274 | -- (sendmsg EINVAL) | 269 | -- (sendmsg EINVAL) |
275 | sendQuery :: BEncode a => Socket -> SockAddr -> a -> IO () | 270 | sendQuery :: BEncode a => Socket -> SockAddr -> a -> IO () |
@@ -284,7 +279,21 @@ sendQuery sock addr q = handle sockError $ sendMessage sock addr q | |||
284 | -- respond with @error@ message or the query timeout expires. | 279 | -- respond with @error@ message or the query timeout expires. |
285 | -- | 280 | -- |
286 | query :: forall h m a b. (MonadKRPC h m, KRPC a b) => SockAddr -> a -> m b | 281 | query :: forall h m a b. (MonadKRPC h m, KRPC a b) => SockAddr -> a -> m b |
287 | query addr params = do | 282 | query addr params = queryK addr params (\_ x _ -> x) |
283 | |||
284 | -- | Like 'query' but possibly returns your externally routable IP address. | ||
285 | query' :: forall h m a b. (MonadKRPC h m, KRPC a b) => SockAddr -> a -> m (b, Maybe ReflectedIP) | ||
286 | query' addr params = queryK addr params (const (,)) | ||
287 | |||
288 | -- | Enqueue a query, but give us the complete BEncoded content sent by the | ||
289 | -- remote Node. This is useful for handling extensions that this library does | ||
290 | -- not otherwise support. | ||
291 | queryRaw :: forall h m a b. (MonadKRPC h m, KRPC a b) => SockAddr -> a -> m (b, BValue) | ||
292 | queryRaw addr params = queryK addr params (\raw x _ -> (x,raw)) | ||
293 | |||
294 | queryK :: forall h m a b x. (MonadKRPC h m, KRPC a b) => | ||
295 | SockAddr -> a -> (BValue -> b -> Maybe ReflectedIP -> x) -> m x | ||
296 | queryK addr params kont = do | ||
288 | Manager {..} <- getManager | 297 | Manager {..} <- getManager |
289 | tid <- liftIO $ genTransactionId transactionCounter | 298 | tid <- liftIO $ genTransactionId transactionCounter |
290 | let queryMethod = method :: Method a b | 299 | let queryMethod = method :: Method a b |
@@ -299,7 +308,13 @@ query addr params = do | |||
299 | `onException` unregisterQuery (tid, addr) pendingCalls | 308 | `onException` unregisterQuery (tid, addr) pendingCalls |
300 | 309 | ||
301 | timeout (optQueryTimeout options * 10 ^ (6 :: Int)) $ do | 310 | timeout (optQueryTimeout options * 10 ^ (6 :: Int)) $ do |
302 | queryResponse ares | 311 | (raw,res) <- readMVar ares |
312 | case res of | ||
313 | Left (KError c m _) -> throwIO $ QueryFailed c (T.decodeUtf8 m) | ||
314 | Right (KResponse {..}) -> | ||
315 | case fromBEncode respVals of | ||
316 | Right r -> pure $ kont raw r respIP | ||
317 | Left e -> throwIO $ QueryFailed ProtocolError (T.pack e) | ||
303 | 318 | ||
304 | case mres of | 319 | case mres of |
305 | Just res -> do | 320 | Just res -> do |
@@ -378,7 +393,7 @@ runHandler h addr KQuery {..} = Lifted.catches wrapper failbacks | |||
378 | 393 | ||
379 | Right a -> do | 394 | Right a -> do |
380 | $(logDebugS) "handler.success" signature | 395 | $(logDebugS) "handler.success" signature |
381 | return $ Right $ KResponse a queryId | 396 | return $ Right $ KResponse a queryId (Just $ ReflectedIP addr) |
382 | 397 | ||
383 | failbacks = | 398 | failbacks = |
384 | [ E.Handler $ \ (e :: HandlerFailure) -> do | 399 | [ E.Handler $ \ (e :: HandlerFailure) -> do |
@@ -419,20 +434,20 @@ handleQuery q addr = void $ fork $ do | |||
419 | res <- dispatchHandler q addr | 434 | res <- dispatchHandler q addr |
420 | sendMessage sock addr $ either toBEncode toBEncode res | 435 | sendMessage sock addr $ either toBEncode toBEncode res |
421 | 436 | ||
422 | handleResponse :: MonadKRPC h m => KResult -> SockAddr -> m () | 437 | handleResponse :: MonadKRPC h m => BValue -> KResult -> SockAddr -> m () |
423 | handleResponse result addr = do | 438 | handleResponse raw result addr = do |
424 | Manager {..} <- getManager | 439 | Manager {..} <- getManager |
425 | liftIO $ do | 440 | liftIO $ do |
426 | let resultId = either errorId respId result | 441 | let resultId = either errorId respId result |
427 | mcall <- unregisterQuery (resultId, addr) pendingCalls | 442 | mcall <- unregisterQuery (resultId, addr) pendingCalls |
428 | case mcall of | 443 | case mcall of |
429 | Nothing -> return () | 444 | Nothing -> return () |
430 | Just ares -> putMVar ares result | 445 | Just ares -> putMVar ares (raw,result) |
431 | 446 | ||
432 | handleMessage :: MonadKRPC h m => KMessage -> SockAddr -> m () | 447 | handleMessage :: MonadKRPC h m => BValue -> KMessage -> SockAddr -> m () |
433 | handleMessage (Q q) = handleQuery q | 448 | handleMessage _ (Q q) = handleQuery q |
434 | handleMessage (R r) = handleResponse (Right r) | 449 | handleMessage raw (R r) = handleResponse raw (Right r) |
435 | handleMessage (E e) = handleResponse (Left e) | 450 | handleMessage raw (E e) = handleResponse raw (Left e) |
436 | 451 | ||
437 | listener :: MonadKRPC h m => m () | 452 | listener :: MonadKRPC h m => m () |
438 | listener = do | 453 | listener = do |
@@ -441,10 +456,10 @@ listener = do | |||
441 | (bs, addr) <- liftIO $ do | 456 | (bs, addr) <- liftIO $ do |
442 | handle exceptions $ BS.recvFrom sock (optMaxMsgSize options) | 457 | handle exceptions $ BS.recvFrom sock (optMaxMsgSize options) |
443 | 458 | ||
444 | case BE.decode bs of | 459 | case BE.parse bs >>= \r -> (,) r <$> BE.decode bs of |
445 | -- TODO ignore unknown messages at all? | 460 | -- TODO ignore unknown messages at all? |
446 | Left e -> liftIO $ sendMessage sock addr $ unknownMessage e | 461 | Left e -> liftIO $ sendMessage sock addr $ unknownMessage e |
447 | Right m -> handleMessage m addr | 462 | Right (raw,m) -> handleMessage raw m addr |
448 | where | 463 | where |
449 | exceptions :: IOError -> IO (BS.ByteString, SockAddr) | 464 | exceptions :: IOError -> IO (BS.ByteString, SockAddr) |
450 | exceptions e | 465 | exceptions e |
diff --git a/src/Network/KRPC/Message.hs b/src/Network/KRPC/Message.hs index ebf5573e..6f4ae620 100644 --- a/src/Network/KRPC/Message.hs +++ b/src/Network/KRPC/Message.hs | |||
@@ -35,17 +35,22 @@ module Network.KRPC.Message | |||
35 | 35 | ||
36 | -- * Response | 36 | -- * Response |
37 | , KResponse(..) | 37 | , KResponse(..) |
38 | , ReflectedIP(..) | ||
38 | 39 | ||
39 | -- * Message | 40 | -- * Message |
40 | , KMessage (..) | 41 | , KMessage (..) |
41 | ) where | 42 | ) where |
42 | 43 | ||
43 | import Control.Applicative | 44 | import Control.Applicative |
45 | import Control.Arrow | ||
44 | import Control.Exception.Lifted as Lifted | 46 | import Control.Exception.Lifted as Lifted |
45 | import Data.BEncode as BE | 47 | import Data.BEncode as BE |
46 | import Data.ByteString as B | 48 | import Data.ByteString as B |
47 | import Data.ByteString.Char8 as BC | 49 | import Data.ByteString.Char8 as BC |
50 | import qualified Data.Serialize as S | ||
51 | import Data.Word | ||
48 | import Data.Typeable | 52 | import Data.Typeable |
53 | import Network.Socket (SockAddr (..),PortNumber,HostAddress) | ||
49 | 54 | ||
50 | 55 | ||
51 | -- | This transaction ID is generated by the querying node and is | 56 | -- | This transaction ID is generated by the querying node and is |
@@ -188,6 +193,35 @@ instance BEncode KQuery where | |||
188 | KQuery <$>! "a" <*>! "q" <*>! "t" | 193 | KQuery <$>! "a" <*>! "q" <*>! "t" |
189 | {-# INLINE fromBEncode #-} | 194 | {-# INLINE fromBEncode #-} |
190 | 195 | ||
196 | newtype ReflectedIP = ReflectedIP SockAddr | ||
197 | deriving (Eq, Ord, Show) | ||
198 | |||
199 | instance BEncode ReflectedIP where | ||
200 | toBEncode (ReflectedIP addr) = BString (encodeAddr addr) | ||
201 | fromBEncode (BString bs) = ReflectedIP <$> decodeAddr bs | ||
202 | fromBEncode _ = Left "ReflectedIP should be a bencoded string" | ||
203 | |||
204 | port16 :: Word16 -> PortNumber | ||
205 | port16 = fromIntegral | ||
206 | |||
207 | decodeAddr :: ByteString -> Either String SockAddr | ||
208 | decodeAddr bs | B.length bs == 6 | ||
209 | = ( \(a,p) -> SockAddrInet <$> fmap port16 p <*> a ) | ||
210 | $ (S.runGet S.getWord32host *** S.decode ) | ||
211 | $ B.splitAt 4 bs | ||
212 | decodeAddr bs | B.length bs == 18 | ||
213 | = ( \(a,p) -> flip SockAddrInet6 0 <$> fmap port16 p <*> a <*> pure 0 ) | ||
214 | $ (S.decode *** S.decode ) | ||
215 | $ B.splitAt 16 bs | ||
216 | decodeAddr _ = Left "incorrectly sized address and port" | ||
217 | |||
218 | encodeAddr :: SockAddr -> ByteString | ||
219 | encodeAddr (SockAddrInet port addr) | ||
220 | = S.runPut (S.putWord32host addr >> S.put (fromIntegral port :: Word16)) | ||
221 | encodeAddr (SockAddrInet6 port _ addr _) | ||
222 | = S.runPut (S.put addr >> S.put (fromIntegral port :: Word16)) | ||
223 | encodeAddr _ = B.empty | ||
224 | |||
191 | {----------------------------------------------------------------------- | 225 | {----------------------------------------------------------------------- |
192 | -- Response messages | 226 | -- Response messages |
193 | -----------------------------------------------------------------------} | 227 | -----------------------------------------------------------------------} |
@@ -206,7 +240,8 @@ instance BEncode KQuery where | |||
206 | data KResponse = KResponse | 240 | data KResponse = KResponse |
207 | { respVals :: BValue -- ^ 'BDict' containing return values; | 241 | { respVals :: BValue -- ^ 'BDict' containing return values; |
208 | , respId :: TransactionId -- ^ match to the corresponding 'queryId'. | 242 | , respId :: TransactionId -- ^ match to the corresponding 'queryId'. |
209 | } deriving (Show, Read, Eq, Ord, Typeable) | 243 | , respIP :: Maybe ReflectedIP |
244 | } deriving (Show, Eq, Ord, Typeable) | ||
210 | 245 | ||
211 | -- | Responses, or KRPC message dictionaries with a \"y\" value of | 246 | -- | Responses, or KRPC message dictionaries with a \"y\" value of |
212 | -- \"r\", contain one additional key \"r\". The value of \"r\" is a | 247 | -- \"r\", contain one additional key \"r\". The value of \"r\" is a |
@@ -218,7 +253,8 @@ data KResponse = KResponse | |||
218 | -- | 253 | -- |
219 | instance BEncode KResponse where | 254 | instance BEncode KResponse where |
220 | toBEncode KResponse {..} = toDict $ | 255 | toBEncode KResponse {..} = toDict $ |
221 | "r" .=! respVals | 256 | "ip" .=? respIP |
257 | .: "r" .=! respVals | ||
222 | .: "t" .=! respId | 258 | .: "t" .=! respId |
223 | .: "y" .=! ("r" :: ByteString) | 259 | .: "y" .=! ("r" :: ByteString) |
224 | .: endDict | 260 | .: endDict |
@@ -226,7 +262,8 @@ instance BEncode KResponse where | |||
226 | 262 | ||
227 | fromBEncode = fromDict $ do | 263 | fromBEncode = fromDict $ do |
228 | lookAhead $ match "y" (BString "r") | 264 | lookAhead $ match "y" (BString "r") |
229 | KResponse <$>! "r" <*>! "t" | 265 | addr <- optional (field (req "ip")) |
266 | (\r t -> KResponse r t addr) <$>! "r" <*>! "t" | ||
230 | {-# INLINE fromBEncode #-} | 267 | {-# INLINE fromBEncode #-} |
231 | 268 | ||
232 | {----------------------------------------------------------------------- | 269 | {----------------------------------------------------------------------- |
@@ -249,4 +286,4 @@ instance BEncode KMessage where | |||
249 | Q <$> fromBEncode b | 286 | Q <$> fromBEncode b |
250 | <|> R <$> fromBEncode b | 287 | <|> R <$> fromBEncode b |
251 | <|> E <$> fromBEncode b | 288 | <|> E <$> fromBEncode b |
252 | <|> decodingError "KMessage: unknown message or message tag" \ No newline at end of file | 289 | <|> decodingError "KMessage: unknown message or message tag" |
diff --git a/src/Network/KRPC/Method.hs b/src/Network/KRPC/Method.hs index ea9da958..916b38a8 100644 --- a/src/Network/KRPC/Method.hs +++ b/src/Network/KRPC/Method.hs | |||
@@ -47,8 +47,7 @@ newtype Method param result = Method { methodName :: MethodName } | |||
47 | instance (Typeable a, Typeable b) => Show (Method a b) where | 47 | instance (Typeable a, Typeable b) => Show (Method a b) where |
48 | showsPrec _ = showsMethod | 48 | showsPrec _ = showsMethod |
49 | 49 | ||
50 | showsMethod :: forall a. forall b. Typeable a => Typeable b | 50 | showsMethod :: forall a b. ( Typeable a , Typeable b ) => Method a b -> ShowS |
51 | => Method a b -> ShowS | ||
52 | showsMethod (Method name) = | 51 | showsMethod (Method name) = |
53 | showString (BC.unpack name) <> | 52 | showString (BC.unpack name) <> |
54 | showString " :: " <> | 53 | showString " :: " <> |
diff --git a/tests/Network/KRPC/MessageSpec.hs b/tests/Network/KRPC/MessageSpec.hs index 7aca4489..498ef679 100644 --- a/tests/Network/KRPC/MessageSpec.hs +++ b/tests/Network/KRPC/MessageSpec.hs | |||
@@ -20,7 +20,8 @@ instance Arbitrary KQuery where | |||
20 | arbitrary = KQuery <$> pure (BInteger 0) <*> arbitrary <*> arbitrary | 20 | arbitrary = KQuery <$> pure (BInteger 0) <*> arbitrary <*> arbitrary |
21 | 21 | ||
22 | instance Arbitrary KResponse where | 22 | instance Arbitrary KResponse where |
23 | arbitrary = KResponse <$> pure (BList []) <*> arbitrary | 23 | -- TODO: Abitrary instance for ReflectedIP |
24 | arbitrary = KResponse <$> pure (BList []) <*> arbitrary <*> pure Nothing | ||
24 | 25 | ||
25 | instance Arbitrary KMessage where | 26 | instance Arbitrary KMessage where |
26 | arbitrary = frequency | 27 | arbitrary = frequency |
@@ -64,8 +65,8 @@ spec = do | |||
64 | 65 | ||
65 | it "properly bencoded" $ do | 66 | it "properly bencoded" $ do |
66 | BE.decode "d1:rle1:t2:aa1:y1:re" `shouldBe` | 67 | BE.decode "d1:rle1:t2:aa1:y1:re" `shouldBe` |
67 | Right (KResponse (BList []) "aa") | 68 | Right (KResponse (BList []) "aa" Nothing) |
68 | 69 | ||
69 | describe "generic message" $ do | 70 | describe "generic message" $ do |
70 | it "properly bencoded (iso)" $ property $ \ km -> | 71 | it "properly bencoded (iso)" $ property $ \ km -> |
71 | BE.decode (BL.toStrict (BE.encode km)) `shouldBe` Right (km :: KMessage) \ No newline at end of file | 72 | BE.decode (BL.toStrict (BE.encode km)) `shouldBe` Right (km :: KMessage) |