From 19efaca1db008a58a1c1b65395465550ec0b72b6 Mon Sep 17 00:00:00 2001 From: Sam T Date: Tue, 9 Jul 2013 00:09:20 +0400 Subject: ~ Move DHT to single module. --- src/Network/BitTorrent/DHT.hs | 165 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 2 deletions(-) (limited to 'src/Network/BitTorrent/DHT.hs') diff --git a/src/Network/BitTorrent/DHT.hs b/src/Network/BitTorrent/DHT.hs index 2d2073f0..60f4566a 100644 --- a/src/Network/BitTorrent/DHT.hs +++ b/src/Network/BitTorrent/DHT.hs @@ -1,14 +1,175 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} module Network.BitTorrent.DHT - ( + ( dhtServer ) where +import Control.Applicative +import Control.Concurrent.STM import Data.ByteString +import Data.Serialize as S +import Data.Function +import Data.Ord +import Data.HashMap.Strict as HM + import Network +import Network.Socket +import Remote.KRPC +import Data.BEncode +import Data.Torrent import Data.Kademlia.Routing.Table +{----------------------------------------------------------------------- + Node +-----------------------------------------------------------------------} + +type NodeId = ByteString + +-- WARN is the 'system' random suitable for this? +-- | Generate random NodeID used for the entire session. +-- Distribution of ID's should be as uniform as possible. +-- +genNodeID :: IO NodeId +genNodeID = undefined -- randomIO + +instance Serialize PortNumber where + get = fromIntegral <$> getWord16be + put = putWord16be . fromIntegral + + +data NodeAddr = NodeAddr { + nodeIP :: !HostAddress + , nodePort :: !PortNumber + } deriving (Show, Eq) + +instance Serialize NodeAddr where + get = NodeAddr <$> getWord32be <*> get + put NodeAddr {..} = do + putWord32be nodeIP + put nodePort + + +data NodeInfo = NodeInfo { + nodeID :: !NodeId + , nodeAddr :: !NodeAddr + } deriving (Show, Eq) + +instance Serialize NodeInfo where + get = NodeInfo <$> getByteString 20 <*> get + put NodeInfo {..} = put nodeID >> put nodeAddr + +type CompactInfo = ByteString + +decodeCompact :: CompactInfo -> Either String [NodeInfo] +decodeCompact = S.runGet (many get) + +encodeCompact :: [NodeId] -> CompactInfo +encodeCompact = S.runPut . mapM_ put + +type Distance = NodeId + --type DHT k v = StateT (Table k v) IO --findNode :: NodeID -> DHT k v [NodeInfo] ---findNode = undefined \ No newline at end of file +--findNode = undefined + +genSecret :: IO Secret +genSecret = undefined + +type Token = Int +type Secret = Int + +token :: NodeAddr -> Secret -> Token +token = return undefined + +defaultToken :: Token +defaultToken = 0xdeadbeef + +{----------------------------------------------------------------------- + Routing table +-----------------------------------------------------------------------} + +-- TODO use more compact routing table +type RoutingTable = HashMap NodeId NodeAddr + +type Alpha = Int + +kclosest :: Int -> NodeId -> RoutingTable -> [NodeId] +kclosest = undefined + +{----------------------------------------------------------------------- + Node session +-----------------------------------------------------------------------} + +data NodeSession = NodeSession { + nodeId :: !NodeId + , routingTable :: !(TVar RoutingTable) + , alpha :: !Alpha + } + +instance Eq NodeSession where + (==) = (==) `on` nodeId + +instance Ord NodeSession where + compare = comparing nodeId + +{----------------------------------------------------------------------- + Queries +-----------------------------------------------------------------------} + +instance BEncodable PortNumber where + +pingM :: Method NodeId NodeId +pingM = method "ping" ["id"] ["id"] + +findNodeM :: Method (NodeId, NodeId) (NodeId, CompactInfo) +findNodeM = method "find_node" ["id", "target"] ["id", "nodes"] + +-- | Lookup peers by a torrent infohash. +getPeersM :: Method (NodeId, InfoHash) (NodeId, Token, CompactInfo) -- use Map ByteString BEncode +getPeersM = method "get_peers" ["id", "info_hash"] ["id", "token", "nodes"] + +-- | Used to announce that the peer, controlling the quering node is +-- downloading a torrent on a port. +announcePeerM :: Method (NodeId, InfoHash, PortNumber, Token) NodeId +announcePeerM = method "announce_peer" ["id", "info_hash", "port", "token"] ["id"] + +pingC :: NodeSession -> NodeAddr -> IO () +pingC NodeSession {..} addr @ NodeAddr {..} = do + nid <- call (nodeIP, nodePort) pingM nodeId + atomically $ modifyTVar' routingTable $ HM.insert nid addr + +--getPeerC :: NodeSession -> NodeAddr -> InfoHash -> IO (Either CompactInfo ) +getPeerC NodeSession {..} addr @ NodeAddr {..} ih = do + call (nodeIP, nodePort) getPeersM + +type ServerHandler a b = NodeSession -> NodeAddr -> a -> IO b + +pingS :: ServerHandler NodeId NodeId +pingS NodeSession {..} addr nid = do + atomically $ modifyTVar' routingTable $ HM.insert nid addr + return nodeId + +findNodeS :: ServerHandler (NodeId, NodeId) (NodeId, CompactInfo) +findNodeS NodeSession {..} addr (nid, qnid) = do + rt <- atomically $ readTVar routingTable + return (nodeId, encodeCompact $ kclosest alpha qnid rt) + +getPeersS :: ServerHandler (NodeId, InfoHash) (NodeId, Token, CompactInfo) +getPeersS NodeSession {..} addr (nid, ih) = do + + return (nodeId, defaultToken, error "compact info") + +announcePeerS :: ServerHandler (NodeId, InfoHash, PortNumber, Token) NodeId +announcePeerS NodeSession {..} addr (nid, ih, port, token) = do + let right = (error "checkToken") + return nodeId + +dhtServer :: PortNumber -> NodeSession -> IO () +dhtServer p s = server p + [ pingM ==> pingS s undefined + , findNodeM ==> findNodeS s undefined + , getPeersM ==> getPeersS s undefined + , announcePeerM ==> announcePeerS s undefined + ] \ No newline at end of file -- cgit v1.2.3