summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bittorrent.cabal2
-rw-r--r--src/Network/BitTorrent/Core/PeerAddr.hs41
-rw-r--r--src/Network/BitTorrent/Exchange/Wire.hs2
-rw-r--r--src/Network/BitTorrent/Tracker/Message.hs67
m---------sub/bencoding0
-rw-r--r--tests/Network/BitTorrent/Tracker/MessageSpec.hs62
-rw-r--r--tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs1
7 files changed, 136 insertions, 39 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal
index 8e7fda46..faf72c75 100644
--- a/bittorrent.cabal
+++ b/bittorrent.cabal
@@ -124,7 +124,7 @@ library
124 , base16-bytestring >= 0.1 124 , base16-bytestring >= 0.1
125 , base32-bytestring >= 0.2 125 , base32-bytestring >= 0.2
126 , base64-bytestring >= 1.0 126 , base64-bytestring >= 1.0
127 , bencoding >= 0.4.2 127 , bencoding >= 0.4.2.1
128 , cereal >= 0.3.5 128 , cereal >= 0.3.5
129 129
130 -- Time 130 -- Time
diff --git a/src/Network/BitTorrent/Core/PeerAddr.hs b/src/Network/BitTorrent/Core/PeerAddr.hs
index 6c6056c9..88239d0b 100644
--- a/src/Network/BitTorrent/Core/PeerAddr.hs
+++ b/src/Network/BitTorrent/Core/PeerAddr.hs
@@ -21,11 +21,6 @@ module Network.BitTorrent.Core.PeerAddr
21 PeerAddr(..) 21 PeerAddr(..)
22 , defaultPorts 22 , defaultPorts
23 , peerSockAddr 23 , peerSockAddr
24
25 -- * IP
26 , mergeIPLists
27 , splitIPList
28 , IPAddress ()
29 ) where 24 ) where
30 25
31import Control.Applicative 26import Control.Applicative
@@ -36,8 +31,6 @@ import Data.BEncode.BDict (BKey)
36import Data.ByteString.Char8 as BS8 31import Data.ByteString.Char8 as BS8
37import Data.Char 32import Data.Char
38import Data.Default 33import Data.Default
39import Data.Either
40import Data.Foldable
41import Data.IP 34import Data.IP
42import Data.List as L 35import Data.List as L
43import Data.List.Split 36import Data.List.Split
@@ -86,13 +79,16 @@ class IPAddress i where
86 79
87instance IPAddress IPv4 where 80instance IPAddress IPv4 where
88 toHostAddr = Left . toHostAddress 81 toHostAddr = Left . toHostAddress
82 {-# INLINE toHostAddr #-}
89 83
90instance IPAddress IPv6 where 84instance IPAddress IPv6 where
91 toHostAddr = Right . toHostAddress6 85 toHostAddr = Right . toHostAddress6
86 {-# INLINE toHostAddr #-}
92 87
93instance IPAddress IP where 88instance IPAddress IP where
94 toHostAddr (IPv4 ip) = toHostAddr ip 89 toHostAddr (IPv4 ip) = toHostAddr ip
95 toHostAddr (IPv6 ip) = toHostAddr ip 90 toHostAddr (IPv6 ip) = toHostAddr ip
91 {-# INLINE toHostAddr #-}
96 92
97deriving instance Typeable IP 93deriving instance Typeable IP
98deriving instance Typeable IPv4 94deriving instance Typeable IPv4
@@ -100,6 +96,7 @@ deriving instance Typeable IPv6
100 96
101ipToBEncode :: Show i => i -> BValue 97ipToBEncode :: Show i => i -> BValue
102ipToBEncode ip = BString $ BS8.pack $ show ip 98ipToBEncode ip = BString $ BS8.pack $ show ip
99{-# INLINE ipToBEncode #-}
103 100
104ipFromBEncode :: Read a => BValue -> BS.Result a 101ipFromBEncode :: Read a => BValue -> BS.Result a
105ipFromBEncode (BString (BS8.unpack -> ipStr)) 102ipFromBEncode (BString (BS8.unpack -> ipStr))
@@ -107,17 +104,25 @@ ipFromBEncode (BString (BS8.unpack -> ipStr))
107 | otherwise = decodingError $ "IP: " ++ ipStr 104 | otherwise = decodingError $ "IP: " ++ ipStr
108ipFromBEncode _ = decodingError $ "IP: addr should be a bstring" 105ipFromBEncode _ = decodingError $ "IP: addr should be a bstring"
109 106
107instance Ord IP where
108
110instance BEncode IP where 109instance BEncode IP where
111 toBEncode = ipToBEncode 110 toBEncode = ipToBEncode
111 {-# INLINE toBEncode #-}
112 fromBEncode = ipFromBEncode 112 fromBEncode = ipFromBEncode
113 {-# INLINE fromBEncode #-}
113 114
114instance BEncode IPv4 where 115instance BEncode IPv4 where
115 toBEncode = ipToBEncode 116 toBEncode = ipToBEncode
117 {-# INLINE toBEncode #-}
116 fromBEncode = ipFromBEncode 118 fromBEncode = ipFromBEncode
119 {-# INLINE fromBEncode #-}
117 120
118instance BEncode IPv6 where 121instance BEncode IPv6 where
119 toBEncode = ipToBEncode 122 toBEncode = ipToBEncode
123 {-# INLINE toBEncode #-}
120 fromBEncode = ipFromBEncode 124 fromBEncode = ipFromBEncode
125 {-# INLINE fromBEncode #-}
121 126
122instance Serialize IPv4 where 127instance Serialize IPv4 where
123 put = putWord32host . toHostAddress 128 put = putWord32host . toHostAddress
@@ -136,9 +141,14 @@ instance Serialize IPv6 where
136-- compact list encoding. 141-- compact list encoding.
137data PeerAddr a = PeerAddr 142data PeerAddr a = PeerAddr
138 { peerId :: !(Maybe PeerId) 143 { peerId :: !(Maybe PeerId)
144
145 -- | This is usually 'IPv4', 'IPv6', 'IP' or unresolved
146 -- 'HostName'.
139 , peerHost :: !a 147 , peerHost :: !a
148
149 -- | The port the peer listenning for incoming P2P sessions.
140 , peerPort :: {-# UNPACK #-} !PortNumber 150 , peerPort :: {-# UNPACK #-} !PortNumber
141 } deriving (Show, Eq, Typeable, Functor) 151 } deriving (Show, Eq, Ord, Typeable, Functor)
142 152
143peer_ip_key, peer_id_key, peer_port_key :: BKey 153peer_ip_key, peer_id_key, peer_port_key :: BKey
144peer_ip_key = "ip" 154peer_ip_key = "ip"
@@ -160,17 +170,6 @@ instance (Typeable a, BEncode a) => BEncode (PeerAddr a) where
160 where 170 where
161 peerAddr = flip PeerAddr 171 peerAddr = flip PeerAddr
162 172
163mergeIPLists :: [PeerAddr IPv4] -> Maybe [PeerAddr IPv6] -> [PeerAddr IP]
164mergeIPLists v4 v6 = (fmap IPv4 `L.map` v4)
165 ++ (fmap IPv6 `L.map` Data.Foldable.concat v6)
166
167splitIPList :: [PeerAddr IP] -> ([PeerAddr IPv4],[PeerAddr IPv6])
168splitIPList xs = partitionEithers $ toEither <$> xs
169 where
170 toEither :: PeerAddr IP -> Either (PeerAddr IPv4) (PeerAddr IPv6)
171 toEither pa@(PeerAddr _ (IPv4 _) _) = Left (ipv4 <$> pa)
172 toEither pa@(PeerAddr _ (IPv6 _) _) = Right (ipv6 <$> pa)
173
174-- | The tracker's 'compact peer list' compatible encoding. The 173-- | The tracker's 'compact peer list' compatible encoding. The
175-- 'peerId' is always 'Nothing'. 174-- 'peerId' is always 'Nothing'.
176-- 175--
@@ -231,8 +230,8 @@ defaultPorts = [6881..6889]
231_resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i 230_resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i
232_resolvePeerAddr = undefined 231_resolvePeerAddr = undefined
233 232
234-- | Convert peer info from tracker response to socket address. Used 233-- | Convert peer info from tracker or DHT announce query response to
235-- for establish connection between peers. 234-- socket address. Usually used to intiate connection between peers.
236-- 235--
237peerSockAddr :: PeerAddr IP -> SockAddr 236peerSockAddr :: PeerAddr IP -> SockAddr
238peerSockAddr PeerAddr {..} = 237peerSockAddr PeerAddr {..} =
diff --git a/src/Network/BitTorrent/Exchange/Wire.hs b/src/Network/BitTorrent/Exchange/Wire.hs
index 2a7d2aeb..5f7b0ebe 100644
--- a/src/Network/BitTorrent/Exchange/Wire.hs
+++ b/src/Network/BitTorrent/Exchange/Wire.hs
@@ -673,7 +673,7 @@ connectWire hs addr extCaps wire =
673-- 673--
674-- This function can throw 'WireFailure' exception. 674-- This function can throw 'WireFailure' exception.
675-- 675--
676acceptWire :: (IPAddress i) => Socket -> PeerAddr i -> Wire () -> IO () 676acceptWire :: Socket -> PeerAddr IP -> Wire () -> IO ()
677acceptWire sock peerAddr wire = do 677acceptWire sock peerAddr wire = do
678 bracket (return sock) close $ \ _ -> do 678 bracket (return sock) close $ \ _ -> do
679 error "acceptWire: not implemented" 679 error "acceptWire: not implemented"
diff --git a/src/Network/BitTorrent/Tracker/Message.hs b/src/Network/BitTorrent/Tracker/Message.hs
index d0be1c36..da46628b 100644
--- a/src/Network/BitTorrent/Tracker/Message.hs
+++ b/src/Network/BitTorrent/Tracker/Message.hs
@@ -70,7 +70,6 @@ module Network.BitTorrent.Tracker.Message
70 70
71import Control.Applicative 71import Control.Applicative
72import Control.Monad 72import Control.Monad
73import Data.Aeson (ToJSON(..), FromJSON(..))
74import Data.Aeson.TH 73import Data.Aeson.TH
75import Data.BEncode as BE hiding (Result) 74import Data.BEncode as BE hiding (Result)
76import Data.BEncode.BDict as BE 75import Data.BEncode.BDict as BE
@@ -79,8 +78,10 @@ import Data.ByteString.Char8 as BC
79import Data.Char as Char 78import Data.Char as Char
80import Data.Convertible 79import Data.Convertible
81import Data.Default 80import Data.Default
81import Data.Either
82import Data.List as L 82import Data.List as L
83import Data.Maybe 83import Data.Maybe
84import Data.Monoid
84import Data.Serialize as S hiding (Result) 85import Data.Serialize as S hiding (Result)
85import Data.String 86import Data.String
86import Data.Text (Text) 87import Data.Text (Text)
@@ -193,9 +194,9 @@ instance Serialize AnnounceQuery where
193 put reqPeerId 194 put reqPeerId
194 put reqProgress 195 put reqProgress
195 putEvent reqEvent 196 putEvent reqEvent
196 putWord32be $ fromMaybe 0 reqIP 197 putWord32host $ fromMaybe 0 reqIP
197 putWord32be $ 0 -- TODO what the fuck is "key"? 198 putWord32be $ 0 -- TODO what the fuck is "key"?
198 putWord32be $ fromIntegral $ fromMaybe (-1) reqNumWant 199 putWord32be $ fromIntegral $ fromMaybe (-1) reqNumWant
199 200
200 put reqPort 201 put reqPort
201 202
@@ -415,7 +416,9 @@ data AnnounceRequest = AnnounceRequest
415 } deriving (Show, Eq, Typeable) 416 } deriving (Show, Eq, Typeable)
416 417
417instance QueryLike AnnounceRequest where 418instance QueryLike AnnounceRequest where
418 toQuery AnnounceRequest{..} = toQuery announceAdvises ++ toQuery announceQuery 419 toQuery AnnounceRequest{..} =
420 toQuery announceAdvises <>
421 toQuery announceQuery
419 422
420-- | Parse announce request from query string. 423-- | Parse announce request from query string.
421parseAnnounceRequest :: SimpleQuery -> ParseResult AnnounceRequest 424parseAnnounceRequest :: SimpleQuery -> ParseResult AnnounceRequest
@@ -441,6 +444,11 @@ data PeerList ip
441 | CompactPeerList [PeerAddr ip] 444 | CompactPeerList [PeerAddr ip]
442 deriving (Show, Eq, Typeable, Functor) 445 deriving (Show, Eq, Typeable, Functor)
443 446
447-- | The empty non-compact peer list.
448instance Default (PeerList IP) where
449 def = PeerList []
450 {-# INLINE def #-}
451
444getPeerList :: PeerList IP -> [PeerAddr IP] 452getPeerList :: PeerList IP -> [PeerAddr IP]
445getPeerList (PeerList xs) = xs 453getPeerList (PeerList xs) = xs
446getPeerList (CompactPeerList xs) = xs 454getPeerList (CompactPeerList xs) = xs
@@ -482,6 +490,17 @@ data AnnounceInfo =
482 , respWarning :: !(Maybe Text) 490 , respWarning :: !(Maybe Text)
483 } deriving (Show, Eq, Typeable) 491 } deriving (Show, Eq, Typeable)
484 492
493-- | Empty peer list with default reannounce interval.
494instance Default AnnounceInfo where
495 def = AnnounceInfo
496 { respComplete = Nothing
497 , respIncomplete = Nothing
498 , respInterval = defaultReannounceInterval
499 , respMinInterval = Nothing
500 , respPeers = def
501 , respWarning = Nothing
502 }
503
485-- | HTTP tracker protocol compatible encoding. 504-- | HTTP tracker protocol compatible encoding.
486instance BEncode AnnounceInfo where 505instance BEncode AnnounceInfo where
487 toBEncode (Failure t) = toDict $ 506 toBEncode (Failure t) = toDict $
@@ -494,10 +513,24 @@ instance BEncode AnnounceInfo where
494 .: "interval" .=! respInterval 513 .: "interval" .=! respInterval
495 .: "min interval" .=? respMinInterval 514 .: "min interval" .=? respMinInterval
496 .: "peers" .=! peers 515 .: "peers" .=! peers
497 .: "peers6" .=! peers6 516 .: "peers6" .=? peers6
498 .: "warning message" .=? respWarning 517 .: "warning message" .=? respWarning
499 .: endDict 518 .: endDict
500 where (peers,peers6) = splitIPList $ getPeerList respPeers 519 where
520 (peers, peers6) = prttn respPeers
521
522 prttn :: PeerList IP -> (PeerList IPv4, Maybe (PeerList IPv6))
523 prttn (PeerList xs) = (PeerList xs, Nothing)
524 prttn (CompactPeerList xs) = mk $ partitionEithers $ toEither <$> xs
525 where
526 mk (v4s, v6s)
527 | L.null v6s = (CompactPeerList v4s, Nothing)
528 | otherwise = (CompactPeerList v4s, Just (CompactPeerList v6s))
529
530 toEither :: PeerAddr IP -> Either (PeerAddr IPv4) (PeerAddr IPv6)
531 toEither PeerAddr {..} = case peerHost of
532 IPv4 ipv4 -> Left $ PeerAddr peerId ipv4 peerPort
533 IPv6 ipv6 -> Right $ PeerAddr peerId ipv6 peerPort
501 534
502 fromBEncode (BDict d) 535 fromBEncode (BDict d)
503 | Just t <- BE.lookup "failure reason" d = Failure <$> fromBEncode t 536 | Just t <- BE.lookup "failure reason" d = Failure <$> fromBEncode t
@@ -507,8 +540,26 @@ instance BEncode AnnounceInfo where
507 <*>? "incomplete" 540 <*>? "incomplete"
508 <*>! "interval" 541 <*>! "interval"
509 <*>? "min interval" 542 <*>? "min interval"
510 <*> (PeerList <$> (mergeIPLists <$>! "peers" <*>? "peers6")) 543 <*> (uncurry merge =<< (,) <$>! "peers" <*>? "peers6")
511 <*>? "warning message" 544 <*>? "warning message"
545 where
546 merge :: PeerList IPv4 -> Maybe (PeerList IPv6) -> BE.Get (PeerList IP)
547 merge (PeerList ips) Nothing = pure (PeerList ips)
548 merge (PeerList _ ) (Just _)
549 = fail "PeerList: non-compact peer list provided, \
550 \but the `peers6' field present"
551
552 merge (CompactPeerList ipv4s) Nothing
553 = pure $ CompactPeerList (fmap IPv4 <$> ipv4s)
554
555 merge (CompactPeerList _ ) (Just (PeerList _))
556 = fail "PeerList: the `peers6' field value \
557 \should contain *compact* peer list"
558
559 merge (CompactPeerList ipv4s) (Just (CompactPeerList ipv6s))
560 = pure $ CompactPeerList $
561 (fmap IPv4 <$> ipv4s) <> (fmap IPv6 <$> ipv6s)
562
512 fromBEncode _ = decodingError "Announce info" 563 fromBEncode _ = decodingError "Announce info"
513 564
514-- | UDP tracker protocol compatible encoding. 565-- | UDP tracker protocol compatible encoding.
diff --git a/sub/bencoding b/sub/bencoding
Subproject fa7861cc092fb3d423d6e3c05df36d3651068de Subproject d80b4fbe1ffe8478a1c72b1e96bbc5b44991d96
diff --git a/tests/Network/BitTorrent/Tracker/MessageSpec.hs b/tests/Network/BitTorrent/Tracker/MessageSpec.hs
index c3de7b30..5949de7a 100644
--- a/tests/Network/BitTorrent/Tracker/MessageSpec.hs
+++ b/tests/Network/BitTorrent/Tracker/MessageSpec.hs
@@ -1,6 +1,7 @@
1{-# LANGUAGE RecordWildCards #-} 1{-# LANGUAGE RecordWildCards #-}
2{-# LANGUAGE ViewPatterns #-} 2{-# LANGUAGE ViewPatterns #-}
3{-# OPTIONS -fno-warn-orphans #-} 3{-# LANGUAGE FlexibleInstances #-}
4{-# OPTIONS -fno-warn-orphans #-}
4module Network.BitTorrent.Tracker.MessageSpec 5module Network.BitTorrent.Tracker.MessageSpec
5 ( spec 6 ( spec
6 , validateInfo 7 , validateInfo
@@ -39,6 +40,20 @@ instance Arbitrary AnnounceQuery where
39 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary 40 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
40 <*> arbitrary <*> arbitrary <*> arbitrary 41 <*> arbitrary <*> arbitrary <*> arbitrary
41 42
43instance Arbitrary (PeerList IP) where
44 arbitrary = frequency
45 [ (1, (PeerList . maybeToList) <$> arbitrary)
46 , (1, (CompactPeerList . maybeToList . fmap zeroPeerId) <$> arbitrary)
47 ]
48
49 shrink ( PeerList xs) = PeerList <$> shrink xs
50 shrink (CompactPeerList xs) = CompactPeerList <$> shrink xs
51
52instance Arbitrary AnnounceInfo where
53 arbitrary = AnnounceInfo
54 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
55 <*> arbitrary <*> arbitrary
56
42validateInfo :: AnnounceQuery -> AnnounceInfo -> Expectation 57validateInfo :: AnnounceQuery -> AnnounceInfo -> Expectation
43validateInfo _ Message.Failure {..} = error "validateInfo: failure" 58validateInfo _ Message.Failure {..} = error "validateInfo: failure"
44validateInfo AnnounceQuery {..} AnnounceInfo {..} = do 59validateInfo AnnounceQuery {..} AnnounceInfo {..} = do
@@ -47,7 +62,8 @@ validateInfo AnnounceQuery {..} AnnounceInfo {..} = do
47 respMinInterval `shouldSatisfy` isNothing 62 respMinInterval `shouldSatisfy` isNothing
48 respWarning `shouldSatisfy` isNothing 63 respWarning `shouldSatisfy` isNothing
49 peerList `shouldSatisfy` L.all (isNothing . peerId) 64 peerList `shouldSatisfy` L.all (isNothing . peerId)
50 fromJust respComplete + fromJust respIncomplete `shouldBe` L.length peerList 65 fromJust respComplete + fromJust respIncomplete
66 `shouldBe` L.length peerList
51 where 67 where
52 peerList = getPeerList respPeers 68 peerList = getPeerList respPeers
53 69
@@ -125,15 +141,47 @@ spec = do
125 errorCall "fromString: unable to decode AnnounceInfo: \ 141 errorCall "fromString: unable to decode AnnounceInfo: \
126 \required field `peers' not found" 142 \required field `peers' not found"
127 143
128 it "parses peer list" $ do -- TODO 144 it "parses `peer' list" $ do -- TODO
129 "d8:intervali0e\ 145 "d8:intervali0e\
130 \5:peersl\ 146 \5:peersl\
131 \d2:ip7:1.2.3.4\ 147 \d2:ip7:1.2.3.4\
132 \4:porti80e\ 148 \4:porti80e\
149 \e\
150 \d2:ip3:::1\
151 \4:porti80e\
133 \e\ 152 \e\
134 \e\ 153 \e\
135 \e" `shouldBe` 154 \e" `shouldBe`
136 AnnounceInfo Nothing Nothing 0 Nothing (PeerList ["1.2.3.4:80"]) Nothing 155 let xs = PeerList ["1.2.3.4:80", "[::1]:80"] in
156 AnnounceInfo Nothing Nothing 0 Nothing xs Nothing
157
158 it "parses `peers6' list" $ do
159 "d8:intervali0e\
160 \5:peers0:\
161 \6:peers60:\
162 \e" `shouldBe`
163 AnnounceInfo Nothing Nothing 0 Nothing (CompactPeerList []) Nothing
164
165 it "fails on invalid combinations of the peer lists" $ do
166 BE.decode "d8:intervali0e\
167 \5:peers0:\
168 \6:peers6le\
169 \e"
170 `shouldBe` (Left
171 "PeerList: the `peers6' field value should contain \
172 \*compact* peer list" :: BE.Result AnnounceInfo)
173
174 BE.decode "d8:intervali0e\
175 \5:peersle\
176 \6:peers60:\
177 \e"
178 `shouldBe` (Left
179 "PeerList: non-compact peer list provided, \
180 \but the `peers6' field present" :: BE.Result AnnounceInfo)
181
182 it "properly bencoded (iso)" $ property $ \ info ->
183 BE.decode (BL.toStrict (BE.encode info))
184 `shouldBe` Right (info :: AnnounceInfo)
137 185
138 describe "Scrape" $ do 186 describe "Scrape" $ do
139 return () 187 return ()
diff --git a/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs b/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs
index 37029b75..eb549516 100644
--- a/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs
+++ b/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs
@@ -1,7 +1,6 @@
1module Network.BitTorrent.Tracker.RPC.HTTPSpec (spec, trackerURIs) where 1module Network.BitTorrent.Tracker.RPC.HTTPSpec (spec, trackerURIs) where
2 2
3import Control.Monad 3import Control.Monad
4import Control.Monad.Trans
5import Control.Monad.Trans.Resource 4import Control.Monad.Trans.Resource
6import Data.Default 5import Data.Default
7import Data.List as L 6import Data.List as L