diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Data/Torrent/Client.hs | 9 | ||||
-rw-r--r-- | src/Network/BitTorrent/Core.hs | 1 | ||||
-rw-r--r-- | src/Network/BitTorrent/Core/PeerId.hs | 3 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange.hs | 66 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Bus.hs | 63 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 2 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Session.hs | 147 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Server.hs | 4 |
8 files changed, 221 insertions, 74 deletions
diff --git a/src/Data/Torrent/Client.hs b/src/Data/Torrent/Client.hs index 6437cbbf..f21a5e92 100644 --- a/src/Data/Torrent/Client.hs +++ b/src/Data/Torrent/Client.hs | |||
@@ -26,13 +26,9 @@ module Data.Torrent.Client | |||
26 | , libClientInfo | 26 | , libClientInfo |
27 | ) where | 27 | ) where |
28 | 28 | ||
29 | import Control.Applicative | ||
30 | import Data.ByteString as BS | ||
31 | import Data.ByteString.Char8 as BC | ||
32 | import Data.Default | 29 | import Data.Default |
33 | import Data.List as L | 30 | import Data.List as L |
34 | import Data.List.Split as L | 31 | import Data.List.Split as L |
35 | import Data.Maybe | ||
36 | import Data.Monoid | 32 | import Data.Monoid |
37 | import Data.String | 33 | import Data.String |
38 | import Data.Text as T | 34 | import Data.Text as T |
@@ -40,8 +36,11 @@ import Data.Version | |||
40 | import Text.PrettyPrint hiding ((<>)) | 36 | import Text.PrettyPrint hiding ((<>)) |
41 | import Text.PrettyPrint.Class | 37 | import Text.PrettyPrint.Class |
42 | import Text.Read (readMaybe) | 38 | import Text.Read (readMaybe) |
43 | import Paths_bittorrent (version) | 39 | -- import Paths_bittorrent (version) |
44 | 40 | ||
41 | -- TODO FIXME | ||
42 | version :: Version | ||
43 | version = Version [0, 0, 0, 3] [] | ||
45 | 44 | ||
46 | -- | List of registered client versions + 'IlibHSbittorrent' (this | 45 | -- | List of registered client versions + 'IlibHSbittorrent' (this |
47 | -- package) + 'IUnknown' (for not recognized software). All names are | 46 | -- package) + 'IUnknown' (for not recognized software). All names are |
diff --git a/src/Network/BitTorrent/Core.hs b/src/Network/BitTorrent/Core.hs new file mode 100644 index 00000000..70fe83c3 --- /dev/null +++ b/src/Network/BitTorrent/Core.hs | |||
@@ -0,0 +1 @@ | |||
module Network.BitTorrent.Core () where \ No newline at end of file | |||
diff --git a/src/Network/BitTorrent/Core/PeerId.hs b/src/Network/BitTorrent/Core/PeerId.hs index 3f90347c..8bd175e5 100644 --- a/src/Network/BitTorrent/Core/PeerId.hs +++ b/src/Network/BitTorrent/Core/PeerId.hs | |||
@@ -58,7 +58,6 @@ import System.Locale (defaultTimeLocale) | |||
58 | import Text.PrettyPrint hiding ((<>)) | 58 | import Text.PrettyPrint hiding ((<>)) |
59 | import Text.PrettyPrint.Class | 59 | import Text.PrettyPrint.Class |
60 | import Text.Read (readMaybe) | 60 | import Text.Read (readMaybe) |
61 | import Paths_bittorrent (version) | ||
62 | 61 | ||
63 | import Data.Torrent.Client | 62 | import Data.Torrent.Client |
64 | 63 | ||
@@ -173,7 +172,7 @@ defaultClientId = "HS" | |||
173 | -- package. Version is taken from .cabal file. | 172 | -- package. Version is taken from .cabal file. |
174 | defaultVersionNumber :: ByteString | 173 | defaultVersionNumber :: ByteString |
175 | defaultVersionNumber = BS.take 4 $ BC.pack $ foldMap show $ | 174 | defaultVersionNumber = BS.take 4 $ BC.pack $ foldMap show $ |
176 | versionBranch version | 175 | versionBranch $ ciVersion libClientInfo |
177 | 176 | ||
178 | {----------------------------------------------------------------------- | 177 | {----------------------------------------------------------------------- |
179 | -- Generation | 178 | -- Generation |
diff --git a/src/Network/BitTorrent/Exchange.hs b/src/Network/BitTorrent/Exchange.hs index 74aeab94..57b2c81f 100644 --- a/src/Network/BitTorrent/Exchange.hs +++ b/src/Network/BitTorrent/Exchange.hs | |||
@@ -98,72 +98,6 @@ import Network.BitTorrent.Exchange.Protocol | |||
98 | import Network.BitTorrent.Sessions.Types | 98 | import Network.BitTorrent.Sessions.Types |
99 | import System.Torrent.Storage | 99 | import System.Torrent.Storage |
100 | 100 | ||
101 | {----------------------------------------------------------------------- | ||
102 | Peer wire | ||
103 | -----------------------------------------------------------------------} | ||
104 | |||
105 | type PeerWire = ConduitM Message Message IO | ||
106 | |||
107 | runPeerWire :: Socket -> PeerWire () -> IO () | ||
108 | runPeerWire sock action = | ||
109 | sourceSocket sock $= | ||
110 | S.conduitGet S.get $= | ||
111 | -- B.conduitDecode $= | ||
112 | action $= | ||
113 | S.conduitPut S.put $$ | ||
114 | -- B.conduitEncode $$ | ||
115 | sinkSocket sock | ||
116 | |||
117 | awaitMessage :: P2P Message | ||
118 | awaitMessage = P2P $ ReaderT $ const $ {-# SCC awaitMessage #-} go | ||
119 | where | ||
120 | go = await >>= maybe (monadThrow PeerDisconnected) return | ||
121 | {-# INLINE awaitMessage #-} | ||
122 | |||
123 | yieldMessage :: Message -> P2P () | ||
124 | yieldMessage msg = P2P $ ReaderT $ const $ {-# SCC yieldMessage #-} do | ||
125 | C.yield msg | ||
126 | {-# INLINE yieldMessage #-} | ||
127 | |||
128 | -- TODO send vectored | ||
129 | flushPending :: P2P () | ||
130 | flushPending = {-# SCC flushPending #-} do | ||
131 | session <- ask | ||
132 | queue <- liftIO (getPending session) | ||
133 | mapM_ yieldMessage queue | ||
134 | |||
135 | {----------------------------------------------------------------------- | ||
136 | P2P monad | ||
137 | -----------------------------------------------------------------------} | ||
138 | |||
139 | -- | | ||
140 | -- Exceptions: | ||
141 | -- | ||
142 | -- * SessionException: is visible only within one peer | ||
143 | -- session. Use this exception to terminate P2P session, but not | ||
144 | -- the swarm session. | ||
145 | -- | ||
146 | newtype P2P a = P2P { | ||
147 | unP2P :: ReaderT PeerSession PeerWire a | ||
148 | } deriving ( Functor, Applicative, Monad | ||
149 | , MonadIO, MonadThrow, MonadActive | ||
150 | , MonadReader PeerSession | ||
151 | ) | ||
152 | |||
153 | instance MonadState SessionState P2P where | ||
154 | {-# SPECIALIZE instance MonadState SessionState P2P #-} | ||
155 | get = asks sessionState >>= liftIO . readIORef | ||
156 | {-# INLINE get #-} | ||
157 | put !s = asks sessionState >>= \ref -> liftIO $ writeIORef ref s | ||
158 | {-# INLINE put #-} | ||
159 | |||
160 | runP2P :: (Socket, PeerSession) -> P2P () -> IO () | ||
161 | runP2P (sock, ses) action = | ||
162 | handle isIOException $ | ||
163 | runPeerWire sock (runReaderT (unP2P action) ses) | ||
164 | where | ||
165 | isIOException :: IOException -> IO () | ||
166 | isIOException _ = return () | ||
167 | 101 | ||
168 | {----------------------------------------------------------------------- | 102 | {----------------------------------------------------------------------- |
169 | Exceptions | 103 | Exceptions |
diff --git a/src/Network/BitTorrent/Exchange/Bus.hs b/src/Network/BitTorrent/Exchange/Bus.hs new file mode 100644 index 00000000..4800c4a0 --- /dev/null +++ b/src/Network/BitTorrent/Exchange/Bus.hs | |||
@@ -0,0 +1,63 @@ | |||
1 | module Network.BitTorrent.Exchange.Bus ( ) where | ||
2 | |||
3 | type PeerWire = ConduitM Message Message IO | ||
4 | |||
5 | runPeerWire :: Socket -> PeerWire () -> IO () | ||
6 | runPeerWire sock action = | ||
7 | sourceSocket sock $= | ||
8 | S.conduitGet S.get $= | ||
9 | -- B.conduitDecode $= | ||
10 | action $= | ||
11 | S.conduitPut S.put $$ | ||
12 | -- B.conduitEncode $$ | ||
13 | sinkSocket sock | ||
14 | |||
15 | awaitMessage :: P2P Message | ||
16 | awaitMessage = P2P $ ReaderT $ const $ {-# SCC awaitMessage #-} go | ||
17 | where | ||
18 | go = await >>= maybe (monadThrow PeerDisconnected) return | ||
19 | {-# INLINE awaitMessage #-} | ||
20 | |||
21 | yieldMessage :: Message -> P2P () | ||
22 | yieldMessage msg = P2P $ ReaderT $ const $ {-# SCC yieldMessage #-} do | ||
23 | C.yield msg | ||
24 | {-# INLINE yieldMessage #-} | ||
25 | |||
26 | -- TODO send vectored | ||
27 | flushPending :: P2P () | ||
28 | flushPending = {-# SCC flushPending #-} do | ||
29 | session <- ask | ||
30 | queue <- liftIO (getPending session) | ||
31 | mapM_ yieldMessage queue | ||
32 | |||
33 | {----------------------------------------------------------------------- | ||
34 | P2P monad | ||
35 | -----------------------------------------------------------------------} | ||
36 | |||
37 | -- | | ||
38 | -- Exceptions: | ||
39 | -- | ||
40 | -- * SessionException: is visible only within one peer | ||
41 | -- session. Use this exception to terminate P2P session, but not | ||
42 | -- the swarm session. | ||
43 | -- | ||
44 | newtype P2P a = P2P { | ||
45 | unP2P :: ReaderT PeerSession PeerWire a | ||
46 | } deriving ( Functor, Applicative, Monad | ||
47 | , MonadIO, MonadThrow, MonadActive | ||
48 | , MonadReader PeerSession | ||
49 | ) | ||
50 | |||
51 | instance MonadState SessionState P2P where | ||
52 | get = asks sessionState >>= liftIO . readIORef | ||
53 | {-# INLINE get #-} | ||
54 | put !s = asks sessionState >>= \ref -> liftIO $ writeIORef ref s | ||
55 | {-# INLINE put #-} | ||
56 | |||
57 | runP2P :: (Socket, PeerSession) -> P2P () -> IO () | ||
58 | runP2P (sock, ses) action = | ||
59 | handle isIOException $ | ||
60 | runPeerWire sock (runReaderT (unP2P action) ses) | ||
61 | where | ||
62 | isIOException :: IOException -> IO () | ||
63 | isIOException _ = return () | ||
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs index 4ef7baf3..4d4a97e2 100644 --- a/src/Network/BitTorrent/Exchange/Message.hs +++ b/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -27,7 +27,7 @@ | |||
27 | -- | 27 | -- |
28 | {-# LANGUAGE TemplateHaskell #-} | 28 | {-# LANGUAGE TemplateHaskell #-} |
29 | {-# OPTIONS -fno-warn-orphans #-} | 29 | {-# OPTIONS -fno-warn-orphans #-} |
30 | module Network.BitTorrent.Exchange.Protocol | 30 | module Network.BitTorrent.Exchange.Message |
31 | ( -- * Initial handshake | 31 | ( -- * Initial handshake |
32 | Handshake(..) | 32 | Handshake(..) |
33 | , handshake | 33 | , handshake |
diff --git a/src/Network/BitTorrent/Exchange/Session.hs b/src/Network/BitTorrent/Exchange/Session.hs new file mode 100644 index 00000000..ffc7816e --- /dev/null +++ b/src/Network/BitTorrent/Exchange/Session.hs | |||
@@ -0,0 +1,147 @@ | |||
1 | {-# LANGUAGE TemplateHaskell #-} | ||
2 | {-# LANGUAGE DeriveDataTypeable #-} | ||
3 | module Network.BitTorrent.Exchange.Session | ||
4 | ( | ||
5 | ) where | ||
6 | |||
7 | import Control.Concurrent.STM | ||
8 | import Control.Exception | ||
9 | import Control.Lens | ||
10 | import Data.Function | ||
11 | import Data.IORef | ||
12 | import Data.Ord | ||
13 | import Data.Typeable | ||
14 | import Text.PrettyPrint | ||
15 | |||
16 | import Data.Torrent.Bitfield | ||
17 | import Data.Torrent.InfoHash | ||
18 | import Network.BitTorrent.Core.PeerAddr | ||
19 | import Network.BitTorrent.Exchange.Message | ||
20 | import Network.BitTorrent.Exchange.Status | ||
21 | |||
22 | |||
23 | type Extension = () | ||
24 | |||
25 | -- | Peer session contain all data necessary for peer to peer | ||
26 | -- communication. | ||
27 | data ExchangeSession = ExchangeSession | ||
28 | { -- | Used as unique identifier of the session. | ||
29 | connectedPeerAddr :: !PeerAddr | ||
30 | |||
31 | -- | Extensions such that both peer and client support. | ||
32 | , enabledExtensions :: [Extension] | ||
33 | |||
34 | -- | Broadcast messages waiting to be sent to peer. | ||
35 | , pendingMessages :: !(TChan Message) | ||
36 | |||
37 | -- | Dymanic P2P data. | ||
38 | , sessionState :: !(IORef SessionState) | ||
39 | } | ||
40 | |||
41 | instance Eq ExchangeSession where | ||
42 | (==) = (==) `on` connectedPeerAddr | ||
43 | {-# INLINE (==) #-} | ||
44 | |||
45 | instance Ord ExchangeSession where | ||
46 | compare = comparing connectedPeerAddr | ||
47 | {-# INLINE compare #-} | ||
48 | |||
49 | enqueueBroadcast :: ExchangeSession -> Message -> IO () | ||
50 | enqueueBroadcast = undefined | ||
51 | |||
52 | dequeueBroadcast :: ExchangeSession -> IO Message | ||
53 | dequeueBroadcast = undefined | ||
54 | |||
55 | {----------------------------------------------------------------------- | ||
56 | -- Session state | ||
57 | -----------------------------------------------------------------------} | ||
58 | |||
59 | data SessionState = SessionState | ||
60 | { _bitfield :: !Bitfield -- ^ Other peer Have bitfield. | ||
61 | , _status :: !SessionStatus -- ^ Status of both peers. | ||
62 | } deriving (Show, Eq) | ||
63 | |||
64 | $(makeLenses ''SessionState) | ||
65 | |||
66 | --initialSessionState :: PieceCount -> SessionState | ||
67 | --initialSessionState pc = SessionState (haveNone pc) def | ||
68 | |||
69 | --getSessionState :: PeerSession -> IO SessionState | ||
70 | --getSessionState PeerSession {..} = readIORef sessionState | ||
71 | |||
72 | {----------------------------------------------------------------------- | ||
73 | -- Exceptions | ||
74 | -----------------------------------------------------------------------} | ||
75 | |||
76 | -- | Exceptions used to interrupt the current P2P session. This | ||
77 | -- exceptions will NOT affect other P2P sessions, DHT, peer <-> | ||
78 | -- tracker, or any other session. | ||
79 | -- | ||
80 | data ExchangeFailure | ||
81 | = PeerDisconnected | ||
82 | | ProtocolError Doc | ||
83 | | UnknownTorrent InfoHash | ||
84 | deriving (Show, Typeable) | ||
85 | |||
86 | instance Exception ExchangeFailure | ||
87 | |||
88 | -- | Do nothing with exception, used with 'handle' or 'try'. | ||
89 | isSessionException :: Monad m => ExchangeFailure -> m () | ||
90 | isSessionException _ = return () | ||
91 | |||
92 | -- | The same as 'isSessionException' but output to stdout the catched | ||
93 | -- exception, for debugging purposes only. | ||
94 | putSessionException :: ExchangeFailure -> IO () | ||
95 | putSessionException = print | ||
96 | {- | ||
97 | {----------------------------------------------------------------------- | ||
98 | -- Broadcasting: Have, Cancel, Bitfield, SuggestPiece | ||
99 | -----------------------------------------------------------------------} | ||
100 | {- | ||
101 | Here we should enqueue broadcast messages and keep in mind that: | ||
102 | * We should enqueue broadcast events as they are appear. | ||
103 | * We should yield broadcast messages as fast as we get them. | ||
104 | |||
105 | these 2 phases might differ in time significantly | ||
106 | |||
107 | **TODO**: do this; but only when it'll be clean which other broadcast | ||
108 | messages & events we should send. | ||
109 | |||
110 | 1. Update client have bitfield --\____ in one transaction; | ||
111 | 2. Update downloaded stats --/ | ||
112 | 3. Signal to the all other peer about this. | ||
113 | -} | ||
114 | |||
115 | available :: Bitfield -> SwarmSession -> STM () | ||
116 | available bf SwarmSession {..} = {-# SCC available #-} do | ||
117 | updateProgress >> broadcast | ||
118 | where | ||
119 | updateProgress = do | ||
120 | let piLen = ciPieceLength $ tInfo $ torrentMeta | ||
121 | let bytes = piLen * BF.haveCount bf | ||
122 | modifyTVar' (currentProgress clientSession) (downloadedProgress bytes) | ||
123 | |||
124 | broadcast = mapM_ (writeTChan broadcastMessages . Have) (BF.toList bf) | ||
125 | |||
126 | -- TODO compute size of messages: if it's faster to send Bitfield | ||
127 | -- instead many Have do that | ||
128 | |||
129 | -- Also if there is single Have message in queue then the | ||
130 | -- corresponding piece is likely still in memory or disc cache, | ||
131 | -- when we can send SuggestPiece. | ||
132 | |||
133 | readAvail :: TChan a -> STM [a] | ||
134 | readAvail chan = do | ||
135 | m <- tryReadTChan chan | ||
136 | case m of | ||
137 | Just a -> (:) <$> pure a <*> readAvail chan | ||
138 | Nothing -> return [] | ||
139 | |||
140 | -- | Get pending messages queue appeared in result of asynchronously | ||
141 | -- changed client state. Resulting queue should be sent to a peer | ||
142 | -- immediately. | ||
143 | -- | ||
144 | getPending :: PeerSession -> IO [Message] | ||
145 | getPending PeerSession {..} = {-# SCC getPending #-} do | ||
146 | atomically (readAvail pendingMessages) | ||
147 | -} \ No newline at end of file | ||
diff --git a/src/Network/BitTorrent/Tracker/Server.hs b/src/Network/BitTorrent/Tracker/Server.hs new file mode 100644 index 00000000..4ed19588 --- /dev/null +++ b/src/Network/BitTorrent/Tracker/Server.hs | |||
@@ -0,0 +1,4 @@ | |||
1 | module Network.BitTorrent.Tracker.Server | ||
2 | ( | ||
3 | ) where | ||
4 | |||