diff options
Diffstat (limited to 'src/Network/BitTorrent')
-rw-r--r-- | src/Network/BitTorrent/DHT.hs | 137 |
1 files changed, 111 insertions, 26 deletions
diff --git a/src/Network/BitTorrent/DHT.hs b/src/Network/BitTorrent/DHT.hs index 60f4566a..5c5d017b 100644 --- a/src/Network/BitTorrent/DHT.hs +++ b/src/Network/BitTorrent/DHT.hs | |||
@@ -6,10 +6,14 @@ module Network.BitTorrent.DHT | |||
6 | 6 | ||
7 | import Control.Applicative | 7 | import Control.Applicative |
8 | import Control.Concurrent.STM | 8 | import Control.Concurrent.STM |
9 | import Control.Monad | ||
9 | import Data.ByteString | 10 | import Data.ByteString |
10 | import Data.Serialize as S | 11 | import Data.Serialize as S |
11 | import Data.Function | 12 | import Data.Function |
12 | import Data.Ord | 13 | import Data.Ord |
14 | import Data.Maybe | ||
15 | import Data.List as L | ||
16 | import Data.Map as M | ||
13 | import Data.HashMap.Strict as HM | 17 | import Data.HashMap.Strict as HM |
14 | 18 | ||
15 | import Network | 19 | import Network |
@@ -19,6 +23,7 @@ import Remote.KRPC | |||
19 | import Data.BEncode | 23 | import Data.BEncode |
20 | import Data.Torrent | 24 | import Data.Torrent |
21 | import Data.Kademlia.Routing.Table | 25 | import Data.Kademlia.Routing.Table |
26 | import Network.BitTorrent.Peer | ||
22 | 27 | ||
23 | {----------------------------------------------------------------------- | 28 | {----------------------------------------------------------------------- |
24 | Node | 29 | Node |
@@ -39,8 +44,8 @@ instance Serialize PortNumber where | |||
39 | 44 | ||
40 | 45 | ||
41 | data NodeAddr = NodeAddr { | 46 | data NodeAddr = NodeAddr { |
42 | nodeIP :: !HostAddress | 47 | nodeIP :: {-# UNPACK #-} !HostAddress |
43 | , nodePort :: !PortNumber | 48 | , nodePort :: {-# UNPACK #-} !PortNumber |
44 | } deriving (Show, Eq) | 49 | } deriving (Show, Eq) |
45 | 50 | ||
46 | instance Serialize NodeAddr where | 51 | instance Serialize NodeAddr where |
@@ -61,40 +66,64 @@ instance Serialize NodeInfo where | |||
61 | 66 | ||
62 | type CompactInfo = ByteString | 67 | type CompactInfo = ByteString |
63 | 68 | ||
64 | decodeCompact :: CompactInfo -> Either String [NodeInfo] | 69 | decodeCompact :: CompactInfo -> [NodeInfo] |
65 | decodeCompact = S.runGet (many get) | 70 | decodeCompact = either (const []) id . S.runGet (many get) |
66 | 71 | ||
67 | encodeCompact :: [NodeId] -> CompactInfo | 72 | encodeCompact :: [NodeId] -> CompactInfo |
68 | encodeCompact = S.runPut . mapM_ put | 73 | encodeCompact = S.runPut . mapM_ put |
69 | 74 | ||
75 | decodePeerList :: [BEncode] -> [PeerAddr] | ||
76 | decodePeerList = undefined | ||
77 | |||
78 | encodePeerList :: [PeerAddr] -> [BEncode] | ||
79 | encodePeerList = undefined | ||
80 | |||
70 | type Distance = NodeId | 81 | type Distance = NodeId |
71 | 82 | ||
72 | --type DHT k v = StateT (Table k v) IO | ||
73 | 83 | ||
74 | --findNode :: NodeID -> DHT k v [NodeInfo] | 84 | {----------------------------------------------------------------------- |
75 | --findNode = undefined | 85 | Tokens |
86 | -----------------------------------------------------------------------} | ||
87 | |||
88 | type Secret = Int | ||
76 | 89 | ||
77 | genSecret :: IO Secret | 90 | genSecret :: IO Secret |
78 | genSecret = undefined | 91 | genSecret = undefined |
79 | 92 | ||
80 | type Token = Int | 93 | -- | Instead of periodically loop over the all nodes in the routing |
81 | type Secret = Int | 94 | -- table with some given interval (or some other tricky method |
95 | -- e.g. using timeouts) we can just update tokens on demand - if no | ||
96 | -- one asks for a token then the token _should_ not change at all. | ||
97 | -- | ||
98 | type Token = ByteString | ||
82 | 99 | ||
83 | token :: NodeAddr -> Secret -> Token | 100 | genToken :: NodeAddr -> Secret -> Token |
84 | token = return undefined | 101 | genToken = return undefined |
85 | 102 | ||
86 | defaultToken :: Token | 103 | defaultToken :: Token |
87 | defaultToken = 0xdeadbeef | 104 | defaultToken = "0xdeadbeef" |
88 | 105 | ||
89 | {----------------------------------------------------------------------- | 106 | {----------------------------------------------------------------------- |
90 | Routing table | 107 | Routing table |
91 | -----------------------------------------------------------------------} | 108 | -----------------------------------------------------------------------} |
92 | 109 | ||
110 | type ContactInfo = HashMap InfoHash [PeerAddr] | ||
111 | |||
112 | insertPeer :: InfoHash -> PeerAddr -> ContactInfo -> ContactInfo | ||
113 | insertPeer ih addr = HM.insertWith (++) ih [addr] | ||
114 | |||
115 | lookupPeers :: InfoHash -> ContactInfo -> [PeerAddr] | ||
116 | lookupPeers ih = fromMaybe [] . HM.lookup ih | ||
117 | |||
93 | -- TODO use more compact routing table | 118 | -- TODO use more compact routing table |
94 | type RoutingTable = HashMap NodeId NodeAddr | 119 | type RoutingTable = HashMap NodeId NodeAddr |
95 | 120 | ||
121 | insertNode :: NodeId -> NodeAddr -> RoutingTable -> RoutingTable | ||
122 | insertNode = HM.insert | ||
123 | |||
96 | type Alpha = Int | 124 | type Alpha = Int |
97 | 125 | ||
126 | -- TODO | ||
98 | kclosest :: Int -> NodeId -> RoutingTable -> [NodeId] | 127 | kclosest :: Int -> NodeId -> RoutingTable -> [NodeId] |
99 | kclosest = undefined | 128 | kclosest = undefined |
100 | 129 | ||
@@ -105,6 +134,7 @@ kclosest = undefined | |||
105 | data NodeSession = NodeSession { | 134 | data NodeSession = NodeSession { |
106 | nodeId :: !NodeId | 135 | nodeId :: !NodeId |
107 | , routingTable :: !(TVar RoutingTable) | 136 | , routingTable :: !(TVar RoutingTable) |
137 | , contactInfo :: !(TVar ContactInfo) | ||
108 | , alpha :: !Alpha | 138 | , alpha :: !Alpha |
109 | } | 139 | } |
110 | 140 | ||
@@ -114,41 +144,79 @@ instance Eq NodeSession where | |||
114 | instance Ord NodeSession where | 144 | instance Ord NodeSession where |
115 | compare = comparing nodeId | 145 | compare = comparing nodeId |
116 | 146 | ||
147 | assignToken :: NodeSession -> NodeId -> IO Token | ||
148 | assignToken _ _ = return "" | ||
149 | |||
150 | -- TODO | ||
151 | checkToken :: NodeId -> Token -> NodeSession -> IO Bool | ||
152 | checkToken nid token _ = return True | ||
153 | |||
117 | {----------------------------------------------------------------------- | 154 | {----------------------------------------------------------------------- |
118 | Queries | 155 | Queries |
119 | -----------------------------------------------------------------------} | 156 | -----------------------------------------------------------------------} |
120 | 157 | ||
121 | instance BEncodable PortNumber where | ||
122 | |||
123 | pingM :: Method NodeId NodeId | 158 | pingM :: Method NodeId NodeId |
124 | pingM = method "ping" ["id"] ["id"] | 159 | pingM = method "ping" ["id"] ["id"] |
125 | 160 | ||
126 | findNodeM :: Method (NodeId, NodeId) (NodeId, CompactInfo) | 161 | findNodeM :: Method (NodeId, NodeId) (NodeId, CompactInfo) |
127 | findNodeM = method "find_node" ["id", "target"] ["id", "nodes"] | 162 | findNodeM = method "find_node" ["id", "target"] ["id", "nodes"] |
128 | 163 | ||
129 | -- | Lookup peers by a torrent infohash. | 164 | -- | Lookup peers by a torrent infohash. This method might return |
130 | getPeersM :: Method (NodeId, InfoHash) (NodeId, Token, CompactInfo) -- use Map ByteString BEncode | 165 | -- different kind of responses depending on the routing table of |
131 | getPeersM = method "get_peers" ["id", "info_hash"] ["id", "token", "nodes"] | 166 | -- queried node: |
167 | -- | ||
168 | -- * If quieried node contains a peer list for the given infohash | ||
169 | -- then the node should return the list in a "value" key. Note that | ||
170 | -- list is encoded as compact peer address, not a compact node info. | ||
171 | -- The result of 'get_peers' method have the following scheme: | ||
172 | -- | ||
173 | -- > { "id" : "dht_server_node_id" | ||
174 | -- > , "token" : "assigned_token" | ||
175 | -- > , "values" : ["_IP_PO", "_ip_po"] | ||
176 | -- > } | ||
177 | -- | ||
178 | -- * If quieried node does not contain a list of peers associated | ||
179 | -- with the given infohash, then node should return | ||
180 | -- | ||
181 | -- > { "id" : "dht_server_node_id" | ||
182 | -- > , "token" : "assigned_token" | ||
183 | -- > , "nodes" : "compact_nodes_info" | ||
184 | -- > } | ||
185 | -- | ||
186 | -- The resulting dictionaries might differ only in a values\/nodes | ||
187 | -- keys. | ||
188 | -- | ||
189 | getPeersM :: Method (NodeId, InfoHash) BEncode | ||
190 | getPeersM = method "get_peers" ["id", "info_hash"] [] | ||
132 | 191 | ||
133 | -- | Used to announce that the peer, controlling the quering node is | 192 | -- | Used to announce that the peer, controlling the quering node is |
134 | -- downloading a torrent on a port. | 193 | -- downloading a torrent on a port. |
135 | announcePeerM :: Method (NodeId, InfoHash, PortNumber, Token) NodeId | 194 | announcePeerM :: Method (NodeId, InfoHash, PortNumber, Token) NodeId |
136 | announcePeerM = method "announce_peer" ["id", "info_hash", "port", "token"] ["id"] | 195 | announcePeerM = method "announce_peer" ["id", "info_hash", "port", "token"] ["id"] |
137 | 196 | ||
197 | {----------------------------------------------------------------------- | ||
198 | Tracker | ||
199 | -----------------------------------------------------------------------} | ||
200 | |||
138 | pingC :: NodeSession -> NodeAddr -> IO () | 201 | pingC :: NodeSession -> NodeAddr -> IO () |
139 | pingC NodeSession {..} addr @ NodeAddr {..} = do | 202 | pingC NodeSession {..} addr @ NodeAddr {..} = do |
140 | nid <- call (nodeIP, nodePort) pingM nodeId | 203 | nid <- call (nodeIP, nodePort) pingM nodeId |
141 | atomically $ modifyTVar' routingTable $ HM.insert nid addr | 204 | atomically $ modifyTVar' routingTable $ HM.insert nid addr |
142 | 205 | ||
143 | --getPeerC :: NodeSession -> NodeAddr -> InfoHash -> IO (Either CompactInfo ) | 206 | getPeerC :: NodeSession -> NodeAddr -> InfoHash -> IO () |
144 | getPeerC NodeSession {..} addr @ NodeAddr {..} ih = do | 207 | getPeerC NodeSession {..} addr @ NodeAddr {..} ih = do |
145 | call (nodeIP, nodePort) getPeersM | 208 | call (nodeIP, nodePort) getPeersM undefined |
209 | return () | ||
210 | |||
211 | {----------------------------------------------------------------------- | ||
212 | Server | ||
213 | -----------------------------------------------------------------------} | ||
146 | 214 | ||
147 | type ServerHandler a b = NodeSession -> NodeAddr -> a -> IO b | 215 | type ServerHandler a b = NodeSession -> NodeAddr -> a -> IO b |
148 | 216 | ||
149 | pingS :: ServerHandler NodeId NodeId | 217 | pingS :: ServerHandler NodeId NodeId |
150 | pingS NodeSession {..} addr nid = do | 218 | pingS NodeSession {..} addr nid = do |
151 | atomically $ modifyTVar' routingTable $ HM.insert nid addr | 219 | atomically $ modifyTVar' routingTable $ insertNode nid addr |
152 | return nodeId | 220 | return nodeId |
153 | 221 | ||
154 | findNodeS :: ServerHandler (NodeId, NodeId) (NodeId, CompactInfo) | 222 | findNodeS :: ServerHandler (NodeId, NodeId) (NodeId, CompactInfo) |
@@ -156,14 +224,31 @@ findNodeS NodeSession {..} addr (nid, qnid) = do | |||
156 | rt <- atomically $ readTVar routingTable | 224 | rt <- atomically $ readTVar routingTable |
157 | return (nodeId, encodeCompact $ kclosest alpha qnid rt) | 225 | return (nodeId, encodeCompact $ kclosest alpha qnid rt) |
158 | 226 | ||
159 | getPeersS :: ServerHandler (NodeId, InfoHash) (NodeId, Token, CompactInfo) | 227 | getPeersS :: ServerHandler (NodeId, InfoHash) BEncode |
160 | getPeersS NodeSession {..} addr (nid, ih) = do | 228 | getPeersS ses @ NodeSession {..} _ (nid, ih) = do |
161 | 229 | mkResp <$> assignToken ses nid <*> findPeers | |
162 | return (nodeId, defaultToken, error "compact info") | 230 | where |
231 | findPeers = do | ||
232 | list <- lookupPeers ih <$> readTVarIO contactInfo | ||
233 | if not (L.null list) | ||
234 | then return $ Right list | ||
235 | else do | ||
236 | rt <- readTVarIO routingTable | ||
237 | let nodes = kclosest alpha (getInfoHash ih) rt | ||
238 | return $ Left nodes | ||
239 | |||
240 | mkDict tok res = [("id",BString nodeId), ("token", BString tok), res] | ||
241 | mkResult (Left nodes ) = ("nodes", BString $ encodeCompact nodes) | ||
242 | mkResult (Right values) = ("values", BList $ encodePeerList values) | ||
243 | mkResp tok = BDict . M.fromList . mkDict tok . mkResult | ||
163 | 244 | ||
164 | announcePeerS :: ServerHandler (NodeId, InfoHash, PortNumber, Token) NodeId | 245 | announcePeerS :: ServerHandler (NodeId, InfoHash, PortNumber, Token) NodeId |
165 | announcePeerS NodeSession {..} addr (nid, ih, port, token) = do | 246 | announcePeerS ses @ NodeSession {..} NodeAddr {..} (nid, ih, port, token) = do |
166 | let right = (error "checkToken") | 247 | registered <- checkToken nid token ses |
248 | when registered $ do | ||
249 | atomically $ do | ||
250 | let peerAddr = PeerAddr Nothing nodeIP port | ||
251 | modifyTVar contactInfo $ insertPeer ih peerAddr | ||
167 | return nodeId | 252 | return nodeId |
168 | 253 | ||
169 | dhtServer :: PortNumber -> NodeSession -> IO () | 254 | dhtServer :: PortNumber -> NodeSession -> IO () |