summaryrefslogtreecommitdiff
path: root/dht/src/Network/BitTorrent/Tracker/Transport.hs
blob: 5d225a7eb87b247b3c3ea4172106c679e384f53c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
{-# LANGUAGE TupleSections #-}
module Network.BitTorrent.Tracker.Transport
    ( implementTracker
    , Callbacks(..)
    , parseTracker
    , encodeTracker
    ) where

import Control.Concurrent.STM
import Crypto.Random
import Data.ByteString (ByteString)
import Data.Functor.Contravariant
import Data.IntMap.Strict (IntMap)
import qualified Data.Serialize as S
import Network.Socket (SockAddr)

import Data.QueryResponse.Table
import Data.TableMethods
import Network.Address
import Network.BitTorrent.Tracker.Message
import Network.QueryResponse as QR

-- | For use with 'layerTransport'.
parseTracker :: ByteString -> SockAddr -> Either String (Transaction Request, SockAddr)
parseTracker bs saddr = fmap (, saddr) $ S.decode bs

-- | For use with 'layerTransport'.
encodeTracker :: Transaction Response -> SockAddr -> (ByteString, SockAddr)
encodeTracker resp saddr = (S.encode resp,saddr)

type Handler = MethodHandlerA String TransactionId SockAddr (Transaction Request) (Transaction Response)

type Client = QR.ClientA String MessageId TransactionId SockAddr (Transaction Request) (Transaction Response)


-- | Handlers for inbound tracker requests.  It is safe to throw 'DropQuery'
-- from these, which should be done if the ConnectionId for an address does not
-- match the one issued by the 'callOnConnect' method.
data Callbacks = Callbacks
    { callOnConnect  :: SockAddr -> IO ConnectionId
    , callOnAnnounce :: SockAddr -> ConnectionId -> AnnounceQuery -> IO AnnounceInfo
    , callOnScrape   :: SockAddr -> ConnectionId -> ScrapeQuery -> IO [ScrapeEntry]
    }

handlers :: Callbacks -> MessageId -> Maybe Handler
handlers c ConnectId  = Just $ MethodHandler (const $ Right ())
                                             (\qid from to conid -> TransactionR qid $ Connected conid)
                                             (\from () -> callOnConnect c from)
handlers c AnnounceId = Just $ MethodHandler (\TransactionQ{connIdQ=cid,request=Announce qry} -> Right (cid,qry))
                                             (\qid from to info -> TransactionR qid $ Announced info)
                                             (uncurry . callOnAnnounce c)
handlers c ScrapeId   = Just $ MethodHandler (\TransactionQ{connIdQ=cid,request=Scrape qry} -> Right (cid,qry))
                                             (\qid from to entries -> TransactionR qid $ Scraped entries)
                                             (uncurry . callOnScrape c)
handlers c ErrorId    = Nothing
handlers c _          = Nothing

methodOfRequest :: Request -> MessageId
methodOfRequest Connect  {} = ConnectId
methodOfRequest Announce {} = AnnounceId
methodOfRequest Scrape   {} = ScrapeId

classify :: Transaction Request -> MessageClass String MessageId TransactionId SockAddr (Transaction Request)
classify qry = IsQuery (methodOfRequest $ request qry) (transIdQ qry)

dispatch :: DRG g =>
            Callbacks
            -> DispatchMethodsA
                        (g, IntMap (Maybe (Transaction Request) -> IO ()))
                        String
                        MessageId
                        TransactionId
                        SockAddr
                        (Transaction Request)
                        (Transaction Response)
dispatch c = DispatchMethods
    { classifyInbound = classify
    , lookupHandler   = handlers c
    , tableMethods    = transactionMethods (contramap fromEnum intMapMethods) genTransactionId
    }

implementTracker :: ErrorReporter SockAddr (Transaction Request) MessageId TransactionId String
                     -> TransportA String SockAddr (Transaction Request) (Transaction Response)
                     -> Callbacks
                     -> IO (TransportA String SockAddr (Transaction Request) (Transaction Response))
implementTracker err net c = do
    drg <- drgNew
    state <- newTVarIO (drg, mempty)
    let client = QR.Client
                    { clientNet           = net
                    , clientDispatcher    = dispatch c
                    , clientErrorReporter = err
                    , clientPending       = state
                    , clientAddress       = const $ return localhost4
                    , clientResponseId    = return
                    }
    return $ addHandler (\err -> return ()) (handleMessage client) net