summaryrefslogtreecommitdiff
path: root/bittorrent/src/Network/BitTorrent/Tracker/Session.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-09-15 06:22:10 -0400
committerjoe <joe@jerkface.net>2017-09-15 06:22:10 -0400
commit12cbb3af2413dc28838ed271351dda16df8f7bdb (patch)
tree2db77a787e18a81a8369a8d73fee369d8826f064 /bittorrent/src/Network/BitTorrent/Tracker/Session.hs
parent362357c6d08cbd8dffa627a1e80199dcb9ba231f (diff)
Separating dht-client library from bittorrent package.
Diffstat (limited to 'bittorrent/src/Network/BitTorrent/Tracker/Session.hs')
-rw-r--r--bittorrent/src/Network/BitTorrent/Tracker/Session.hs306
1 files changed, 306 insertions, 0 deletions
diff --git a/bittorrent/src/Network/BitTorrent/Tracker/Session.hs b/bittorrent/src/Network/BitTorrent/Tracker/Session.hs
new file mode 100644
index 00000000..aa4a832f
--- /dev/null
+++ b/bittorrent/src/Network/BitTorrent/Tracker/Session.hs
@@ -0,0 +1,306 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2014
3-- License : BSD
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- Multitracker sessions.
9--
10{-# LANGUAGE FlexibleInstances #-}
11{-# LANGUAGE TypeFamilies #-}
12{-# LANGUAGE TypeSynonymInstances #-}
13{-# LANGUAGE TemplateHaskell #-}
14module Network.BitTorrent.Tracker.Session
15 ( -- * Session
16 Session
17 , Event (..)
18 , newSession
19 , closeSession
20 , withSession
21
22 -- * Client send notifications
23 , notify
24 , askPeers
25
26 -- * Session state
27 -- ** Status
28 , Status (..)
29 , getStatus
30
31 -- ** Single tracker sessions
32 , LastScrape (..)
33 , TrackerSession
34 , trackerPeers
35 , trackerScrape
36 , getSessionState
37
38 -- * Tracker Exchange
39 -- | BEP28: <http://www.bittorrent.org/beps/bep_0028.html>
40 , addTracker
41 , removeTracker
42 , getTrustedTrackers
43 ) where
44
45import Control.Applicative
46import Control.Exception
47import Control.Concurrent
48import Control.Concurrent.Chan.Split as CS
49import Control.Monad
50import Data.Default
51import Data.Fixed
52import Data.Foldable as F
53import Data.IORef
54import Data.List as L
55import Data.Maybe
56import Data.Time
57import Data.Traversable
58import Network.URI
59
60import Data.Torrent
61import Network.Address
62import Network.BitTorrent.Internal.Cache
63import Network.BitTorrent.Internal.Types
64import Network.BitTorrent.Tracker.List as TL
65import Network.BitTorrent.Tracker.Message
66import Network.BitTorrent.Tracker.RPC as RPC
67
68{-----------------------------------------------------------------------
69-- Single tracker session
70-----------------------------------------------------------------------}
71
72-- | Status of this client.
73data Status
74 = Running -- ^ This client is announced and listenning for incoming
75 -- connections.
76 | Paused -- ^ This client does not expecting incoming connections.
77 deriving (Show, Eq, Bounded, Enum)
78
79-- | Client starting in the paused state.
80instance Default Status where
81 def = Paused
82
83-- | Tracker session starts with scrape unknown.
84instance Default LastScrape where
85 def = LastScrape Nothing Nothing
86
87data LastScrape = LastScrape
88 { -- | Count of leechers the tracker aware of.
89 scrapeLeechers :: Maybe Int
90
91 -- | Count of seeders the tracker aware of.
92 , scrapeSeeders :: Maybe Int
93 } deriving (Show, Eq)
94
95-- | Single tracker session.
96data TrackerSession = TrackerSession
97 { -- | Used to notify 'Stopped' and 'Completed' events.
98 statusSent :: !(Maybe Status)
99
100 -- | Can be used to retrieve peer set.
101 , trackerPeers :: Cached [PeerAddr IP]
102
103 -- | Can be used to show brief swarm stats in client GUI.
104 , trackerScrape :: Cached LastScrape
105 }
106
107-- | Not contacted.
108instance Default TrackerSession where
109 def = TrackerSession Nothing def def
110
111-- | Do we need to notify this /specific/ tracker?
112needNotify :: AnnounceEvent -> Maybe Status -> Maybe Bool
113needNotify Started Nothing = Just True
114needNotify Stopped Nothing = Just False
115needNotify Completed Nothing = Just False
116needNotify Started (Just Running) = Nothing
117needNotify Stopped (Just Running) = Just True
118needNotify Completed (Just Running) = Just True
119needNotify Started (Just Paused ) = Just True
120needNotify Stopped (Just Paused ) = Just False
121needNotify Completed (Just Paused ) = Just True
122
123-- | Client status after event announce succeed.
124nextStatus :: AnnounceEvent -> Maybe Status
125nextStatus Started = Just Running
126nextStatus Stopped = Just Paused
127nextStatus Completed = Nothing -- must keep previous status
128
129seconds :: Int -> NominalDiffTime
130seconds n = realToFrac (toEnum n :: Uni)
131
132cachePeers :: AnnounceInfo -> IO (Cached [PeerAddr IP])
133cachePeers AnnounceInfo {..} =
134 newCached (seconds respInterval)
135 (seconds (fromMaybe respInterval respMinInterval))
136 (getPeerList respPeers)
137
138cacheScrape :: AnnounceInfo -> IO (Cached LastScrape)
139cacheScrape AnnounceInfo {..} =
140 newCached (seconds respInterval)
141 (seconds (fromMaybe respInterval respMinInterval))
142 LastScrape
143 { scrapeSeeders = respComplete
144 , scrapeLeechers = respIncomplete
145 }
146
147-- | Make announce request to specific tracker returning new state.
148notifyTo :: Manager -> Session -> AnnounceEvent
149 -> TierEntry TrackerSession -> IO TrackerSession
150notifyTo mgr s @ Session {..} event (uri, entry @ TrackerSession {..}) = do
151 let shouldNotify = needNotify event statusSent
152 mustNotify <- maybe (isExpired trackerPeers) return shouldNotify
153 if not mustNotify
154 then return entry
155 else do
156 let q = SAnnounceQuery sessionTopic def Nothing (Just event)
157 res <- RPC.announce mgr uri q
158 when (statusSent == Nothing) $ do
159 send sessionEvents (TrackerConfirmed uri)
160 send sessionEvents (AnnouncedTo uri)
161 let status' = nextStatus event <|> statusSent
162 TrackerSession status' <$> cachePeers res <*> cacheScrape res
163
164{-----------------------------------------------------------------------
165-- Multitracker Session
166-----------------------------------------------------------------------}
167
168-- | Multitracker session.
169data Session = Session
170 { -- | Infohash to announce at each 'announce' request.
171 sessionTopic :: !InfoHash
172
173 -- | Current status of this client is used to filter duplicated
174 -- notifications, for e.g. we don't want to notify a tracker with
175 -- ['Stopped', 'Stopped'], the last should be ignored.
176 , sessionStatus :: !(IORef Status)
177
178 -- | A set of single-tracker sessions. Any request to a tracker
179 -- must take a lock.
180 , sessionTrackers :: !(MVar (TrackerList TrackerSession))
181
182 , sessionEvents :: !(SendPort (Event Session))
183 }
184
185instance EventSource Session where
186 data Event Session
187 = TrackerAdded URI
188 | TrackerConfirmed URI
189 | TrackerRemoved URI
190 | AnnouncedTo URI
191 | SessionClosed
192
193 listen Session {..} = CS.listen sessionEvents
194
195
196-- | Create a new multitracker session in paused state. Tracker list
197-- must contant only /trusted/ tracker uris. To start announcing
198-- client presence use 'notify'.
199newSession :: InfoHash -> TrackerList () -> IO Session
200newSession ih origUris = do
201 urisList <- shuffleTiers origUris
202 statusRef <- newIORef def
203 entriesVar <- newMVar (fmap (const def) urisList)
204 eventStream <- newSendPort
205 return Session
206 { sessionTopic = ih
207 , sessionStatus = statusRef
208 , sessionTrackers = entriesVar
209 , sessionEvents = eventStream
210 }
211
212-- | Release scarce resources associated with the given session. This
213-- function block until all trackers tied with this peer notified with
214-- 'Stopped' event.
215closeSession :: Manager -> Session -> IO ()
216closeSession m s @ Session {..} = do
217 notify m s Stopped
218 send sessionEvents SessionClosed
219
220{-----------------------------------------------------------------------
221-- Operations
222-----------------------------------------------------------------------}
223
224-- | Normally you need to use 'Control.Monad.Trans.Resource.alloc'.
225withSession :: Manager -> InfoHash -> TrackerList ()
226 -> (Session -> IO ()) -> IO ()
227withSession m ih uris = bracket (newSession ih uris) (closeSession m)
228
229-- | Get last announced status. The only action can alter this status
230-- is 'notify'.
231getStatus :: Session -> IO Status
232getStatus Session {..} = readIORef sessionStatus
233
234getSessionState :: Session -> IO [[TierEntry TrackerSession]]
235getSessionState Session {..} = TL.toList <$> readMVar sessionTrackers
236
237-- | Do we need to sent this event to a first working tracker or to
238-- the all known good trackers?
239allNotify :: AnnounceEvent -> Bool
240allNotify Started = False
241allNotify Stopped = True
242allNotify Completed = True
243
244notifyAll :: Manager -> Session -> AnnounceEvent -> IO ()
245notifyAll mgr s @ Session {..} event = do
246 modifyMVar_ sessionTrackers $
247 (traversal (notifyTo mgr s event))
248 where
249 traversal
250 | allNotify event = traverseAll
251 | otherwise = traverseTiers
252
253-- TODO send notifications to tracker periodically.
254-- |
255--
256-- This function /may/ block until tracker query proceed.
257notify :: Manager -> Session -> AnnounceEvent -> IO ()
258notify mgr ses event = do
259 prevStatus <- atomicModifyIORef (sessionStatus ses) $ \ s ->
260 (fromMaybe s (nextStatus event), s)
261 when (needNotify event (Just prevStatus) == Just True) $ do
262 notifyAll mgr ses event
263
264-- TODO run announce if sesion have no peers
265-- | The returned list of peers can have duplicates.
266-- This function /may/ block. Use async if needed.
267askPeers :: Manager -> Session -> IO [PeerAddr IP]
268askPeers _mgr ses = do
269 list <- readMVar (sessionTrackers ses)
270 L.concat <$> collect (tryTakeData . trackerPeers) list
271
272collect :: (a -> IO (Maybe b)) -> TrackerList a -> IO [b]
273collect f lst = (catMaybes . F.toList) <$> traverse f lst
274
275--sourcePeers :: Session -> Source (PeerAddr IP)
276--sourcePeers
277
278{-----------------------------------------------------------------------
279-- Tracker exchange
280-----------------------------------------------------------------------}
281
282-- Trackers discovered through this protocol SHOULD be treated with a
283-- certain amount of suspicion. Since the source of a tracker exchange
284-- message cannot be trusted, an implementation SHOULD have a lower
285-- number of retries before giving up entirely.
286
287addTracker :: Session -> URI -> IO ()
288addTracker Session {..} uri = do
289 undefined
290 send sessionEvents (TrackerAdded uri)
291
292removeTracker :: Manager -> Session -> URI -> IO ()
293removeTracker m Session {..} uri = do
294 send sessionEvents (TrackerRemoved uri)
295
296-- Also, as specified under the definitions section, a tracker that
297-- has not worked should never be propagated to other peers over the
298-- tracker exchange protocol.
299
300-- | Return all known trackers.
301getTrackers :: Session -> IO [URI]
302getTrackers = undefined
303
304-- | Return trackers from torrent file and
305getTrustedTrackers :: Session -> IO [URI]
306getTrustedTrackers = undefined