diff options
Diffstat (limited to 'src/Network/BitTorrent/Core/PeerId.hs')
-rw-r--r-- | src/Network/BitTorrent/Core/PeerId.hs | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Core/PeerId.hs b/src/Network/BitTorrent/Core/PeerId.hs new file mode 100644 index 00000000..a32aa990 --- /dev/null +++ b/src/Network/BitTorrent/Core/PeerId.hs | |||
@@ -0,0 +1,278 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam Truzjan 2013 | ||
3 | -- License : BSD3 | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- 'PeerID' represent self assigned peer identificator. Ideally each | ||
9 | -- host in the network should have unique peer id to avoid | ||
10 | -- collisions, therefore for peer ID generation we use good entropy | ||
11 | -- source. (FIX not really) Peer ID is sent in /tracker request/, | ||
12 | -- sent and received in /peer handshakes/ and used in /distributed | ||
13 | -- hash table/ queries. | ||
14 | -- | ||
15 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
16 | module Network.BitTorrent.Core.PeerId | ||
17 | ( -- * PeerId | ||
18 | PeerId (getPeerId) | ||
19 | , ppPeerId | ||
20 | |||
21 | -- * Generation | ||
22 | , genPeerId | ||
23 | , timestamp | ||
24 | , entropy | ||
25 | |||
26 | -- * Encoding | ||
27 | , azureusStyle | ||
28 | , shadowStyle | ||
29 | |||
30 | -- * Decoding | ||
31 | , clientInfo | ||
32 | |||
33 | -- ** Extra | ||
34 | , byteStringPadded | ||
35 | , defaultClientId | ||
36 | , defaultVersionNumber | ||
37 | ) where | ||
38 | |||
39 | import Control.Applicative | ||
40 | import Data.Aeson | ||
41 | import Data.BEncode as BE | ||
42 | import Data.ByteString as BS | ||
43 | import Data.ByteString.Char8 as BC | ||
44 | import qualified Data.ByteString.Lazy as BL | ||
45 | import qualified Data.ByteString.Lazy.Builder as BS | ||
46 | import Data.Default | ||
47 | import Data.Foldable (foldMap) | ||
48 | import Data.List as L | ||
49 | import Data.Maybe (fromMaybe) | ||
50 | import Data.Monoid | ||
51 | import Data.Serialize as S | ||
52 | import Data.Time.Clock (getCurrentTime) | ||
53 | import Data.Time.Format (formatTime) | ||
54 | import Data.URLEncoded | ||
55 | import Data.Version (Version(Version), versionBranch) | ||
56 | import System.Entropy (getEntropy) | ||
57 | import System.Locale (defaultTimeLocale) | ||
58 | import Text.PrettyPrint hiding ((<>)) | ||
59 | import Text.Read (readMaybe) | ||
60 | import Paths_bittorrent (version) | ||
61 | |||
62 | import Data.Torrent.Client | ||
63 | |||
64 | |||
65 | -- | Peer identifier is exactly 20 bytes long bytestring. | ||
66 | newtype PeerId = PeerId { getPeerId :: ByteString } | ||
67 | deriving (Show, Eq, Ord, BEncode, ToJSON, FromJSON) | ||
68 | |||
69 | instance Serialize PeerId where | ||
70 | put = putByteString . getPeerId | ||
71 | get = PeerId <$> getBytes 20 | ||
72 | |||
73 | instance URLShow PeerId where | ||
74 | urlShow = BC.unpack . getPeerId | ||
75 | |||
76 | -- | Format peer id in human readable form. | ||
77 | ppPeerId :: PeerId -> Doc | ||
78 | ppPeerId = text . BC.unpack . getPeerId | ||
79 | |||
80 | {----------------------------------------------------------------------- | ||
81 | -- Encoding | ||
82 | -----------------------------------------------------------------------} | ||
83 | |||
84 | -- | Pad bytestring so it's becomes exactly request length. Conversion | ||
85 | -- is done like so: | ||
86 | -- | ||
87 | -- * length < size: Complete bytestring by given charaters. | ||
88 | -- | ||
89 | -- * length = size: Output bytestring as is. | ||
90 | -- | ||
91 | -- * length > size: Drop last (length - size) charaters from a | ||
92 | -- given bytestring. | ||
93 | -- | ||
94 | byteStringPadded :: ByteString -- ^ bytestring to be padded. | ||
95 | -> Int -- ^ size of result builder. | ||
96 | -> Char -- ^ character used for padding. | ||
97 | -> BS.Builder | ||
98 | byteStringPadded bs s c = | ||
99 | BS.byteString (BS.take s bs) <> | ||
100 | BS.byteString (BC.replicate padLen c) | ||
101 | where | ||
102 | padLen = s - min (BS.length bs) s | ||
103 | |||
104 | -- | Azureus-style encoding have the following layout: | ||
105 | -- | ||
106 | -- * 1 byte : '-' | ||
107 | -- | ||
108 | -- * 2 bytes: client id | ||
109 | -- | ||
110 | -- * 4 bytes: version number | ||
111 | -- | ||
112 | -- * 1 byte : '-' | ||
113 | -- | ||
114 | -- * 12 bytes: random number | ||
115 | -- | ||
116 | azureusStyle :: ByteString -- ^ 2 character client ID, padded with 'H'. | ||
117 | -> ByteString -- ^ Version number, padded with 'X'. | ||
118 | -> ByteString -- ^ Random number, padded with '0'. | ||
119 | -> PeerId -- ^ Azureus-style encoded peer ID. | ||
120 | azureusStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $ | ||
121 | BS.char8 '-' <> | ||
122 | byteStringPadded cid 2 'H' <> | ||
123 | byteStringPadded ver 4 'X' <> | ||
124 | BS.char8 '-' <> | ||
125 | byteStringPadded rnd 12 '0' | ||
126 | |||
127 | -- | Shadow-style encoding have the following layout: | ||
128 | -- | ||
129 | -- * 1 byte : client id. | ||
130 | -- | ||
131 | -- * 0-4 bytes: version number. If less than 4 then padded with | ||
132 | -- '-' char. | ||
133 | -- | ||
134 | -- * 15 bytes : random number. If length is less than 15 then | ||
135 | -- padded with '0' char. | ||
136 | -- | ||
137 | shadowStyle :: Char -- ^ Client ID. | ||
138 | -> ByteString -- ^ Version number. | ||
139 | -> ByteString -- ^ Random number. | ||
140 | -> PeerId -- ^ Shadow style encoded peer ID. | ||
141 | shadowStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $ | ||
142 | BS.char8 cid <> | ||
143 | byteStringPadded ver 4 '-' <> | ||
144 | byteStringPadded rnd 15 '0' | ||
145 | |||
146 | |||
147 | -- | "HS" - 2 bytes long client identifier. | ||
148 | defaultClientId :: ByteString | ||
149 | defaultClientId = "HS" | ||
150 | |||
151 | -- | Gives exactly 4 bytes long version number for any version of the | ||
152 | -- package. Version is taken from .cabal. | ||
153 | defaultVersionNumber :: ByteString | ||
154 | defaultVersionNumber = BS.take 4 $ BC.pack $ foldMap show $ | ||
155 | versionBranch version | ||
156 | |||
157 | {----------------------------------------------------------------------- | ||
158 | -- Generation | ||
159 | -----------------------------------------------------------------------} | ||
160 | |||
161 | -- | Gives 15 characters long decimal timestamp such that: | ||
162 | -- | ||
163 | -- * 6 bytes : first 6 characters from picoseconds obtained with %q. | ||
164 | -- | ||
165 | -- * 1 bytes : character '.' for readability. | ||
166 | -- | ||
167 | -- * 9..* bytes: number of whole seconds since the Unix epoch | ||
168 | -- (!)REVERSED. | ||
169 | -- | ||
170 | -- Can be used both with shadow and azureus style encoding. This | ||
171 | -- format is used to make the ID's readable(for debugging) and more | ||
172 | -- or less random. | ||
173 | -- | ||
174 | timestamp :: IO ByteString | ||
175 | timestamp = (BC.pack . format) <$> getCurrentTime | ||
176 | where | ||
177 | format t = L.take 6 (formatTime defaultTimeLocale "%q" t) ++ "." ++ | ||
178 | L.take 9 (L.reverse (formatTime defaultTimeLocale "%s" t)) | ||
179 | |||
180 | -- | Gives 15 character long random bytestring. This is more robust | ||
181 | -- method for generation of random part of peer ID than timestamp. | ||
182 | entropy :: IO ByteString | ||
183 | entropy = getEntropy 15 | ||
184 | |||
185 | -- NOTE: entropy generates incorrrect peer id | ||
186 | |||
187 | -- | Here we use Azureus-style encoding with the following args: | ||
188 | -- | ||
189 | -- * 'HS' for the client id. | ||
190 | -- | ||
191 | -- * Version of the package for the version number | ||
192 | -- | ||
193 | -- * UTC time day ++ day time for the random number. | ||
194 | -- | ||
195 | genPeerId :: IO PeerId | ||
196 | genPeerId = azureusStyle defaultClientId defaultVersionNumber <$> timestamp | ||
197 | |||
198 | {----------------------------------------------------------------------- | ||
199 | -- Decoding | ||
200 | -----------------------------------------------------------------------} | ||
201 | |||
202 | parseImpl :: ByteString -> ClientImpl | ||
203 | parseImpl = f . BC.unpack | ||
204 | where | ||
205 | f "AG" = IAres | ||
206 | f "A~" = IAres | ||
207 | f "AR" = IArctic | ||
208 | f "AV" = IAvicora | ||
209 | f "AX" = IBitPump | ||
210 | f "AZ" = IAzureus | ||
211 | f "BB" = IBitBuddy | ||
212 | f "BC" = IBitComet | ||
213 | f "BF" = IBitflu | ||
214 | f "BG" = IBTG | ||
215 | f "BR" = IBitRocket | ||
216 | f "BS" = IBTSlave | ||
217 | f "BX" = IBittorrentX | ||
218 | f "CD" = IEnhancedCTorrent | ||
219 | f "CT" = ICTorrent | ||
220 | f "DE" = IDelugeTorrent | ||
221 | f "DP" = IPropagateDataClient | ||
222 | f "EB" = IEBit | ||
223 | f "ES" = IElectricSheep | ||
224 | f "FT" = IFoxTorrent | ||
225 | f "GS" = IGSTorrent | ||
226 | f "HL" = IHalite | ||
227 | f "HS" = IlibHSbittorrent | ||
228 | f "HN" = IHydranode | ||
229 | f "KG" = IKGet | ||
230 | f "KT" = IKTorrent | ||
231 | f "LH" = ILH_ABC | ||
232 | f "LP" = ILphant | ||
233 | f "LT" = ILibtorrent | ||
234 | f "lt" = ILibTorrent | ||
235 | f "LW" = ILimeWire | ||
236 | f "MO" = IMonoTorrent | ||
237 | f "MP" = IMooPolice | ||
238 | f "MR" = IMiro | ||
239 | f "MT" = IMoonlightTorrent | ||
240 | f "NX" = INetTransport | ||
241 | f "PD" = IPando | ||
242 | f "qB" = IqBittorrent | ||
243 | f "QD" = IQQDownload | ||
244 | f "QT" = IQt4TorrentExample | ||
245 | f "RT" = IRetriever | ||
246 | f "S~" = IShareaza | ||
247 | f "SB" = ISwiftbit | ||
248 | f "SS" = ISwarmScope | ||
249 | f "ST" = ISymTorrent | ||
250 | f "st" = Isharktorrent | ||
251 | f "SZ" = IShareaza | ||
252 | f "TN" = ITorrentDotNET | ||
253 | f "TR" = ITransmission | ||
254 | f "TS" = ITorrentstorm | ||
255 | f "TT" = ITuoTu | ||
256 | f "UL" = IuLeecher | ||
257 | f "UT" = IuTorrent | ||
258 | f "VG" = IVagaa | ||
259 | f "WT" = IBitLet | ||
260 | f "WY" = IFireTorrent | ||
261 | f "XL" = IXunlei | ||
262 | f "XT" = IXanTorrent | ||
263 | f "XX" = IXtorrent | ||
264 | f "ZT" = IZipTorrent | ||
265 | f _ = IUnknown | ||
266 | |||
267 | -- | Tries to extract meaningful information from peer ID bytes. If | ||
268 | -- peer id uses unknown coding style then client info returned is | ||
269 | -- 'def'. | ||
270 | -- | ||
271 | clientInfo :: PeerId -> ClientInfo | ||
272 | clientInfo pid = either (const def) id $ runGet getCI (getPeerId pid) | ||
273 | where -- TODO other styles | ||
274 | getCI = getWord8 >> ClientInfo <$> getClientImpl <*> getClientVersion | ||
275 | getClientImpl = parseImpl <$> getByteString 2 | ||
276 | getClientVersion = mkVer <$> getByteString 4 | ||
277 | where | ||
278 | mkVer bs = ClientVersion $ Version [fromMaybe 0 $ readMaybe $ BC.unpack bs] [] | ||