diff options
Diffstat (limited to 'src/Network/BitTorrent/Core/PeerId.hs')
-rw-r--r-- | src/Network/BitTorrent/Core/PeerId.hs | 364 |
1 files changed, 0 insertions, 364 deletions
diff --git a/src/Network/BitTorrent/Core/PeerId.hs b/src/Network/BitTorrent/Core/PeerId.hs deleted file mode 100644 index a180ff30..00000000 --- a/src/Network/BitTorrent/Core/PeerId.hs +++ /dev/null | |||
@@ -1,364 +0,0 @@ | |||
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. Peer ID is sent in /tracker request/, sent and received in | ||
12 | -- /peer handshakes/ and used in DHT queries. | ||
13 | -- | ||
14 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
15 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
16 | {-# LANGUAGE DeriveDataTypeable #-} | ||
17 | module Network.BitTorrent.Core.PeerId | ||
18 | ( -- * PeerId | ||
19 | PeerId | ||
20 | |||
21 | -- * Generation | ||
22 | , genPeerId | ||
23 | , timestamp | ||
24 | , entropy | ||
25 | |||
26 | -- * Encoding | ||
27 | , azureusStyle | ||
28 | , shadowStyle | ||
29 | , defaultClientId | ||
30 | , defaultVersionNumber | ||
31 | |||
32 | -- * Decoding | ||
33 | , fingerprint | ||
34 | ) where | ||
35 | |||
36 | import Control.Applicative | ||
37 | import Data.BEncode as BE | ||
38 | import Data.ByteString as BS | ||
39 | import Data.ByteString.Internal as BS | ||
40 | import Data.ByteString.Char8 as BC | ||
41 | import qualified Data.ByteString.Lazy as BL | ||
42 | import qualified Data.ByteString.Lazy.Builder as BS | ||
43 | import Data.Convertible | ||
44 | import Data.Default | ||
45 | import Data.Foldable (foldMap) | ||
46 | import Data.List as L | ||
47 | import Data.List.Split as L | ||
48 | import Data.Maybe (fromMaybe, catMaybes) | ||
49 | import Data.Monoid | ||
50 | import Data.Hashable | ||
51 | import Data.Serialize as S | ||
52 | import Data.String | ||
53 | import Data.Time.Clock (getCurrentTime) | ||
54 | import Data.Time.Format (formatTime) | ||
55 | import Data.Typeable | ||
56 | import Data.Version (Version(Version), versionBranch) | ||
57 | import Network.HTTP.Types.QueryLike | ||
58 | import System.Entropy (getEntropy) | ||
59 | import System.Locale (defaultTimeLocale) | ||
60 | import Text.PrettyPrint hiding ((<>)) | ||
61 | import Text.PrettyPrint.Class | ||
62 | import Text.Read (readMaybe) | ||
63 | |||
64 | import Network.BitTorrent.Core.Fingerprint | ||
65 | |||
66 | -- TODO use unpacked Word160 form (length is known statically) | ||
67 | |||
68 | -- | Peer identifier is exactly 20 bytes long bytestring. | ||
69 | newtype PeerId = PeerId { getPeerId :: ByteString } | ||
70 | deriving (Show, Eq, Ord, BEncode, Typeable) | ||
71 | |||
72 | peerIdLen :: Int | ||
73 | peerIdLen = 20 | ||
74 | |||
75 | -- | For testing purposes only. | ||
76 | instance Default PeerId where | ||
77 | def = azureusStyle defaultClientId defaultVersionNumber "" | ||
78 | |||
79 | instance Hashable PeerId where | ||
80 | hashWithSalt = hashUsing getPeerId | ||
81 | {-# INLINE hashWithSalt #-} | ||
82 | |||
83 | instance Serialize PeerId where | ||
84 | put = putByteString . getPeerId | ||
85 | get = PeerId <$> getBytes peerIdLen | ||
86 | |||
87 | instance QueryValueLike PeerId where | ||
88 | toQueryValue (PeerId pid) = Just pid | ||
89 | {-# INLINE toQueryValue #-} | ||
90 | |||
91 | instance IsString PeerId where | ||
92 | fromString str | ||
93 | | BS.length bs == peerIdLen = PeerId bs | ||
94 | | otherwise = error $ "Peer id should be 20 bytes long: " ++ show str | ||
95 | where | ||
96 | bs = fromString str | ||
97 | |||
98 | instance Pretty PeerId where | ||
99 | pretty = text . BC.unpack . getPeerId | ||
100 | |||
101 | instance Convertible BS.ByteString PeerId where | ||
102 | safeConvert bs | ||
103 | | BS.length bs == peerIdLen = pure (PeerId bs) | ||
104 | | otherwise = convError "invalid length" bs | ||
105 | |||
106 | {----------------------------------------------------------------------- | ||
107 | -- Encoding | ||
108 | -----------------------------------------------------------------------} | ||
109 | |||
110 | -- | Pad bytestring so it's becomes exactly request length. Conversion | ||
111 | -- is done like so: | ||
112 | -- | ||
113 | -- * length < size: Complete bytestring by given charaters. | ||
114 | -- | ||
115 | -- * length = size: Output bytestring as is. | ||
116 | -- | ||
117 | -- * length > size: Drop last (length - size) charaters from a | ||
118 | -- given bytestring. | ||
119 | -- | ||
120 | byteStringPadded :: ByteString -- ^ bytestring to be padded. | ||
121 | -> Int -- ^ size of result builder. | ||
122 | -> Char -- ^ character used for padding. | ||
123 | -> BS.Builder | ||
124 | byteStringPadded bs s c = | ||
125 | BS.byteString (BS.take s bs) <> | ||
126 | BS.byteString (BC.replicate padLen c) | ||
127 | where | ||
128 | padLen = s - min (BS.length bs) s | ||
129 | |||
130 | -- | Azureus-style encoding have the following layout: | ||
131 | -- | ||
132 | -- * 1 byte : '-' | ||
133 | -- | ||
134 | -- * 2 bytes: client id | ||
135 | -- | ||
136 | -- * 4 bytes: version number | ||
137 | -- | ||
138 | -- * 1 byte : '-' | ||
139 | -- | ||
140 | -- * 12 bytes: random number | ||
141 | -- | ||
142 | azureusStyle :: ByteString -- ^ 2 character client ID, padded with 'H'. | ||
143 | -> ByteString -- ^ Version number, padded with 'X'. | ||
144 | -> ByteString -- ^ Random number, padded with '0'. | ||
145 | -> PeerId -- ^ Azureus-style encoded peer ID. | ||
146 | azureusStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $ | ||
147 | BS.char8 '-' <> | ||
148 | byteStringPadded cid 2 'H' <> | ||
149 | byteStringPadded ver 4 'X' <> | ||
150 | BS.char8 '-' <> | ||
151 | byteStringPadded rnd 12 '0' | ||
152 | |||
153 | -- | Shadow-style encoding have the following layout: | ||
154 | -- | ||
155 | -- * 1 byte : client id. | ||
156 | -- | ||
157 | -- * 0-4 bytes: version number. If less than 4 then padded with | ||
158 | -- '-' char. | ||
159 | -- | ||
160 | -- * 15 bytes : random number. If length is less than 15 then | ||
161 | -- padded with '0' char. | ||
162 | -- | ||
163 | shadowStyle :: Char -- ^ Client ID. | ||
164 | -> ByteString -- ^ Version number. | ||
165 | -> ByteString -- ^ Random number. | ||
166 | -> PeerId -- ^ Shadow style encoded peer ID. | ||
167 | shadowStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $ | ||
168 | BS.char8 cid <> | ||
169 | byteStringPadded ver 4 '-' <> | ||
170 | byteStringPadded rnd 15 '0' | ||
171 | |||
172 | |||
173 | -- | 'HS'- 2 bytes long client identifier. | ||
174 | defaultClientId :: ByteString | ||
175 | defaultClientId = "HS" | ||
176 | |||
177 | -- | Gives exactly 4 bytes long version number for any version of the | ||
178 | -- package. Version is taken from .cabal file. | ||
179 | defaultVersionNumber :: ByteString | ||
180 | defaultVersionNumber = BS.take 4 $ BC.pack $ foldMap show $ | ||
181 | versionBranch $ ciVersion libFingerprint | ||
182 | |||
183 | {----------------------------------------------------------------------- | ||
184 | -- Generation | ||
185 | -----------------------------------------------------------------------} | ||
186 | |||
187 | -- | Gives 15 characters long decimal timestamp such that: | ||
188 | -- | ||
189 | -- * 6 bytes : first 6 characters from picoseconds obtained with %q. | ||
190 | -- | ||
191 | -- * 1 byte : character \'.\' for readability. | ||
192 | -- | ||
193 | -- * 9..* bytes: number of whole seconds since the Unix epoch | ||
194 | -- (!)REVERSED. | ||
195 | -- | ||
196 | -- Can be used both with shadow and azureus style encoding. This | ||
197 | -- format is used to make the ID's readable for debugging purposes. | ||
198 | -- | ||
199 | timestamp :: IO ByteString | ||
200 | timestamp = (BC.pack . format) <$> getCurrentTime | ||
201 | where | ||
202 | format t = L.take 6 (formatTime defaultTimeLocale "%q" t) ++ "." ++ | ||
203 | L.take 9 (L.reverse (formatTime defaultTimeLocale "%s" t)) | ||
204 | |||
205 | -- | Gives 15 character long random bytestring. This is more robust | ||
206 | -- method for generation of random part of peer ID than 'timestamp'. | ||
207 | entropy :: IO ByteString | ||
208 | entropy = getEntropy 15 | ||
209 | |||
210 | -- NOTE: entropy generates incorrrect peer id | ||
211 | |||
212 | -- | Here we use 'azureusStyle' encoding with the following args: | ||
213 | -- | ||
214 | -- * 'HS' for the client id; ('defaultClientId') | ||
215 | -- | ||
216 | -- * Version of the package for the version number; | ||
217 | -- ('defaultVersionNumber') | ||
218 | -- | ||
219 | -- * UTC time day ++ day time for the random number. ('timestamp') | ||
220 | -- | ||
221 | genPeerId :: IO PeerId | ||
222 | genPeerId = azureusStyle defaultClientId defaultVersionNumber <$> timestamp | ||
223 | |||
224 | {----------------------------------------------------------------------- | ||
225 | -- Decoding | ||
226 | -----------------------------------------------------------------------} | ||
227 | |||
228 | parseImpl :: ByteString -> ClientImpl | ||
229 | parseImpl = f . BC.unpack | ||
230 | where | ||
231 | f "AG" = IAres | ||
232 | f "A~" = IAres | ||
233 | f "AR" = IArctic | ||
234 | f "AV" = IAvicora | ||
235 | f "AX" = IBitPump | ||
236 | f "AZ" = IAzureus | ||
237 | f "BB" = IBitBuddy | ||
238 | f "BC" = IBitComet | ||
239 | f "BF" = IBitflu | ||
240 | f "BG" = IBTG | ||
241 | f "BR" = IBitRocket | ||
242 | f "BS" = IBTSlave | ||
243 | f "BX" = IBittorrentX | ||
244 | f "CD" = IEnhancedCTorrent | ||
245 | f "CT" = ICTorrent | ||
246 | f "DE" = IDelugeTorrent | ||
247 | f "DP" = IPropagateDataClient | ||
248 | f "EB" = IEBit | ||
249 | f "ES" = IElectricSheep | ||
250 | f "FT" = IFoxTorrent | ||
251 | f "GS" = IGSTorrent | ||
252 | f "HL" = IHalite | ||
253 | f "HS" = IlibHSbittorrent | ||
254 | f "HN" = IHydranode | ||
255 | f "KG" = IKGet | ||
256 | f "KT" = IKTorrent | ||
257 | f "LH" = ILH_ABC | ||
258 | f "LP" = ILphant | ||
259 | f "LT" = ILibtorrent | ||
260 | f "lt" = ILibTorrent | ||
261 | f "LW" = ILimeWire | ||
262 | f "MO" = IMonoTorrent | ||
263 | f "MP" = IMooPolice | ||
264 | f "MR" = IMiro | ||
265 | f "ML" = IMLdonkey | ||
266 | f "MT" = IMoonlightTorrent | ||
267 | f "NX" = INetTransport | ||
268 | f "PD" = IPando | ||
269 | f "qB" = IqBittorrent | ||
270 | f "QD" = IQQDownload | ||
271 | f "QT" = IQt4TorrentExample | ||
272 | f "RT" = IRetriever | ||
273 | f "S~" = IShareaza | ||
274 | f "SB" = ISwiftbit | ||
275 | f "SS" = ISwarmScope | ||
276 | f "ST" = ISymTorrent | ||
277 | f "st" = Isharktorrent | ||
278 | f "SZ" = IShareaza | ||
279 | f "TN" = ITorrentDotNET | ||
280 | f "TR" = ITransmission | ||
281 | f "TS" = ITorrentstorm | ||
282 | f "TT" = ITuoTu | ||
283 | f "UL" = IuLeecher | ||
284 | f "UT" = IuTorrent | ||
285 | f "VG" = IVagaa | ||
286 | f "WT" = IBitLet | ||
287 | f "WY" = IFireTorrent | ||
288 | f "XL" = IXunlei | ||
289 | f "XT" = IXanTorrent | ||
290 | f "XX" = IXtorrent | ||
291 | f "ZT" = IZipTorrent | ||
292 | f _ = IUnknown | ||
293 | |||
294 | -- TODO use regexps | ||
295 | |||
296 | -- | Tries to extract meaningful information from peer ID bytes. If | ||
297 | -- peer id uses unknown coding style then client info returned is | ||
298 | -- 'def'. | ||
299 | -- | ||
300 | fingerprint :: PeerId -> Fingerprint | ||
301 | fingerprint pid = either (const def) id $ runGet getCI (getPeerId pid) | ||
302 | where | ||
303 | getCI = do | ||
304 | leading <- BS.w2c <$> getWord8 | ||
305 | case leading of | ||
306 | '-' -> Fingerprint <$> getAzureusImpl <*> getAzureusVersion | ||
307 | 'M' -> Fingerprint <$> pure IMainline <*> getMainlineVersion | ||
308 | 'e' -> Fingerprint <$> getBitCometImpl <*> getBitCometVersion | ||
309 | 'F' -> Fingerprint <$> getBitCometImpl <*> getBitCometVersion | ||
310 | c -> do | ||
311 | c1 <- w2c <$> S.lookAhead getWord8 | ||
312 | if c1 == 'P' | ||
313 | then do | ||
314 | _ <- getWord8 | ||
315 | Fingerprint <$> pure IOpera <*> getOperaVersion | ||
316 | else Fingerprint <$> pure (getShadowImpl c) <*> getShadowVersion | ||
317 | |||
318 | getMainlineVersion = do | ||
319 | str <- BC.unpack <$> getByteString 7 | ||
320 | let mnums = L.filter (not . L.null) $ L.linesBy ('-' ==) str | ||
321 | return $ Version (fromMaybe [] $ sequence $ L.map readMaybe mnums) [] | ||
322 | |||
323 | getAzureusImpl = parseImpl <$> getByteString 2 | ||
324 | getAzureusVersion = mkVer <$> getByteString 4 | ||
325 | where | ||
326 | mkVer bs = Version [fromMaybe 0 $ readMaybe $ BC.unpack bs] [] | ||
327 | |||
328 | getBitCometImpl = do | ||
329 | bs <- getByteString 3 | ||
330 | S.lookAhead $ do | ||
331 | _ <- getByteString 2 | ||
332 | lr <- getByteString 4 | ||
333 | return $ | ||
334 | if lr == "LORD" then IBitLord else | ||
335 | if bs == "UTB" then IBitComet else | ||
336 | if bs == "xbc" then IBitComet else def | ||
337 | |||
338 | getBitCometVersion = do | ||
339 | x <- getWord8 | ||
340 | y <- getWord8 | ||
341 | return $ Version [fromIntegral x, fromIntegral y] [] | ||
342 | |||
343 | getOperaVersion = do | ||
344 | str <- BC.unpack <$> getByteString 4 | ||
345 | return $ Version [fromMaybe 0 $ readMaybe str] [] | ||
346 | |||
347 | getShadowImpl 'A' = IABC | ||
348 | getShadowImpl 'O' = IOspreyPermaseed | ||
349 | getShadowImpl 'Q' = IBTQueue | ||
350 | getShadowImpl 'R' = ITribler | ||
351 | getShadowImpl 'S' = IShadow | ||
352 | getShadowImpl 'T' = IBitTornado | ||
353 | getShadowImpl _ = IUnknown | ||
354 | |||
355 | decodeShadowVerNr :: Char -> Maybe Int | ||
356 | decodeShadowVerNr c | ||
357 | | '0' < c && c <= '9' = Just (fromEnum c - fromEnum '0') | ||
358 | | 'A' < c && c <= 'Z' = Just ((fromEnum c - fromEnum 'A') + 10) | ||
359 | | 'a' < c && c <= 'z' = Just ((fromEnum c - fromEnum 'a') + 36) | ||
360 | | otherwise = Nothing | ||
361 | |||
362 | getShadowVersion = do | ||
363 | str <- BC.unpack <$> getByteString 5 | ||
364 | return $ Version (catMaybes $ L.map decodeShadowVerNr str) [] | ||