summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Core/PeerId.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Core/PeerId.hs')
-rw-r--r--src/Network/BitTorrent/Core/PeerId.hs278
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 #-}
16module 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
39import Control.Applicative
40import Data.Aeson
41import Data.BEncode as BE
42import Data.ByteString as BS
43import Data.ByteString.Char8 as BC
44import qualified Data.ByteString.Lazy as BL
45import qualified Data.ByteString.Lazy.Builder as BS
46import Data.Default
47import Data.Foldable (foldMap)
48import Data.List as L
49import Data.Maybe (fromMaybe)
50import Data.Monoid
51import Data.Serialize as S
52import Data.Time.Clock (getCurrentTime)
53import Data.Time.Format (formatTime)
54import Data.URLEncoded
55import Data.Version (Version(Version), versionBranch)
56import System.Entropy (getEntropy)
57import System.Locale (defaultTimeLocale)
58import Text.PrettyPrint hiding ((<>))
59import Text.Read (readMaybe)
60import Paths_bittorrent (version)
61
62import Data.Torrent.Client
63
64
65-- | Peer identifier is exactly 20 bytes long bytestring.
66newtype PeerId = PeerId { getPeerId :: ByteString }
67 deriving (Show, Eq, Ord, BEncode, ToJSON, FromJSON)
68
69instance Serialize PeerId where
70 put = putByteString . getPeerId
71 get = PeerId <$> getBytes 20
72
73instance URLShow PeerId where
74 urlShow = BC.unpack . getPeerId
75
76-- | Format peer id in human readable form.
77ppPeerId :: PeerId -> Doc
78ppPeerId = 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--
94byteStringPadded :: ByteString -- ^ bytestring to be padded.
95 -> Int -- ^ size of result builder.
96 -> Char -- ^ character used for padding.
97 -> BS.Builder
98byteStringPadded 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--
116azureusStyle :: 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.
120azureusStyle 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--
137shadowStyle :: Char -- ^ Client ID.
138 -> ByteString -- ^ Version number.
139 -> ByteString -- ^ Random number.
140 -> PeerId -- ^ Shadow style encoded peer ID.
141shadowStyle 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.
148defaultClientId :: ByteString
149defaultClientId = "HS"
150
151-- | Gives exactly 4 bytes long version number for any version of the
152-- package. Version is taken from .cabal.
153defaultVersionNumber :: ByteString
154defaultVersionNumber = 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--
174timestamp :: IO ByteString
175timestamp = (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.
182entropy :: IO ByteString
183entropy = 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--
195genPeerId :: IO PeerId
196genPeerId = azureusStyle defaultClientId defaultVersionNumber <$> timestamp
197
198{-----------------------------------------------------------------------
199-- Decoding
200-----------------------------------------------------------------------}
201
202parseImpl :: ByteString -> ClientImpl
203parseImpl = 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--
271clientInfo :: PeerId -> ClientInfo
272clientInfo 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] []