diff options
-rw-r--r-- | examples/dhtd.hs | 39 | ||||
-rw-r--r-- | src/Network/BitTorrent/DHT/ContactInfo.hs | 59 | ||||
-rw-r--r-- | src/Network/BitTorrent/DHT/Session.hs | 22 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/RPC/HTTP.hs | 2 |
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 | |||
18 | import Data.String | 18 | import Data.String |
19 | import qualified Data.ByteString as B (ByteString,writeFile,readFile) | 19 | import qualified Data.ByteString as B (ByteString,writeFile,readFile) |
20 | ; import Data.ByteString (ByteString) | 20 | ; import Data.ByteString (ByteString) |
21 | import qualified Data.ByteString.Char8 as B8 | ||
21 | import System.IO | 22 | import System.IO |
22 | import System.IO.Error | 23 | import System.IO.Error |
23 | import Text.PrettyPrint.HughesPJClass | 24 | import 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 | |||
15 | import Data.PSQueue as PSQ | 15 | import Data.PSQueue as PSQ |
16 | import Data.Time.Clock.POSIX | 16 | import Data.Time.Clock.POSIX |
17 | import Data.ByteString (ByteString) | 17 | import Data.ByteString (ByteString) |
18 | import Data.Word | ||
19 | import Network.Socket (SockAddr(..)) | ||
18 | 20 | ||
19 | import Data.Torrent | 21 | import Data.Torrent |
20 | import Network.BitTorrent.Address | 22 | import Network.BitTorrent.Address |
@@ -122,6 +124,40 @@ data SwarmData ip = SwarmData | |||
122 | , name :: Maybe ByteString | 124 | , name :: Maybe ByteString |
123 | } | 125 | } |
124 | 126 | ||
127 | |||
128 | newtype SerializeAddress a = SerializeAddress { unserializeAddress :: a } | ||
129 | |||
130 | instance 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 | |||
147 | instance (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 | |||
125 | knownSwarms :: PeerStore ip -> [ (InfoHash, Int, Maybe ByteString) ] | 161 | knownSwarms :: PeerStore ip -> [ (InfoHash, Int, Maybe ByteString) ] |
126 | knownSwarms (PeerStore m) = L.map (\(ih,SwarmData q n) -> (ih,PSQ.size q,n)) $ HM.toList m | 162 | knownSwarms (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 | ||
175 | isSwarmOccupied SwarmData{..} = not $ PSQ.null peers | ||
139 | 176 | ||
140 | -- | Empty store. | 177 | -- | Empty store. |
141 | instance Default (PeerStore a) where | 178 | instance 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 | 183 | instance 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. |
156 | instance Serialize (PeerStore a) where | 193 | instance (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. |
161 | lookup :: Ord a => InfoHash -> PeerStore a -> [PeerAddr a] | 198 | lookup :: 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 | |||
84 | import Data.Hashable | 86 | import Data.Hashable |
85 | import Data.List as L | 87 | import Data.List as L |
86 | import Data.Maybe | 88 | import Data.Maybe |
89 | import Data.Monoid | ||
87 | import Data.Set as S | 90 | import Data.Set as S |
88 | import Data.Time | 91 | import Data.Time |
89 | import Network (PortNumber) | 92 | import Network (PortNumber) |
@@ -91,6 +94,7 @@ import System.Random (randomIO) | |||
91 | import Data.Time.Clock.POSIX | 94 | import Data.Time.Clock.POSIX |
92 | import Data.Text as Text | 95 | import Data.Text as Text |
93 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) | 96 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) |
97 | import Data.Serialize as S | ||
94 | 98 | ||
95 | 99 | ||
96 | import Data.Torrent as Torrent | 100 | import 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 | ||
416 | savePeerStore :: (Ord ip, Address ip) => DHT ip ByteString | ||
417 | savePeerStore = do | ||
418 | var <- asks contactInfo | ||
419 | peers <- liftIO $ atomically $ readTVar var | ||
420 | return $ S.encode peers | ||
421 | |||
422 | mergeSavedPeers :: (Ord ip, Address ip) => ByteString -> DHT ip () | ||
423 | mergeSavedPeers 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 | |||
412 | allPeers :: Ord ip => InfoHash -> DHT ip [ PeerAddr ip ] | 430 | allPeers :: Ord ip => InfoHash -> DHT ip [ PeerAddr ip ] |
413 | allPeers ih = do | 431 | allPeers 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 | ||
130 | httpTracker :: BEncode a => Manager -> URI -> SimpleQuery -> IO a | 130 | httpTracker :: BEncode a => Manager -> URI -> SimpleQuery -> IO a |
131 | httpTracker Manager {..} uri q = packHttpException $ do | 131 | httpTracker 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) |