summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Address.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Address.hs')
-rw-r--r--src/Network/BitTorrent/Address.hs1254
1 files changed, 1254 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Address.hs b/src/Network/BitTorrent/Address.hs
new file mode 100644
index 00000000..a72a8a07
--- /dev/null
+++ b/src/Network/BitTorrent/Address.hs
@@ -0,0 +1,1254 @@
1-- |
2-- Module : Network.BitTorrent.Address
3-- Copyright : (c) Sam Truzjan 2013
4-- (c) Daniel Gröber 2013
5-- License : BSD3
6-- Maintainer : pxqr.sta@gmail.com
7-- Stability : provisional
8-- Portability : portable
9--
10-- Peer and Node addresses.
11--
12{-# LANGUAGE CPP #-}
13{-# LANGUAGE FlexibleInstances #-}
14{-# LANGUAGE RecordWildCards #-}
15{-# LANGUAGE StandaloneDeriving #-}
16{-# LANGUAGE ViewPatterns #-}
17{-# LANGUAGE GeneralizedNewtypeDeriving #-}
18{-# LANGUAGE MultiParamTypeClasses #-}
19{-# LANGUAGE DeriveDataTypeable #-}
20{-# LANGUAGE DeriveFunctor #-}
21{-# LANGUAGE DeriveFoldable #-}
22{-# LANGUAGE DeriveTraversable #-}
23{-# LANGUAGE TemplateHaskell #-}
24{-# OPTIONS -fno-warn-orphans #-}
25module Network.BitTorrent.Address
26 ( -- * Address
27 Address (..)
28 , fromAddr
29
30 -- ** IP
31 , IPv4
32 , IPv6
33 , IP (..)
34
35 -- * PeerId
36 -- $peer-id
37 , PeerId
38
39 -- ** Generation
40 , genPeerId
41 , timestamp
42 , entropy
43
44 -- ** Encoding
45 , azureusStyle
46 , shadowStyle
47 , defaultClientId
48 , defaultVersionNumber
49
50 -- * PeerAddr
51 -- $peer-addr
52 , PeerAddr(..)
53 , defaultPorts
54 , peerSockAddr
55 , peerSocket
56
57 -- * Node
58 -- ** Id
59 , NodeId(..)
60 , nodeIdSize
61 , testIdBit
62 , NodeDistance
63 , distance
64 , genNodeId
65 , bucketRange
66 , genBucketSample
67 , bep42
68 , bep42s
69
70 -- ** Info
71 , NodeAddr (..)
72 , NodeInfo (..)
73 , rank
74
75 -- * Fingerprint
76 -- $fingerprint
77 , Software (..)
78 , Fingerprint (..)
79 , libFingerprint
80 , fingerprint
81
82 -- * Utils
83 , libUserAgent
84 ) where
85
86import Control.Applicative
87import Control.Monad
88import Data.BEncode as BE
89import Data.BEncode as BS
90import Data.BEncode.BDict (BKey)
91import Data.Bits
92import Data.ByteString as BS
93import Data.ByteString.Internal as BS
94import Data.ByteString.Base16 as Base16
95import Data.ByteString.Char8 as BC
96import Data.ByteString.Char8 as BS8
97import qualified Data.ByteString.Lazy as BL
98import qualified Data.ByteString.Lazy.Builder as BS
99import Data.Char
100import Data.Convertible
101import Data.Default
102import Data.IP
103import Data.List as L
104import Data.List.Split as L
105import Data.Maybe (fromMaybe, catMaybes, mapMaybe)
106import Data.Monoid
107import Data.Hashable
108import Data.Ord
109import Data.Serialize as S
110import Data.String
111import Data.Time
112import Data.Typeable
113import Data.Version
114import Data.Word
115import qualified Text.ParserCombinators.ReadP as RP
116import Text.Read (readMaybe)
117import Network.HTTP.Types.QueryLike
118import Network.Socket
119import Text.PrettyPrint as PP hiding ((<>))
120import Text.PrettyPrint.HughesPJClass hiding ((<>),($$))
121#if !MIN_VERSION_time(1,5,0)
122import System.Locale (defaultTimeLocale)
123#endif
124import System.Entropy
125import Data.Digest.CRC32C
126
127-- import Paths_bittorrent (version)
128
129{-----------------------------------------------------------------------
130-- Address
131-----------------------------------------------------------------------}
132
133instance Pretty UTCTime where
134 pPrint = PP.text . show
135
136class (Eq a, Serialize a, Typeable a, Hashable a, Pretty a)
137 => Address a where
138 toSockAddr :: a -> SockAddr
139 fromSockAddr :: SockAddr -> Maybe a
140
141fromAddr :: (Address a, Address b) => a -> Maybe b
142fromAddr = fromSockAddr . toSockAddr
143
144-- | Note that port is zeroed.
145instance Address IPv4 where
146 toSockAddr = SockAddrInet 0 . toHostAddress
147 fromSockAddr (SockAddrInet _ h) = Just (fromHostAddress h)
148 fromSockAddr _ = Nothing
149
150-- | Note that port is zeroed.
151instance Address IPv6 where
152 toSockAddr h = SockAddrInet6 0 0 (toHostAddress6 h) 0
153 fromSockAddr (SockAddrInet6 _ _ h _) = Just (fromHostAddress6 h)
154 fromSockAddr _ = Nothing
155
156-- | Note that port is zeroed.
157instance Address IP where
158 toSockAddr (IPv4 h) = toSockAddr h
159 toSockAddr (IPv6 h) = toSockAddr h
160 fromSockAddr sa =
161 IPv4 <$> fromSockAddr sa
162 <|> IPv6 <$> fromSockAddr sa
163
164setPort :: PortNumber -> SockAddr -> SockAddr
165setPort port (SockAddrInet _ h ) = SockAddrInet port h
166setPort port (SockAddrInet6 _ f h s) = SockAddrInet6 port f h s
167setPort _ addr = addr
168{-# INLINE setPort #-}
169
170getPort :: SockAddr -> Maybe PortNumber
171getPort (SockAddrInet p _ ) = Just p
172getPort (SockAddrInet6 p _ _ _) = Just p
173getPort _ = Nothing
174{-# INLINE getPort #-}
175
176instance Address a => Address (NodeAddr a) where
177 toSockAddr NodeAddr {..} = setPort nodePort $ toSockAddr nodeHost
178 fromSockAddr sa = NodeAddr <$> fromSockAddr sa <*> getPort sa
179
180instance Address a => Address (PeerAddr a) where
181 toSockAddr PeerAddr {..} = setPort peerPort $ toSockAddr peerHost
182 fromSockAddr sa = PeerAddr Nothing <$> fromSockAddr sa <*> getPort sa
183
184{-----------------------------------------------------------------------
185-- Peer id
186-----------------------------------------------------------------------}
187-- $peer-id
188--
189-- 'PeerID' represent self assigned peer identificator. Ideally each
190-- host in the network should have unique peer id to avoid
191-- collisions, therefore for peer ID generation we use good entropy
192-- source. Peer ID is sent in /tracker request/, sent and received in
193-- /peer handshakes/ and used in DHT queries.
194--
195
196-- TODO use unpacked Word160 form (length is known statically)
197
198-- | Peer identifier is exactly 20 bytes long bytestring.
199newtype PeerId = PeerId { getPeerId :: ByteString }
200 deriving (Show, Eq, Ord, BEncode, Typeable)
201
202peerIdLen :: Int
203peerIdLen = 20
204
205-- | For testing purposes only.
206instance Default PeerId where
207 def = azureusStyle defaultClientId defaultVersionNumber ""
208
209instance Hashable PeerId where
210 hashWithSalt = hashUsing getPeerId
211 {-# INLINE hashWithSalt #-}
212
213instance Serialize PeerId where
214 put = putByteString . getPeerId
215 get = PeerId <$> getBytes peerIdLen
216
217instance QueryValueLike PeerId where
218 toQueryValue (PeerId pid) = Just pid
219 {-# INLINE toQueryValue #-}
220
221instance IsString PeerId where
222 fromString str
223 | BS.length bs == peerIdLen = PeerId bs
224 | otherwise = error $ "Peer id should be 20 bytes long: " ++ show str
225 where
226 bs = fromString str
227
228instance Pretty PeerId where
229 pPrint = text . BC.unpack . getPeerId
230
231instance Convertible BS.ByteString PeerId where
232 safeConvert bs
233 | BS.length bs == peerIdLen = pure (PeerId bs)
234 | otherwise = convError "invalid length" bs
235
236------------------------------------------------------------------------
237
238-- | Pad bytestring so it's becomes exactly request length. Conversion
239-- is done like so:
240--
241-- * length < size: Complete bytestring by given charaters.
242--
243-- * length = size: Output bytestring as is.
244--
245-- * length > size: Drop last (length - size) charaters from a
246-- given bytestring.
247--
248byteStringPadded :: ByteString -- ^ bytestring to be padded.
249 -> Int -- ^ size of result builder.
250 -> Char -- ^ character used for padding.
251 -> BS.Builder
252byteStringPadded bs s c =
253 BS.byteString (BS.take s bs) <>
254 BS.byteString (BC.replicate padLen c)
255 where
256 padLen = s - min (BS.length bs) s
257
258-- | Azureus-style encoding have the following layout:
259--
260-- * 1 byte : '-'
261--
262-- * 2 bytes: client id
263--
264-- * 4 bytes: version number
265--
266-- * 1 byte : '-'
267--
268-- * 12 bytes: random number
269--
270azureusStyle :: ByteString -- ^ 2 character client ID, padded with 'H'.
271 -> ByteString -- ^ Version number, padded with 'X'.
272 -> ByteString -- ^ Random number, padded with '0'.
273 -> PeerId -- ^ Azureus-style encoded peer ID.
274azureusStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $
275 BS.char8 '-' <>
276 byteStringPadded cid 2 'H' <>
277 byteStringPadded ver 4 'X' <>
278 BS.char8 '-' <>
279 byteStringPadded rnd 12 '0'
280
281-- | Shadow-style encoding have the following layout:
282--
283-- * 1 byte : client id.
284--
285-- * 0-4 bytes: version number. If less than 4 then padded with
286-- '-' char.
287--
288-- * 15 bytes : random number. If length is less than 15 then
289-- padded with '0' char.
290--
291shadowStyle :: Char -- ^ Client ID.
292 -> ByteString -- ^ Version number.
293 -> ByteString -- ^ Random number.
294 -> PeerId -- ^ Shadow style encoded peer ID.
295shadowStyle cid ver rnd = PeerId $ BL.toStrict $ BS.toLazyByteString $
296 BS.char8 cid <>
297 byteStringPadded ver 4 '-' <>
298 byteStringPadded rnd 15 '0'
299
300
301-- | 'HS'- 2 bytes long client identifier.
302defaultClientId :: ByteString
303defaultClientId = "HS"
304
305-- | Gives exactly 4 bytes long version number for any version of the
306-- package. Version is taken from .cabal file.
307defaultVersionNumber :: ByteString
308defaultVersionNumber = BS.take 4 $ BC.pack $ foldMap show $
309 versionBranch myVersion
310 where
311 Fingerprint _ myVersion = libFingerprint
312
313------------------------------------------------------------------------
314
315-- | Gives 15 characters long decimal timestamp such that:
316--
317-- * 6 bytes : first 6 characters from picoseconds obtained with %q.
318--
319-- * 1 byte : character \'.\' for readability.
320--
321-- * 9..* bytes: number of whole seconds since the Unix epoch
322-- (!)REVERSED.
323--
324-- Can be used both with shadow and azureus style encoding. This
325-- format is used to make the ID's readable for debugging purposes.
326--
327timestamp :: IO ByteString
328timestamp = (BC.pack . format) <$> getCurrentTime
329 where
330 format t = L.take 6 (formatTime defaultTimeLocale "%q" t) ++ "." ++
331 L.take 9 (L.reverse (formatTime defaultTimeLocale "%s" t))
332
333-- | Gives 15 character long random bytestring. This is more robust
334-- method for generation of random part of peer ID than 'timestamp'.
335entropy :: IO ByteString
336entropy = getEntropy 15
337
338-- NOTE: entropy generates incorrrect peer id
339
340-- | Here we use 'azureusStyle' encoding with the following args:
341--
342-- * 'HS' for the client id; ('defaultClientId')
343--
344-- * Version of the package for the version number;
345-- ('defaultVersionNumber')
346--
347-- * UTC time day ++ day time for the random number. ('timestamp')
348--
349genPeerId :: IO PeerId
350genPeerId = azureusStyle defaultClientId defaultVersionNumber <$> timestamp
351
352{-----------------------------------------------------------------------
353-- Peer Addr
354-----------------------------------------------------------------------}
355-- $peer-addr
356--
357-- 'PeerAddr' is used to represent peer address. Currently it's
358-- just peer IP and peer port but this might change in future.
359--
360
361{-----------------------------------------------------------------------
362-- Port number
363-----------------------------------------------------------------------}
364
365instance BEncode PortNumber where
366 toBEncode = toBEncode . fromEnum
367 fromBEncode = fromBEncode >=> portNumber
368 where
369 portNumber :: Integer -> BS.Result PortNumber
370 portNumber n
371 | 0 <= n && n <= fromIntegral (maxBound :: Word16)
372 = pure $ fromIntegral n
373 | otherwise = decodingError $ "PortNumber: " ++ show n
374
375instance Serialize PortNumber where
376 get = fromIntegral <$> getWord16be
377 {-# INLINE get #-}
378 put = putWord16be . fromIntegral
379 {-# INLINE put #-}
380
381instance Hashable PortNumber where
382 hashWithSalt s = hashWithSalt s . fromEnum
383 {-# INLINE hashWithSalt #-}
384
385instance Pretty PortNumber where
386 pPrint = PP.int . fromEnum
387 {-# INLINE pPrint #-}
388
389{-----------------------------------------------------------------------
390-- IP addr
391-----------------------------------------------------------------------}
392
393class IPAddress i where
394 toHostAddr :: i -> Either HostAddress HostAddress6
395
396instance IPAddress IPv4 where
397 toHostAddr = Left . toHostAddress
398 {-# INLINE toHostAddr #-}
399
400instance IPAddress IPv6 where
401 toHostAddr = Right . toHostAddress6
402 {-# INLINE toHostAddr #-}
403
404instance IPAddress IP where
405 toHostAddr (IPv4 ip) = toHostAddr ip
406 toHostAddr (IPv6 ip) = toHostAddr ip
407 {-# INLINE toHostAddr #-}
408
409deriving instance Typeable IP
410deriving instance Typeable IPv4
411deriving instance Typeable IPv6
412
413ipToBEncode :: Show i => i -> BValue
414ipToBEncode ip = BString $ BS8.pack $ show ip
415{-# INLINE ipToBEncode #-}
416
417ipFromBEncode :: Read a => BValue -> BS.Result a
418ipFromBEncode (BString (BS8.unpack -> ipStr))
419 | Just ip <- readMaybe (ipStr) = pure ip
420 | otherwise = decodingError $ "IP: " ++ ipStr
421ipFromBEncode _ = decodingError $ "IP: addr should be a bstring"
422
423instance BEncode IP where
424 toBEncode = ipToBEncode
425 {-# INLINE toBEncode #-}
426 fromBEncode = ipFromBEncode
427 {-# INLINE fromBEncode #-}
428
429instance BEncode IPv4 where
430 toBEncode = ipToBEncode
431 {-# INLINE toBEncode #-}
432 fromBEncode = ipFromBEncode
433 {-# INLINE fromBEncode #-}
434
435instance BEncode IPv6 where
436 toBEncode = ipToBEncode
437 {-# INLINE toBEncode #-}
438 fromBEncode = ipFromBEncode
439 {-# INLINE fromBEncode #-}
440
441-- | When 'get'ing an IP it must be 'isolate'd to the appropriate
442-- number of bytes since we have no other way of telling which
443-- address type we are trying to parse
444instance Serialize IP where
445 put (IPv4 ip) = put ip
446 put (IPv6 ip) = put ip
447
448 get = do
449 n <- remaining
450 case n of
451 4 -> IPv4 <$> get
452 16 -> IPv6 <$> get
453 _ -> fail (show n ++ " is the wrong number of remaining bytes to parse IP")
454
455instance Serialize IPv4 where
456 put = putWord32host . toHostAddress
457 get = fromHostAddress <$> getWord32host
458
459instance Serialize IPv6 where
460 put ip = put $ toHostAddress6 ip
461 get = fromHostAddress6 <$> get
462
463instance Pretty IPv4 where
464 pPrint = PP.text . show
465 {-# INLINE pPrint #-}
466
467instance Pretty IPv6 where
468 pPrint = PP.text . show
469 {-# INLINE pPrint #-}
470
471instance Pretty IP where
472 pPrint = PP.text . show
473 {-# INLINE pPrint #-}
474
475instance Hashable IPv4 where
476 hashWithSalt = hashUsing toHostAddress
477 {-# INLINE hashWithSalt #-}
478
479instance Hashable IPv6 where
480 hashWithSalt s a = hashWithSalt s (toHostAddress6 a)
481
482instance Hashable IP where
483 hashWithSalt s (IPv4 h) = hashWithSalt s h
484 hashWithSalt s (IPv6 h) = hashWithSalt s h
485
486{-----------------------------------------------------------------------
487-- Peer addr
488-----------------------------------------------------------------------}
489-- TODO check semantic of ord and eq instances
490
491-- | Peer address info normally extracted from peer list or peer
492-- compact list encoding.
493data PeerAddr a = PeerAddr
494 { peerId :: !(Maybe PeerId)
495
496 -- | This is usually 'IPv4', 'IPv6', 'IP' or unresolved
497 -- 'HostName'.
498 , peerHost :: !a
499
500 -- | The port the peer listenning for incoming P2P sessions.
501 , peerPort :: {-# UNPACK #-} !PortNumber
502 } deriving (Show, Eq, Ord, Typeable, Functor)
503
504peer_ip_key, peer_id_key, peer_port_key :: BKey
505peer_ip_key = "ip"
506peer_id_key = "peer id"
507peer_port_key = "port"
508
509-- | The tracker's 'announce response' compatible encoding.
510instance (Typeable a, BEncode a) => BEncode (PeerAddr a) where
511 toBEncode PeerAddr {..} = toDict $
512 peer_ip_key .=! peerHost
513 .: peer_id_key .=? peerId
514 .: peer_port_key .=! peerPort
515 .: endDict
516
517 fromBEncode = fromDict $ do
518 peerAddr <$>! peer_ip_key
519 <*>? peer_id_key
520 <*>! peer_port_key
521 where
522 peerAddr = flip PeerAddr
523
524-- | The tracker's 'compact peer list' compatible encoding. The
525-- 'peerId' is always 'Nothing'.
526--
527-- For more info see: <http://www.bittorrent.org/beps/bep_0023.html>
528--
529-- TODO: test byte order
530instance (Serialize a) => Serialize (PeerAddr a) where
531 put PeerAddr {..} = put peerHost >> put peerPort
532 get = PeerAddr Nothing <$> get <*> get
533
534-- | @127.0.0.1:6881@
535instance Default (PeerAddr IPv4) where
536 def = "127.0.0.1:6881"
537
538-- | @127.0.0.1:6881@
539instance Default (PeerAddr IP) where
540 def = IPv4 <$> def
541
542-- | Example:
543--
544-- @peerPort \"127.0.0.1:6881\" == 6881@
545--
546instance IsString (PeerAddr IPv4) where
547 fromString str
548 | [hostAddrStr, portStr] <- splitWhen (== ':') str
549 , Just hostAddr <- readMaybe hostAddrStr
550 , Just portNum <- toEnum <$> readMaybe portStr
551 = PeerAddr Nothing hostAddr portNum
552 | otherwise = error $ "fromString: unable to parse (PeerAddr IPv4): " ++ str
553
554instance Read (PeerAddr IPv4) where
555 readsPrec i = RP.readP_to_S $ do
556 ipv4 <- RP.readS_to_P (readsPrec i)
557 _ <- RP.char ':'
558 port <- toEnum <$> RP.readS_to_P (readsPrec i)
559 return $ PeerAddr Nothing ipv4 port
560
561readsIPv6_port :: String -> [((IPv6, PortNumber), String)]
562readsIPv6_port = RP.readP_to_S $ do
563 ip <- RP.char '[' *> (RP.readS_to_P reads) <* RP.char ']'
564 _ <- RP.char ':'
565 port <- toEnum <$> read <$> (RP.many1 $ RP.satisfy isDigit) <* RP.eof
566 return (ip,port)
567
568instance IsString (PeerAddr IPv6) where
569 fromString str
570 | [((ip,port),"")] <- readsIPv6_port str =
571 PeerAddr Nothing ip port
572 | otherwise = error $ "fromString: unable to parse (PeerAddr IPv6): " ++ str
573
574instance IsString (PeerAddr IP) where
575 fromString str
576 | '[' `L.elem` str = IPv6 <$> fromString str
577 | otherwise = IPv4 <$> fromString str
578
579-- | fingerprint + "at" + dotted.host.inet.addr:port
580-- TODO: instances for IPv6, HostName
581instance Pretty a => Pretty (PeerAddr a) where
582 pPrint PeerAddr {..}
583 | Just pid <- peerId = pPrint (fingerprint pid) <+> "at" <+> paddr
584 | otherwise = paddr
585 where
586 paddr = pPrint peerHost <> ":" <> text (show peerPort)
587
588instance Hashable a => Hashable (PeerAddr a) where
589 hashWithSalt s PeerAddr {..} =
590 s `hashWithSalt` peerId `hashWithSalt` peerHost `hashWithSalt` peerPort
591
592-- | Ports typically reserved for bittorrent P2P listener.
593defaultPorts :: [PortNumber]
594defaultPorts = [6881..6889]
595
596_resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i
597_resolvePeerAddr = undefined
598
599_peerSockAddr :: PeerAddr IP -> (Family, SockAddr)
600_peerSockAddr PeerAddr {..} =
601 case peerHost of
602 IPv4 ipv4 ->
603 (AF_INET, SockAddrInet peerPort (toHostAddress ipv4))
604 IPv6 ipv6 ->
605 (AF_INET6, SockAddrInet6 peerPort 0 (toHostAddress6 ipv6) 0)
606
607peerSockAddr :: PeerAddr IP -> SockAddr
608peerSockAddr = snd . _peerSockAddr
609
610-- | Create a socket connected to the address specified in a peerAddr
611peerSocket :: SocketType -> PeerAddr IP -> IO Socket
612peerSocket socketType pa = do
613 let (family, addr) = _peerSockAddr pa
614 sock <- socket family socketType defaultProtocol
615 connect sock addr
616 return sock
617
618{-----------------------------------------------------------------------
619-- Node info
620-----------------------------------------------------------------------}
621-- $node-info
622--
623-- A \"node\" is a client\/server listening on a UDP port
624-- implementing the distributed hash table protocol. The DHT is
625-- composed of nodes and stores the location of peers. BitTorrent
626-- clients include a DHT node, which is used to contact other nodes
627-- in the DHT to get the location of peers to download from using
628-- the BitTorrent protocol.
629
630-- TODO more compact representation ('ShortByteString's?)
631
632-- | Each node has a globally unique identifier known as the \"node
633-- ID.\"
634--
635-- Normally, /this/ node id should be saved between invocations
636-- of the client software.
637newtype NodeId = NodeId ByteString
638 deriving (Show, Eq, Ord, BEncode, Typeable)
639
640nodeIdSize :: Int
641nodeIdSize = 20
642
643-- | Meaningless node id, for testing purposes only.
644instance Default NodeId where
645 def = NodeId (BS.replicate nodeIdSize 0)
646
647instance Serialize NodeId where
648 get = NodeId <$> getByteString nodeIdSize
649 {-# INLINE get #-}
650 put (NodeId bs) = putByteString bs
651 {-# INLINE put #-}
652
653-- | ASCII encoded.
654instance IsString NodeId where
655 fromString str
656 | L.length str == nodeIdSize = NodeId (fromString str)
657 | L.length str == 2 * nodeIdSize = NodeId (fst $ Base16.decode $ fromString str)
658 | otherwise = error "fromString: invalid NodeId length"
659 {-# INLINE fromString #-}
660
661-- | base16 encoded.
662instance Pretty NodeId where
663 pPrint (NodeId nid) = PP.text $ BC.unpack $ Base16.encode nid
664
665-- | Test if the nth bit is set.
666testIdBit :: NodeId -> Word -> Bool
667testIdBit (NodeId bs) i
668 | fromIntegral i < nodeIdSize * 8
669 , (q, r) <- quotRem (fromIntegral i) 8
670 = testBit (BS.index bs q) (7 - r)
671 | otherwise = False
672{-# INLINE testIdBit #-}
673
674-- TODO WARN is the 'system' random suitable for this?
675-- | Generate random NodeID used for the entire session.
676-- Distribution of ID's should be as uniform as possible.
677--
678genNodeId :: IO NodeId
679genNodeId = NodeId <$> getEntropy nodeIdSize
680
681------------------------------------------------------------------------
682
683-- | In Kademlia, the distance metric is XOR and the result is
684-- interpreted as an unsigned integer.
685newtype NodeDistance = NodeDistance BS.ByteString
686 deriving (Eq, Ord)
687
688instance Pretty NodeDistance where
689 pPrint (NodeDistance bs) = foldMap bitseq $ BS.unpack bs
690 where
691 listBits w = L.map (testBit w) (L.reverse [0..finiteBitSize w - 1])
692 bitseq = foldMap (int . fromEnum) . listBits
693
694-- | distance(A,B) = |A xor B| Smaller values are closer.
695distance :: NodeId -> NodeId -> NodeDistance
696distance (NodeId a) (NodeId b) = NodeDistance (BS.pack (BS.zipWith xor a b))
697
698-- | Accepts a depth/index of a bucket and whether or not it is the last one,
699-- yields:
700--
701-- count of leading bytes to be copied from your node id.
702--
703-- mask to clear the extra bits of the last copied byte
704--
705-- mask to toggle the last copied bit if it is not the last bucket
706--
707-- Normally this is used with 'genBucketSample' to obtain a random id suitable
708-- for refreshing a particular bucket.
709bucketRange :: Int -> Bool -> (Int, Word8, Word8)
710bucketRange depth is_last = (q,m,b)
711 where
712 (q,r) = divMod ((if is_last then (+7) else (+8)) depth) 8
713 m = 2^(7-r) - 1
714 b = if is_last then 0 else 2^(7-r)
715
716-- | Generate a random 'NodeId' within a range suitable for a bucket. To
717-- obtain a sample for bucket number /index/ where /is_last/ indicates if this
718-- is for the current deepest bucket in our routing table:
719--
720-- > sample <- genBucketSample nid (bucketRange index is_last)
721genBucketSample :: NodeId -> (Int,Word8,Word8) -> IO NodeId
722genBucketSample n qmb = genBucketSample' getEntropy n qmb
723
724-- | Generalizion of 'genBucketSample' that accepts a byte generator
725-- function to use instead of the system entropy.
726genBucketSample' :: Applicative m =>
727 (Int -> m ByteString) -> NodeId -> (Int,Word8,Word8) -> m NodeId
728genBucketSample' gen (NodeId self) (q,m,b)
729 | q <= 0 = NodeId <$> gen nodeIdSize
730 | q >= nodeIdSize = pure (NodeId self)
731 | otherwise = NodeId . build <$> gen (nodeIdSize - q + 1)
732 where
733 build tl = BS.init hd <> BS.cons (h .|. t) (BS.tail tl)
734 where
735 hd = BS.take q self
736 h = xor b (complement m .&. BS.last hd)
737 t = m .&. BS.head tl
738
739------------------------------------------------------------------------
740
741data NodeAddr a = NodeAddr
742 { nodeHost :: !a
743 , nodePort :: {-# UNPACK #-} !PortNumber
744 } deriving (Eq, Typeable, Functor, Foldable, Traversable)
745
746instance Show a => Show (NodeAddr a) where
747 showsPrec i NodeAddr {..}
748 = showsPrec i nodeHost <> showString ":" <> showsPrec i nodePort
749
750instance Read (NodeAddr IPv4) where
751 readsPrec i x = [ (fromPeerAddr a, s) | (a, s) <- readsPrec i x ]
752
753-- | @127.0.0.1:6882@
754instance Default (NodeAddr IPv4) where
755 def = "127.0.0.1:6882"
756
757-- | KRPC compatible encoding.
758instance Serialize a => Serialize (NodeAddr a) where
759 get = NodeAddr <$> get <*> get
760 {-# INLINE get #-}
761 put NodeAddr {..} = put nodeHost >> put nodePort
762 {-# INLINE put #-}
763
764-- | Torrent file compatible encoding.
765instance BEncode a => BEncode (NodeAddr a) where
766 toBEncode NodeAddr {..} = toBEncode (nodeHost, nodePort)
767 {-# INLINE toBEncode #-}
768 fromBEncode b = uncurry NodeAddr <$> fromBEncode b
769 {-# INLINE fromBEncode #-}
770
771instance Hashable a => Hashable (NodeAddr a) where
772 hashWithSalt s NodeAddr {..} = hashWithSalt s (nodeHost, nodePort)
773 {-# INLINE hashWithSalt #-}
774
775instance Pretty ip => Pretty (NodeAddr ip) where
776 pPrint NodeAddr {..} = pPrint nodeHost <> ":" <> pPrint nodePort
777
778-- | Example:
779--
780-- @nodePort \"127.0.0.1:6881\" == 6881@
781--
782instance IsString (NodeAddr IPv4) where
783 fromString = fromPeerAddr . fromString
784
785fromPeerAddr :: PeerAddr a -> NodeAddr a
786fromPeerAddr PeerAddr {..} = NodeAddr
787 { nodeHost = peerHost
788 , nodePort = peerPort
789 }
790
791------------------------------------------------------------------------
792
793data NodeInfo a = NodeInfo
794 { nodeId :: !NodeId
795 , nodeAddr :: !(NodeAddr a)
796 } deriving (Show, Eq, Functor, Foldable, Traversable)
797
798instance Eq a => Ord (NodeInfo a) where
799 compare = comparing nodeId
800
801-- | KRPC 'compact list' compatible encoding: contact information for
802-- nodes is encoded as a 26-byte string. Also known as "Compact node
803-- info" the 20-byte Node ID in network byte order has the compact
804-- IP-address/port info concatenated to the end.
805instance Serialize a => Serialize (NodeInfo a) where
806 get = NodeInfo <$> get <*> get
807 put NodeInfo {..} = put nodeId >> put nodeAddr
808
809instance Pretty ip => Pretty (NodeInfo ip) where
810 pPrint NodeInfo {..} = pPrint nodeId <> "@(" <> pPrint nodeAddr <> ")"
811
812instance Pretty ip => Pretty [NodeInfo ip] where
813 pPrint = PP.vcat . PP.punctuate "," . L.map pPrint
814
815-- | Order by closeness: nearest nodes first.
816rank :: (x -> NodeId) -> NodeId -> [x] -> [x]
817rank f nid = L.sortBy (comparing (distance nid . f))
818
819{-----------------------------------------------------------------------
820-- Fingerprint
821-----------------------------------------------------------------------}
822-- $fingerprint
823--
824-- 'Fingerprint' is used to identify the client implementation and
825-- version which also contained in 'Peer'. For exsample first 6
826-- bytes of peer id of this this library are @-HS0100-@ while for
827-- mainline we have @M4-3-6--@. We could extract this info and
828-- print in human-friendly form: this is useful for debugging and
829-- logging.
830--
831-- For more information see:
832-- <http://bittorrent.org/beps/bep_0020.html>
833--
834--
835-- NOTE: Do /not/ use this information to control client
836-- capabilities (such as supported enchancements), this should be
837-- done using 'Network.BitTorrent.Extension'!
838--
839
840-- TODO FIXME
841version :: Version
842version = Version [0, 0, 0, 3] []
843
844-- | List of registered client versions + 'IlibHSbittorrent' (this
845-- package) + 'IUnknown' (for not recognized software). All names are
846-- prefixed by \"I\" because some of them starts from lowercase letter
847-- but that is not a valid Haskell constructor name.
848--
849data Software =
850 IUnknown
851
852 | IMainline
853
854 | IABC
855 | IOspreyPermaseed
856 | IBTQueue
857 | ITribler
858 | IShadow
859 | IBitTornado
860
861-- UPnP(!) Bit Torrent !???
862-- 'U' - UPnP NAT Bit Torrent
863 | IBitLord
864 | IOpera
865 | IMLdonkey
866
867 | IAres
868 | IArctic
869 | IAvicora
870 | IBitPump
871 | IAzureus
872 | IBitBuddy
873 | IBitComet
874 | IBitflu
875 | IBTG
876 | IBitRocket
877 | IBTSlave
878 | IBittorrentX
879 | IEnhancedCTorrent
880 | ICTorrent
881 | IDelugeTorrent
882 | IPropagateDataClient
883 | IEBit
884 | IElectricSheep
885 | IFoxTorrent
886 | IGSTorrent
887 | IHalite
888 | IlibHSbittorrent
889 | IHydranode
890 | IKGet
891 | IKTorrent
892 | ILH_ABC
893 | ILphant
894 | ILibtorrent
895 | ILibTorrent
896 | ILimeWire
897 | IMonoTorrent
898 | IMooPolice
899 | IMiro
900 | IMoonlightTorrent
901 | INetTransport
902 | IPando
903 | IqBittorrent
904 | IQQDownload
905 | IQt4TorrentExample
906 | IRetriever
907 | IShareaza
908 | ISwiftbit
909 | ISwarmScope
910 | ISymTorrent
911 | Isharktorrent
912 | ITorrentDotNET
913 | ITransmission
914 | ITorrentstorm
915 | ITuoTu
916 | IuLeecher
917 | IuTorrent
918 | IVagaa
919 | IBitLet
920 | IFireTorrent
921 | IXunlei
922 | IXanTorrent
923 | IXtorrent
924 | IZipTorrent
925 deriving (Show, Eq, Ord, Enum, Bounded)
926
927parseSoftware :: ByteString -> Software
928parseSoftware = f . BC.unpack
929 where
930 f "AG" = IAres
931 f "A~" = IAres
932 f "AR" = IArctic
933 f "AV" = IAvicora
934 f "AX" = IBitPump
935 f "AZ" = IAzureus
936 f "BB" = IBitBuddy
937 f "BC" = IBitComet
938 f "BF" = IBitflu
939 f "BG" = IBTG
940 f "BR" = IBitRocket
941 f "BS" = IBTSlave
942 f "BX" = IBittorrentX
943 f "CD" = IEnhancedCTorrent
944 f "CT" = ICTorrent
945 f "DE" = IDelugeTorrent
946 f "DP" = IPropagateDataClient
947 f "EB" = IEBit
948 f "ES" = IElectricSheep
949 f "FT" = IFoxTorrent
950 f "GS" = IGSTorrent
951 f "HL" = IHalite
952 f "HS" = IlibHSbittorrent
953 f "HN" = IHydranode
954 f "KG" = IKGet
955 f "KT" = IKTorrent
956 f "LH" = ILH_ABC
957 f "LP" = ILphant
958 f "LT" = ILibtorrent
959 f "lt" = ILibTorrent
960 f "LW" = ILimeWire
961 f "MO" = IMonoTorrent
962 f "MP" = IMooPolice
963 f "MR" = IMiro
964 f "ML" = IMLdonkey
965 f "MT" = IMoonlightTorrent
966 f "NX" = INetTransport
967 f "PD" = IPando
968 f "qB" = IqBittorrent
969 f "QD" = IQQDownload
970 f "QT" = IQt4TorrentExample
971 f "RT" = IRetriever
972 f "S~" = IShareaza
973 f "SB" = ISwiftbit
974 f "SS" = ISwarmScope
975 f "ST" = ISymTorrent
976 f "st" = Isharktorrent
977 f "SZ" = IShareaza
978 f "TN" = ITorrentDotNET
979 f "TR" = ITransmission
980 f "TS" = ITorrentstorm
981 f "TT" = ITuoTu
982 f "UL" = IuLeecher
983 f "UT" = IuTorrent
984 f "VG" = IVagaa
985 f "WT" = IBitLet
986 f "WY" = IFireTorrent
987 f "XL" = IXunlei
988 f "XT" = IXanTorrent
989 f "XX" = IXtorrent
990 f "ZT" = IZipTorrent
991 f _ = IUnknown
992
993-- | Used to represent a not recognized implementation
994instance Default Software where
995 def = IUnknown
996 {-# INLINE def #-}
997
998-- | Example: @\"BitLet\" == 'IBitLet'@
999instance IsString Software where
1000 fromString str
1001 | Just impl <- L.lookup str alist = impl
1002 | otherwise = error $ "fromString: not recognized " ++ str
1003 where
1004 alist = L.map mk [minBound..maxBound]
1005 mk x = (L.tail $ show x, x)
1006
1007-- | Example: @pPrint 'IBitLet' == \"IBitLet\"@
1008instance Pretty Software where
1009 pPrint = text . L.tail . show
1010
1011-- | Just the '0' version.
1012instance Default Version where
1013 def = Version [0] []
1014 {-# INLINE def #-}
1015
1016-- | For dot delimited version strings.
1017-- Example: @fromString \"0.1.0.2\" == Version [0, 1, 0, 2]@
1018--
1019instance IsString Version where
1020 fromString str
1021 | Just nums <- chunkNums str = Version nums []
1022 | otherwise = error $ "fromString: invalid version string " ++ str
1023 where
1024 chunkNums = sequence . L.map readMaybe . L.linesBy ('.' ==)
1025
1026instance Pretty Version where
1027 pPrint = text . showVersion
1028
1029-- | The all sensible infomation that can be obtained from a peer
1030-- identifier or torrent /createdBy/ field.
1031data Fingerprint = Fingerprint Software Version
1032 deriving (Show, Eq, Ord)
1033
1034-- | Unrecognized client implementation.
1035instance Default Fingerprint where
1036 def = Fingerprint def def
1037 {-# INLINE def #-}
1038
1039-- | Example: @\"BitComet-1.2\" == ClientInfo IBitComet (Version [1, 2] [])@
1040instance IsString Fingerprint where
1041 fromString str
1042 | _ : ver <- _ver = Fingerprint (fromString impl) (fromString ver)
1043 | otherwise = error $ "fromString: invalid client info string" ++ str
1044 where
1045 (impl, _ver) = L.span ((/=) '-') str
1046
1047instance Pretty Fingerprint where
1048 pPrint (Fingerprint s v) = pPrint s <+> "version" <+> pPrint v
1049
1050-- | Fingerprint of this (the bittorrent library) package. Normally,
1051-- applications should introduce its own fingerprints, otherwise they
1052-- can use 'libFingerprint' value.
1053--
1054libFingerprint :: Fingerprint
1055libFingerprint = Fingerprint IlibHSbittorrent version
1056
1057-- | HTTP user agent of this (the bittorrent library) package. Can be
1058-- used in HTTP tracker requests.
1059libUserAgent :: String
1060libUserAgent = render (pPrint IlibHSbittorrent <> "/" <> pPrint version)
1061
1062{-----------------------------------------------------------------------
1063-- For torrent file
1064-----------------------------------------------------------------------}
1065-- TODO collect information about createdBy torrent field
1066-- renderImpl :: ClientImpl -> Text
1067-- renderImpl = T.pack . L.tail . show
1068--
1069-- renderVersion :: Version -> Text
1070-- renderVersion = undefined
1071--
1072-- renderClientInfo :: ClientInfo -> Text
1073-- renderClientInfo ClientInfo {..} = renderImpl ciImpl <> "/" <> renderVersion ciVersion
1074--
1075-- parseClientInfo :: Text -> ClientImpl
1076-- parseClientInfo t = undefined
1077
1078
1079-- code used for generation; remove it later on
1080--
1081-- mkEnumTyDef :: NM -> String
1082-- mkEnumTyDef = unlines . map (" | I" ++) . nub . map snd
1083--
1084-- mkPars :: NM -> String
1085-- mkPars = unlines . map (\(code, impl) -> " f \"" ++ code ++ "\" = " ++ "I" ++ impl)
1086--
1087-- type NM = [(String, String)]
1088-- nameMap :: NM
1089-- nameMap =
1090-- [ ("AG", "Ares")
1091-- , ("A~", "Ares")
1092-- , ("AR", "Arctic")
1093-- , ("AV", "Avicora")
1094-- , ("AX", "BitPump")
1095-- , ("AZ", "Azureus")
1096-- , ("BB", "BitBuddy")
1097-- , ("BC", "BitComet")
1098-- , ("BF", "Bitflu")
1099-- , ("BG", "BTG")
1100-- , ("BR", "BitRocket")
1101-- , ("BS", "BTSlave")
1102-- , ("BX", "BittorrentX")
1103-- , ("CD", "EnhancedCTorrent")
1104-- , ("CT", "CTorrent")
1105-- , ("DE", "DelugeTorrent")
1106-- , ("DP", "PropagateDataClient")
1107-- , ("EB", "EBit")
1108-- , ("ES", "ElectricSheep")
1109-- , ("FT", "FoxTorrent")
1110-- , ("GS", "GSTorrent")
1111-- , ("HL", "Halite")
1112-- , ("HS", "libHSnetwork_bittorrent")
1113-- , ("HN", "Hydranode")
1114-- , ("KG", "KGet")
1115-- , ("KT", "KTorrent")
1116-- , ("LH", "LH_ABC")
1117-- , ("LP", "Lphant")
1118-- , ("LT", "Libtorrent")
1119-- , ("lt", "LibTorrent")
1120-- , ("LW", "LimeWire")
1121-- , ("MO", "MonoTorrent")
1122-- , ("MP", "MooPolice")
1123-- , ("MR", "Miro")
1124-- , ("MT", "MoonlightTorrent")
1125-- , ("NX", "NetTransport")
1126-- , ("PD", "Pando")
1127-- , ("qB", "qBittorrent")
1128-- , ("QD", "QQDownload")
1129-- , ("QT", "Qt4TorrentExample")
1130-- , ("RT", "Retriever")
1131-- , ("S~", "Shareaza")
1132-- , ("SB", "Swiftbit")
1133-- , ("SS", "SwarmScope")
1134-- , ("ST", "SymTorrent")
1135-- , ("st", "sharktorrent")
1136-- , ("SZ", "Shareaza")
1137-- , ("TN", "TorrentDotNET")
1138-- , ("TR", "Transmission")
1139-- , ("TS", "Torrentstorm")
1140-- , ("TT", "TuoTu")
1141-- , ("UL", "uLeecher")
1142-- , ("UT", "uTorrent")
1143-- , ("VG", "Vagaa")
1144-- , ("WT", "BitLet")
1145-- , ("WY", "FireTorrent")
1146-- , ("XL", "Xunlei")
1147-- , ("XT", "XanTorrent")
1148-- , ("XX", "Xtorrent")
1149-- , ("ZT", "ZipTorrent")
1150-- ]
1151
1152-- TODO use regexps
1153
1154-- | Tries to extract meaningful information from peer ID bytes. If
1155-- peer id uses unknown coding style then client info returned is
1156-- 'def'.
1157--
1158fingerprint :: PeerId -> Fingerprint
1159fingerprint pid = either (const def) id $ runGet getCI (getPeerId pid)
1160 where
1161 getCI = do
1162 leading <- BS.w2c <$> getWord8
1163 case leading of
1164 '-' -> Fingerprint <$> getAzureusImpl <*> getAzureusVersion
1165 'M' -> Fingerprint <$> pure IMainline <*> getMainlineVersion
1166 'e' -> Fingerprint <$> getBitCometImpl <*> getBitCometVersion
1167 'F' -> Fingerprint <$> getBitCometImpl <*> getBitCometVersion
1168 c -> do
1169 c1 <- w2c <$> S.lookAhead getWord8
1170 if c1 == 'P'
1171 then do
1172 _ <- getWord8
1173 Fingerprint <$> pure IOpera <*> getOperaVersion
1174 else Fingerprint <$> pure (getShadowImpl c) <*> getShadowVersion
1175
1176 getMainlineVersion = do
1177 str <- BC.unpack <$> getByteString 7
1178 let mnums = L.filter (not . L.null) $ L.linesBy ('-' ==) str
1179 return $ Version (fromMaybe [] $ sequence $ L.map readMaybe mnums) []
1180
1181 getAzureusImpl = parseSoftware <$> getByteString 2
1182 getAzureusVersion = mkVer <$> getByteString 4
1183 where
1184 mkVer bs = Version [fromMaybe 0 $ readMaybe $ BC.unpack bs] []
1185
1186 getBitCometImpl = do
1187 bs <- getByteString 3
1188 S.lookAhead $ do
1189 _ <- getByteString 2
1190 lr <- getByteString 4
1191 return $
1192 if lr == "LORD" then IBitLord else
1193 if bs == "UTB" then IBitComet else
1194 if bs == "xbc" then IBitComet else def
1195
1196 getBitCometVersion = do
1197 x <- getWord8
1198 y <- getWord8
1199 return $ Version [fromIntegral x, fromIntegral y] []
1200
1201 getOperaVersion = do
1202 str <- BC.unpack <$> getByteString 4
1203 return $ Version [fromMaybe 0 $ readMaybe str] []
1204
1205 getShadowImpl 'A' = IABC
1206 getShadowImpl 'O' = IOspreyPermaseed
1207 getShadowImpl 'Q' = IBTQueue
1208 getShadowImpl 'R' = ITribler
1209 getShadowImpl 'S' = IShadow
1210 getShadowImpl 'T' = IBitTornado
1211 getShadowImpl _ = IUnknown
1212
1213 decodeShadowVerNr :: Char -> Maybe Int
1214 decodeShadowVerNr c
1215 | '0' < c && c <= '9' = Just (fromEnum c - fromEnum '0')
1216 | 'A' < c && c <= 'Z' = Just ((fromEnum c - fromEnum 'A') + 10)
1217 | 'a' < c && c <= 'z' = Just ((fromEnum c - fromEnum 'a') + 36)
1218 | otherwise = Nothing
1219
1220 getShadowVersion = do
1221 str <- BC.unpack <$> getByteString 5
1222 return $ Version (catMaybes $ L.map decodeShadowVerNr str) []
1223
1224
1225-- | Yields all 8 DHT neighborhoods available to you given a particular ip
1226-- address.
1227bep42s :: Address a => a -> NodeId -> [NodeId]
1228bep42s addr (NodeId r) = mapMaybe (bep42 addr) rs
1229 where
1230 rs = L.map (NodeId . change3bits r) [0..7]
1231
1232change3bits :: ByteString -> Word8 -> ByteString
1233change3bits bs n = BS.snoc (BS.init bs) (BS.last bs .&. 0xF8 .|. n)
1234
1235-- | Modifies a purely random 'NodeId' to one that is related to a given
1236-- routable address in accordance with BEP 42.
1237bep42 :: Address a => a -> NodeId -> Maybe NodeId
1238bep42 addr (NodeId r)
1239 | Just ip <- fmap S.encode (fromAddr addr :: Maybe IPv4)
1240 <|> fmap S.encode (fromAddr addr :: Maybe IPv6)
1241 = genBucketSample' retr (NodeId $ crc $ applyMask ip) (3,0x07,0)
1242 | otherwise
1243 = Nothing
1244 where
1245 ip4mask = "\x03\x0f\x3f\xff" :: ByteString
1246 ip6mask = "\x01\x03\x07\x0f\x1f\x3f\x7f\xff" :: ByteString
1247 nbhood_select = BS.last r .&. 7
1248 retr n = pure $ BS.drop (BS.length r - n) r
1249 crc = S.encode . crc32c . BS.pack
1250 applyMask ip = case BS.zipWith (.&.) msk ip of
1251 (b:bs) -> (b .|. shiftL nbhood_select 5) : bs
1252 bs -> bs
1253 where msk | BS.length ip == 4 = ip4mask
1254 | otherwise = ip6mask