diff options
-rw-r--r-- | src/Network/BitTorrent/Tracker/UDP.hs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Tracker/UDP.hs b/src/Network/BitTorrent/Tracker/UDP.hs new file mode 100644 index 00000000..369750bd --- /dev/null +++ b/src/Network/BitTorrent/Tracker/UDP.hs | |||
@@ -0,0 +1,155 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam T. 2013 | ||
3 | -- License : MIT | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- This module implement low-level UDP tracker protocol. | ||
9 | -- For more info see: http://www.bittorrent.org/beps/bep_0015.html | ||
10 | -- | ||
11 | {-# LANGUAGE RecordWildCards #-} | ||
12 | {-# LANGUAGE FlexibleInstances #-} | ||
13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
14 | module Network.BitTorrent.Tracker.UDP | ||
15 | ( Request(..), Response(..) | ||
16 | ) where | ||
17 | |||
18 | import Control.Applicative | ||
19 | import Control.Monad | ||
20 | import Data.Serialize | ||
21 | import Data.Word | ||
22 | import Data.Text | ||
23 | import Data.Text.Encoding | ||
24 | |||
25 | import Data.Torrent () | ||
26 | import Network.BitTorrent.Tracker.Protocol | ||
27 | |||
28 | |||
29 | -- | Connection Id is used for entire tracker session. | ||
30 | newtype ConnId = ConnId { getConnId :: Word64 } | ||
31 | deriving (Show, Serialize) | ||
32 | |||
33 | -- | Transaction Id is used for within UDP RPC. | ||
34 | newtype TransId = TransId { getTransId :: Word32 } | ||
35 | deriving (Show, Serialize) | ||
36 | |||
37 | genConnectionId :: IO ConnId | ||
38 | genConnectionId = return (ConnId 0) | ||
39 | |||
40 | genTransactionId :: IO TransId | ||
41 | genTransactionId = return (TransId 0) | ||
42 | |||
43 | data Request = Connect | ||
44 | | Announce AnnounceQuery | ||
45 | | Scrape ScrapeQuery | ||
46 | |||
47 | data Response = Connected | ||
48 | | Announced AnnounceInfo | ||
49 | | Scraped [ScrapeInfo] | ||
50 | | Failed Text | ||
51 | |||
52 | -- TODO rename to message? | ||
53 | data Transaction a = Transaction | ||
54 | { connId :: !ConnId | ||
55 | , transId :: !TransId | ||
56 | , body :: !a | ||
57 | } deriving Show | ||
58 | |||
59 | type MessageId = Word32 | ||
60 | |||
61 | connectId, announceId, scrapeId, errorId :: MessageId | ||
62 | connectId = 0 | ||
63 | announceId = 1 | ||
64 | scrapeId = 2 | ||
65 | errorId = 3 | ||
66 | |||
67 | instance Serialize (Transaction Request) where | ||
68 | put Transaction {..} = do | ||
69 | case body of | ||
70 | Connect -> do | ||
71 | put connId | ||
72 | put connectId | ||
73 | put transId | ||
74 | |||
75 | Announce query -> do | ||
76 | put connId | ||
77 | put announceId | ||
78 | put transId | ||
79 | put query | ||
80 | |||
81 | Scrape hashes -> do | ||
82 | put connId | ||
83 | put announceId | ||
84 | put transId | ||
85 | forM_ hashes put | ||
86 | |||
87 | get = do | ||
88 | cid <- get | ||
89 | rid <- getWord32be | ||
90 | tid <- get | ||
91 | bod <- getBody rid | ||
92 | |||
93 | return $ Transaction { | ||
94 | connId = cid | ||
95 | , transId = tid | ||
96 | , body = bod | ||
97 | } | ||
98 | where | ||
99 | getBody :: MessageId -> Get Request | ||
100 | getBody msgId | ||
101 | | msgId == connectId = return Connect | ||
102 | | msgId == announceId = Announce <$> get | ||
103 | | msgId == scrapeId = Scrape <$> many get | ||
104 | | otherwise = fail "unknown message id" | ||
105 | |||
106 | instance Serialize (Transaction Response) where | ||
107 | put Transaction {..} = do | ||
108 | case body of | ||
109 | Connected -> do | ||
110 | put connId | ||
111 | put connectId | ||
112 | put transId | ||
113 | |||
114 | Announced info -> do | ||
115 | put connId | ||
116 | put announceId | ||
117 | put transId | ||
118 | put info | ||
119 | |||
120 | Scraped infos -> do | ||
121 | put connId | ||
122 | put scrapeId | ||
123 | put transId | ||
124 | forM_ infos put | ||
125 | |||
126 | Failed info -> do | ||
127 | put connId | ||
128 | put errorId | ||
129 | put transId | ||
130 | put (encodeUtf8 info) | ||
131 | |||
132 | |||
133 | get = do | ||
134 | cid <- get | ||
135 | rid <- getWord32be | ||
136 | tid <- get | ||
137 | bod <- getBody rid | ||
138 | |||
139 | return $ Transaction { | ||
140 | connId = cid | ||
141 | , transId = tid | ||
142 | , body = bod | ||
143 | } | ||
144 | where | ||
145 | getBody :: MessageId -> Get Response | ||
146 | getBody msgId | ||
147 | | msgId == connectId = return $ Connected | ||
148 | | msgId == announceId = Announced <$> get | ||
149 | | msgId == scrapeId = Scraped <$> many get | ||
150 | | msgId == errorId = do | ||
151 | bs <- get | ||
152 | case decodeUtf8' bs of | ||
153 | Left ex -> fail (show ex) | ||
154 | Right msg -> return $ Failed msg | ||
155 | | otherwise = fail "unknown message id" | ||