summaryrefslogtreecommitdiff
path: root/src/Network
diff options
context:
space:
mode:
authorSam T <pxqr.sta@gmail.com>2013-07-09 06:16:48 +0400
committerSam T <pxqr.sta@gmail.com>2013-07-09 06:16:48 +0400
commit2f8b349bafd7ff3d0877bd99c99420848f5cdfc2 (patch)
treebeb335f55af4c7975d0b7bd2e2a765a61469932f /src/Network
parent19efaca1db008a58a1c1b65395465550ec0b72b6 (diff)
Implement DHT server methods.
Diffstat (limited to 'src/Network')
-rw-r--r--src/Network/BitTorrent/DHT.hs137
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
7import Control.Applicative 7import Control.Applicative
8import Control.Concurrent.STM 8import Control.Concurrent.STM
9import Control.Monad
9import Data.ByteString 10import Data.ByteString
10import Data.Serialize as S 11import Data.Serialize as S
11import Data.Function 12import Data.Function
12import Data.Ord 13import Data.Ord
14import Data.Maybe
15import Data.List as L
16import Data.Map as M
13import Data.HashMap.Strict as HM 17import Data.HashMap.Strict as HM
14 18
15import Network 19import Network
@@ -19,6 +23,7 @@ import Remote.KRPC
19import Data.BEncode 23import Data.BEncode
20import Data.Torrent 24import Data.Torrent
21import Data.Kademlia.Routing.Table 25import Data.Kademlia.Routing.Table
26import Network.BitTorrent.Peer
22 27
23{----------------------------------------------------------------------- 28{-----------------------------------------------------------------------
24 Node 29 Node
@@ -39,8 +44,8 @@ instance Serialize PortNumber where
39 44
40 45
41data NodeAddr = NodeAddr { 46data 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
46instance Serialize NodeAddr where 51instance Serialize NodeAddr where
@@ -61,40 +66,64 @@ instance Serialize NodeInfo where
61 66
62type CompactInfo = ByteString 67type CompactInfo = ByteString
63 68
64decodeCompact :: CompactInfo -> Either String [NodeInfo] 69decodeCompact :: CompactInfo -> [NodeInfo]
65decodeCompact = S.runGet (many get) 70decodeCompact = either (const []) id . S.runGet (many get)
66 71
67encodeCompact :: [NodeId] -> CompactInfo 72encodeCompact :: [NodeId] -> CompactInfo
68encodeCompact = S.runPut . mapM_ put 73encodeCompact = S.runPut . mapM_ put
69 74
75decodePeerList :: [BEncode] -> [PeerAddr]
76decodePeerList = undefined
77
78encodePeerList :: [PeerAddr] -> [BEncode]
79encodePeerList = undefined
80
70type Distance = NodeId 81type 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
88type Secret = Int
76 89
77genSecret :: IO Secret 90genSecret :: IO Secret
78genSecret = undefined 91genSecret = undefined
79 92
80type Token = Int 93-- | Instead of periodically loop over the all nodes in the routing
81type 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--
98type Token = ByteString
82 99
83token :: NodeAddr -> Secret -> Token 100genToken :: NodeAddr -> Secret -> Token
84token = return undefined 101genToken = return undefined
85 102
86defaultToken :: Token 103defaultToken :: Token
87defaultToken = 0xdeadbeef 104defaultToken = "0xdeadbeef"
88 105
89{----------------------------------------------------------------------- 106{-----------------------------------------------------------------------
90 Routing table 107 Routing table
91-----------------------------------------------------------------------} 108-----------------------------------------------------------------------}
92 109
110type ContactInfo = HashMap InfoHash [PeerAddr]
111
112insertPeer :: InfoHash -> PeerAddr -> ContactInfo -> ContactInfo
113insertPeer ih addr = HM.insertWith (++) ih [addr]
114
115lookupPeers :: InfoHash -> ContactInfo -> [PeerAddr]
116lookupPeers ih = fromMaybe [] . HM.lookup ih
117
93-- TODO use more compact routing table 118-- TODO use more compact routing table
94type RoutingTable = HashMap NodeId NodeAddr 119type RoutingTable = HashMap NodeId NodeAddr
95 120
121insertNode :: NodeId -> NodeAddr -> RoutingTable -> RoutingTable
122insertNode = HM.insert
123
96type Alpha = Int 124type Alpha = Int
97 125
126-- TODO
98kclosest :: Int -> NodeId -> RoutingTable -> [NodeId] 127kclosest :: Int -> NodeId -> RoutingTable -> [NodeId]
99kclosest = undefined 128kclosest = undefined
100 129
@@ -105,6 +134,7 @@ kclosest = undefined
105data NodeSession = NodeSession { 134data 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
114instance Ord NodeSession where 144instance Ord NodeSession where
115 compare = comparing nodeId 145 compare = comparing nodeId
116 146
147assignToken :: NodeSession -> NodeId -> IO Token
148assignToken _ _ = return ""
149
150-- TODO
151checkToken :: NodeId -> Token -> NodeSession -> IO Bool
152checkToken nid token _ = return True
153
117{----------------------------------------------------------------------- 154{-----------------------------------------------------------------------
118 Queries 155 Queries
119-----------------------------------------------------------------------} 156-----------------------------------------------------------------------}
120 157
121instance BEncodable PortNumber where
122
123pingM :: Method NodeId NodeId 158pingM :: Method NodeId NodeId
124pingM = method "ping" ["id"] ["id"] 159pingM = method "ping" ["id"] ["id"]
125 160
126findNodeM :: Method (NodeId, NodeId) (NodeId, CompactInfo) 161findNodeM :: Method (NodeId, NodeId) (NodeId, CompactInfo)
127findNodeM = method "find_node" ["id", "target"] ["id", "nodes"] 162findNodeM = 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
130getPeersM :: Method (NodeId, InfoHash) (NodeId, Token, CompactInfo) -- use Map ByteString BEncode 165-- different kind of responses depending on the routing table of
131getPeersM = 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--
189getPeersM :: Method (NodeId, InfoHash) BEncode
190getPeersM = 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.
135announcePeerM :: Method (NodeId, InfoHash, PortNumber, Token) NodeId 194announcePeerM :: Method (NodeId, InfoHash, PortNumber, Token) NodeId
136announcePeerM = method "announce_peer" ["id", "info_hash", "port", "token"] ["id"] 195announcePeerM = method "announce_peer" ["id", "info_hash", "port", "token"] ["id"]
137 196
197{-----------------------------------------------------------------------
198 Tracker
199-----------------------------------------------------------------------}
200
138pingC :: NodeSession -> NodeAddr -> IO () 201pingC :: NodeSession -> NodeAddr -> IO ()
139pingC NodeSession {..} addr @ NodeAddr {..} = do 202pingC 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 ) 206getPeerC :: NodeSession -> NodeAddr -> InfoHash -> IO ()
144getPeerC NodeSession {..} addr @ NodeAddr {..} ih = do 207getPeerC 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
147type ServerHandler a b = NodeSession -> NodeAddr -> a -> IO b 215type ServerHandler a b = NodeSession -> NodeAddr -> a -> IO b
148 216
149pingS :: ServerHandler NodeId NodeId 217pingS :: ServerHandler NodeId NodeId
150pingS NodeSession {..} addr nid = do 218pingS 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
154findNodeS :: ServerHandler (NodeId, NodeId) (NodeId, CompactInfo) 222findNodeS :: 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
159getPeersS :: ServerHandler (NodeId, InfoHash) (NodeId, Token, CompactInfo) 227getPeersS :: ServerHandler (NodeId, InfoHash) BEncode
160getPeersS NodeSession {..} addr (nid, ih) = do 228getPeersS 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
164announcePeerS :: ServerHandler (NodeId, InfoHash, PortNumber, Token) NodeId 245announcePeerS :: ServerHandler (NodeId, InfoHash, PortNumber, Token) NodeId
165announcePeerS NodeSession {..} addr (nid, ih, port, token) = do 246announcePeerS 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
169dhtServer :: PortNumber -> NodeSession -> IO () 254dhtServer :: PortNumber -> NodeSession -> IO ()