diff options
Diffstat (limited to 'src/Network')
-rw-r--r-- | src/Network/BitTorrent/Client/Swarm.hs | 2 | ||||
-rw-r--r-- | src/Network/BitTorrent/Core/PeerAddr.hs | 133 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Assembler.hs | 9 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 4 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Wire.hs | 6 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Message.hs | 41 |
6 files changed, 142 insertions, 53 deletions
diff --git a/src/Network/BitTorrent/Client/Swarm.hs b/src/Network/BitTorrent/Client/Swarm.hs index a9dca048..1901905c 100644 --- a/src/Network/BitTorrent/Client/Swarm.hs +++ b/src/Network/BitTorrent/Client/Swarm.hs | |||
@@ -43,7 +43,7 @@ getAnnounceQuery Swarm {..} = AnnounceQuery | |||
43 | , reqEvent = Nothing | 43 | , reqEvent = Nothing |
44 | } | 44 | } |
45 | 45 | ||
46 | askPeers :: Swarm -> IO [PeerAddr] | 46 | askPeers :: Swarm -> IO [PeerAddr IP] |
47 | askPeers s @ Swarm {..} = do | 47 | askPeers s @ Swarm {..} = do |
48 | AnnounceInfo {..} <- RPC.announce (getAnnounceQuery s) trackerConn | 48 | AnnounceInfo {..} <- RPC.announce (getAnnounceQuery s) trackerConn |
49 | return (getPeerList respPeers) | 49 | return (getPeerList respPeers) |
diff --git a/src/Network/BitTorrent/Core/PeerAddr.hs b/src/Network/BitTorrent/Core/PeerAddr.hs index e7a4ea61..1da4c81a 100644 --- a/src/Network/BitTorrent/Core/PeerAddr.hs +++ b/src/Network/BitTorrent/Core/PeerAddr.hs | |||
@@ -12,12 +12,18 @@ | |||
12 | {-# LANGUAGE StandaloneDeriving #-} | 12 | {-# LANGUAGE StandaloneDeriving #-} |
13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
14 | {-# LANGUAGE DeriveDataTypeable #-} | 14 | {-# LANGUAGE DeriveDataTypeable #-} |
15 | {-# LANGUAGE FlexibleInstances #-} | ||
16 | {-# LANGUAGE DeriveFunctor #-} | ||
15 | {-# OPTIONS -fno-warn-orphans #-} -- for PortNumber instances | 17 | {-# OPTIONS -fno-warn-orphans #-} -- for PortNumber instances |
16 | module Network.BitTorrent.Core.PeerAddr | 18 | module Network.BitTorrent.Core.PeerAddr |
17 | ( -- * Peer address | 19 | ( -- * Peer address |
18 | PeerAddr(..) | 20 | PeerAddr(..) |
19 | , defaultPorts | 21 | , defaultPorts |
20 | , peerSockAddr | 22 | , peerSockAddr |
23 | , mergeIPLists | ||
24 | , splitIPList | ||
25 | , IP, IPv4, IPv6 --re-export Data.IP constructors | ||
26 | , IPAddress () | ||
21 | ) where | 27 | ) where |
22 | 28 | ||
23 | import Control.Applicative | 29 | import Control.Applicative |
@@ -26,6 +32,8 @@ import Data.Aeson (ToJSON, FromJSON) | |||
26 | import Data.Aeson.TH | 32 | import Data.Aeson.TH |
27 | import Data.BEncode as BS | 33 | import Data.BEncode as BS |
28 | import Data.BEncode.BDict (BKey) | 34 | import Data.BEncode.BDict (BKey) |
35 | import Data.ByteString | ||
36 | import Data.ByteString.Char8 as BS8 | ||
29 | import Data.Bits | 37 | import Data.Bits |
30 | import Data.Char | 38 | import Data.Char |
31 | import Data.Default | 39 | import Data.Default |
@@ -35,10 +43,15 @@ import Data.Serialize as S | |||
35 | import Data.String | 43 | import Data.String |
36 | import Data.Typeable | 44 | import Data.Typeable |
37 | import Data.Word | 45 | import Data.Word |
46 | import Data.IP | ||
47 | import Data.Maybe | ||
48 | import Data.Foldable | ||
49 | import Data.Either | ||
38 | import Network.Socket | 50 | import Network.Socket |
39 | import Text.PrettyPrint | 51 | import Text.PrettyPrint |
40 | import Text.PrettyPrint.Class | 52 | import Text.PrettyPrint.Class |
41 | import Text.Read (readMaybe) | 53 | import Text.Read (readMaybe) |
54 | import qualified Text.ParserCombinators.ReadP as RP | ||
42 | import System.IO.Unsafe | 55 | import System.IO.Unsafe |
43 | 56 | ||
44 | import Data.Torrent.JSON | 57 | import Data.Torrent.JSON |
@@ -58,30 +71,74 @@ instance Serialize PortNumber where | |||
58 | put = putWord16be . fromIntegral | 71 | put = putWord16be . fromIntegral |
59 | {-# INLINE put #-} | 72 | {-# INLINE put #-} |
60 | 73 | ||
74 | class (Show i, Read i) => IPAddress i where | ||
75 | showIp :: i -> String | ||
76 | showIp = show | ||
77 | |||
78 | readIp :: String -> i | ||
79 | readIp = read | ||
80 | |||
81 | toHostAddr :: i -> Either HostAddress HostAddress6 | ||
82 | |||
83 | instance IPAddress IPv4 where | ||
84 | toHostAddr = Left . toHostAddress | ||
85 | |||
86 | instance IPAddress IPv6 where | ||
87 | toHostAddr = Right . toHostAddress6 | ||
88 | |||
89 | instance IPAddress IP where | ||
90 | toHostAddr (IPv4 ip) = toHostAddr ip | ||
91 | toHostAddr (IPv6 ip) = toHostAddr ip | ||
92 | |||
93 | |||
94 | deriving instance Typeable IP | ||
95 | deriving instance Typeable IPv4 | ||
96 | deriving instance Typeable IPv6 | ||
97 | |||
98 | ipToBEncode ip = BString $ BS8.pack $ showIp ip | ||
99 | ipFromBEncode (BString ip) = return $ readIp $ BS8.unpack ip | ||
100 | |||
101 | instance BEncode IP where | ||
102 | toBEncode = ipToBEncode | ||
103 | fromBEncode = ipFromBEncode | ||
104 | |||
105 | instance BEncode IPv4 where | ||
106 | toBEncode = ipToBEncode | ||
107 | fromBEncode = ipFromBEncode | ||
108 | |||
109 | instance BEncode IPv6 where | ||
110 | toBEncode = ipToBEncode | ||
111 | fromBEncode = ipFromBEncode | ||
112 | |||
113 | instance Serialize IPv4 where | ||
114 | put ip = put $ toHostAddress ip | ||
115 | get = fromHostAddress <$> get | ||
116 | |||
117 | instance Serialize IPv6 where | ||
118 | put ip = put $ toHostAddress6 ip | ||
119 | get = fromHostAddress6 <$> get | ||
120 | |||
61 | -- TODO check semantic of ord and eq instances | 121 | -- TODO check semantic of ord and eq instances |
62 | -- TODO use SockAddr instead of peerIP and peerPort | 122 | -- TODO use SockAddr instead of peerIP and peerPort |
63 | 123 | ||
64 | -- | Peer address info normally extracted from peer list or peer | 124 | -- | Peer address info normally extracted from peer list or peer |
65 | -- compact list encoding. | 125 | -- compact list encoding. |
66 | data PeerAddr = PeerAddr | 126 | data PeerAddr a = PeerAddr |
67 | { peerId :: !(Maybe PeerId) | 127 | { peerId :: !(Maybe PeerId) |
68 | , peerIP :: {-# UNPACK #-} !HostAddress | 128 | , peerAddr :: a |
69 | , peerPort :: {-# UNPACK #-} !PortNumber | 129 | , peerPort :: {-# UNPACK #-} !PortNumber |
70 | } deriving (Show, Eq, Ord, Typeable) | 130 | } deriving (Show, Eq, Typeable, Functor) |
71 | |||
72 | $(deriveJSON omitRecordPrefix ''PeerAddr) | ||
73 | 131 | ||
74 | peer_id_key, peer_ip_key, peer_port_key :: BKey | 132 | peer_id_key, peer_ip_key, peer_port_key :: BKey |
75 | peer_id_key = "peer id" | 133 | peer_id_key = "peer id" |
76 | peer_ip_key = "ip" | 134 | peer_ip_key = "ip" |
77 | peer_port_key = "port" | 135 | peer_port_key = "port" |
78 | 136 | ||
79 | -- FIXME do we need to byteswap peerIP in bencode instance? | ||
80 | -- | The tracker's 'announce response' compatible encoding. | 137 | -- | The tracker's 'announce response' compatible encoding. |
81 | instance BEncode PeerAddr where | 138 | instance (Typeable a, BEncode a) => BEncode (PeerAddr a) where |
82 | toBEncode PeerAddr {..} = toDict $ | 139 | toBEncode PeerAddr {..} = toDict $ |
83 | peer_id_key .=? peerId | 140 | peer_id_key .=? peerId |
84 | .: peer_ip_key .=! peerIP | 141 | .: peer_ip_key .=! peerAddr |
85 | .: peer_port_key .=! peerPort | 142 | .: peer_port_key .=! peerPort |
86 | .: endDict | 143 | .: endDict |
87 | 144 | ||
@@ -90,19 +147,32 @@ instance BEncode PeerAddr where | |||
90 | <*>! peer_ip_key | 147 | <*>! peer_ip_key |
91 | <*>! peer_port_key | 148 | <*>! peer_port_key |
92 | 149 | ||
150 | mergeIPLists :: [PeerAddr IPv4] -> Maybe [PeerAddr IPv6] -> [PeerAddr IP] | ||
151 | mergeIPLists v4 v6 = (fmap IPv4 `L.map` v4) | ||
152 | ++ (fmap IPv6 `L.map` Data.Foldable.concat v6) | ||
153 | |||
154 | splitIPList :: [PeerAddr IP] -> ([PeerAddr IPv4],[PeerAddr IPv6]) | ||
155 | splitIPList xs = partitionEithers $ toEither <$> xs | ||
156 | where | ||
157 | toEither :: PeerAddr IP -> Either (PeerAddr IPv4) (PeerAddr IPv6) | ||
158 | toEither pa@(PeerAddr _ (IPv4 _) _) = Left (ipv4 <$> pa) | ||
159 | toEither pa@(PeerAddr _ (IPv6 _) _) = Right (ipv6 <$> pa) | ||
160 | |||
161 | |||
93 | -- | The tracker's 'compact peer list' compatible encoding. The | 162 | -- | The tracker's 'compact peer list' compatible encoding. The |
94 | -- 'peerId' is always 'Nothing'. | 163 | -- 'peerId' is always 'Nothing'. |
95 | -- | 164 | -- |
96 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> | 165 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> |
97 | -- | 166 | -- |
98 | instance Serialize PeerAddr where | 167 | -- TODO: test byte order |
99 | put PeerAddr {..} = putWord32host peerIP >> put peerPort | 168 | instance (Serialize a) => Serialize (PeerAddr a) where |
100 | {-# INLINE put #-} | 169 | put PeerAddr {..} = |
101 | get = PeerAddr Nothing <$> getWord32host <*> get | 170 | put peerAddr >> put peerPort |
102 | {-# INLINE get #-} | 171 | get = |
172 | PeerAddr Nothing <$> get <*> get | ||
103 | 173 | ||
104 | -- | @127.0.0.1:6881@ | 174 | -- | @127.0.0.1:6881@ |
105 | instance Default PeerAddr where | 175 | instance Default (PeerAddr IPv4) where |
106 | def = "127.0.0.1:6881" | 176 | def = "127.0.0.1:6881" |
107 | 177 | ||
108 | -- inet_addr is pure; so it is safe to throw IO | 178 | -- inet_addr is pure; so it is safe to throw IO |
@@ -117,28 +187,49 @@ unsafeCatchIO m = unsafePerformIO $ | |||
117 | -- | 187 | -- |
118 | -- @peerPort \"127.0.0.1:6881\" == 6881@ | 188 | -- @peerPort \"127.0.0.1:6881\" == 6881@ |
119 | -- | 189 | -- |
120 | instance IsString PeerAddr where | 190 | instance IsString (PeerAddr IPv4) where |
121 | fromString str | 191 | fromString str |
122 | | [hostAddrStr, portStr] <- splitWhen (== ':') str | 192 | | [hostAddrStr, portStr] <- splitWhen (== ':') str |
123 | , Just hostAddr <- unsafeCatchIO $ inet_addr hostAddrStr | 193 | , hostAddr <- read hostAddrStr |
124 | , Just portNum <- toEnum <$> readMaybe portStr | 194 | , Just portNum <- toEnum <$> readMaybe portStr |
125 | = PeerAddr Nothing hostAddr portNum | 195 | = PeerAddr Nothing hostAddr portNum |
126 | | otherwise = error $ "fromString: unable to parse PeerAddr: " ++ str | 196 | | otherwise = error $ "fromString: unable to parse (PeerAddr IPv4): " ++ str |
197 | |||
198 | readsIPv6_port :: String -> [((IPv6, PortNumber), String)] | ||
199 | readsIPv6_port = RP.readP_to_S $ do | ||
200 | ip <- RP.char '[' *> (RP.readS_to_P reads) <* RP.char ']' | ||
201 | RP.char ':' | ||
202 | port <- toEnum <$> read <$> (RP.many1 $ RP.satisfy isDigit) <* RP.eof | ||
203 | return (ip,port) | ||
204 | |||
205 | instance IsString (PeerAddr IPv6) where | ||
206 | fromString str | ||
207 | | [((ip,port),"")] <- readsIPv6_port str = | ||
208 | PeerAddr Nothing ip port | ||
209 | | otherwise = error $ "fromString: unable to parse (PeerAddr IPv6): " ++ str | ||
127 | 210 | ||
128 | -- | fingerprint + "at" + dotted.host.inet.addr:port | 211 | -- | fingerprint + "at" + dotted.host.inet.addr:port |
129 | instance Pretty PeerAddr where | 212 | -- TODO: instances for IPv6, HostName |
213 | instance Pretty (PeerAddr IP) where | ||
130 | pretty p @ PeerAddr {..} | 214 | pretty p @ PeerAddr {..} |
131 | | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr | 215 | | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr |
132 | | otherwise = paddr | 216 | | otherwise = paddr |
133 | where | 217 | where |
134 | paddr = text (show (peerSockAddr p)) | 218 | paddr = text (show peerAddr ++ ":" ++ show peerPort) |
135 | 219 | ||
136 | -- | Ports typically reserved for bittorrent P2P listener. | 220 | -- | Ports typically reserved for bittorrent P2P listener. |
137 | defaultPorts :: [PortNumber] | 221 | defaultPorts :: [PortNumber] |
138 | defaultPorts = [6881..6889] | 222 | defaultPorts = [6881..6889] |
139 | 223 | ||
224 | resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i | ||
225 | resolvePeerAddr = undefined | ||
226 | |||
140 | -- | Convert peer info from tracker response to socket address. Used | 227 | -- | Convert peer info from tracker response to socket address. Used |
141 | -- for establish connection between peers. | 228 | -- for establish connection between peers. |
142 | -- | 229 | -- |
143 | peerSockAddr :: PeerAddr -> SockAddr | 230 | peerSockAddr :: (IPAddress i) => PeerAddr i -> SockAddr |
144 | peerSockAddr = SockAddrInet <$> peerPort <*> peerIP | 231 | peerSockAddr PeerAddr {..} |
232 | | Left hAddr <- toHostAddr peerAddr = | ||
233 | SockAddrInet peerPort hAddr | ||
234 | | Right hAddr <- toHostAddr peerAddr = | ||
235 | SockAddrInet6 peerPort 0 hAddr 0 | ||
diff --git a/src/Network/BitTorrent/Exchange/Assembler.hs b/src/Network/BitTorrent/Exchange/Assembler.hs index 5dc7c5ca..aa009f49 100644 --- a/src/Network/BitTorrent/Exchange/Assembler.hs +++ b/src/Network/BitTorrent/Exchange/Assembler.hs | |||
@@ -65,6 +65,7 @@ import Data.IntMap.Strict as IM | |||
65 | import Data.List as L | 65 | import Data.List as L |
66 | import Data.Map as M | 66 | import Data.Map as M |
67 | import Data.Maybe | 67 | import Data.Maybe |
68 | import Data.IP | ||
68 | 69 | ||
69 | import Data.Torrent.Piece | 70 | import Data.Torrent.Piece |
70 | import Network.BitTorrent.Core | 71 | import Network.BitTorrent.Core |
@@ -79,7 +80,7 @@ type PieceMap = IntMap | |||
79 | 80 | ||
80 | data Assembler = Assembler | 81 | data Assembler = Assembler |
81 | { -- | A set of blocks that have been 'Request'ed but not yet acked. | 82 | { -- | A set of blocks that have been 'Request'ed but not yet acked. |
82 | _inflight :: Map PeerAddr (PieceMap [BlockRange]) | 83 | _inflight :: Map (PeerAddr IP) (PieceMap [BlockRange]) |
83 | 84 | ||
84 | -- | A set of blocks that but not yet assembled. | 85 | -- | A set of blocks that but not yet assembled. |
85 | , _pending :: PieceMap Bucket | 86 | , _pending :: PieceMap Bucket |
@@ -114,7 +115,7 @@ allowPiece pix a @ Assembler {..} = over pending (IM.insert pix bkt) a | |||
114 | where | 115 | where |
115 | bkt = B.empty (piPieceLength info) | 116 | bkt = B.empty (piPieceLength info) |
116 | 117 | ||
117 | allowedSet :: PeerAddr -> Assembler -> [BlockIx] | 118 | allowedSet :: (PeerAddr IP) -> Assembler -> [BlockIx] |
118 | allowedSet = undefined | 119 | allowedSet = undefined |
119 | 120 | ||
120 | --inflight :: PeerAddr -> BlockIx -> Assembler -> Assembler | 121 | --inflight :: PeerAddr -> BlockIx -> Assembler -> Assembler |
@@ -123,7 +124,7 @@ allowedSet = undefined | |||
123 | -- You should check if a returned by peer block is actually have | 124 | -- You should check if a returned by peer block is actually have |
124 | -- been requested and in-flight. This is needed to avoid "I send | 125 | -- been requested and in-flight. This is needed to avoid "I send |
125 | -- random corrupted block" attacks. | 126 | -- random corrupted block" attacks. |
126 | insert :: PeerAddr -> Block a -> Assembler -> Assembler | 127 | insert :: (PeerAddr IP) -> Block a -> Assembler -> Assembler |
127 | insert = undefined | 128 | insert = undefined |
128 | 129 | ||
129 | {- | 130 | {- |
@@ -156,4 +157,4 @@ inserta :: Block a | |||
156 | -> (PieceMap [Block a], Maybe (Piece a)) | 157 | -> (PieceMap [Block a], Maybe (Piece a)) |
157 | inserta = undefined | 158 | inserta = undefined |
158 | 159 | ||
159 | -} \ No newline at end of file | 160 | -} |
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs index 0adb8299..fb3a5c82 100644 --- a/src/Network/BitTorrent/Exchange/Message.hs +++ b/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -678,8 +678,8 @@ instance Default ExtendedHandshake where | |||
678 | 678 | ||
679 | instance BEncode ExtendedHandshake where | 679 | instance BEncode ExtendedHandshake where |
680 | toBEncode ExtendedHandshake {..} = toDict $ | 680 | toBEncode ExtendedHandshake {..} = toDict $ |
681 | "ipv4" .=? ehsIPv4 -- FIXME invalid encoding | 681 | "ipv4" .=? (S.encode <$> ehsIPv4) |
682 | .: "ipv6" .=? ehsIPv6 -- FIXME invalid encoding | 682 | .: "ipv6" .=? (S.encode <$> ehsIPv6) |
683 | .: "m" .=! ehsCaps | 683 | .: "m" .=! ehsCaps |
684 | .: "metadata_size" .=? ehsMetadataSize | 684 | .: "metadata_size" .=? ehsMetadataSize |
685 | .: "p" .=? ehsPort | 685 | .: "p" .=? ehsPort |
diff --git a/src/Network/BitTorrent/Exchange/Wire.hs b/src/Network/BitTorrent/Exchange/Wire.hs index ae9babb3..27b4be12 100644 --- a/src/Network/BitTorrent/Exchange/Wire.hs +++ b/src/Network/BitTorrent/Exchange/Wire.hs | |||
@@ -505,7 +505,7 @@ initiateHandshake sock hs = do | |||
505 | recvHandshake sock | 505 | recvHandshake sock |
506 | 506 | ||
507 | -- | Tries to connect to peer using reasonable default parameters. | 507 | -- | Tries to connect to peer using reasonable default parameters. |
508 | connectToPeer :: PeerAddr -> IO Socket | 508 | connectToPeer :: (IPAddress i) => PeerAddr i -> IO Socket |
509 | connectToPeer p = do | 509 | connectToPeer p = do |
510 | sock <- socket AF_INET Stream Network.Socket.defaultProtocol | 510 | sock <- socket AF_INET Stream Network.Socket.defaultProtocol |
511 | connect sock (peerSockAddr p) | 511 | connect sock (peerSockAddr p) |
@@ -628,7 +628,7 @@ reconnect = undefined | |||
628 | -- | 628 | -- |
629 | -- This function can throw 'WireFailure' exception. | 629 | -- This function can throw 'WireFailure' exception. |
630 | -- | 630 | -- |
631 | connectWire :: Handshake -> PeerAddr -> ExtendedCaps -> Wire () -> IO () | 631 | connectWire :: (IPAddress i) => Handshake -> PeerAddr i -> ExtendedCaps -> Wire () -> IO () |
632 | connectWire hs addr extCaps wire = | 632 | connectWire hs addr extCaps wire = |
633 | bracket (connectToPeer addr) close $ \ sock -> do | 633 | bracket (connectToPeer addr) close $ \ sock -> do |
634 | hs' <- initiateHandshake sock hs | 634 | hs' <- initiateHandshake sock 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 | -- |
676 | acceptWire :: Socket -> PeerAddr -> Wire () -> IO () | 676 | acceptWire :: (IPAddress i) => Socket -> PeerAddr i -> Wire () -> IO () |
677 | acceptWire sock peerAddr wire = do | 677 | acceptWire 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 fe7686cb..95b9c7ca 100644 --- a/src/Network/BitTorrent/Tracker/Message.hs +++ b/src/Network/BitTorrent/Tracker/Message.hs | |||
@@ -22,6 +22,8 @@ | |||
22 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 22 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
23 | {-# LANGUAGE TemplateHaskell #-} | 23 | {-# LANGUAGE TemplateHaskell #-} |
24 | {-# LANGUAGE DeriveDataTypeable #-} | 24 | {-# LANGUAGE DeriveDataTypeable #-} |
25 | {-# LANGUAGE DeriveFunctor #-} | ||
26 | {-# LANGUAGE ScopedTypeVariables #-} | ||
25 | {-# OPTIONS -fno-warn-orphans #-} | 27 | {-# OPTIONS -fno-warn-orphans #-} |
26 | module Network.BitTorrent.Tracker.Message | 28 | module Network.BitTorrent.Tracker.Message |
27 | ( -- * Announce | 29 | ( -- * Announce |
@@ -83,6 +85,7 @@ import Data.Text (Text) | |||
83 | import Data.Text.Encoding | 85 | import Data.Text.Encoding |
84 | import Data.Typeable | 86 | import Data.Typeable |
85 | import Data.Word | 87 | import Data.Word |
88 | import Data.IP | ||
86 | import Network | 89 | import Network |
87 | import Network.HTTP.Types.QueryLike | 90 | import Network.HTTP.Types.QueryLike |
88 | import Network.HTTP.Types.URI hiding (urlEncode) | 91 | import Network.HTTP.Types.URI hiding (urlEncode) |
@@ -431,24 +434,18 @@ renderAnnounceRequest = queryToSimpleQuery . toQuery | |||
431 | -- | 434 | -- |
432 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> | 435 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> |
433 | -- | 436 | -- |
434 | data PeerList | 437 | data PeerList a |
435 | = PeerList { getPeerList :: [PeerAddr] } | 438 | = PeerList { getPeerList :: [PeerAddr a] } |
436 | | CompactPeerList { getPeerList :: [PeerAddr] } | 439 | | CompactPeerList { getPeerList :: [PeerAddr a] } |
437 | deriving (Show, Eq, Typeable) | 440 | deriving (Show, Eq, Typeable, Functor) |
438 | 441 | ||
439 | instance ToJSON PeerList where | 442 | putCompactPeerList :: (Serialize a) => S.Putter [PeerAddr a] |
440 | toJSON = toJSON . getPeerList | ||
441 | |||
442 | instance FromJSON PeerList where | ||
443 | parseJSON v = PeerList <$> parseJSON v | ||
444 | |||
445 | putCompactPeerList :: S.Putter [PeerAddr] | ||
446 | putCompactPeerList = mapM_ put | 443 | putCompactPeerList = mapM_ put |
447 | 444 | ||
448 | getCompactPeerList :: S.Get [PeerAddr] | 445 | getCompactPeerList :: (Serialize a) => S.Get [PeerAddr a] |
449 | getCompactPeerList = many get | 446 | getCompactPeerList = many get |
450 | 447 | ||
451 | instance BEncode PeerList where | 448 | instance (Typeable a, BEncode a, Serialize a) => BEncode (PeerList a) where |
452 | toBEncode (PeerList xs) = toBEncode xs | 449 | toBEncode (PeerList xs) = toBEncode xs |
453 | toBEncode (CompactPeerList xs) = toBEncode $ runPut (putCompactPeerList xs) | 450 | toBEncode (CompactPeerList xs) = toBEncode $ runPut (putCompactPeerList xs) |
454 | 451 | ||
@@ -479,14 +476,12 @@ data AnnounceInfo = | |||
479 | , respMinInterval :: !(Maybe Int) | 476 | , respMinInterval :: !(Maybe Int) |
480 | 477 | ||
481 | -- | Peers that must be contacted. | 478 | -- | Peers that must be contacted. |
482 | , respPeers :: !PeerList | 479 | , respPeers :: !(PeerList IP) |
483 | 480 | ||
484 | -- | Human readable warning. | 481 | -- | Human readable warning. |
485 | , respWarning :: !(Maybe Text) | 482 | , respWarning :: !(Maybe Text) |
486 | } deriving (Show, Typeable) | 483 | } deriving (Show, Typeable) |
487 | 484 | ||
488 | $(deriveJSON omitRecordPrefix ''AnnounceInfo) | ||
489 | |||
490 | -- | HTTP tracker protocol compatible encoding. | 485 | -- | HTTP tracker protocol compatible encoding. |
491 | instance BEncode AnnounceInfo where | 486 | instance BEncode AnnounceInfo where |
492 | toBEncode (Failure t) = toDict $ | 487 | toBEncode (Failure t) = toDict $ |
@@ -498,19 +493,21 @@ instance BEncode AnnounceInfo where | |||
498 | .: "incomplete" .=? respIncomplete | 493 | .: "incomplete" .=? respIncomplete |
499 | .: "interval" .=! respInterval | 494 | .: "interval" .=! respInterval |
500 | .: "min interval" .=? respMinInterval | 495 | .: "min interval" .=? respMinInterval |
501 | .: "peers" .=! respPeers | 496 | .: "peers" .=! peers |
497 | .: "peers6" .=! peers6 | ||
502 | .: "warning message" .=? respWarning | 498 | .: "warning message" .=? respWarning |
503 | .: endDict | 499 | .: endDict |
500 | where (peers,peers6) = splitIPList $ getPeerList respPeers | ||
504 | 501 | ||
505 | fromBEncode (BDict d) | 502 | fromBEncode (BDict d) |
506 | | Just t <- BE.lookup "failure reason" d = Failure <$> fromBEncode t | 503 | | Just t <- BE.lookup "failure reason" d = Failure <$> fromBEncode t |
507 | | otherwise = (`fromDict` (BDict d)) $ do | 504 | | otherwise = (`fromDict` (BDict d)) $ |
508 | AnnounceInfo | 505 | AnnounceInfo |
509 | <$>? "complete" | 506 | <$>? "complete" |
510 | <*>? "incomplete" | 507 | <*>? "incomplete" |
511 | <*>! "interval" | 508 | <*>! "interval" |
512 | <*>? "min interval" | 509 | <*>? "min interval" |
513 | <*>! "peers" | 510 | <*> (PeerList <$> (mergeIPLists <$>! "peers" <*>? "peers6")) |
514 | <*>? "warning message" | 511 | <*>? "warning message" |
515 | fromBEncode _ = decodingError "Announce info" | 512 | fromBEncode _ = decodingError "Announce info" |
516 | 513 | ||
@@ -521,13 +518,13 @@ instance Serialize AnnounceInfo where | |||
521 | putWord32be $ fromIntegral respInterval | 518 | putWord32be $ fromIntegral respInterval |
522 | putWord32be $ fromIntegral $ fromMaybe 0 respIncomplete | 519 | putWord32be $ fromIntegral $ fromMaybe 0 respIncomplete |
523 | putWord32be $ fromIntegral $ fromMaybe 0 respComplete | 520 | putWord32be $ fromIntegral $ fromMaybe 0 respComplete |
524 | forM_ (getPeerList respPeers) put | 521 | forM_ (fmap ipv4 <$> getPeerList respPeers) put |
525 | 522 | ||
526 | get = do | 523 | get = do |
527 | interval <- getWord32be | 524 | interval <- getWord32be |
528 | leechers <- getWord32be | 525 | leechers <- getWord32be |
529 | seeders <- getWord32be | 526 | seeders <- getWord32be |
530 | peers <- many get | 527 | peers <- many $ fmap IPv4 <$> get |
531 | 528 | ||
532 | return $ AnnounceInfo { | 529 | return $ AnnounceInfo { |
533 | respWarning = Nothing | 530 | respWarning = Nothing |