summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/dhtd.hs39
-rw-r--r--src/Network/BitTorrent/DHT/ContactInfo.hs59
-rw-r--r--src/Network/BitTorrent/DHT/Session.hs22
-rw-r--r--src/Network/BitTorrent/Tracker/RPC/HTTP.hs2
4 files changed, 95 insertions, 27 deletions
diff --git a/examples/dhtd.hs b/examples/dhtd.hs
index 3df77190..19b45acb 100644
--- a/examples/dhtd.hs
+++ b/examples/dhtd.hs
@@ -18,6 +18,7 @@ import Data.Maybe
18import Data.String 18import Data.String
19import qualified Data.ByteString as B (ByteString,writeFile,readFile) 19import qualified Data.ByteString as B (ByteString,writeFile,readFile)
20 ; import Data.ByteString (ByteString) 20 ; import Data.ByteString (ByteString)
21import qualified Data.ByteString.Char8 as B8
21import System.IO 22import System.IO
22import System.IO.Error 23import System.IO.Error
23import Text.PrettyPrint.HughesPJClass 24import Text.PrettyPrint.HughesPJClass
@@ -114,15 +115,16 @@ clientSession st signalQuit sock n h = do
114 line <- map toLower . dropWhile isSpace <$> hGetLine h 115 line <- map toLower . dropWhile isSpace <$> hGetLine h
115 let cmd0 action = action >> clientSession st signalQuit sock n h 116 let cmd0 action = action >> clientSession st signalQuit sock n h
116 cmd action = cmd0 $ join $ runDHT st action 117 cmd action = cmd0 $ join $ runDHT st action
117 case line of 118 (c,args) = second (dropWhile isSpace) $ break isSpace line
119 case (c,args) of
118 120
119 "quit" -> hPutClient h "" >> hClose h 121 ("quit", _) -> hPutClient h "" >> hClose h
120 122
121 "stop" -> do hPutClient h "Terminating DHT Daemon." 123 ("stop", _) -> do hPutClient h "Terminating DHT Daemon."
122 hClose h 124 hClose h
123 putMVar signalQuit () 125 putMVar signalQuit ()
124 126
125 "ls" -> cmd $ do 127 ("ls", _) -> cmd $ do
126 tbl <- getTable 128 tbl <- getTable
127 t <- showTable 129 t <- showTable
128 me <- myNodeIdAccordingTo (read "8.8.8.8:6881") 130 me <- myNodeIdAccordingTo (read "8.8.8.8:6881")
@@ -135,13 +137,13 @@ clientSession st signalQuit sock n h = do
135 , ("internet address", show ip) 137 , ("internet address", show ip)
136 , ("buckets", show $ R.shape tbl)] 138 , ("buckets", show $ R.shape tbl)]
137 ] 139 ]
138 "external-ip" -> cmd $ do 140 ("external-ip", _) -> cmd $ do
139 ip <- routableAddress 141 ip <- routableAddress
140 return $ do 142 return $ do
141 hPutClient h $ maybe "" (takeWhile (/=':') . show) ip 143 hPutClient h $ maybe "" (takeWhile (/=':') . show) ip
142 144
143 s | s=="swarms" || "swarms " `isPrefixOf` s -> cmd $ do 145 ("swarms", s) -> cmd $ do
144 let fltr = case dropWhile isSpace (drop 7 s) of 146 let fltr = case s of
145 ('-':'v':cs) | all isSpace (take 1 cs) 147 ('-':'v':cs) | all isSpace (take 1 cs)
146 -> const True 148 -> const True
147 _ -> (\(h,c,n) -> c/=0 ) 149 _ -> (\(h,c,n) -> c/=0 )
@@ -151,8 +153,8 @@ clientSession st signalQuit sock n h = do
151 return $ do 153 return $ do
152 hPutClient h $ showReport r 154 hPutClient h $ showReport r
153 155
154 s | "peers " `isPrefixOf` s -> cmd $ do 156 ("peers ", s) -> cmd $ do
155 let ih = fromString (drop 6 s) 157 let ih = fromString s
156 ps <- allPeers ih 158 ps <- allPeers ih
157 return $ do 159 return $ do
158 hPutClient h $ showReport $ map (((,) "") . show . pPrint) ps 160 hPutClient h $ showReport $ map (((,) "") . show . pPrint) ps
@@ -168,6 +170,15 @@ main = do
168 (Lifted.ioError $ userError "unable to resolve bootstrap nodes") 170 (Lifted.ioError $ userError "unable to resolve bootstrap nodes")
169 saved_nodes <- resume 171 saved_nodes <- resume
170 172
173 peers'trial <- liftIO $ tryIOError $ B.readFile "bt-peers.dat"
174 saved_peers <-
175 either (const $ do liftIO $ putStrLn "Error reading bt-peers.dat"
176 return Nothing)
177 (return . Just)
178 peers'trial
179
180 maybe (return ()) mergeSavedPeers saved_peers
181
171 when (isJust saved_nodes) $ do 182 when (isJust saved_nodes) $ do
172 b <- isBootstrapped 183 b <- isBootstrapped
173 tbl <- getTable 184 tbl <- getTable
@@ -209,7 +220,9 @@ main = do
209 ,("buckets", show $ R.shape tbl) 220 ,("buckets", show $ R.shape tbl)
210 ,("optBucketCount", show bc) 221 ,("optBucketCount", show bc)
211 ] 222 ]
212 snapshot >>= liftIO . B.writeFile "dht-nodes.dat"
213 223
214 waitForSignal 224 waitForSignal -- Await unix socket to signal termination.
225
226 snapshot >>= liftIO . B.writeFile "dht-nodes.dat"
227 savePeerStore >>= liftIO . B.writeFile "bt-peers.dat"
215 228
diff --git a/src/Network/BitTorrent/DHT/ContactInfo.hs b/src/Network/BitTorrent/DHT/ContactInfo.hs
index 4302288c..117325fc 100644
--- a/src/Network/BitTorrent/DHT/ContactInfo.hs
+++ b/src/Network/BitTorrent/DHT/ContactInfo.hs
@@ -15,6 +15,8 @@ import Data.Serialize
15import Data.PSQueue as PSQ 15import Data.PSQueue as PSQ
16import Data.Time.Clock.POSIX 16import Data.Time.Clock.POSIX
17import Data.ByteString (ByteString) 17import Data.ByteString (ByteString)
18import Data.Word
19import Network.Socket (SockAddr(..))
18 20
19import Data.Torrent 21import Data.Torrent
20import Network.BitTorrent.Address 22import Network.BitTorrent.Address
@@ -122,6 +124,40 @@ data SwarmData ip = SwarmData
122 , name :: Maybe ByteString 124 , name :: Maybe ByteString
123 } 125 }
124 126
127
128newtype SerializeAddress a = SerializeAddress { unserializeAddress :: a }
129
130instance Address a => Serialize (SerializeAddress a) where
131 get = SerializeAddress <$> do
132 c <- get
133 case (c::Word8) of
134 0x34 -> do ip4 <- get
135 return $ fromJust $ fromAddr (ip4::IPv4)
136 0x36 -> do ip6 <- get
137 return $ fromJust $ fromAddr (ip6::IPv6)
138 _ -> return $ error "cannot deserialize non-IP SerializeAddress"
139 put (SerializeAddress a)
140 | Just ip4 <- fromAddr a
141 = put (0x34::Word8) >> put (ip4::IPv4)
142 | Just ip6 <- fromAddr a
143 = put (0x36::Word8) >> put (ip6::IPv6)
144 | otherwise = return $ error "cannot serialize non-IP SerializeAddress"
145
146
147instance (Ord ip, Address ip) => Serialize (SwarmData ip) where
148 get = flip SwarmData <$> get
149 <*> ( PSQ.fromList . L.map parseAddr <$> get )
150 where
151 parseAddr addr = (unserializeAddress <$> addr)
152 :-> 0
153
154 put SwarmData{..} = do
155 put name
156 put $ L.map (\(addr :-> _) -> (SerializeAddress <$> addr))
157 -- XXX: should we serialize the timestamp?
158 $ PSQ.toList peers
159
160
125knownSwarms :: PeerStore ip -> [ (InfoHash, Int, Maybe ByteString) ] 161knownSwarms :: PeerStore ip -> [ (InfoHash, Int, Maybe ByteString) ]
126knownSwarms (PeerStore m) = L.map (\(ih,SwarmData q n) -> (ih,PSQ.size q,n)) $ HM.toList m 162knownSwarms (PeerStore m) = L.map (\(ih,SwarmData q n) -> (ih,PSQ.size q,n)) $ HM.toList m
127 163
@@ -136,26 +172,27 @@ swarmInsert old new = SwarmData
136 , name = name new <|> name old -- TODO: decodeUtf8' check 172 , name = name new <|> name old -- TODO: decodeUtf8' check
137 } 173 }
138 174
175isSwarmOccupied SwarmData{..} = not $ PSQ.null peers
139 176
140-- | Empty store. 177-- | Empty store.
141instance Default (PeerStore a) where 178instance Default (PeerStore a) where
142 def = PeerStore HM.empty 179 def = PeerStore HM.empty
143 {-# INLINE def #-} 180 {-# INLINE def #-}
144 181
145-- -- | Monoid under union operation. 182-- | Monoid under union operation.
146-- instance Eq a => Monoid (PeerStore a) where 183instance Ord a => Monoid (PeerStore a) where
147-- mempty = def 184 mempty = def
148-- {-# INLINE mempty #-} 185 {-# INLINE mempty #-}
149-- 186
150-- mappend (PeerStore a) (PeerStore b) = 187 mappend (PeerStore a) (PeerStore b) =
151-- PeerStore (HM.unionWith L.union a b) 188 PeerStore (HM.unionWith swarmInsert a b)
152-- {-# INLINE mappend #-} 189 {-# INLINE mappend #-}
153 190
154-- | Can be used to store peers between invocations of the client 191-- | Can be used to store peers between invocations of the client
155-- software. 192-- software.
156instance Serialize (PeerStore a) where 193instance (Ord a, Address a) => Serialize (PeerStore a) where
157 get = undefined 194 get = PeerStore . HM.fromList <$> get
158 put = undefined 195 put (PeerStore m) = put (L.filter (isSwarmOccupied . snd) $ HM.toList m)
159 196
160-- | Returns all peers associated with a given info hash. 197-- | Returns all peers associated with a given info hash.
161lookup :: Ord a => InfoHash -> PeerStore a -> [PeerAddr a] 198lookup :: Ord a => InfoHash -> PeerStore a -> [PeerAddr a]
diff --git a/src/Network/BitTorrent/DHT/Session.hs b/src/Network/BitTorrent/DHT/Session.hs
index bc9fda91..c08021c7 100644
--- a/src/Network/BitTorrent/DHT/Session.hs
+++ b/src/Network/BitTorrent/DHT/Session.hs
@@ -54,14 +54,16 @@ module Network.BitTorrent.DHT.Session
54 -- ** Routing table 54 -- ** Routing table
55 , getTable 55 , getTable
56 , getClosest 56 , getClosest
57 , getSwarms
58 , allPeers
59 57
60 -- ** Peer storage 58 -- ** Peer storage
61 , insertPeer 59 , insertPeer
62 , getPeerList 60 , getPeerList
63 , insertTopic 61 , insertTopic
64 , deleteTopic 62 , deleteTopic
63 , getSwarms
64 , savePeerStore
65 , mergeSavedPeers
66 , allPeers
65 67
66 -- ** Messaging 68 -- ** Messaging
67 , queryParallel 69 , queryParallel
@@ -84,6 +86,7 @@ import Data.Fixed
84import Data.Hashable 86import Data.Hashable
85import Data.List as L 87import Data.List as L
86import Data.Maybe 88import Data.Maybe
89import Data.Monoid
87import Data.Set as S 90import Data.Set as S
88import Data.Time 91import Data.Time
89import Network (PortNumber) 92import Network (PortNumber)
@@ -91,6 +94,7 @@ import System.Random (randomIO)
91import Data.Time.Clock.POSIX 94import Data.Time.Clock.POSIX
92import Data.Text as Text 95import Data.Text as Text
93import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) 96import Text.PrettyPrint.HughesPJClass hiding ((<>),($$))
97import Data.Serialize as S
94 98
95 99
96import Data.Torrent as Torrent 100import Data.Torrent as Torrent
@@ -409,6 +413,20 @@ getSwarms = do
409 store <- asks contactInfo >>= liftIO . atomically . readTVar 413 store <- asks contactInfo >>= liftIO . atomically . readTVar
410 return $ P.knownSwarms store 414 return $ P.knownSwarms store
411 415
416savePeerStore :: (Ord ip, Address ip) => DHT ip ByteString
417savePeerStore = do
418 var <- asks contactInfo
419 peers <- liftIO $ atomically $ readTVar var
420 return $ S.encode peers
421
422mergeSavedPeers :: (Ord ip, Address ip) => ByteString -> DHT ip ()
423mergeSavedPeers bs = do
424 var <- asks contactInfo
425 case S.decode bs of
426 Right newbies -> liftIO $ atomically $ modifyTVar' var (<> newbies)
427 Left _ -> return ()
428
429
412allPeers :: Ord ip => InfoHash -> DHT ip [ PeerAddr ip ] 430allPeers :: Ord ip => InfoHash -> DHT ip [ PeerAddr ip ]
413allPeers ih = do 431allPeers ih = do
414 store <- asks contactInfo >>= liftIO . atomically . readTVar 432 store <- asks contactInfo >>= liftIO . atomically . readTVar
diff --git a/src/Network/BitTorrent/Tracker/RPC/HTTP.hs b/src/Network/BitTorrent/Tracker/RPC/HTTP.hs
index de3fc5f5..bc52bddd 100644
--- a/src/Network/BitTorrent/Tracker/RPC/HTTP.hs
+++ b/src/Network/BitTorrent/Tracker/RPC/HTTP.hs
@@ -129,7 +129,7 @@ fillRequest Options {..} q r = r
129 129
130httpTracker :: BEncode a => Manager -> URI -> SimpleQuery -> IO a 130httpTracker :: BEncode a => Manager -> URI -> SimpleQuery -> IO a
131httpTracker Manager {..} uri q = packHttpException $ do 131httpTracker Manager {..} uri q = packHttpException $ do
132 request <- fillRequest options q <$> setUri def uri 132 request <- fillRequest options q <$> setUri def {- http-client instance for Request -} uri
133 response <- runResourceT $ httpLbs request httpMgr 133 response <- runResourceT $ httpLbs request httpMgr
134 case BE.decode $ BL.toStrict $ responseBody response of 134 case BE.decode $ BL.toStrict $ responseBody response of
135 Left msg -> throwIO (ParserFailure msg) 135 Left msg -> throwIO (ParserFailure msg)