summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Core
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Core')
-rw-r--r--src/Network/BitTorrent/Core/PeerAddr.hs134
1 files changed, 109 insertions, 25 deletions
diff --git a/src/Network/BitTorrent/Core/PeerAddr.hs b/src/Network/BitTorrent/Core/PeerAddr.hs
index 94510bba..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
16module Network.BitTorrent.Core.PeerAddr 18module 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
23import Control.Applicative 29import Control.Applicative
@@ -38,10 +44,14 @@ import Data.String
38import Data.Typeable 44import Data.Typeable
39import Data.Word 45import Data.Word
40import Data.IP 46import Data.IP
47import Data.Maybe
48import Data.Foldable
49import Data.Either
41import Network.Socket 50import Network.Socket
42import Text.PrettyPrint 51import Text.PrettyPrint
43import Text.PrettyPrint.Class 52import Text.PrettyPrint.Class
44import Text.Read (readMaybe) 53import Text.Read (readMaybe)
54import qualified Text.ParserCombinators.ReadP as RP
45import System.IO.Unsafe 55import System.IO.Unsafe
46 56
47import Data.Torrent.JSON 57import Data.Torrent.JSON
@@ -61,32 +71,74 @@ instance Serialize PortNumber where
61 put = putWord16be . fromIntegral 71 put = putWord16be . fromIntegral
62 {-# INLINE put #-} 72 {-# INLINE put #-}
63 73
74class (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
83instance IPAddress IPv4 where
84 toHostAddr = Left . toHostAddress
85
86instance IPAddress IPv6 where
87 toHostAddr = Right . toHostAddress6
88
89instance IPAddress IP where
90 toHostAddr (IPv4 ip) = toHostAddr ip
91 toHostAddr (IPv6 ip) = toHostAddr ip
92
93
94deriving instance Typeable IP
95deriving instance Typeable IPv4
96deriving instance Typeable IPv6
97
98ipToBEncode ip = BString $ BS8.pack $ showIp ip
99ipFromBEncode (BString ip) = return $ readIp $ BS8.unpack ip
100
101instance BEncode IP where
102 toBEncode = ipToBEncode
103 fromBEncode = ipFromBEncode
104
105instance BEncode IPv4 where
106 toBEncode = ipToBEncode
107 fromBEncode = ipFromBEncode
108
109instance BEncode IPv6 where
110 toBEncode = ipToBEncode
111 fromBEncode = ipFromBEncode
112
113instance Serialize IPv4 where
114 put ip = put $ toHostAddress ip
115 get = fromHostAddress <$> get
116
117instance Serialize IPv6 where
118 put ip = put $ toHostAddress6 ip
119 get = fromHostAddress6 <$> get
120
64-- TODO check semantic of ord and eq instances 121-- TODO check semantic of ord and eq instances
65-- TODO use SockAddr instead of peerIP and peerPort 122-- TODO use SockAddr instead of peerIP and peerPort
66 123
67-- | Peer address info normally extracted from peer list or peer 124-- | Peer address info normally extracted from peer list or peer
68-- compact list encoding. 125-- compact list encoding.
69data PeerAddr = PeerAddr 126data PeerAddr a = PeerAddr
70 { peerId :: !(Maybe PeerId) 127 { peerId :: !(Maybe PeerId)
71 , peerIP :: {-# UNPACK #-} !IP 128 , peerAddr :: a
72 , peerPort :: {-# UNPACK #-} !PortNumber 129 , peerPort :: {-# UNPACK #-} !PortNumber
73 } deriving (Show, Eq, Typeable) 130 } deriving (Show, Eq, Typeable, Functor)
74
75instance BEncode IP where
76 toBEncode ip = toBEncode $ BS8.pack $ show ip
77 fromBEncode (BString ip) = return $ fromString $ BS8.unpack ip
78 131
79peer_id_key, peer_ip_key, peer_port_key :: BKey 132peer_id_key, peer_ip_key, peer_port_key :: BKey
80peer_id_key = "peer id" 133peer_id_key = "peer id"
81peer_ip_key = "ip" 134peer_ip_key = "ip"
82peer_port_key = "port" 135peer_port_key = "port"
83 136
84-- FIXME do we need to byteswap peerIP in bencode instance?
85-- | The tracker's 'announce response' compatible encoding. 137-- | The tracker's 'announce response' compatible encoding.
86instance BEncode PeerAddr where 138instance (Typeable a, BEncode a) => BEncode (PeerAddr a) where
87 toBEncode PeerAddr {..} = toDict $ 139 toBEncode PeerAddr {..} = toDict $
88 peer_id_key .=? peerId 140 peer_id_key .=? peerId
89 .: peer_ip_key .=! BS8.pack (show peerIP) 141 .: peer_ip_key .=! peerAddr
90 .: peer_port_key .=! peerPort 142 .: peer_port_key .=! peerPort
91 .: endDict 143 .: endDict
92 144
@@ -95,19 +147,32 @@ instance BEncode PeerAddr where
95 <*>! peer_ip_key 147 <*>! peer_ip_key
96 <*>! peer_port_key 148 <*>! peer_port_key
97 149
150mergeIPLists :: [PeerAddr IPv4] -> Maybe [PeerAddr IPv6] -> [PeerAddr IP]
151mergeIPLists v4 v6 = (fmap IPv4 `L.map` v4)
152 ++ (fmap IPv6 `L.map` Data.Foldable.concat v6)
153
154splitIPList :: [PeerAddr IP] -> ([PeerAddr IPv4],[PeerAddr IPv6])
155splitIPList 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
98-- | The tracker's 'compact peer list' compatible encoding. The 162-- | The tracker's 'compact peer list' compatible encoding. The
99-- 'peerId' is always 'Nothing'. 163-- 'peerId' is always 'Nothing'.
100-- 164--
101-- 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>
102-- 166--
103instance Serialize PeerAddr where -- TODO do it properly 167-- TODO: test byte order
104 put PeerAddr {..} = (putWord32host $ toHostAddress $ ipv4 peerIP) >> put peerPort 168instance (Serialize a) => Serialize (PeerAddr a) where
105 {-# INLINE put #-} 169 put PeerAddr {..} =
106 get = PeerAddr Nothing <$> (IPv4 . fromHostAddress <$> getWord32host) <*> get 170 put peerAddr >> put peerPort
107 {-# INLINE get #-} 171 get =
172 PeerAddr Nothing <$> get <*> get
108 173
109-- | @127.0.0.1:6881@ 174-- | @127.0.0.1:6881@
110instance Default PeerAddr where 175instance Default (PeerAddr IPv4) where
111 def = "127.0.0.1:6881" 176 def = "127.0.0.1:6881"
112 177
113-- inet_addr is pure; so it is safe to throw IO 178-- inet_addr is pure; so it is safe to throw IO
@@ -122,30 +187,49 @@ unsafeCatchIO m = unsafePerformIO $
122-- 187--
123-- @peerPort \"127.0.0.1:6881\" == 6881@ 188-- @peerPort \"127.0.0.1:6881\" == 6881@
124-- 189--
125instance IsString PeerAddr where 190instance IsString (PeerAddr IPv4) where
126 fromString str -- TODO IPv6 191 fromString str
127 | [hostAddrStr, portStr] <- splitWhen (== ':') str 192 | [hostAddrStr, portStr] <- splitWhen (== ':') str
128 , Just hostAddr <- read hostAddrStr 193 , hostAddr <- read hostAddrStr
129 , Just portNum <- toEnum <$> readMaybe portStr 194 , Just portNum <- toEnum <$> readMaybe portStr
130 = PeerAddr Nothing hostAddr portNum 195 = PeerAddr Nothing hostAddr portNum
131 | otherwise = error $ "fromString: unable to parse PeerAddr: " ++ str 196 | otherwise = error $ "fromString: unable to parse (PeerAddr IPv4): " ++ str
197
198readsIPv6_port :: String -> [((IPv6, PortNumber), String)]
199readsIPv6_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
205instance 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
132 210
133-- | fingerprint + "at" + dotted.host.inet.addr:port 211-- | fingerprint + "at" + dotted.host.inet.addr:port
134instance Pretty PeerAddr where 212-- TODO: instances for IPv6, HostName
213instance Pretty (PeerAddr IP) where
135 pretty p @ PeerAddr {..} 214 pretty p @ PeerAddr {..}
136 | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr 215 | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr
137 | otherwise = paddr 216 | otherwise = paddr
138 where 217 where
139 paddr = text (show (peerSockAddr p)) 218 paddr = text (show peerAddr ++ ":" ++ show peerPort)
140 219
141-- | Ports typically reserved for bittorrent P2P listener. 220-- | Ports typically reserved for bittorrent P2P listener.
142defaultPorts :: [PortNumber] 221defaultPorts :: [PortNumber]
143defaultPorts = [6881..6889] 222defaultPorts = [6881..6889]
144 223
224resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i
225resolvePeerAddr = undefined
226
145-- | Convert peer info from tracker response to socket address. Used 227-- | Convert peer info from tracker response to socket address. Used
146-- for establish connection between peers. 228-- for establish connection between peers.
147-- 229--
148peerSockAddr :: PeerAddr -> SockAddr 230peerSockAddr :: (IPAddress i) => PeerAddr i -> SockAddr
149peerSockAddr PeerAddr {..} 231peerSockAddr PeerAddr {..}
150 | IPv4 v4 <- peerIP = SockAddrInet peerPort (toHostAddress v4) 232 | Left hAddr <- toHostAddr peerAddr =
151 | IPv6 v6 <- peerIP = SockAddrInet6 peerPort 0 (toHostAddress6 v6) 0 233 SockAddrInet peerPort hAddr
234 | Right hAddr <- toHostAddr peerAddr =
235 SockAddrInet6 peerPort 0 hAddr 0